summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon McVittie <simon.mcvittie@collabora.co.uk>2013-10-31 16:47:55 +0000
committerSimon McVittie <simon.mcvittie@collabora.co.uk>2013-10-31 16:47:55 +0000
commitae86b52c435aa39b78ae14cb23a75e4e639b0d5a (patch)
treeb526eda7fda8d3469e1db44ea258f3065017d9a7
parentbd2903877be3a3bd38a2a3fff6bd6e8d030e7306 (diff)
parent301d3d7d9b0f025d71390dbc9211923308e4386b (diff)
Merge remote-tracking branch 'mylogger/next' into next
Conflicts: .gitignore COPYING Makefile.am autogen.sh configure.ac docs/Makefile.am docs/reference/Makefile.am m4/tp-compiler-flag.m4 m4/tp-compiler-warnings.m4 tests/Makefile.am tests/lib/Makefile.am tests/lib/simple-account.c tests/lib/simple-account.h
-rw-r--r--.gitignore42
-rw-r--r--Makefile.am23
-rwxr-xr-xautogen.sh2
-rw-r--r--configure.ac107
-rw-r--r--data/Logger.Call.client.in8
-rw-r--r--data/Logger.Observer.client.in3
-rw-r--r--data/Logger.Recover.client.in2
-rw-r--r--data/Logger.Text.client.in8
-rw-r--r--data/Makefile.am45
-rw-r--r--data/im.telepathy1.Client.Logger.service.in3
-rw-r--r--data/im.telepathy1.Logger.service.in3
-rw-r--r--data/log-manager.xsl157
-rw-r--r--data/org.freedesktop.Telepathy.Logger.gschema.xml.in17
-rw-r--r--docs/Makefile.am10
-rw-r--r--docs/reference/Makefile.am4
-rw-r--r--docs/reference/libtelepathy-logger/Makefile.am135
-rw-r--r--docs/reference/libtelepathy-logger/docs.xml22
-rw-r--r--docs/templates/devhelp.devhelp218
-rw-r--r--docs/templates/errors.html60
-rw-r--r--docs/templates/fullindex.html60
-rw-r--r--docs/templates/generic-types.html59
-rw-r--r--docs/templates/index.html66
-rw-r--r--docs/templates/interface.html424
-rw-r--r--docs/templates/interfaces.html50
-rw-r--r--docs/templates/style.css224
-rw-r--r--m4/.gitignore9
-rw-r--r--m4/empathy-args.m419
-rw-r--r--m4/empathy-valgrind.m431
-rw-r--r--m4/gsettings.m493
-rw-r--r--m4/python.m466
-rw-r--r--po/POTFILES.in1
-rw-r--r--telepathy-logger/AUTHORS2
-rw-r--r--telepathy-logger/Makefile.am159
-rw-r--r--telepathy-logger/NEWS452
-rw-r--r--telepathy-logger/README20
-rw-r--r--telepathy-logger/TESTING19
-rw-r--r--telepathy-logger/abi.am16
-rw-r--r--telepathy-logger/action-chain-internal.h47
-rw-r--r--telepathy-logger/action-chain.c172
-rw-r--r--telepathy-logger/call-channel-internal.h78
-rw-r--r--telepathy-logger/call-channel.c492
-rw-r--r--telepathy-logger/call-event-internal.h47
-rw-r--r--telepathy-logger/call-event.c284
-rw-r--r--telepathy-logger/call-event.h49
-rw-r--r--telepathy-logger/client-factory-internal.h60
-rw-r--r--telepathy-logger/client-factory.c119
-rw-r--r--telepathy-logger/conf-internal.h58
-rw-r--r--telepathy-logger/conf.c308
-rw-r--r--telepathy-logger/dbus-service-internal.h80
-rw-r--r--telepathy-logger/dbus-service.c871
-rw-r--r--telepathy-logger/debug-internal.h114
-rw-r--r--telepathy-logger/debug.c126
-rw-r--r--telepathy-logger/entity-internal.h44
-rw-r--r--telepathy-logger/entity.c409
-rw-r--r--telepathy-logger/entity.h80
-rw-r--r--telepathy-logger/event-internal.h51
-rw-r--r--telepathy-logger/event.c403
-rw-r--r--telepathy-logger/event.h55
-rw-r--r--telepathy-logger/extensions/Logger.xml153
-rw-r--r--telepathy-logger/extensions/Makefile.am173
-rw-r--r--telepathy-logger/extensions/all.xml34
-rw-r--r--telepathy-logger/extensions/extensions-cli.c33
-rw-r--r--telepathy-logger/extensions/extensions.c9
-rw-r--r--telepathy-logger/extensions/extensions.h20
-rw-r--r--telepathy-logger/extensions/misc.xml10
-rw-r--r--telepathy-logger/extensions/tpl-extensions.pc.in11
-rw-r--r--telepathy-logger/log-iter-internal.h79
-rw-r--r--telepathy-logger/log-iter-pidgin-internal.h78
-rw-r--r--telepathy-logger/log-iter-pidgin.c325
-rw-r--r--telepathy-logger/log-iter-xml-internal.h79
-rw-r--r--telepathy-logger/log-iter-xml.c326
-rw-r--r--telepathy-logger/log-iter.c91
-rw-r--r--telepathy-logger/log-manager-internal.h81
-rw-r--r--telepathy-logger/log-manager.c1682
-rw-r--r--telepathy-logger/log-manager.h193
-rw-r--r--telepathy-logger/log-store-empathy-internal.h54
-rw-r--r--telepathy-logger/log-store-empathy.c67
-rw-r--r--telepathy-logger/log-store-factory-internal.h41
-rw-r--r--telepathy-logger/log-store-factory.c99
-rw-r--r--telepathy-logger/log-store-internal.h118
-rw-r--r--telepathy-logger/log-store-pidgin-internal.h63
-rw-r--r--telepathy-logger/log-store-pidgin.c1161
-rw-r--r--telepathy-logger/log-store-sqlite-internal.h103
-rw-r--r--telepathy-logger/log-store-sqlite.c992
-rw-r--r--telepathy-logger/log-store-xml-internal.h62
-rw-r--r--telepathy-logger/log-store-xml.c1939
-rw-r--r--telepathy-logger/log-store.c377
-rw-r--r--telepathy-logger/log-walker-internal.h38
-rw-r--r--telepathy-logger/log-walker.c975
-rw-r--r--telepathy-logger/log-walker.h94
-rw-r--r--telepathy-logger/observer-internal.h66
-rw-r--r--telepathy-logger/observer.c363
-rw-r--r--telepathy-logger/telepathy-logger-1-uninstalled.pc.in11
-rw-r--r--telepathy-logger/telepathy-logger-1.pc.in11
-rw-r--r--telepathy-logger/telepathy-logger.c185
-rw-r--r--telepathy-logger/telepathy-logger.h31
-rw-r--r--telepathy-logger/test-api.c76
-rw-r--r--telepathy-logger/text-channel-internal.h84
-rw-r--r--telepathy-logger/text-channel.c722
-rw-r--r--telepathy-logger/text-event-internal.h62
-rw-r--r--telepathy-logger/text-event.c410
-rw-r--r--telepathy-logger/text-event.h52
-rw-r--r--telepathy-logger/tpl-marshal.list3
-rw-r--r--telepathy-logger/util-internal.h41
-rw-r--r--telepathy-logger/util.c135
-rw-r--r--tests/Makefile.am5
-rw-r--r--tests/lib/Makefile.am14
-rw-r--r--tests/lib/logger-test-helper.c86
-rw-r--r--tests/lib/logger-test-helper.h40
-rw-r--r--tests/logger/Makefile.am54
-rw-r--r--tests/logger/README48
-rw-r--r--tests/logger/constants.h2
-rw-r--r--tests/logger/dbus/Makefile.am89
-rw-r--r--tests/logger/dbus/dbus-1/session.conf.in30
-rw-r--r--tests/logger/dbus/test-entity.c171
-rw-r--r--tests/logger/dbus/test-log-manager.c807
-rw-r--r--tests/logger/dbus/test-tpl-log-iter-pidgin.c849
-rw-r--r--tests/logger/dbus/test-tpl-log-iter-xml.c446
-rw-r--r--tests/logger/dbus/test-tpl-log-store-pidgin.c622
-rw-r--r--tests/logger/dbus/test-tpl-log-store-sqlite.c40
-rw-r--r--tests/logger/dbus/test-tpl-log-store-xml.c1120
-rw-r--r--tests/logger/dbus/test-tpl-log-walker.c463
-rw-r--r--tests/logger/dbus/test-tpl-observer.c35
-rw-r--r--tests/logger/logs/Empathy/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100113.log10
-rw-r--r--tests/logger/logs/Empathy/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100208.log5
-rw-r--r--tests/logger/logs/Empathy/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100216.log14
-rw-r--r--tests/logger/logs/Empathy/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100217.log19
-rw-r--r--tests/logger/logs/Empathy/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100218.log6
-rw-r--r--tests/logger/logs/TpLogger/logs/gabble_jabber_test2_40collabora_2eco_2euk0/chatrooms/meego@conference.collabora.co.uk/20110112.log5
-rw-r--r--tests/logger/logs/TpLogger/logs/gabble_jabber_test2_40collabora_2eco_2euk0/derek.foreman@collabora.co.uk/20110210.log5
-rw-r--r--tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100113.log10
-rw-r--r--tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100208.log5
-rw-r--r--tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100216.log14
-rw-r--r--tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100217.log19
-rw-r--r--tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100218.log6
-rw-r--r--tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user3@collabora.co.uk/20100113.call.log5
-rw-r--r--tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user4@collabora.co.uk/20100113.call.log8
-rw-r--r--tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user4@collabora.co.uk/20100113.log7
-rw-r--r--tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user5@collabora.co.uk/20100111.log11
-rw-r--r--tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user5@collabora.co.uk/20100112.log10
-rw-r--r--tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user5@collabora.co.uk/20100113.call.log5
-rw-r--r--tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user5@collabora.co.uk/20100113.log5
-rw-r--r--tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user5@collabora.co.uk/20100114.log10
-rw-r--r--tests/logger/logs/purple/bonjour/user@host/user2@host2/2010-04-29.140346+0100BST.html6
-rw-r--r--tests/logger/logs/purple/icq/12345678/87654321/2010-02-06.130032+0000GMT.htmlbin0 -> 938 bytes
-rw-r--r--tests/logger/logs/purple/icq/12345678/87654321/2010-02-07.130033+0000GMT.html0
-rw-r--r--tests/logger/logs/purple/icq/12345678/87654321/2010-02-08.130034+0000GMT.html8
-rw-r--r--tests/logger/logs/purple/icq/12345678/87654321/2010-02-08.134023+0000GMT.html12
-rw-r--r--tests/logger/logs/purple/irc/user@irc.freenode.net/#telepathy.chat/2010-11-30.124947+0000GMT.html178
-rw-r--r--tests/logger/logs/purple/jabber/user@collabora.co.uk/.system/2010-12-10.162531+0000GMT.txt5
-rw-r--r--tests/logger/logs/purple/jabber/user@collabora.co.uk/test@conference.collabora.co.uk.chat/2010-04-12.122703+0100BST.html12
-rw-r--r--tests/logger/logs/purple/jabber/user@collabora.co.uk/test@conference.collabora.co.uk.chat/2010-04-29.140846+0100BST.html11
-rw-r--r--tests/logger/logs/purple/jabber/user@collabora.co.uk/user2@collabora.co.uk/2010-12-10.162702+0000GMT.txt3
-rw-r--r--tests/logger/logs/purple/jabber/user@collabora.co.uk/user5@collabora.co.uk/2010-01-10.000101+0000GMT.txt3
-rw-r--r--tests/logger/logs/purple/jabber/user@collabora.co.uk/user5@collabora.co.uk/2010-01-11.000101+0000GMT.txt17
-rw-r--r--tests/logger/logs/purple/jabber/user@collabora.co.uk/user5@collabora.co.uk/2010-01-14.000101+0000GMT.txt12
-rw-r--r--tests/logger/test-basic-connect.sh2
-rw-r--r--tests/logger/test-tpl-conf.c31
-rw-r--r--tests/suppressions/Makefile.am1
-rw-r--r--tests/suppressions/tpl.supp321
160 files changed, 23913 insertions, 7 deletions
diff --git a/.gitignore b/.gitignore
index bf290d652..776dd86fb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,6 +16,7 @@ Makefile
*.bak
*~
*.pyc
+*.pyo
*.loT
*.gir
*.typelib
@@ -27,15 +28,34 @@ INSTALL
aclocal.m4
autom4te.cache
/build-aux
+/ChangeLog
config.guess
config.h
config.h.in
+/config.h.in~
config.log
config.status
config.sub
configure
cscope.out
depcomp
+/data/*.client
+/data/*.service
+/data/org.freedesktop.Telepathy.Logger.gschema.valid
+/data/org.freedesktop.Telepathy.Logger.gschema.xml
+/docs/reference/libtelepathy-logger/*.stamp
+/docs/reference/libtelepathy-logger/*.bak
+/docs/reference/libtelepathy-logger/html/
+/docs/reference/libtelepathy-logger/telepathy-logger-*.bak
+/docs/reference/libtelepathy-logger/telepathy-logger-*.txt
+/docs/reference/libtelepathy-logger/telepathy-logger.args
+/docs/reference/libtelepathy-logger/telepathy-logger.hierarchy
+/docs/reference/libtelepathy-logger/telepathy-logger.interfaces
+/docs/reference/libtelepathy-logger/telepathy-logger.prerequisites
+/docs/reference/libtelepathy-logger/telepathy-logger.signals
+/docs/reference/libtelepathy-logger/telepathy-logger.types
+/docs/reference/libtelepathy-logger/tmpl/
+/docs/reference/libtelepathy-logger/xml/
/docs/reference/telepathy-glib/*-decl.txt
/docs/reference/telepathy-glib/*-decl-list.txt
/docs/reference/telepathy-glib/*-undeclared.txt
@@ -62,7 +82,11 @@ examples/client/stream-tubes/accepter
examples/client/stream-tubes/offerer
/extensions/extensions.html
gtk-doc.make
+/INSTALL
install-sh
+/intltool-extract.in
+/intltool-merge.in
+/intltool-update.in
lcov.info
lcov.info.tmp
lcov.html
@@ -75,18 +99,36 @@ m4/ltsugar.m4
m4/ltversion.m4
m4/lt~obsolete.m4
missing
+/po/Makefile.in.in
+/po/POTFILES
+/po/stamp-it
/release-mail
stamp-h1
+/TAGS
tags
/telepathy-glib-0.*
/telepathy-glib/tmp-introspect*/
telepathy-glib/version.h
+/telepathy-logger/extensions/tpl-extensions.pc
+/telepathy-logger/extensions/_gen/
+/telepathy-logger/extensions/doc/
+/telepathy-logger/telepathy-logger
+/telepathy-logger/test-api
+/telepathy-logger/telepathy-logger-1-uninstalled.pc
+/telepathy-logger/telepathy-logger-1.pc
+/telepathy-logger/TelepathyLogger1-1.typelib
+/telepathy-logger/TelepathyLogger1-1.gir
+/telepathy-logger/_gen/
+/telepathy-logger/tpl-marshal.[ch]
/test-driver
tests/dbus/dbus-installed/session.conf
tests/dbus/dbus-uninstalled/session.conf
tests/dbus/run-test.sh
tests/dbus/test-*
tests/dbus/tpglib-dbus-tests.list
+/tests/logger/test-*[^ch]
+/tests/logger/dbus/test-*[^ch]
+/tests/logger/dbus/dbus-1/session.conf
tests/test-*
/tests/tools/actual.h
/tests/tools/actual-body.h
diff --git a/Makefile.am b/Makefile.am
index 4010e9adb..d61417eef 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -4,7 +4,9 @@ SUBDIRS = \
m4 \
tools \
spec \
+ data \
telepathy-glib \
+ po \
$(NULL)
if HAVE_INTROSPECTION
@@ -13,13 +15,26 @@ SUBDIRS += vala
endif
endif
+if ENABLE_LOGGER
+SUBDIRS += telepathy-logger
+endif
+
+# build examples and tests after all the things under test!
SUBDIRS += \
examples \
tests \
- docs \
$(NULL)
-DISTCHECK_CONFIGURE_FLAGS = --enable-gtk-doc --disable-debug
+# do this last, because it's slow - in a compile/test/fix cycle, we don't
+# want to bother building the docs until the tests have passed
+SUBDIRS += docs
+
+DISTCHECK_CONFIGURE_FLAGS = \
+ --enable-gtk-doc \
+ --disable-debug \
+ --disable-scrollkeeper \
+ --disable-schemas-install \
+ $(NULL)
EXTRA_DIST = \
autogen.sh \
@@ -68,3 +83,7 @@ clean-for-new-branch:
$(MAKE) -C telepathy-glib distclean
$(SHELL) ./config.status --recheck
$(SHELL) ./config.status
+
+# Workaround broken scrollkeeper that doesn't remove its files on
+# uninstall.
+distuninstallcheck_listfiles = find . -type f -print | grep -v '^\./var/scrollkeeper'
diff --git a/autogen.sh b/autogen.sh
index fa642bed1..2b40c1075 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -1,8 +1,8 @@
#!/bin/sh
set -e
+intltoolize --force --copy --automake
gtkdocize
-
autoreconf -i -f
# Honor NOCONFIGURE for compatibility with gnome-autogen.sh
diff --git a/configure.ac b/configure.ac
index ff307ee96..93071d80f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,4 +1,8 @@
-AC_PREREQ([2.60])
+AC_PREREQ([2.65])
+AC_COPYRIGHT([
+ Copyright (C) 2003-2007 Imendio AB
+ Copyright (C) 2007-2013 Collabora Ltd.
+])
# Making releases:
# set the new version number:
@@ -37,6 +41,16 @@ m4_define([tp_glib_lt_dbus_current], [0])
m4_define([tp_glib_lt_dbus_revision], [0])
m4_define([tp_glib_lt_dbus_age], [0])
+# The same for the telepathy-logger library
+m4_define([tpl_lt_current], [0])
+m4_define([tpl_lt_revision], [0])
+m4_define([tpl_lt_age], [0])
+
+# The same for the extensions library produced by --enable-public-extensions
+m4_define([tpl_ext_lt_current], [0])
+m4_define([tpl_ext_lt_revision], [0])
+m4_define([tpl_ext_lt_age], [0])
+
# Some magic
m4_define([tp_glib_base_version],
[tp_glib_major_version.tp_glib_minor_version.tp_glib_micro_version])
@@ -90,6 +104,24 @@ AC_SUBST([LT_DBUS_CURRENT])
AC_SUBST([LT_DBUS_REVISION])
AC_SUBST([LT_DBUS_AGE])
+TPL_LT_CURRENT=tpl_lt_current
+TPL_LT_REVISION=tpl_lt_revision
+TPL_LT_AGE=tpl_lt_age
+AC_SUBST([TPL_LT_CURRENT])
+AC_SUBST([TPL_LT_REVISION])
+AC_SUBST([TPL_LT_AGE])
+# The ABI version is the end of the SONAME on Linux, and would appear in the
+# name of a plugin directory, for instance
+TPL_ABI_VERSION=`expr ${TPL_LT_CURRENT} - ${TPL_LT_AGE}`
+AC_SUBST([TPL_ABI_VERSION])
+
+TPL_EXT_LT_CURRENT=tpl_ext_lt_current
+TPL_EXT_LT_REVISION=tpl_ext_lt_revision
+TPL_EXT_LT_AGE=tpl_ext_lt_age
+AC_SUBST([TPL_EXT_LT_CURRENT])
+AC_SUBST([TPL_EXT_LT_REVISION])
+AC_SUBST([TPL_EXT_LT_AGE])
+
dnl optimizations, etc.
COMPILER_OPTIMISATIONS
COMPILER_COVERAGE
@@ -238,8 +270,10 @@ AC_SUBST(GLIB_GENMARSHAL)
GLIB_MKENUMS=`$PKG_CONFIG --variable=glib_mkenums glib-2.0`
AC_SUBST(GLIB_MKENUMS)
+GLIB_GSETTINGS
+
dnl Check for D-Bus
-PKG_CHECK_MODULES(DBUS, [dbus-1 >= 0.95, dbus-glib-1 >= 0.90])
+PKG_CHECK_MODULES(DBUS, [dbus-1 >= 1.1, dbus-glib-1 >= 0.90])
AC_SUBST(DBUS_CFLAGS)
AC_SUBST(DBUS_LIBS)
@@ -319,9 +353,52 @@ CXXFLAGS="$_TP_OLD_CXXFLAGS"
AC_LANG_POP([C++])
AM_CONDITIONAL([HAVE_CXX], [test $tp_cxx_works = yes])
-AC_OUTPUT( Makefile \
+IT_PROG_INTLTOOL([0.35.0])
+
+AC_ARG_ENABLE([logger],
+ [AS_HELP_STRING([--enable-logger],
+ [build the Telepathy Logger (default=auto)])],
+ [],
+ [enable_logger=auto])
+
+AS_IF([test "x$enable_logger" = xauto],
+ [
+ PKG_CHECK_MODULES([LOGGER_DEPENDENCIES],
+ [sqlite3, libxml-2.0],
+ [enable_logger=yes],
+ [enable_logger=no])
+ ])
+
+AS_IF([test "x$enable_logger" = xyes],
+ [
+ PKG_CHECK_MODULES([LIBXML], [libxml-2.0])
+ PKG_CHECK_MODULES([SQLITE], [sqlite3])
+
+ TPL_CFLAGS="$DBUS_CFLAGS $GLIB_CFLAGS $LIBXML_CFLAGS $SQLITE_CFLAGS"
+ TPL_LIBS="$DBUS_LIBS $GLIB_LIBS $LIBXML_LIBS $SQLITE_LIBS"
+ AC_SUBST([TPL_CFLAGS])
+ AC_SUBST([TPL_LIBS])
+ ])
+
+AM_CONDITIONAL([ENABLE_LOGGER], [test "x$enable_logger" = xyes])
+
+AC_ARG_ENABLE([public_extensions],
+ AS_HELP_STRING([--enable-public-extensions],
+ [build a public wrapper for the Telepathy Logger extensions library]),
+ [],
+ [enable_public_extensions=no])
+
+AS_IF([test x$enable_public_extensions = xyes],
+ [AC_DEFINE(ENABLE_PUBLIC_EXTENSIONS, [], [Enable Public Extensions])])
+AM_CONDITIONAL([ENABLE_PUBLIC_EXTENSIONS],
+ [test "x$enable_public_extensions" = xyes])
+
+AC_CONFIG_FILES([
+ Makefile \
+ data/Makefile \
docs/Makefile \
docs/reference/Makefile \
+ docs/reference/libtelepathy-logger/Makefile \
docs/reference/telepathy-glib/Makefile \
docs/reference/telepathy-glib/version.xml \
examples/Makefile \
@@ -338,6 +415,7 @@ AC_OUTPUT( Makefile \
examples/cm/extended/Makefile \
examples/cm/no-protocols/Makefile \
examples/extensions/Makefile \
+ po/Makefile.in \
spec/Makefile \
telepathy-glib/Makefile \
telepathy-glib/telepathy-glib-1-dbus.pc \
@@ -345,11 +423,32 @@ AC_OUTPUT( Makefile \
telepathy-glib/telepathy-glib-1.pc \
telepathy-glib/telepathy-glib-1-uninstalled.pc \
telepathy-glib/version.h \
+ telepathy-logger/Makefile \
+ telepathy-logger/extensions/Makefile \
+ telepathy-logger/extensions/tpl-extensions.pc \
+ telepathy-logger/telepathy-logger-1.pc \
+ telepathy-logger/telepathy-logger-1-uninstalled.pc \
tests/Makefile \
tests/lib/Makefile \
tests/dbus/Makefile \
+ tests/logger/Makefile \
+ tests/logger/dbus/Makefile \
+ tests/suppressions/Makefile \
tests/tools/Makefile \
tools/Makefile \
m4/Makefile \
vala/Makefile
-)
+])
+AC_OUTPUT
+
+echo "
+Configure summary:
+
+ Compiler....................: ${CC}
+ Compiler Flags..............: ${CFLAGS} ${ERROR_CFLAGS}
+ Prefix......................: ${prefix}
+ Coding style checks.........: ${ENABLE_CODING_STYLE_CHECKS}
+ Bugreporting URL............: ${PACKAGE_BUGREPORT}
+ Public extensions library...: ${enable_public_extensions}
+ Introspection support.......: ${found_introspection}
+"
diff --git a/data/Logger.Call.client.in b/data/Logger.Call.client.in
new file mode 100644
index 000000000..0ae64b933
--- /dev/null
+++ b/data/Logger.Call.client.in
@@ -0,0 +1,8 @@
+[im.telepathy1.Client.Observer.ObserverChannelFilter 3]
+im.telepathy1.Channel.ChannelType s=im.telepathy1.Channel.Type.Call1
+im.telepathy1.Channel.TargetHandleType u=1
+
+[im.telepathy1.Client.Observer.ObserverChannelFilter 4]
+im.telepathy1.Channel.ChannelType s=im.telepathy1.Channel.Type.Call1
+im.telepathy1.Channel.TargetHandleType u=2
+
diff --git a/data/Logger.Observer.client.in b/data/Logger.Observer.client.in
new file mode 100644
index 000000000..a1c9dfa59
--- /dev/null
+++ b/data/Logger.Observer.client.in
@@ -0,0 +1,3 @@
+[im.telepathy1.Client]
+Interfaces=im.telepathy1.Client.Observer
+
diff --git a/data/Logger.Recover.client.in b/data/Logger.Recover.client.in
new file mode 100644
index 000000000..f18cd72ea
--- /dev/null
+++ b/data/Logger.Recover.client.in
@@ -0,0 +1,2 @@
+[im.telepathy1.Client.Observer]
+Recover=true
diff --git a/data/Logger.Text.client.in b/data/Logger.Text.client.in
new file mode 100644
index 000000000..eae677c70
--- /dev/null
+++ b/data/Logger.Text.client.in
@@ -0,0 +1,8 @@
+[im.telepathy1.Client.Observer.ObserverChannelFilter 0]
+im.telepathy1.Channel.ChannelType s=im.telepathy1.Channel.Type.Text
+im.telepathy1.Channel.TargetHandleType u=1
+
+[im.telepathy1.Client.Observer.ObserverChannelFilter 1]
+im.telepathy1.Channel.ChannelType s=im.telepathy1.Channel.Type.Text
+im.telepathy1.Channel.TargetHandleType u=2
+
diff --git a/data/Makefile.am b/data/Makefile.am
new file mode 100644
index 000000000..92533b62b
--- /dev/null
+++ b/data/Makefile.am
@@ -0,0 +1,45 @@
+if ENABLE_LOGGER
+
+gsettings_files = \
+ org.freedesktop.Telepathy.Logger.gschema.xml.in \
+ $(NULL)
+gsettings_SCHEMAS = $(gsettings_files:.xml.in=.xml)
+@INTLTOOL_XML_NOMERGE_RULE@
+@GSETTINGS_RULES@
+
+servicefiledir = $(datadir)/dbus-1/services
+servicefile_in_files = \
+ im.telepathy1.Client.Logger.service.in \
+ im.telepathy1.Logger.service.in
+servicefile_DATA = $(servicefile_in_files:.service.in=.service)
+%.service: %.service.in Makefile
+ $(AM_V_GEN)sed -e "s|[@]libexecdir[@]|$(libexecdir)|" $< > $@
+
+clientfiledir = $(datarootdir)/telepathy-1/clients
+
+clientfile_parts = $(top_srcdir)/data/Logger.Observer.client.in \
+ $(top_srcdir)/data/Logger.Text.client.in \
+ $(top_srcdir)/data/Logger.Call.client.in \
+ $(top_srcdir)/data/Logger.Recover.client.in
+
+clientfile_DATA = Logger.client
+
+EXTRA_DIST = \
+ $(gsettings_files) \
+ $(clientfile_DATA) \
+ $(servicefile_in_files) \
+ $(clientfile_parts)
+
+CLEANFILES = \
+ $(gsettings_SCHEMAS) \
+ $(servicefile_DATA) \
+ $(clientfile_DATA)
+
+# Generates Logger.client file
+Logger.client: $(clientfile_parts)
+ @cp $(top_srcdir)/data/Logger.Observer.client.in $(clientfile_DATA)
+ @cat $(top_srcdir)/data/Logger.Text.client.in >> $(clientfile_DATA)
+ @cat $(top_srcdir)/data/Logger.Call.client.in >> $(clientfile_DATA)
+ $(AM_V_GEN)cat $(top_srcdir)/data/Logger.Recover.client.in >> $(clientfile_DATA)
+
+endif
diff --git a/data/im.telepathy1.Client.Logger.service.in b/data/im.telepathy1.Client.Logger.service.in
new file mode 100644
index 000000000..cf6f348bd
--- /dev/null
+++ b/data/im.telepathy1.Client.Logger.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=im.telepathy1.Client.Logger
+Exec=@libexecdir@/telepathy-logger
diff --git a/data/im.telepathy1.Logger.service.in b/data/im.telepathy1.Logger.service.in
new file mode 100644
index 000000000..118e00d03
--- /dev/null
+++ b/data/im.telepathy1.Logger.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=im.telepathy1.Logger
+Exec=@libexecdir@/telepathy-logger
diff --git a/data/log-manager.xsl b/data/log-manager.xsl
new file mode 100644
index 000000000..b81fb1896
--- /dev/null
+++ b/data/log-manager.xsl
@@ -0,0 +1,157 @@
+<!--
+This XSL stylesheet generates readable HTML output from an Empathy-style XML
+log file.
+
+To generate a HTML file, try something like:
+
+ xsltproc \-\-stringparam title "Chat Log" log-manager.xsl logfile.log
+-->
+
+<xsl:stylesheet version = '1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
+
+ <xsl:output method="html" encoding="utf-8" indent="yes"/>
+
+ <xsl:template match="/">
+ <html>
+ <head>
+ <style type="text/css">
+ <xsl:text>
+ body {
+ background: #fff;
+ font-family: Verdana, "Bitstream Vera Sans", Sans-Serif;
+ font-size: 10pt;
+ }
+ .stamp {
+ color: #999;
+ }
+ .top-day-stamp {
+ color: #999;
+ text-align: center;
+ margin-bottom: 1em;
+ }
+ .new-day-stamp {
+ color: #999;
+ text-align: center;
+ margin-bottom: 1em;
+ margin-top: 1em;
+ }
+ .nick {
+ color: rgb(54,100, 139);
+ }
+ .nick-self {
+ color: rgb(46,139,87);
+ }
+ .nick-highlight {
+ color: rgb(205,92,92);
+ }
+ </xsl:text>
+ </style>
+ <title><xsl:value-of select="$title"/></title>
+ </head>
+ <body>
+ <xsl:apply-templates/>
+ </body>
+ </html>
+ </xsl:template>
+
+ <xsl:template name="get-day">
+ <xsl:param name="stamp"/>
+ <xsl:value-of select="substring ($stamp, 1, 8)"/>
+ </xsl:template>
+
+ <xsl:template name="format-stamp">
+ <xsl:param name="stamp"/>
+ <xsl:variable name="hour" select="substring ($stamp, 10, 2)"/>
+ <xsl:variable name="min" select="substring ($stamp, 13, 2)"/>
+
+ <xsl:value-of select="$hour"/>:<xsl:value-of select="$min"/>
+ </xsl:template>
+
+ <xsl:template name="format-day-stamp">
+ <xsl:param name="stamp"/>
+ <xsl:variable name="year" select="substring ($stamp, 1, 4)"/>
+ <xsl:variable name="month" select="substring ($stamp, 5, 2)"/>
+ <xsl:variable name="day" select="substring ($stamp, 7, 2)"/>
+
+ <xsl:value-of select="$year"/>-<xsl:value-of select="$month"/>-<xsl:value-of select="$day"/>
+ </xsl:template>
+
+ <xsl:template name="header">
+ <xsl:param name="stamp"/>
+ <div class="top-day-stamp">
+ <xsl:call-template name="format-day-stamp">
+ <xsl:with-param name="stamp" select="@time"/>
+ </xsl:call-template>
+ </div>
+ </xsl:template>
+
+ <xsl:template match="a">
+ <xsl:text disable-output-escaping="yes">&lt;a href="</xsl:text>
+
+ <xsl:value-of disable-output-escaping="yes" select="@href"/>
+
+ <xsl:text disable-output-escaping="yes">"&gt;</xsl:text>
+
+ <xsl:value-of select="@href"/>
+ <xsl:text disable-output-escaping="yes">&lt;/a&gt;</xsl:text>
+ </xsl:template>
+
+ <xsl:template match="log">
+
+ <div class="top-day-stamp">
+ <xsl:call-template name="format-day-stamp">
+ <xsl:with-param name="stamp" select="//message[1]/@time"/>
+ </xsl:call-template>
+ </div>
+
+ <xsl:for-each select="*">
+
+ <xsl:variable name="prev-time">
+ <xsl:call-template name="get-day">
+ <xsl:with-param name="stamp" select="preceding-sibling::*[1]/@time"/>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:variable name="this-time">
+ <xsl:call-template name="get-day">
+ <xsl:with-param name="stamp" select="@time"/>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:if test="$prev-time &lt; $this-time">
+ <div class="new-day-stamp">
+ <xsl:call-template name="format-day-stamp">
+ <xsl:with-param name="stamp" select="@time"/>
+ </xsl:call-template>
+ </div>
+ </xsl:if>
+
+ <xsl:variable name="stamp">
+ <xsl:call-template name="format-stamp">
+ <xsl:with-param name="stamp" select="@time"/>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <span class="stamp">
+ <xsl:value-of select="$stamp"/>
+ </span>
+
+ <xsl:variable name="nick-class">
+ <xsl:choose>
+ <xsl:when test="not(string(@id))">nick-self</xsl:when>
+ <xsl:otherwise>nick</xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <span class="{$nick-class}">
+ &lt;<xsl:value-of select="@name"/>&gt;
+ </span>
+
+ <xsl:apply-templates/>
+ <br/>
+
+ </xsl:for-each>
+
+ </xsl:template>
+
+</xsl:stylesheet>
diff --git a/data/org.freedesktop.Telepathy.Logger.gschema.xml.in b/data/org.freedesktop.Telepathy.Logger.gschema.xml.in
new file mode 100644
index 000000000..ca39e36b9
--- /dev/null
+++ b/data/org.freedesktop.Telepathy.Logger.gschema.xml.in
@@ -0,0 +1,17 @@
+<schemalist>
+ <schema id="org.freedesktop.Telepathy.Logger" path="/apps/telepathy-logger/">
+ <key name="enabled" type="b">
+ <default>true</default>
+ <_summary>Enable logging</_summary>
+ <_description>
+ Globally enable or disable the Telepathy logger system. Setting it to
+ "false" will completely disable all logging.
+ </_description>
+ </key>
+ <key name="ignorelist" type="as">
+ <default>[]</default>
+ <_summary>Ignore list</_summary>
+ <_description>Conversations with entities with ID listed here will not be logged.</_description>
+ </key>
+ </schema>
+</schemalist>
diff --git a/docs/Makefile.am b/docs/Makefile.am
index f3ddc22dd..9fc661826 100644
--- a/docs/Makefile.am
+++ b/docs/Makefile.am
@@ -1 +1,11 @@
SUBDIRS = reference
+
+EXTRA_DIST = \
+ templates/errors.html \
+ templates/interfaces.html \
+ templates/style.css \
+ templates/generic-types.html \
+ templates/index.html \
+ templates/interface.html \
+ templates/devhelp.devhelp2 \
+ templates/fullindex.html
diff --git a/docs/reference/Makefile.am b/docs/reference/Makefile.am
index b2d87e85c..0310d80f7 100644
--- a/docs/reference/Makefile.am
+++ b/docs/reference/Makefile.am
@@ -1,3 +1,7 @@
SUBDIRS = \
telepathy-glib \
$(NULL)
+
+if ENABLE_LOGGER
+SUBDIRS += libtelepathy-logger
+endif
diff --git a/docs/reference/libtelepathy-logger/Makefile.am b/docs/reference/libtelepathy-logger/Makefile.am
new file mode 100644
index 000000000..4e647d7d5
--- /dev/null
+++ b/docs/reference/libtelepathy-logger/Makefile.am
@@ -0,0 +1,135 @@
+## Process this file with automake to produce Makefile.in
+
+abs_top_builddir = @abs_top_builddir@
+
+# We require automake 1.6 at least.
+AUTOMAKE_OPTIONS = 1.6
+
+# This is a blank Makefile.am for using gtk-doc.
+# Copy this to your project's API docs directory and modify the variables to
+# suit your project. See the GTK+ Makefiles in gtk+/docs/reference for examples
+# of using the various options.
+
+# The name of the module, e.g. 'glib'.
+DOC_MODULE=telepathy-logger-1
+
+# The top-level SGML file. You can change this if you want to.
+DOC_MAIN_SGML_FILE=docs.xml
+
+# The directory containing the source code. Relative to $(srcdir).
+# gtk-doc will search all .c & .h files beneath here for inline comments
+# documenting the functions and macros.
+# e.g. DOC_SOURCE_DIR=../../../gtk
+DOC_SOURCE_DIR=${abs_top_srcdir}/telepathy-logger
+
+# Extra options to pass to gtkdoc-scangobj. Not normally needed.
+SCANGOBJ_OPTIONS=
+
+# Extra options to supply to gtkdoc-scan.
+# e.g. SCAN_OPTIONS=--deprecated-guards="GTK_DISABLE_DEPRECATED"
+# --rebuild-sections means we get a section (xml/*.xml) per header file: this
+# should be removed if we want to maintain the sections file by hand, to split
+# or combine sections.
+SCAN_OPTIONS= \
+ --deprecated-guards=TP_DISABLE_DEPRECATED \
+ --rebuild-sections \
+ --rebuild-types
+
+# Extra options to supply to gtkdoc-mkdb.
+# e.g. MKDB_OPTIONS=--sgml-mode --output-format=xml
+MKDB_OPTIONS=--sgml-mode --output-format=xml
+
+# Extra options to supply to gtkdoc-mktmpl
+# e.g. MKTMPL_OPTIONS=--only-section-tmpl
+MKTMPL_OPTIONS=
+
+# Extra options to supply to gtkdoc-fixref. Not normally needed.
+# e.g. FIXXREF_OPTIONS=--extra-dir=../gdk-pixbuf/html --extra-dir=../gdk/html
+FIXXREF_OPTIONS=
+
+# Used for dependencies. The docs will be rebuilt if any of these change.
+# e.g. HFILE_GLOB=$(top_srcdir)/gtk/*.h
+# e.g. CFILE_GLOB=$(top_srcdir)/gtk/*.c
+HFILE_GLOB=$(top_srcdir)/telepathy-logger/*.h
+CFILE_GLOB=$(top_srcdir)/telepathy-logger/*.c
+
+# Header files to ignore when scanning.
+# e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h
+IGNORE_HFILES=\
+ action-chain-internal.h \
+ call-channel-internal.h \
+ call-event-internal.h \
+ channel-factory-internal.h \
+ channel-internal.h \
+ client-factory-internal.h \
+ conf-internal.h \
+ contact-internal.h \
+ dbus-service-internal.h \
+ debug-internal.h \
+ event-internal.h \
+ log-iter-internal.h \
+ log-iter-pidgin-internal.h \
+ log-iter-xml-internal.h \
+ log-manager-internal.h \
+ log-store.c \
+ log-store-factory-internal.h \
+ log-store-internal.h \
+ log-store-sqlite-internal.h \
+ log-store-xml-internal.h \
+ log-walker-internal.h \
+ observer-internal.h \
+ text-channel-internal.h \
+ text-event-internal.h \
+ util-internal.h
+
+# Images to copy into HTML directory.
+# e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png
+HTML_IMAGES=
+
+# Extra SGML files that are included by $(DOC_MAIN_SGML_FILE).
+# e.g. content_files=running.sgml building.sgml changes-2.0.sgml
+content_files=
+
+# SGML files where gtk-doc abbrevations (#GtkWidget) are expanded
+# These files must be listed here *and* in content_files
+# e.g. expand_content_files=running.sgml
+expand_content_files=
+
+# CFLAGS and LDFLAGS for compiling gtkdoc-scangobj with your library.
+# Only needed if you are using gtkdoc-scangobj to dynamically query widget
+# signals and properties.
+# e.g. AM_CPPFLAGS=-I$(top_srcdir) -I$(top_builddir) $(GTK_DEBUG_FLAGS)
+# e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib)
+AM_CPPFLAGS = \
+ -I$(top_srcdir) -I$(top_builddir) \
+ $(TPL_CFLAGS)
+GTKDOC_LIBS= \
+ $(TPL_LIBS) \
+ $(top_builddir)/telepathy-logger/libtelepathy-logger-1.la \
+ $(top_builddir)/telepathy-glib/libtelepathy-glib-1.la \
+ $(top_builddir)/telepathy-glib/libtelepathy-glib-1-dbus.la \
+ $(top_builddir)/telepathy-glib/libtelepathy-glib-1-core.la \
+ $(NULL)
+
+# This includes the standard gtk-doc make rules, copied by gtkdocize.
+include $(top_srcdir)/gtk-doc.make
+
+if ENABLE_GTK_DOC
+# Enable this enforce the documenting of symbols in `make check`
+# check-local: check-undocumented
+
+check-undocumented:
+ @if grep '^0 symbols incomplete' \
+ $(srcdir)/telepathy-logger-undocumented.txt; then\
+ :; else \
+ cat $(srcdir)/telepathy-logger-undocumented.txt; exit 1; fi
+ @if grep '^0 not documented' \
+ $(srcdir)/telepathy-logger-undocumented.txt; then\
+ :; else \
+ cat $(srcdir)/telepathy-logger-undocumented.txt; exit 1; fi
+ @if grep . $(srcdir)/telepathy-logger-unused.txt; then\
+ echo "^^^ Unused symbols" >&2; exit 1; fi
+ @if test -e $(srcdir)/telepathy-logger-undeclared.txt &&\
+ grep . $(srcdir)/telepathy-logger-undeclared.txt; then\
+ echo "^^^ Undeclared symbols" >&2; exit 1; fi
+endif
diff --git a/docs/reference/libtelepathy-logger/docs.xml b/docs/reference/libtelepathy-logger/docs.xml
new file mode 100644
index 000000000..dd1d09da2
--- /dev/null
+++ b/docs/reference/libtelepathy-logger/docs.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd">
+<book id="index" xmlns:xi="http://www.w3.org/2003/XInclude">
+ <bookinfo>
+ <title>telepathy-logger Reference Manual</title>
+ </bookinfo>
+
+ <chapter id="ch-public">
+ <title>libtelepathy-logger API</title>
+
+ <xi:include href="xml/annotation-glossary.xml"><xi:fallback /></xi:include>
+ <xi:include href="xml/log-manager.xml"/>
+ <xi:include href="xml/log-walker.xml"/>
+ <xi:include href="xml/event.xml"/>
+ <xi:include href="xml/text-event.xml"/>
+ <xi:include href="xml/call-event.xml"/>
+ <xi:include href="xml/entity.xml"/>
+ </chapter>
+
+</book>
+<!-- set sts=2 sw=2 et -->
diff --git a/docs/templates/devhelp.devhelp2 b/docs/templates/devhelp.devhelp2
new file mode 100644
index 000000000..af327fa6d
--- /dev/null
+++ b/docs/templates/devhelp.devhelp2
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<book xmlns="http://www.devhelp.net/book" title="$spec.title" name="$name" link="index.html">
+ <chapters>
+#for $interface in $spec.interfaces
+ <sub name="$interface.name" link="$interface.get_url()"/>
+#end for
+ <sub name="Generic Types" link="generic-types.html"/>
+ <sub name="Errors" link="errors.html"/>
+ <sub name="Full Index" link="fullindex.html"/>
+ </chapters>
+ <functions>
+#for $obj in $spec.everything.values() + $spec.types.values() + $spec.errors.values()
+ <keyword type="$obj.devhelp_name" name="$obj.get_title()" link="$obj.get_url()" #slurp
+#if $obj.deprecated: deprecated="true" #slurp
+/>
+#end for
+ </functions>
+</book>
diff --git a/docs/templates/errors.html b/docs/templates/errors.html
new file mode 100644
index 000000000..907d6601c
--- /dev/null
+++ b/docs/templates/errors.html
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" "">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+ <head>
+ <title>Errors</title>
+ <link rel="stylesheet" href="style.css" type="text/css"/>
+ </head>
+ <body>
+ <div class="header">
+ <h1>Errors</h1>
+ <a href="index.html">Interface Index</a>
+ (<a href="interfaces.html">Compact</a>)
+ | <a href="#summary">Summary</a>
+ | <a href="#errors">Errors</a>
+ </div>
+ <div class="main">
+ <div class="summary">
+ <a name="summary"></a>
+ <h3>Errors</h3>
+ <table class="summary">
+ #for $error in $spec.errors.values()
+ #if $error.deprecated
+ <tr class="deprecated">
+ #else
+ <tr>
+ #end if
+ <td><a href="$error.get_url()">$error.short_name</a></td>
+ <td>
+ #if $error.deprecated: (deprecated)
+ </td>
+ </tr>
+ #end for
+ </table>
+ </div>
+
+ <div class="outset errors error">
+ <a name="errors"></a>
+ <h1>Errors</h1>
+ #for $error in $spec.errors.values()
+ <div class="inset error">
+ <a name="$error.name"></a>
+ <span class="permalink">(<a href="$error.get_url()">Permalink</a>)</span>
+ <h2>
+ $error.short_name
+ </h2>
+
+ <div class="indent">
+ <code>$error.name</code>
+ </div>
+
+ $error.get_added()
+ $error.get_deprecated()
+ $error.get_docstring()
+ </div>
+ #end for
+ </div>
+ </div>
+
+ </body>
+</html>
diff --git a/docs/templates/fullindex.html b/docs/templates/fullindex.html
new file mode 100644
index 000000000..2c465e1dd
--- /dev/null
+++ b/docs/templates/fullindex.html
@@ -0,0 +1,60 @@
+#from itertools import groupby
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" "">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+ <head>
+ <title>Full Index</title>
+ <link rel="stylesheet" href="style.css" type="text/css"/>
+ </head>
+
+#set $star = []
+#for $item in $spec.everything.values() + $spec.errors.values() + $spec.generic_types
+ #echo $star.append(($item.short_name, $item))
+ #slurp
+#end for
+#echo $star.sort(key = lambda t: t[0].title())
+#slurp
+## one use iterators...
+#set $groups = [ (l, list(g)) for l, g in (groupby($star, key = lambda t: t[0][0].upper())) ]
+#set $letters = set(map(lambda t: t[0], groups))
+
+ <body>
+ <div class="header">
+ <h1>Full Index</h1>
+ <a href="index.html">Interface Index</a>
+ (<a href="interfaces.html">Compact</a>)
+ #for $a in map(chr, xrange(ord('A'), ord('Z')+1))
+ #if $a in $letters
+ | <a href="#$a">$a</a>
+ #else
+ | $a
+ #end if
+ #end for
+ </div>
+
+ <div class="main">
+ <table class="summary">
+ #for l, g in $groups
+ <tr><th colspan="3"><a name="$l"></a>$l</th></tr>
+ #for $n in $g
+ #if $n[1].deprecated
+ <tr class="deprecated">
+ #else
+ <tr>
+ #end if
+ <td>
+ <a href="$n[1].get_url()" title="$n[1].get_title()">$n[0]</a>
+ #if $n[1].deprecated: (deprecated)
+ </td>
+ <td>$n[1].get_type_name()</td>
+ <td>
+ #if $n[1].parent.__class__.__name__ == 'Interface': $n[1].parent.name
+ </td>
+ </tr>
+ #end for
+ #end for
+ <table>
+ </div>
+
+ </body>
+</html>
diff --git a/docs/templates/generic-types.html b/docs/templates/generic-types.html
new file mode 100644
index 000000000..0bb209e43
--- /dev/null
+++ b/docs/templates/generic-types.html
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" "">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+ <head>
+ <title>Generic Types</title>
+ <link rel="stylesheet" href="style.css" type="text/css"/>
+ </head>
+ <body>
+ <div class="header">
+ <h1>Generic Types</h1>
+ <a href="index.html">Interface Index</a>
+ (<a href="interfaces.html">Compact</a>)
+ | <a href="#summary">Summary</a>
+ | <a href="#types">Types</a>
+ </div>
+ <div class="main">
+ <div class="summary">
+ <a name="summary"></a>
+ <h3>Generic Types</h3>
+ <table class="summary">
+ #for $type in $spec.generic_types
+ #if $type.deprecated
+ <tr class="deprecated">
+ #else
+ <tr>
+ #end if
+ <td><a href="$type.get_url()">$type.short_name</a></td>
+ <td>$type.get_type_name()</td>
+ <td>$type.dbus_type</td>
+ <td>
+ #if $type.deprecated: (deprecated)
+ </td>
+ </tr>
+ #end for
+ </table>
+ </div>
+
+ <div class="outset types type">
+ <a name="types"></a>
+ <h1>Generic Types</h1>
+ #for $type in $spec.generic_types
+ <div class="inset type">
+ <a name="$type.name"></a>
+ <span class="permalink">$type.get_type_name() (<a href="$type.get_url()">Permalink</a>)</span>
+ <h2>
+ $type.short_name &mdash; $type.dbus_type
+ </h2>
+
+ $type.get_added()
+ $type.get_deprecated()
+ $type.get_docstring()
+ $type.get_breakdown()
+ </div>
+ #end for
+ </div>
+ </div>
+
+ </body>
+</html>
diff --git a/docs/templates/index.html b/docs/templates/index.html
new file mode 100644
index 000000000..efc38d4e8
--- /dev/null
+++ b/docs/templates/index.html
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" "">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+ <head>
+ <title>$spec.title &mdash v$spec.version</title>
+ <link rel="stylesheet" href="style.css" type="text/css"/>
+ </head>
+ <body>
+ <div class="header">
+ <h1>$spec.title</h1>
+ <a href="#interfaces">Interfaces</a>
+ (<a href="interfaces.html">Compact</a>)
+ | <a href="generic-types.html">Generic Types</a>
+ | <a href="errors.html">Errors</a>
+ | <a href="fullindex.html">Full Index</a>
+ </div>
+
+ <div class="main">
+ <h3 class="version">Version $spec.version</h3>
+ <p class="copyrights">
+ #echo '<br/>'.join($spec.copyrights)
+ </p>
+ $spec.license
+
+ <a name="interfaces"></a>
+ <h3>Interfaces</h3>
+ <ul>
+ #def output($items)
+ #for $item in $items
+ #if $item.__class__.__name__ == 'Section'
+ <li class="chapter">$item.short_name</li>
+ $item.get_docstring()
+ <ul>
+ $output($item.items)
+ </ul>
+ #else
+ #if $item.causes_havoc
+ <li class="causes-havoc">
+ #elif $item.deprecated
+ <li class="deprecated">
+ #else
+ <li>
+ #end if
+ <a href="$item.get_url()">$item.name</a>
+ #if $item.causes_havoc
+ (unstable)
+ #elif $item.deprecated
+ (deprecated)
+ #end if
+ </li>
+ #end if
+ #end for
+ #end def
+ $output($spec.items)
+ </ul>
+
+ <a name="other"></a>
+ <h3>Other</h3>
+ <ul>
+ <li><a href="generic-types.html">Generic Types</a></li>
+ <li><a href="errors.html">Errors</a></li>
+ </ul>
+
+ </div>
+ </body>
+</html>
diff --git a/docs/templates/interface.html b/docs/templates/interface.html
new file mode 100644
index 000000000..42ad0cee6
--- /dev/null
+++ b/docs/templates/interface.html
@@ -0,0 +1,424 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" "">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+ <head>
+ <title>$interface.name</title>
+ <link rel="stylesheet" href="style.css" type="text/css"/>
+ </head>
+ <body>
+ <div class="header">
+ <h1>Interface $interface.name</h1>
+ <a href="index.html">Interface Index</a>
+ (<a href="interfaces.html">Compact</a>)
+ | <a href="#summary">Summary</a>
+ #if $interface.docstring: | <a href="#description">Description</a>
+ #if $interface.methods: | <a href="#methods">Methods</a>
+ #if $interface.signals: | <a href="#signals">Signals</a>
+ #if $interface.properties: | <a href="#properties">Properties</a>
+ #if $interface.tpproperties: | <a href="#tpproperties">Telepathy Properties</a>
+ #if $interface.contact_attributes: | <a href="#contact-attributes">Contact Attributes</a>
+ #if $interface.handler_capability_tokens: | <a href="#handler-capability-tokens">Handler Capability Tokens</a>
+ #if $interface.types: | <a href="#types">Types</a>
+ </div>
+ <div class="main">
+
+ #if $interface.methods or $interface.signals or $interface.properties or $interface.types or $interface.tpproperties
+ <div class="summary">
+ <a name="summary"></a>
+ #if $interface.methods
+ <h3>Methods</h3>
+ <table class="summary">
+ #for $method in $interface.methods
+ #if $method.deprecated
+ <tr class="deprecated">
+ #else
+ <tr>
+ #end if
+ <td><a href="$method.get_url()">$method.short_name</a></td>
+ <td>($method.get_in_args())</td>
+ <td>&#8594;</td>
+ <td>$method.get_out_args()</td>
+ <td>
+ #if $method.deprecated: (deprecated)
+ </td>
+ </tr>
+ #end for
+ </table>
+ #end if
+
+ #if $interface.signals
+ <h3>Signals</h3>
+ <table class="summary">
+ #for $signal in $interface.signals
+ #if $signal.deprecated
+ <tr class="deprecated">
+ #else
+ <tr>
+ #end if
+ <td><a href="$signal.get_url()">$signal.short_name</a></td>
+ <td>($signal.get_args())</td>
+ <td>
+ #if $signal.deprecated: (deprecated)
+ </td>
+ </tr>
+ #end for
+ </table>
+ #end if
+
+ #if $interface.properties
+ <h3>Properties</h3>
+ <table class="summary">
+ #for $property in $interface.properties
+ #if $property.deprecated
+ <tr class="deprecated">
+ #else
+ <tr>
+ #end if
+ <td><a href="$property.get_url()">$property.short_name</a></td>
+ <td>
+ $property.dbus_type
+ #if $property.type: (<a href="$property.get_type_url()" title="$property.get_type_title()">$property.get_type().short_name</a>)
+ </td>
+ <td>$property.get_access()</td>
+ <td>
+ #if $property.deprecated: (deprecated)
+ </td>
+ </tr>
+ #end for
+ </table>
+ #end if
+
+ #if $interface.tpproperties
+ <h3>Telepathy Properties</h3>
+ <table class="summary">
+ #for $property in $interface.tpproperties
+ <tr class="deprecated">
+ <td><a href="$property.get_url()">$property.short_name</a></td>
+ <td>
+ $property.dbus_type
+ #if $property.type: (<a href="$property.get_type_url()" title="$property.get_type_title()">$property.get_type().short_name</a>)
+ </td>
+ </tr>
+ #end for
+ </table>
+ #end if
+
+ #if $interface.contact_attributes
+ <h3>Contact Attributes</h3>
+ <table class="summary">
+ #for $token in $interface.contact_attributes
+ <tr class="contact-attribute">
+ <td><a href="$token.get_url()">$token.name</a></td>
+ <td>
+ $token.dbus_type
+ #if $token.type: (<a href="$token.get_type_url()" title="$token.get_type_title()">$token.get_type().short_name</a>)
+ </td>
+ </tr>
+ #end for
+ </table>
+ #end if
+
+ #if $interface.handler_capability_tokens
+ <h3>Handler Capability Tokens</h3>
+ <table class="summary">
+ #for $token in $interface.handler_capability_tokens
+ <tr class="handler-capability-token">
+ <td><a href="$token.get_url()">$token.name</a>
+ #if $token.is_family
+ (etc.)
+ #end if
+ </td>
+ <td>
+ </td>
+ </tr>
+ #end for
+ </table>
+ #end if
+
+ #if $interface.types
+ <h3>Types</h3>
+ <table class="summary">
+ #for $type in $interface.types
+ #if type.deprecated
+ <tr class="deprecated">
+ #else
+ <tr>
+ #end if
+ <td><a href="$type.get_url()">$type.short_name</a></td>
+ <td>$type.get_type_name()</td>
+ <td>$type.dbus_type</td>
+ <td>
+ #if $type.deprecated: (deprecated)
+ </td>
+ </tr>
+ #end for
+ </table>
+ #end if
+ </div>
+ #end if
+
+ #if $interface.causes_havoc
+ <div class="havoc"><span class="warning">WARNING:</span>
+ This interface is $interface.causes_havoc and is likely to cause havoc
+ to your API/ABI if bindings are generated. Do not include this interface
+ in libraries that care about compatibility.
+ </div>
+ #end if
+ $interface.get_added()
+ $interface.get_changed()
+ $interface.get_deprecated()
+
+ #if $interface.requires
+ <div class="requires">
+ Objects implementing this interface must also implement:
+ <ul>
+ #for $req in $interface.get_requires()
+ <li><a href="$req.get_url()" title="$req.get_title()">$req.name</a></li>
+ #end for
+ </ul>
+ </div>
+ #end if
+
+ #if $interface.docstring
+ <a name="description"></a>
+ <h3>Description</h3>
+ $interface.get_docstring()
+ #end if
+
+ #if $interface.methods
+ <div class="outset methods method">
+ <a name="methods"></a>
+ <h1>Methods</h1>
+ #for $method in $interface.methods
+ <div class="inset method">
+ <a name="$method.name"></a>
+ <span class="permalink">(<a href="$method.get_url()">Permalink</a>)</span>
+ <h2>$method.short_name ($method.get_in_args()) &#8594; $method.get_out_args()</h2>
+
+ $method.get_added()
+ $method.get_changed()
+ $method.get_deprecated()
+
+ #if $method.in_args
+ <div class="indent">
+ <h3>Parameters</h3>
+ <ul>
+ #for $arg in $method.in_args
+ <li>
+ $arg.short_name &mdash; $arg.dbus_type
+ #if $arg.get_type(): (<a href="$arg.get_type_url()" title="$arg.get_type_title()">$arg.get_type().short_name</a>)
+ </li>
+ $arg.get_added()
+ $arg.get_changed()
+ $arg.get_deprecated()
+ $arg.get_docstring()
+ #end for
+ </ul>
+ </div>
+ #end if
+
+ #if $method.out_args
+ <div class="indent">
+ <h3>Returns</h3>
+ <ul>
+ #for $arg in $method.out_args
+ <li>
+ $arg.short_name &mdash; $arg.dbus_type
+ #if $arg.get_type(): (<a href="$arg.get_type_url()" title="$arg.get_type_title()">$arg.get_type().short_name</a>)
+ </li>
+ $arg.get_added()
+ $arg.get_changed()
+ $arg.get_deprecated()
+ $arg.get_docstring()
+ #end for
+ </ul>
+ </div>
+ #end if
+
+ $method.get_docstring()
+
+ #if $method.possible_errors
+ <hr/>
+ <div class="indent">
+ <h3>Possible Errors</h3>
+ <ul>
+ #for $error in $method.possible_errors
+ <li><a href="$error.get_url()" title="$error.get_title()">$error.get_error().short_name</a></li>
+ $error.get_added()
+ $error.get_changed()
+ $error.get_deprecated()
+ $error.get_docstring()
+ #end for
+ </ul>
+ </div>
+ #end if
+ </div>
+ #end for
+ </div>
+ #end if
+
+ #if $interface.signals
+ <div class="outset signals signal">
+ <a name="signals"></a>
+ <h1>Signals</h1>
+ #for $signal in $interface.signals
+ <div class="inset signal">
+ <a name="$signal.name"></a>
+ <span class="permalink">(<a href="$signal.get_url()">Permalink</a>)</span>
+ <h2>$signal.short_name ($signal.get_args())</h2>
+
+ $signal.get_added()
+ $signal.get_changed()
+ $signal.get_deprecated()
+
+ #if $signal.args
+ <div class="indent">
+ <h3>Parameters</h3>
+ <ul>
+ #for $arg in $signal.args
+ <li>
+ $arg.short_name &mdash; $arg.dbus_type
+ #if $arg.get_type(): (<a href="$arg.get_type_url()" title="$arg.get_type_title()">$arg.get_type().short_name</a>)
+ </li>
+ $arg.get_added()
+ $arg.get_changed()
+ $arg.get_deprecated()
+ $arg.get_docstring()
+ #end for
+ </ul>
+ </div>
+ #end if
+
+ $signal.get_docstring()
+ </div>
+ #end for
+ </div>
+ #end if
+
+ #if $interface.properties
+ <div class="outset properties property">
+ <a name="properties"></a>
+ <h1>Properties</h1>
+ <div>
+ Accessed using the org.freedesktop.DBus.Properties interface.
+ </div>
+ #for $property in $interface.properties
+ <div class="inset property">
+ <a name="$property.name"></a>
+ <span class="permalink">(<a href="$property.get_url()">Permalink</a>)</span>
+ <h2>
+ $property.short_name &mdash; $property.dbus_type
+ #if $property.type: (<a href="$property.get_type_url()" title="$property.get_type_title()">$property.get_type().short_name</a>)
+ </h2>
+ <div class="access">$property.get_access()</div>
+
+ $property.get_added()
+ $property.get_changed()
+ $property.get_deprecated()
+ $property.get_docstring()
+ </div>
+ #end for
+ </div>
+ #end if
+
+ #if $interface.tpproperties
+ <div class="outset tpproperties tpproperty">
+ <a name="tpproperties"></a>
+ <h1>Telepathy Properties</h1>
+ <div>
+ Accessed using the im.telepathy1.Properties interface.
+ </div>
+ #for $property in $interface.tpproperties
+ <div class="inset tpproperty">
+ <a name="$property.name"></a>
+ <span class="permalink">(<a href="$property.get_url()">Permalink</a>)</span>
+ <h2>
+ $property.short_name &mdash; $property.dbus_type
+ #if $property.type: (<a href="$property.get_type_url()" title="$property.get_type_title()">$property.get_type().short_name</a>)
+ </h2>
+ $property.get_added()
+ $property.get_changed()
+ $property.get_deprecated()
+ $property.get_docstring()
+ </div>
+ #end for
+ </div>
+ #end if
+
+ #if $interface.contact_attributes
+ <div class="outset contact-attributes">
+ <a name="contact-attributes"></a>
+ <h1>Contact Attributes</h1>
+ <div>
+ Attributes that a contact can have, accessed with the
+ im.telepathy1.Connection.Interface.Contacts interface.
+ </div>
+ #for $token in $interface.contact_attributes
+ <div class="inset contact-attribute">
+ <a name="$token.name"></a>
+ <span class="permalink">(<a href="$token.get_url()">Permalink</a>)</span>
+ <h2>
+ $token.name &mdash; $token.dbus_type
+ #if $token.type: (<a href="$token.get_type_url()" title="$token.get_type_title()">$token.get_type().short_name</a>)
+ </h2>
+ $token.get_added()
+ $token.get_changed()
+ $token.get_deprecated()
+ $token.get_docstring()
+ </div>
+ #end for
+ </div>
+ #end if
+
+ #if $interface.handler_capability_tokens
+ <div class="outset handler-capability-tokens">
+ <a name="handler-capability-tokens"></a>
+ <h1>Handler Capability Tokens</h1>
+ <div>
+ Tokens representing capabilities that a Client.Handler can have.
+ </div>
+ #for $token in $interface.handler_capability_tokens
+ <div class="inset handler-capability-token">
+ <a name="$token.name"></a>
+ <span class="permalink">(<a href="$token.get_url()">Permalink</a>)</span>
+ <h2>
+ $token.name
+ #if $token.is_family
+ (etc.)
+ #end if
+ </h2>
+ $token.get_added()
+ $token.get_changed()
+ $token.get_deprecated()
+ $token.get_docstring()
+ </div>
+ #end for
+ </div>
+ #end if
+
+ #if $interface.types
+ <div class="outset types type">
+ <a name="types"></a>
+ <h1>Types</h1>
+ #for $type in $interface.types
+ <div class="inset type">
+ <a name="$type.name"></a>
+ <span class="permalink">$type.get_type_name() (<a href="$type.get_url()">Permalink</a>)</span>
+ <h2>
+ $type.short_name &mdash; $type.dbus_type
+ </h2>
+
+ $type.get_added()
+ $type.get_changed()
+ $type.get_deprecated()
+ $type.get_docstring()
+ $type.get_breakdown()
+ </div>
+ #end for
+ </div>
+ #end if
+
+ </div>
+
+ </body>
+</html>
diff --git a/docs/templates/interfaces.html b/docs/templates/interfaces.html
new file mode 100644
index 000000000..a93334c65
--- /dev/null
+++ b/docs/templates/interfaces.html
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" "">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+ <head>
+ <title>$spec.title &mdash v$spec.version</title>
+ <link rel="stylesheet" href="style.css" type="text/css"/>
+ </head>
+ <body>
+ <div class="header">
+ <h1>$spec.title</h1>
+ <a href="index.html">Full</a>
+ | <a href="generic-types.html">Generic Types</a>
+ | <a href="errors.html">Errors</a>
+ | <a href="fullindex.html">Full Index</a>
+ </div>
+
+ <div class="main">
+ <b>Version $spec.version</b>
+
+ <a name="interfaces"></a>
+ <h3>Interfaces</h3>
+ <ul>
+ #for $interface in $spec.interfaces
+ #if $interface.causes_havoc
+ <li class="causes-havoc">
+ #elif $interface.deprecated
+ <li class="deprecated">
+ #else
+ <li>
+ #end if
+ <a href="$interface.get_url()">$interface.name</a>
+ #if $interface.causes_havoc
+ (unstable)
+ #elif $interface.deprecated
+ (deprecated)
+ #end if
+ </li>
+ #end for
+ </ul>
+
+ <a name="other"></a>
+ <h3>Other</h3>
+ <ul>
+ <li><a href="generic-types.html">Generic Types</a></li>
+ <li><a href="errors.html">Errors</a></li>
+ </ul>
+
+ </div>
+ </body>
+</html>
diff --git a/docs/templates/style.css b/docs/templates/style.css
new file mode 100644
index 000000000..964d88bd4
--- /dev/null
+++ b/docs/templates/style.css
@@ -0,0 +1,224 @@
+html, body,
+h1, h2 {
+ margin: 0;
+ padding: 0;
+}
+
+h3 {
+ margin-top: 2pt;
+ margin-bottom: 2pt;
+}
+
+ul {
+ margin: 1ex;
+ margin-left: 1.5em;
+ padding: 0;
+}
+
+hr {
+ border-style: none;
+ color: #cccccc;
+ background-color: #cccccc;
+ height: 1px;
+}
+
+div.header {
+ position: fixed;
+ height: 4em;
+ background-color: white;
+ width: 100%;
+ margin: 0;
+ padding: 0.5ex;
+ border-bottom: 1px solid black;
+ top: 0;
+ left: 0;
+ z-index: 1;
+}
+
+div.header h1 {
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+
+div.main {
+ margin-top: 5em;
+ margin-left: 1ex;
+ margin-right: 1ex;
+ margin-bottom: 1ex;
+}
+
+div.main a[name] {
+ position: relative;
+ top: -4.5em;
+}
+
+div.outset {
+ padding: 1ex;
+ margin-top: 1ex;
+ margin-bottom: 1ex;
+}
+
+div.inset {
+ background-color: white;
+ margin-top: 1ex;
+ margin-bottom: 1ex;
+ padding: 0.5ex;
+}
+
+div.indent {
+ margin-left: 1em;
+}
+
+div.methods {
+ background-color: #fcaf3e;
+}
+
+div.method {
+ border: 1px solid #f57900;
+}
+
+div.signals {
+ background-color: #729fcf;
+}
+
+div.signal {
+ border: 1px solid #3465a4;
+}
+
+div.properties {
+ background-color: #ad7fa8;
+}
+
+div.property {
+ border: 1px solid #75507b;
+}
+
+div.tpproperties {
+ background-color: #999999;
+}
+
+div.tpproperty {
+ border: 1px solid #333333;
+}
+
+div.contact-attributes {
+ background-color: #ccccff;
+ border: 1px solid #9999cc;
+}
+
+div.contact-attribute {
+ border: 1px solid #9999cc;
+}
+
+div.handler-capability-tokens {
+ background-color: #339933;
+ border: 1px solid #228822;
+}
+
+div.handler-capability-token {
+ border: 1px solid #228822;
+}
+
+div.types {
+ background-color: #e9b96e;
+}
+
+div.type {
+ border: 1px solid #c17d11;
+}
+
+div.errors {
+ background-color: #ef2929;
+}
+
+div.error {
+ border: 1px solid #cc0000;
+}
+
+div.access {
+ font-weight: bold;
+ margin-left: 1ex;
+}
+
+div.summary {
+ padding: 0.5ex;
+ background-color: #eeeeec;
+ border: 1px solid #d3d7cf;
+}
+
+table.summary {
+ margin: 1ex;
+ font-size: small;
+}
+
+table.summary td {
+ padding-right: 1ex;
+}
+
+li.chapter {
+ margin-top: 1ex;
+ font-weight: bold;
+}
+
+li.causes-havoc {
+ font-style: italic;
+}
+
+li.deprecated,
+li.deprecated a,
+table.summary tr.deprecated td,
+table.summary tr.deprecated td a {
+ color: gray;
+}
+
+div.requires,
+div.docstring {
+ margin: 1ex;
+}
+
+div.added {
+ border-left: 2px solid #4e9a06;
+ margin: 1ex;
+ padding-left: 1ex;
+}
+
+div.added span.version {
+ color: #4e9a06;
+ font-weight: bold;
+}
+
+div.changed {
+ border-left: 2px solid #8f5902;
+ margin: 1ex;
+ padding-left: 1ex;
+}
+
+div.changed span.version {
+ color: #8f5902;
+ font-weight: bold;
+}
+
+div.deprecated,
+div.havoc {
+ border-left: 2px solid #a40000;
+ margin: 1ex;
+ padding-left: 1ex;
+}
+
+div.deprecated span.version,
+span.warning {
+ color: #a40000;
+ font-weight: bold;
+}
+
+div.rationale {
+ border-left: 2px solid gray;
+ margin: 1ex;
+ padding-left: 1ex;
+}
+
+span.permalink {
+ float: right;
+ font-size: x-small;
+}
diff --git a/m4/.gitignore b/m4/.gitignore
new file mode 100644
index 000000000..46125d52e
--- /dev/null
+++ b/m4/.gitignore
@@ -0,0 +1,9 @@
+gtk-doc.m4
+intltool.m4
+gnome-doc-utils.m4
+libtool.m4
+ltoptions.m4
+ltsugar.m4
+ltversion.m4
+lt~obsolete.m4
+
diff --git a/m4/empathy-args.m4 b/m4/empathy-args.m4
new file mode 100644
index 000000000..3daacbaba
--- /dev/null
+++ b/m4/empathy-args.m4
@@ -0,0 +1,19 @@
+dnl configure-time options for Empathy
+
+dnl EMPATHY_ARG_VALGRIND
+
+AC_DEFUN([EMPATHY_ARG_VALGRIND],
+[
+ dnl valgrind inclusion
+ AC_ARG_ENABLE(valgrind,
+ AC_HELP_STRING([--enable-valgrind],[enable valgrind checking and run-time detection]),
+ [
+ case "${enableval}" in
+ yes|no) enable="${enableval}" ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --enable-valgrind) ;;
+ esac
+ ],
+ [enable=no])
+
+ EMPATHY_VALGRIND($enable, [2.1])
+])
diff --git a/m4/empathy-valgrind.m4 b/m4/empathy-valgrind.m4
new file mode 100644
index 000000000..7a44e103f
--- /dev/null
+++ b/m4/empathy-valgrind.m4
@@ -0,0 +1,31 @@
+dnl Detect Valgrind location and flags
+
+AC_DEFUN([EMPATHY_VALGRIND],
+[
+ enable=$1
+ if test -n "$2"; then
+ valgrind_req=$2
+ else
+ valgrind_req="2.1"
+ fi
+
+ PKG_CHECK_MODULES(VALGRIND, valgrind > "$valgrind_req",
+ have_valgrind_runtime="yes", have_valgrind_runtime="no")
+
+ AC_PATH_PROG(VALGRIND_PATH, valgrind)
+
+ # Compile the instrumentation for valgrind only if the valgrind
+ # libraries are installed and the valgrind executable is found
+ if test "x$enable" = xyes &&
+ test "$have_valgrind_runtime" = yes &&
+ test -n "$VALGRIND_PATH" ;
+ then
+ AC_DEFINE(HAVE_VALGRIND, 1, [Define if valgrind should be used])
+ AC_MSG_NOTICE(using compile-time instrumentation for valgrind)
+ fi
+
+ AC_SUBST(VALGRIND_CFLAGS)
+ AC_SUBST(VALGRIND_LIBS)
+
+ AM_CONDITIONAL(HAVE_VALGRIND, test -n "$VALGRIND_PATH")
+])
diff --git a/m4/gsettings.m4 b/m4/gsettings.m4
new file mode 100644
index 000000000..ac9945e9d
--- /dev/null
+++ b/m4/gsettings.m4
@@ -0,0 +1,93 @@
+dnl GLIB_GSETTINGS
+dnl Defines GSETTINGS_SCHEMAS_INSTALL which controls whether
+dnl the schema should be compiled
+dnl
+
+AC_DEFUN([GLIB_GSETTINGS],
+[
+ m4_pattern_allow([AM_V_GEN])
+ AC_ARG_ENABLE(schemas-compile,
+ AC_HELP_STRING([--disable-schemas-compile],
+ [Disable regeneration of gschemas.compiled on install]),
+ [case ${enableval} in
+ yes) GSETTINGS_DISABLE_SCHEMAS_COMPILE="" ;;
+ no) GSETTINGS_DISABLE_SCHEMAS_COMPILE="1" ;;
+ *) AC_MSG_ERROR([bad value ${enableval} for --enable-schemas-compile]) ;;
+ esac])
+ AC_SUBST([GSETTINGS_DISABLE_SCHEMAS_COMPILE])
+ PKG_PROG_PKG_CONFIG([0.16])
+ AC_SUBST(gsettingsschemadir, [${datadir}/glib-2.0/schemas])
+ if test x$cross_compiling != xyes; then
+ GLIB_COMPILE_SCHEMAS=`$PKG_CONFIG --variable glib_compile_schemas gio-2.0`
+ else
+ AC_PATH_PROG(GLIB_COMPILE_SCHEMAS, glib-compile-schemas)
+ fi
+ AC_SUBST(GLIB_COMPILE_SCHEMAS)
+ if test "x$GLIB_COMPILE_SCHEMAS" = "x"; then
+ ifelse([$2],,[AC_MSG_ERROR([glib-compile-schemas not found.])],[$2])
+ else
+ ifelse([$1],,[:],[$1])
+ fi
+
+ GSETTINGS_RULES='
+.PHONY : uninstall-gsettings-schemas install-gsettings-schemas clean-gsettings-schemas
+
+mostlyclean-am: clean-gsettings-schemas
+
+gsettings__enum_file = $(addsuffix .enums.xml,$(gsettings_ENUM_NAMESPACE))
+
+%.gschema.valid: %.gschema.xml $(gsettings__enum_file)
+ $(AM_V_GEN) if test -f "$<"; then d=; else d="$(srcdir)/"; fi; $(GLIB_COMPILE_SCHEMAS) --dry-run $(addprefix --schema-file=,$(gsettings__enum_file)) --schema-file=$${d}$< && touch [$]@
+
+all-am: $(gsettings_SCHEMAS:.xml=.valid)
+uninstall-am: uninstall-gsettings-schemas
+install-data-am: install-gsettings-schemas
+
+.SECONDARY: $(gsettings_SCHEMAS)
+
+gsettings__base_list = \
+ sed "$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g" | \
+ sed "$$!N;$$!N;$$!N;$$!N;s/\n/ /g"
+
+install-gsettings-schemas: $(gsettings_SCHEMAS:.xml=.valid) $(gsettings__enum_file)
+ @$(NORMAL_INSTALL)
+ test -z "$(gsettingsschemadir)" || $(MKDIR_P) "$(DESTDIR)$(gsettingsschemadir)"
+ @list='\''$(gsettings__enum_file) $(gsettings_SCHEMAS)'\''; test -n "$(gsettingsschemadir)" || list=; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(gsettings__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_DATA) $$files '\''$(DESTDIR)$(gsettingsschemadir)'\''"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(gsettingsschemadir)" || exit $$?; \
+ done
+ test -n "$(GSETTINGS_DISABLE_SCHEMAS_COMPILE)$(DESTDIR)" || $(GLIB_COMPILE_SCHEMAS) $(gsettingsschemadir)
+
+uninstall-gsettings-schemas:
+ @$(NORMAL_UNINSTALL)
+ @list='\''$(gsettings_SCHEMAS) $(gsettings__enum_file)'\''; test -n "$(gsettingsschemadir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e '\''s|^.*/||'\''`; \
+ test -n "$$files" || exit 0; \
+ echo " ( cd '\''$(DESTDIR)$(gsettingsschemadir)'\'' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(gsettingsschemadir)" && rm -f $$files
+ test -n "$(GSETTINGS_DISABLE_SCHEMAS_COMPILE)$(DESTDIR)" || $(GLIB_COMPILE_SCHEMAS) $(gsettingsschemadir)
+
+clean-gsettings-schemas:
+ rm -f $(gsettings_SCHEMAS:.xml=.valid) $(gsettings__enum_file)
+
+ifdef gsettings_ENUM_NAMESPACE
+$(gsettings__enum_file): $(gsettings_ENUM_FILES)
+ $(AM_V_GEN) glib-mkenums --comments '\''<!-- @comment@ -->'\'' --fhead "<schemalist>" --vhead " <@type@ id='\''$(gsettings_ENUM_NAMESPACE).@EnumName@'\''>" --vprod " <value nick='\''@valuenick@'\'' value='\''@valuenum@'\''/>" --vtail " </@type@>" --ftail "</schemalist>" $(gsettings_ENUM_FILES) > [$]@.tmp && mv [$]@.tmp [$]@
+endif
+'
+ _GSETTINGS_SUBST(GSETTINGS_RULES)
+])
+
+dnl _GSETTINGS_SUBST(VARIABLE)
+dnl Abstract macro to do either _AM_SUBST_NOTMAKE or AC_SUBST
+AC_DEFUN([_GSETTINGS_SUBST],
+[
+AC_SUBST([$1])
+m4_ifdef([_AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE([$1])])
+]
+)
diff --git a/m4/python.m4 b/m4/python.m4
new file mode 100644
index 000000000..fe901562d
--- /dev/null
+++ b/m4/python.m4
@@ -0,0 +1,66 @@
+## this one is commonly used with AM_PATH_PYTHONDIR ...
+dnl AM_CHECK_PYMOD(MODNAME [,SYMBOL [,ACTION-IF-FOUND [,ACTION-IF-NOT-FOUND]]])
+dnl Check if a module containing a given symbol is visible to python.
+AC_DEFUN([AM_CHECK_PYMOD],
+[AC_REQUIRE([AM_PATH_PYTHON])
+py_mod_var=`echo $1['_']$2 | sed 'y%./+-%__p_%'`
+AC_MSG_CHECKING(for ifelse([$2],[],,[$2 in ])python module $1)
+AC_CACHE_VAL(py_cv_mod_$py_mod_var, [
+ifelse([$2],[], [prog="
+import sys
+try:
+ import $1
+except ImportError:
+ sys.exit(1)
+except:
+ sys.exit(0)
+sys.exit(0)"], [prog="
+import $1
+$1.$2"])
+if $PYTHON -c "$prog" 1>&AC_FD_CC 2>&AC_FD_CC
+ then
+ eval "py_cv_mod_$py_mod_var=yes"
+ else
+ eval "py_cv_mod_$py_mod_var=no"
+ fi
+])
+py_val=`eval "echo \`echo '$py_cv_mod_'$py_mod_var\`"`
+if test "x$py_val" != xno; then
+ AC_MSG_RESULT(yes)
+ ifelse([$3], [],, [$3
+])dnl
+else
+ AC_MSG_RESULT(no)
+ ifelse([$4], [],, [$4
+])dnl
+fi
+])
+
+dnl a macro to check for ability to create python extensions
+dnl AM_CHECK_PYTHON_HEADERS([ACTION-IF-POSSIBLE], [ACTION-IF-NOT-POSSIBLE])
+dnl function also defines PYTHON_INCLUDES
+AC_DEFUN([AM_CHECK_PYTHON_HEADERS],
+[AC_REQUIRE([AM_PATH_PYTHON])
+AC_MSG_CHECKING(for headers required to compile python extensions)
+dnl deduce PYTHON_INCLUDES
+py_prefix=`$PYTHON -c "import sys; print sys.prefix"`
+py_exec_prefix=`$PYTHON -c "import sys; print sys.exec_prefix"`
+if test -x "$PYTHON-config"; then
+PYTHON_INCLUDES=`$PYTHON-config --includes 2>/dev/null`
+else
+PYTHON_INCLUDES="-I${py_prefix}/include/python${PYTHON_VERSION}"
+if test "$py_prefix" != "$py_exec_prefix"; then
+ PYTHON_INCLUDES="$PYTHON_INCLUDES -I${py_exec_prefix}/include/python${PYTHON_VERSION}"
+fi
+fi
+AC_SUBST(PYTHON_INCLUDES)
+dnl check if the headers exist:
+save_CPPFLAGS="$CPPFLAGS"
+CPPFLAGS="$CPPFLAGS $PYTHON_INCLUDES"
+AC_TRY_CPP([#include <Python.h>],dnl
+[AC_MSG_RESULT(found)
+$1],dnl
+[AC_MSG_RESULT(not found)
+$2])
+CPPFLAGS="$save_CPPFLAGS"
+])
diff --git a/po/POTFILES.in b/po/POTFILES.in
new file mode 100644
index 000000000..300afaf31
--- /dev/null
+++ b/po/POTFILES.in
@@ -0,0 +1 @@
+data/org.freedesktop.Telepathy.Logger.gschema.xml.in
diff --git a/telepathy-logger/AUTHORS b/telepathy-logger/AUTHORS
new file mode 100644
index 000000000..b7bbd9372
--- /dev/null
+++ b/telepathy-logger/AUTHORS
@@ -0,0 +1,2 @@
+Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+Danielle Madeley <danielle.madeley@collabora.co.uk>
diff --git a/telepathy-logger/Makefile.am b/telepathy-logger/Makefile.am
new file mode 100644
index 000000000..9e1dda558
--- /dev/null
+++ b/telepathy-logger/Makefile.am
@@ -0,0 +1,159 @@
+include $(top_srcdir)/tools/flymake.mk
+
+SUBDIRS = extensions .
+
+if HAVE_INTROSPECTION
+include $(INTROSPECTION_MAKEFILE)
+endif
+INTROSPECTION_GIRS =
+INTROSPECTION_SCANNER_ARGS = --add-include-path=$(srcdir) --add-include-path=$(top_srcdir) -I$(top_srcdir)
+INTROSPECTION_COMPILER_ARGS = --includedir=$(srcdir) --includedir=$(top_srcdir)
+
+AM_CPPFLAGS = \
+ -I$(top_builddir) \
+ -I$(top_srcdir) \
+ $(ERROR_CFLAGS) \
+ -DG_LOG_DOMAIN=\"tp-logger\" \
+ -DTPL_DATA_DIR=\"$(PACKAGE_NAME)\" \
+ -DTP_DISABLE_SINGLE_INCLUDE \
+ $(TPL_CFLAGS) \
+ $(DISABLE_DEPRECATED) \
+ $(WARN_CFLAGS)
+
+libexec_PROGRAMS = \
+ telepathy-logger
+
+telepathy_logger_LDADD = \
+ libtelepathy-logger-1.la \
+ $(top_builddir)/telepathy-glib/libtelepathy-glib-1.la \
+ $(top_builddir)/telepathy-glib/libtelepathy-glib-1-dbus.la \
+ $(top_builddir)/telepathy-glib/libtelepathy-glib-1-core.la \
+ $(TPL_LIBS) \
+ $(NULL)
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = telepathy-logger-1.pc
+
+lib_LTLIBRARIES = libtelepathy-logger-1.la
+
+EXTRA_DIST = \
+ tpl-marshal.list \
+ $(NULL)
+
+LIBTPLdir = $(includedir)/telepathy-logger-1/telepathy-logger
+LIBTPL_HEADERS = \
+ entity.h \
+ event.h \
+ log-manager.h \
+ log-walker.h \
+ telepathy-logger.h \
+ text-event.h \
+ call-event.h \
+ $(NULL)
+
+%-marshal.c: %-marshal.list Makefile.am
+ $(AM_V_GEN)echo "#include \"tpl-marshal.h\"" > $@
+ $(AM_V_GEN)glib-genmarshal --body --prefix=tpl_marshal $< >> $@
+
+%-marshal.h: %-marshal.list Makefile.am
+ $(AM_V_GEN)glib-genmarshal --header --prefix=tpl_marshal $< > $@
+
+BUILT_SOURCES = \
+ tpl-marshal.c tpl-marshal.h \
+ $(NULL)
+
+libtelepathy_logger_1_la_SOURCES = \
+ action-chain.c \
+ action-chain-internal.h \
+ call-event.c \
+ call-event-internal.h \
+ client-factory.c \
+ client-factory-internal.h \
+ conf.c \
+ conf-internal.h \
+ entity.c \
+ entity-internal.h \
+ dbus-service.c \
+ dbus-service-internal.h \
+ debug-internal.h \
+ debug.c \
+ event.c \
+ event-internal.h \
+ log-iter.c \
+ log-iter-internal.h \
+ log-iter-pidgin.c \
+ log-iter-pidgin-internal.h \
+ log-iter-xml.c \
+ log-iter-xml-internal.h \
+ log-manager.c \
+ log-manager-internal.h \
+ log-store.c \
+ log-store-internal.h \
+ log-store-xml.c \
+ log-store-xml-internal.h \
+ log-store-empathy.c \
+ log-store-empathy-internal.h \
+ log-store-sqlite.c \
+ log-store-sqlite-internal.h \
+ log-store-pidgin.c \
+ log-store-pidgin-internal.h \
+ log-store-factory.c \
+ log-store-factory-internal.h \
+ log-walker.c \
+ log-walker-internal.h \
+ observer.c \
+ observer-internal.h \
+ text-channel.c \
+ text-channel-internal.h \
+ text-event.c \
+ text-event-internal.h \
+ util-internal.h \
+ util.c \
+ call-channel.c \
+ call-channel-internal.h \
+ $(NULL)
+
+nodist_libtelepathy_logger_1_la_SOURCES = \
+ $(BUILT_SOURCES)
+
+libtelepathy_logger_1_la_LIBADD = \
+ extensions/libtpl-extensions-convenience.la \
+ $(top_builddir)/telepathy-glib/libtelepathy-glib-1.la \
+ $(top_builddir)/telepathy-glib/libtelepathy-glib-1-dbus.la \
+ $(top_builddir)/telepathy-glib/libtelepathy-glib-1-core.la \
+ $(NULL)
+
+check_c_sources = \
+ $(libtelepathy_logger_1_la_SOURCES) \
+ $(telepathy_logger_SOURCES) \
+ $(LIBTPL_HEADERS)
+include $(top_srcdir)/tools/check-coding-style.mk
+check-local: check-coding-style
+
+CLEANFILES = $(BUILT_SOURCES)
+
+if HAVE_INTROSPECTION
+introspection_sources = $(libtelepathy_logger_1_la_SOURCES) $(LIBTPL_HEADERS)
+
+TelepathyLogger1-1.gir: libtelepathy-logger-1.la
+TelepathyLogger1_1_gir_SCANNERFLAGS = --identifier-prefix=Tpl --warn-all
+TelepathyLogger1_1_gir_INCLUDES = GObject-2.0 TelepathyGLib-0.12
+TelepathyLogger1_1_gir_CFLAGS = $(INCLUDES)
+TelepathyLogger1_1_gir_LIBS = libtelepathy-logger-1.la
+TelepathyLogger1_1_gir_FILES = $(filter-out %-internal.h, $(introspection_sources))
+
+INTROSPECTION_GIRS += TelepathyLogger1-1.gir
+
+girdir = $(datadir)/gir-1.0
+gir_DATA = $(INTROSPECTION_GIRS)
+
+typelibdir = $(libdir)/girepository-1.0/
+typelib_DATA = $(INTROSPECTION_GIRS:.gir=.typelib)
+
+CLEANFILES += \
+ $(gir_DATA) \
+ $(typelib_DATA)
+
+endif
+
+include abi.am
diff --git a/telepathy-logger/NEWS b/telepathy-logger/NEWS
new file mode 100644
index 000000000..5561b5fbe
--- /dev/null
+++ b/telepathy-logger/NEWS
@@ -0,0 +1,452 @@
+telepathy-logger 0.8.0 (2013-01-21)
+===================================
+
+The "chicken legs" release.
+
+This is the start of a new stable branch.
+
+Here is the summary of particularly noteworthy changes since 0.6.0:
+
+• GLib 2.28 is now required.
+
+• New TplLogWalker API for iterating over logs. (fd.o#41772)
+
+• Filter out duplicate dates in get_dates. (fd.o#53345)
+
+telepathy-logger 0.6.0 (2012-10-29)
+===================================
+
+This is the start of a new stable branch.
+
+There was never a development release in the 0.5 series so there are
+no changes there. Here is the summary of particularly noteworthy
+changes since 0.4:
+
+• telepathy-glib 0.19.2 is now required and deprecated telepathy-glib
+ usage has been removed.
+
+• Multiple introspection problems have been fixed. (fd.o#50361)
+
+• Streamed Media channel support has been removed.
+
+telepathy-logger 0.4.0 (2012-04-03)
+===================================
+
+This is the start of a new stable branch.
+
+There are no changes since 0.2.13.
+
+For details of the changes since the last stable branch, please look
+at the git log, the list is just too long.
+
+telepathy-logger 0.2.13 (2012-04-03)
+===================================
+
+The "Empathy really, really needs this" release
+
+This release ports the Call support in the logger to Call1. Since Call1 is in
+telepathy-glib, this is no longer a build option, and something we always
+support.
+
+Major changes:
+
+* Support Call1 (Nicolas Dufresne)
+
+telepathy-logger 0.2.12 (2011-11-01)
+===================================
+
+The "Oops" release
+
+This bug fix release correct the accidental so-name bump. Distributions
+should use this release instead of 0.2.11.
+
+
+telepathy-logger 0.2.11 (2011-10-31)
+===================================
+
+The "Halloween" release
+
+This is a bug fix release. Distributions are encouraged to update.
+
+Fixes:
+
+* Fix crash caused by used after free (Nicolas Dufresne)
+* Fix crash wen call sender or actor is not set (Vincent Penquerc'h)
+* Fix missing alias feature when receiving messages (Nicolas Dufresne)
+* Remove use of deprecated g_thread_init() (Nicolas Dufresne)
+* Improve silent build rulesa (Nicolas Dufresne)
+* Improve introspection annotation (Nicolas Dufresne)
+
+
+telepathy-logger 0.2.10 (2011-05-26)
+===================================
+
+The "Malade!" release
+
+This release add support for text edits along with bug fixes. Distributions
+are encouraged to update.
+
+Fixes:
+
+* Add support for text message edits (David Laban)
+* Fix events ordering and merging speed (Nicolas Dufresne)
+* Fix destroy function for get_entities_async (Emilio Pozuelo Monfort)
+* Fix crash in pidgin log store (Guillaume Desmottes)
+
+
+telepathy-logger 0.2.9 (2011-05-06)
+===================================
+
+The "Vendredi au soleil" release
+
+This bug fix release most importantly fixes performance issues. This has been
+possible through the rework of Call logs storage method. Note that older Call
+logs will not be retrieved from store. We tolerate this breakage for two
+reasons, Call support is still experimental and a bug in the .client file was
+causing call logs not to be forwarded to the observer. Distributions are
+encourage to update as this release improves performance when opening chat
+windows in Empathy.
+
+Fixes:
+
+* Work around GLib TimeZone cache bug (Gnome bug #646435)
+* Fix client file to effectively observe Call.DRAFT channels
+* Downgraded autotools requirement to 2.65
+* Fix SQL syntax error and pending message order
+* Store Calls log in seperate file (e.g 20110505.call.log)
+* Make Pidgin store robust to empty file
+* And more unit tests
+
+
+telepathy-logger 0.2.8 (2011-03-31)
+===================================
+
+The "Piquez moi quelqu'un" release
+
+This release fixes wrong libtelepathy-logger shared object version. This
+error was introduced in version 0.2.7, where age has been incremented but
+not the current. Distribution should update to this version to avoid
+libtelepathy-logger shared object version to go backward.
+
+Fixes:
+
+* Fixed wrong shared object version.
+
+
+telepathy-logger 0.2.7 (2011-03-25)
+===================================
+
+The "TGIF" release
+
+This release adds experimental support for call logging, bug fixes and
+optimisations. We suggest distributions to update to this version. Note
+that compilation option '--enable-call' is required to use experimental
+call support.
+
+Fixes:
+
+* Readd AM_PATH_PYTHON back to configure.ac, required for codegen
+* Add support for call logging
+* Search using GRegex to prevent matching XML tags
+* Improve test coverage
+* Avoid using g_list_append() for performance reason
+* Fixed infinit loop in pending message cache logic
+
+
+telepathy-logger 0.2.6 (2011-03-22)
+===================================
+
+The "Slimmer" release
+
+This rework release reimplement text observer on top of Telepathy GLib class
+and reimplement pending message cache. The deprecated Channel.Type.Text API
+support are no longer used. Distributions are encourage to update as this
+release fixes issues with handling of delivery reports.
+
+Fixes:
+
+* Ported all internal time manipulation to GDateTime
+* Observers now cache TplEntity object instead of TpContact to reduce number
+ memory allocations
+* Removed generated log_id
+* Removed unused twisted test framework
+* Port observer to TpTextChannel
+* Reimplemnent pending message cache
+* Update Telepathy GLib requirement to 0.14.0
+
+telepathy-logger 0.2.5 (2011-03-03)
+===================================
+
+The "One liner" release
+
+This bug fix release fixes another crash found on 32bit machines. We strongly
+suggest distributions to update to this version.
+
+Fixes:
+
+* Fix another 32 vs 64bits bug (Emilio)
+
+
+telepathy-logger 0.2.4 (2011-03-01)
+===================================
+
+The "small" release
+
+This bug fix release fixes minor issues. We suggest distributions to update to
+this version as it fixes compilation and unit tests on some configurations.
+
+Fixes:
+
+* Add output annotations in TplLogManager (Jasper)
+* Make the introspection files be 0.2 instead of 1.0 (Emilio)
+* Fix use of uninitialised variable (Nicolas)
+* Remove reference to Gnome in configure.ac (Nicolas)
+* Remove GPL warning in README, Logger is now fully LGPL (Nicolas)
+* Move observer and SQlite tests with dbus enabled tests (Nicolas)
+* Update valgrind suppressions. (Nicolas)
+* Fix XML store test crash on 32bit computer. (Nicolas)
+
+
+telepathy-logger 0.2.3 (2011-02-28)
+===================================
+
+The "I hate mondays" release
+
+This bug fix release fixes potential crash on 32bit machines. We
+strongly suggest distributions to update to this version.
+
+Fixes:
+
+* Convert timestamp from uint to int64 on sent message (Nicolas)
+* Set an error if text event body is empty or NULL (fd.o#31121, Emilio)
+
+
+telepathy-logger 0.2.2 (2011-02-28)
+===================================
+
+The "Monday morning" release
+
+This bug fix release fixes issues around TplEvent with room target.
+It also contains initial work for GIntrospection.
+
+Fixes:
+
+* Start work on introspection support (Jasper and Emilio)
+* Fix TplEvent::get_target() logic so room event are stored correctly (Nicolas)
+* Fix room check when XML log store reads events (Nicolas)
+* Free GDate with g_date_free() instead of g_free ()
+* Fix log_store_xml_get_filtered_events() to return no more than num_events (Nicolas)
+* Add an XML log store test for log_store_xml_add_event() (Nicolas)
+
+
+telepathy-logger 0.2.1 (2011-02-23)
+===================================
+
+The "We are human!" release
+
+This bug fix release fixes memory corruption in GList manipulation. We
+strongly suggest distributions using 0.2.0 version to update to this
+version.
+
+Fixes:
+
+* Use g_list_alloc() instead of g_new0() to allocate list (Sjoerd)
+* Use _async() TplLogManager methods in unit tests (Nicolas)
+* Do not ignore system() return value in unit tests (Guillaume)
+
+
+telepathy-logger 0.2.0 (2011-02-23)
+===================================
+
+This version is a large API refactoring that enables abstraction of
+the logged event type and clarifies the methods. Alongside this large refactoring
+some new features like ability to clear logs (see D-Bus API) and read-only
+Pidgin log store can be found. While it remains compatible on the storage
+side, the API is not.A version of Empathy compatible with this release will be
+released shortly; watch the Telepathy mailing list for details.
+
+Enhancement:
+
+* Read-only support for Pidgin log store.
+* Ability to clear logs (see D-Bus API).
+* Largely reworked API:
+ - TplEntry is now TplEvent.
+ - Sub-class naming is now more natural (e.g. TplTextEvent instead of
+ TplEventText).
+ - D-Bus API has been reduced to the minimum required function.
+ - chat-id is now commonly called target-id to abstract text chat.
+ - TplSearchHit has been cleanup and usage reduced.
+ - TplEntityDirection has been remove from TplEntity.
+ - TplEntity and TplEvent class was cleaned from useless setters and unused getters.
+ - ROOM is now used instead of GROUP to follow Telepathy naming.
+ - _get_chats() method is now replaced by _get_entities().
+ - Unused method _search_in_identitifer() is removed.
+ - TplTextEvent exposes message-type to let client know if it's a /me message.
+ - TplLogManager API now takes TplEntity to identify target.
+ - TplEventTypeMask can be used to filter event type (for future use).
+* A fair amount of test has been added.
+
+
+telepathy-logger 0.1.7 (2010-11-29)
+===================================
+
+This bug fix release fixes a crash and remove a lot of memory leaks. We
+strongly suggest to distributions to update to this version.
+
+Fixes:
+
+• Stop using tp_get_bus(). (smcv)
+• Loads of memory leaks fixed. (cassidy)
+• Don't crash by trying to use the sqlite log store after it has been
+ disposed. (cassidy)
+
+telepathy-logger 0.1.6 (2010-10-13)
+===================================
+
+The “maths lesson” release.
+
+Fixes:
+
+• Running the test suite no longer accesses your session's GSettings.
+ (Danni)
+• Out-of-tree builds now work. (Nicolas)
+• A CM crashing with open text channels no longer causes conversations
+ not to be logged, and to take 25 seconds to be displayed.
+ (fd.o#30824, Will)
+
+telepathy-logger 0.1.5 (2010-08-11)
+===================================
+
+Dependencies:
+
+* GLib ≥ 2.25.11
+* No more dependency on GConf
+
+Enhancements:
+
+* The logger uses GSettings instead of GConf for preferences (fdo #28602). The
+ ignore-accounts key has been removed for now as it was unused and broken.
+
+Fix:
+
+* Make sure that TplLogSearchHit always contains the account if
+ known (fdo #29058).
+
+telepathy-logger 0.1.4 (2010-07-08)
+===================================
+
+Fix:
+
+* Don't crash if the logger is asked to observe the same channel twice. This
+ appens with telepathy-idle because of bug fdo #28918.
+
+telepathy-logger 0.1.3 (2010-06-28)
+===================================
+
+Dependencies:
+
+* telepathy-glib ≥ 0.11.7
+
+Enhancements:
+
+* Properly support the Debug interface.
+
+* Reuse existing TpAccount objects instead of creating new ones. This reduce
+ the D-Bus traffic when logging messages as we don't need to fetch the
+ account properties any more (fdo #28682).
+
+* Cache all the participants of the room when logging group text channels
+ (fdo #28680).
+
+Fixes:
+
+* Correctly set the 'isuser' flag when logging text channels.
+
+* Correctly set the sender ID when logging group text channels.
+
+* Properly sort dates in the list returned by
+ tpl_log_manager_get_filtered_messages_finish.
+
+* Fix a race when logging text messages.
+
+* Correctly set the default value of the ignorelist gconf key.
+
+* Install the logger to $libexec.
+
+* Set the Recover flag in the logger's client file so MissionControl will
+ make it observe existing channels when (re)starting.
+
+* Return earlier from ObserveChannels() to avoid blocking channels dispatching
+ too long (fdo #28787).
+
+
+telepathy-logger 0.1.2 (2010-06-18)
+===================================
+
+The API of libtelepathy-logger has been cleaned up and now only includes:
+- TplEntity (previously TplContact)
+- TplEntry (previously TplLogEntry)
+- TplEntryText (previously TplLogTextEntry)
+- TplLogManager
+
+This API should stay stable in the 0.1.x cycle.
+
+The library has been licensed more permissively and is now available under the
+GNU Lesser General Public License, version 2.1 or (at your option) any
+later version.
+
+Dependencies:
+
+* telepathy-glib ≥ 0.11.5
+* telepathy-mission-control ≥ 5.4.0 is recommended
+
+
+telepathy-logger 0.1.1 (2010-02-26)
+===================================
+
+The ``brick by brick'' release.
+
+This is the second previous release of the telepathy-logger. You should be aware
+that this is a piece of software that is still very much in development,
+everything is subject to change.
+
+The idea behind tp-logger is that it will become a generic logging daemon usable
+anywhere in Telepathy, with any Telepathy client.
+
+Changes in this release:
+ - libtelepathy-logger.pc is now just telepathy-logger.pc
+ - asynchronous API has _finish() methods.
+ - Fix a crasher in handling MUCs
+ - Logger now observes channels that are open when it starts
+ - API now includes support for storing favourite contacts in a way that can
+ be shared between Telepathy clients
+
+telepathy-logger 0.1.0 (2010-02-24)
+===================================
+
+The ``Calamity Ensues'' release
+
+This is the first preview release of the telepathy-logger. You should be aware
+that this is a piece of software that is still very much in development,
+everything is subject to change.
+
+The idea behind tp-logger is that it will become a generic logging daemon usable
+anywhere in Telepathy, with any Telepathy client.
+
+Things that mostly work:
+
+ - logging of text channels into an Empathy style log format using a Telepathy
+ Observer client
+ - Telepathy-style D-Bus API to retrieve logs (will be extended soon with more
+ functionality)
+ - libtelepathy-logger API to allow clients to access logs en-masse
+ - GConf preferences to control what accounts to log
+
+Things that don't work:
+
+ - existing channels won't be logged when the logger starts
+ - preference changes aren't noticed asynchronously
+
+Note that tp-logger won't import your existing logs from Empathy, for the time
+being, if you're running it, you are going to end up with two sets of logs,
+one from Empathy and one from tp-logger.
diff --git a/telepathy-logger/README b/telepathy-logger/README
new file mode 100644
index 000000000..1f7336ef1
--- /dev/null
+++ b/telepathy-logger/README
@@ -0,0 +1,20 @@
+Telepathy-Logger
+================
+
+tp-logger is a headless Observer client that logs information received by the
+Telepathy framework. It features pluggable backends to log different sorts of
+messages, in different formats.
+
+tp-logger features a Telepathy-style D-Bus API to expose logs and interesting
+information related to logging (most frequent contacts, etc.). It also provides
+a GLib-compatible client API for making bulk log requests (e.g. for display
+logs in applications without having to provide lots of information over D-Bus).
+
+TPL is currently under development, please refer to
+http://telepathy.freedesktop.org/wiki/Logger for more information.
+
+Bugs should be filed at http://bugs.freedesktop.org/ under the product
+`Telepathy' and the category `logger'.
+
+Telepathy discussion takes place on the Telepathy mailing list
+<telepathy@lists.freedesktop.org> or on irc.freenode.net/#telepathy
diff --git a/telepathy-logger/TESTING b/telepathy-logger/TESTING
new file mode 100644
index 000000000..2a02fd7a5
--- /dev/null
+++ b/telepathy-logger/TESTING
@@ -0,0 +1,19 @@
+== When testing ==
+
+When testing, be sure to set TPL_TEST_MODE enviromental variable.
+A log base for testing is placed in TPL_TEST_LOG_DIR.
+
+== When writing code ==
+
+If an object should behave in a different way, depending on whether it is in a
+testing enviroment or not, you should rely on the present of TPL_TEST_MODE
+variable.
+
+TplLogStore interface defines a "testmode" property which when set TRUE will set
+the log store supporting it they are in a testing enviroment.
+Add support for this property in case the log store needs it.
+
+TplLogManager will set this property automatically on any automatically added
+store, when TPL_TEST_MODE is set.
+
+
diff --git a/telepathy-logger/abi.am b/telepathy-logger/abi.am
new file mode 100644
index 000000000..d63e84765
--- /dev/null
+++ b/telepathy-logger/abi.am
@@ -0,0 +1,16 @@
+# To be included by Makefile.am.
+
+# The quoting here is unnecessary but harmless, and has the useful side-effect
+# that vim quickfix mode (:make) doesn't interpret the libtool --mode=link
+# command as an error message in a bizarrely named file
+libtelepathy_logger_1_la_LDFLAGS = \
+ -version-info "$(TPL_LT_CURRENT)":"$(TPL_LT_REVISION)":"$(TPL_LT_AGE)"
+
+_gen/abi.txt: libtelepathy-logger-1.la abi.am
+ $(NM) .libs/libtelepathy-logger.a > _gen/abi.nm
+ grep " [DT] " < _gen/abi.nm > _gen/abi.funcs
+ cut -d" " -f3 < _gen/abi.funcs > _gen/abi.funcnames
+ grep "^tpl" < _gen/abi.funcnames > _gen/abi.tpfuncnames
+ $(AM_V_GEN)sort -u < _gen/abi.tpfuncnames > $@
+
+# vim:ft=automake:
diff --git a/telepathy-logger/action-chain-internal.h b/telepathy-logger/action-chain-internal.h
new file mode 100644
index 000000000..674cc2b06
--- /dev/null
+++ b/telepathy-logger/action-chain-internal.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2009 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ */
+
+#ifndef __TPL_ACTION_CHAIN_H__
+#define __TPL_ACTION_CHAIN_H__
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+typedef struct {
+ GQueue *chain;
+ GSimpleAsyncResult *simple;
+} TplActionChain;
+
+TplActionChain *_tpl_action_chain_new_async (GObject *obj,
+ GAsyncReadyCallback cb,
+ gpointer user_data);
+void _tpl_action_chain_free (TplActionChain *self);
+typedef void (*TplPendingAction) (TplActionChain *ctx, gpointer user_data);
+void _tpl_action_chain_append (TplActionChain *self, TplPendingAction func,
+ gpointer user_data);
+void _tpl_action_chain_prepend (TplActionChain *self, TplPendingAction func,
+ gpointer user_data);
+void _tpl_action_chain_continue (TplActionChain *self);
+void _tpl_action_chain_terminate (TplActionChain *self, const GError *error);
+gpointer _tpl_action_chain_get_object (TplActionChain *self);
+gboolean _tpl_action_chain_new_finish (GObject *source,
+ GAsyncResult *result, GError **error);
+
+#endif // __TPL_ACTION_CHAIN_H__
diff --git a/telepathy-logger/action-chain.c b/telepathy-logger/action-chain.c
new file mode 100644
index 000000000..14b9452d0
--- /dev/null
+++ b/telepathy-logger/action-chain.c
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2009 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ */
+
+#include "config.h"
+#include "action-chain-internal.h"
+
+typedef struct {
+ TplPendingAction action;
+ gpointer user_data;
+} TplActionLink;
+
+
+TplActionChain *
+_tpl_action_chain_new_async (GObject *obj,
+ GAsyncReadyCallback cb,
+ gpointer user_data)
+{
+ TplActionChain *ret = g_slice_new0 (TplActionChain);
+
+ ret->chain = g_queue_new ();
+ ret->simple = g_simple_async_result_new (obj, cb, user_data,
+ _tpl_action_chain_new_async);
+
+ g_object_set_data (G_OBJECT (ret->simple), "chain", ret);
+
+ return ret;
+}
+
+
+static void
+link_free (TplActionLink *l)
+{
+ g_slice_free (TplActionLink, l);
+}
+
+
+void
+_tpl_action_chain_free (TplActionChain *self)
+{
+ g_queue_foreach (self->chain, (GFunc) link_free, NULL);
+ g_queue_free (self->chain);
+ g_object_unref (self->simple);
+ g_slice_free (TplActionChain, self);
+}
+
+
+gpointer // FIXME GObject *
+_tpl_action_chain_get_object (TplActionChain *self)
+{
+ GObject *obj;
+
+ g_return_val_if_fail (self != NULL && self->simple != NULL, NULL);
+
+ obj = g_async_result_get_source_object (G_ASYNC_RESULT (self->simple));
+ g_object_unref (obj); /* don't want the extra ref */
+
+ return obj;
+}
+
+
+void
+_tpl_action_chain_prepend (TplActionChain *self,
+ TplPendingAction func,
+ gpointer user_data)
+{
+ TplActionLink *l;
+
+ l = g_slice_new0 (TplActionLink);
+ l->action = func;
+ l->user_data = user_data;
+
+ g_queue_push_head (self->chain, l);
+}
+
+
+void
+_tpl_action_chain_append (TplActionChain *self,
+ TplPendingAction func,
+ gpointer user_data)
+{
+ TplActionLink *l;
+
+ l = g_slice_new0 (TplActionLink);
+ l->action = func;
+ l->user_data = user_data;
+
+ g_queue_push_tail (self->chain, l);
+}
+
+
+void
+_tpl_action_chain_continue (TplActionChain *self)
+{
+ if (g_queue_is_empty (self->chain))
+ {
+ g_simple_async_result_complete (self->simple);
+ _tpl_action_chain_free (self);
+ }
+ else
+ {
+ TplActionLink *l = g_queue_pop_head (self->chain);
+
+ l->action (self, l->user_data);
+ link_free (l);
+ }
+}
+
+
+void
+_tpl_action_chain_terminate (TplActionChain *self,
+ const GError *error)
+{
+ GSimpleAsyncResult *simple = self->simple;
+
+ g_assert (error != NULL);
+
+ g_simple_async_result_set_from_error (simple, error);
+ g_simple_async_result_complete (simple);
+ _tpl_action_chain_free (self);
+}
+
+
+/**
+ * _tpl_action_chain_new_finish:
+ * @source: the #GObject pass to _tpl_action_chain_new_async()
+ * @result: the #GAsyncResult pass in callback
+ * @error: a pointer to a #GError that will be set on error, or NULL to ignore
+ *
+ * Get the result from running the action chain (%TRUE if the chain completed
+ * successfully, %FALSE with @error set if it was terminated).
+ *
+ * This function also frees the chain.
+ *
+ * Returns: %TRUE on success, %FALSE with @error set on error.
+ */
+gboolean
+_tpl_action_chain_new_finish (GObject *source,
+ GAsyncResult *result,
+ GError **error)
+{
+ TplActionChain *chain;
+
+ g_return_val_if_fail (g_simple_async_result_is_valid (result, source,
+ _tpl_action_chain_new_async), FALSE);
+
+ chain = g_object_get_data (G_OBJECT (result), "chain");
+
+ g_return_val_if_fail (chain != NULL, FALSE);
+
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result),
+ error))
+ return FALSE;
+
+ return TRUE;
+}
diff --git a/telepathy-logger/call-channel-internal.h b/telepathy-logger/call-channel-internal.h
new file mode 100644
index 000000000..0f7b9a974
--- /dev/null
+++ b/telepathy-logger/call-channel-internal.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Nicolas Dufresne <nicolas.dufresne@collabora.co.uk>
+ */
+
+#ifndef __TPL_CALL_CHANNEL_H__
+#define __TPL_CALL_CHANNEL_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+G_BEGIN_DECLS
+#define TPL_TYPE_CALL_CHANNEL (_tpl_call_channel_get_type ())
+#define TPL_CALL_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TPL_TYPE_CALL_CHANNEL, TplCallChannel))
+#define TPL_CALL_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TPL_TYPE_CALL_CHANNEL, TplCallChannelClass))
+#define TPL_IS_CALL_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TPL_TYPE_CALL_CHANNEL))
+#define TPL_IS_CALL_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TPL_TYPE_CALL_CHANNEL))
+#define TPL_CALL_CHANNEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TPL_TYPE_CALL_CHANNEL, TplCallChannelClass))
+
+#define TPL_CALL_CHANNEL_ERROR \
+ g_quark_from_static_string ("tpl-call-channel-error-quark")
+
+typedef enum
+{
+ /* generic error */
+ TPL_CALL_CHANNEL_ERROR_FAILED,
+ TPL_CALL_CHANNEL_ERROR_MISSING_TARGET_CONTACT,
+} TplCallChannelError;
+
+#define TPL_CALL_CHANNEL_FEATURE_CORE \
+ _tpl_call_channel_get_feature_quark_core ()
+GQuark _tpl_call_channel_get_feature_quark_core (void) G_GNUC_CONST;
+
+typedef struct _TplCallChannelPriv TplCallChannelPriv;
+typedef struct
+{
+ TpCallChannel parent;
+
+ /* private */
+ TplCallChannelPriv *priv;
+} TplCallChannel;
+
+typedef struct
+{
+ TpCallChannelClass parent_class;
+} TplCallChannelClass;
+
+GType _tpl_call_channel_get_type (void);
+
+TplCallChannel * _tpl_call_channel_new (TpConnection *conn,
+ const gchar *object_path,
+ GHashTable *tp_chan_props,
+ GError **error);
+
+TplCallChannel * _tpl_call_channel_new_with_factory (
+ TpClientFactory *factory,
+ TpConnection *conn,
+ const gchar *object_path,
+ const GHashTable *tp_chan_props,
+ GError **error);
+
+G_END_DECLS
+#endif /* __TPL_CALL_CHANNEL_H__ */
diff --git a/telepathy-logger/call-channel.c b/telepathy-logger/call-channel.c
new file mode 100644
index 000000000..96bf4ea86
--- /dev/null
+++ b/telepathy-logger/call-channel.c
@@ -0,0 +1,492 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Nicolas Dufresne <nicolas.dufresne@collabora.co.uk>
+ */
+
+#include "config.h"
+#include "call-channel-internal.h"
+
+#include <glib.h>
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/telepathy-glib-dbus.h>
+
+#include "call-event.h"
+#include "call-event-internal.h"
+#include "entity-internal.h"
+#include "event-internal.h"
+#include "log-manager-internal.h"
+#include "observer-internal.h"
+#include "tpl-marshal.h"
+#include "util-internal.h"
+
+#define DEBUG_FLAG TPL_DEBUG_CHANNEL
+#include "debug-internal.h"
+
+struct _TplCallChannelPriv
+{
+ TpAccount *account;
+ GHashTable *entities;
+ TplEntity *sender;
+ TplEntity *receiver;
+ GDateTime *timestamp;
+ GTimer *timer;
+ gboolean timer_started;
+ TplEntity *end_actor;
+ TpCallStateChangeReason end_reason;
+ gchar *detailed_end_reason;
+};
+
+G_DEFINE_TYPE (TplCallChannel, _tpl_call_channel, TP_TYPE_CALL_CHANNEL)
+
+
+static gboolean
+get_contacts (TplCallChannel *self,
+ GError **error)
+{
+ TplCallChannelPriv *priv = self->priv;
+ TpChannel *chan = TP_CHANNEL (self);
+ TpConnection *con = tp_channel_get_connection (chan);
+ GHashTable *members;
+ GHashTableIter iter;
+ TpHandle handle;
+ TpHandleType handle_type;
+ gboolean is_room;
+ TpContact *contact;
+ TplEntity *entity;
+
+ /* Get and store entities */
+ members = tp_call_channel_get_members (TP_CALL_CHANNEL (self));
+
+ g_hash_table_iter_init (&iter, members);
+ while (g_hash_table_iter_next (&iter, (gpointer *) &contact, NULL))
+ {
+ handle = tp_contact_get_handle (contact);
+ g_hash_table_insert (priv->entities, GUINT_TO_POINTER (handle),
+ tpl_entity_new_from_tp_contact (contact, TPL_ENTITY_CONTACT));
+ }
+
+ /* Identify target */
+ handle = tp_channel_get_handle (chan, &handle_type);
+ is_room = (handle_type == TP_HANDLE_TYPE_ROOM);
+
+ if (is_room)
+ {
+ priv->receiver =
+ tpl_entity_new_from_room_id (tp_channel_get_identifier (chan));
+ }
+ else
+ {
+ entity = g_hash_table_lookup (priv->entities, GUINT_TO_POINTER (handle));
+
+ if (entity == NULL)
+ {
+ g_set_error (error, TPL_CALL_CHANNEL_ERROR,
+ TPL_CALL_CHANNEL_ERROR_MISSING_TARGET_CONTACT,
+ "Failed to resolve target contact");
+ return FALSE;
+ }
+
+ if (tp_channel_get_requested (chan))
+ priv->receiver = g_object_ref (entity);
+ else
+ priv->sender = g_object_ref (entity);
+ }
+
+ /* Get and store self entity */
+ contact = tp_channel_group_get_self_contact (chan);
+ if (contact == NULL)
+ contact = tp_connection_get_self_contact (con);
+
+ handle = tp_contact_get_handle (contact);
+ entity = tpl_entity_new_from_tp_contact (contact, TPL_ENTITY_SELF);
+ g_hash_table_insert (priv->entities, GUINT_TO_POINTER (handle), entity);
+
+ if (tp_channel_get_requested (chan) || is_room)
+ priv->sender = g_object_ref (entity);
+ else
+ priv->receiver = g_object_ref (entity);
+
+ return TRUE;
+}
+
+
+static void
+call_state_changed_cb (TpCallChannel *call,
+ TpCallState state,
+ TpCallFlags flags,
+ TpCallStateReason *reason,
+ GHashTable *details,
+ gpointer user_data)
+{
+ TplCallChannel *self = TPL_CALL_CHANNEL (user_data);
+ TplCallChannelPriv *priv = self->priv;
+
+ switch (state)
+ {
+ case TP_CALL_STATE_ACCEPTED:
+ {
+ if (!priv->timer_started)
+ {
+ DEBUG ("Moving to ACCEPTED_STATE, start_time=%li",
+ time (NULL));
+ g_timer_start (priv->timer);
+ priv->timer_started = TRUE;
+ }
+ }
+ break;
+
+ case TP_CALL_STATE_ENDED:
+ {
+ if (priv->end_actor != NULL)
+ g_object_unref (priv->end_actor);
+
+ priv->end_actor = g_hash_table_lookup (priv->entities,
+ GUINT_TO_POINTER (reason->actor));
+
+ if (priv->end_actor == NULL)
+ priv->end_actor = tpl_entity_new ("unknown", TPL_ENTITY_UNKNOWN,
+ NULL, NULL);
+ else
+ g_object_ref (priv->end_actor);
+
+ priv->end_reason = reason->reason;
+
+ g_free (priv->detailed_end_reason);
+
+ if (reason->dbus_reason == NULL)
+ priv->detailed_end_reason = g_strdup ("");
+ else
+ priv->detailed_end_reason = g_strdup (reason->dbus_reason);
+
+ g_timer_stop (priv->timer);
+
+ DEBUG (
+ "Moving to ENDED_STATE, duration=%" G_GINT64_FORMAT " reason=%s details=%s",
+ (gint64) (priv->timer_started ? g_timer_elapsed (priv->timer, NULL) : -1),
+ _tpl_call_event_end_reason_to_str (priv->end_reason),
+ priv->detailed_end_reason);
+ }
+ break;
+
+ default:
+ /* just wait */
+ break;
+ }
+}
+
+
+static void
+call_members_changed_cb (TpCallChannel *call,
+ GHashTable *updates,
+ GArray *removed,
+ TpCallStateReason reason,
+ gpointer user_data)
+{
+ TplCallChannel *self = TPL_CALL_CHANNEL (call);
+ TplCallChannelPriv *priv = self->priv;
+ GHashTableIter iter;
+ TpContact *contact;
+
+ g_hash_table_iter_init (&iter, updates);
+ while (g_hash_table_iter_next (&iter, (gpointer *) &contact, NULL))
+ {
+ TpHandle handle = tp_contact_get_handle (contact);
+ TplEntity *entity = g_hash_table_lookup (priv->entities,
+ GUINT_TO_POINTER (handle));
+
+ if (!entity)
+ {
+ entity = tpl_entity_new_from_tp_contact (contact,
+ TPL_ENTITY_CONTACT);
+ g_hash_table_insert (priv->entities, GUINT_TO_POINTER (handle),
+ entity);
+ }
+ }
+}
+
+
+static void
+store_call (TplCallChannel *self)
+{
+ TplCallChannelPriv *priv = self->priv;
+ GError *error = NULL;
+ TplCallEvent *call_log;
+ TplLogManager *logmanager;
+ const gchar *channel_path = tp_proxy_get_object_path (TP_PROXY (self));
+ GTimeSpan duration = -1;
+
+ if (priv->timer_started)
+ duration = g_timer_elapsed (priv->timer, NULL);
+
+ /* Initialize data for TplEntity */
+ call_log = g_object_new (TPL_TYPE_CALL_EVENT,
+ /* TplEvent */
+ "account", priv->account,
+ "channel-path", channel_path,
+ "receiver", priv->receiver,
+ "sender", priv->sender,
+ "timestamp", g_date_time_to_unix (priv->timestamp),
+ /* TplCallEvent */
+ "duration", duration,
+ "end-actor", priv->end_actor,
+ "end-reason", priv->end_reason,
+ "detailed-end-reason", priv->detailed_end_reason,
+ NULL);
+
+ logmanager = tpl_log_manager_dup_singleton ();
+ _tpl_log_manager_add_event (logmanager, TPL_EVENT (call_log), &error);
+
+ if (error != NULL)
+ {
+ PATH_DEBUG (self, "TplCallChannel: %s", error->message);
+ g_error_free (error);
+ }
+
+ g_object_unref (logmanager);
+ g_object_unref (call_log);
+}
+
+
+static void
+channel_invalidated_cb (TpProxy *proxy,
+ guint domain,
+ gint code,
+ gchar *message,
+ gpointer user_data)
+{
+ TpChannel *chan = TP_CHANNEL (user_data);
+ TplObserver *observer = _tpl_observer_dup (NULL);
+
+ g_return_if_fail (observer);
+
+ PATH_DEBUG (chan, "%s #%d %s",
+ g_quark_to_string (domain), code, message);
+
+ store_call (TPL_CALL_CHANNEL (user_data));
+
+ if (!_tpl_observer_unregister_channel (observer, chan))
+ PATH_DEBUG (chan, "Channel couldn't be unregistered correctly (BUG?)");
+
+ g_object_unref (observer);
+}
+
+
+static void
+connect_signals (TplCallChannel *self)
+{
+ tp_g_signal_connect_object (self, "state-changed",
+ G_CALLBACK (call_state_changed_cb), self, 0);
+
+ tp_g_signal_connect_object (self, "members-changed",
+ G_CALLBACK (call_members_changed_cb), self, 0);
+
+ tp_g_signal_connect_object (TP_CHANNEL (self), "invalidated",
+ G_CALLBACK (channel_invalidated_cb), self, 0);
+}
+
+
+static void
+_tpl_call_channel_prepare_core_async (TpProxy *proxy,
+ const TpProxyFeature *feature,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TplCallChannel *self = (TplCallChannel *) proxy;
+ GError *error = NULL;
+
+ connect_signals (self);
+
+ if (!get_contacts (self, &error))
+ {
+ g_simple_async_report_take_gerror_in_idle ((GObject *) self, callback,
+ user_data, error);
+ return;
+ }
+
+ tp_simple_async_report_success_in_idle ((GObject *) self, callback, user_data,
+ _tpl_call_channel_prepare_core_async);
+}
+
+GQuark
+_tpl_call_channel_get_feature_quark_core (void)
+{
+ return g_quark_from_static_string ("tpl-call-channel-feature-core");
+}
+
+enum {
+ FEAT_CORE,
+ N_FEAT
+};
+
+static const TpProxyFeature *
+tpl_call_channel_list_features (TpProxyClass *cls G_GNUC_UNUSED)
+{
+ static TpProxyFeature features[N_FEAT + 1] = { { 0 } };
+
+ if (G_LIKELY (features[0].name != 0))
+ return features;
+
+ features[FEAT_CORE].name = TPL_CALL_CHANNEL_FEATURE_CORE;
+ features[FEAT_CORE].prepare_async = _tpl_call_channel_prepare_core_async;
+
+ /* assert that the terminator at the end is there */
+ g_assert (features[N_FEAT].name == 0);
+
+ return features;
+}
+
+
+static void
+tpl_call_channel_dispose (GObject *obj)
+{
+ TplCallChannelPriv *priv = TPL_CALL_CHANNEL (obj)->priv;
+
+ tp_clear_object (&priv->account);
+ tp_clear_pointer (&priv->entities, g_hash_table_unref);
+ tp_clear_object (&priv->sender);
+ tp_clear_object (&priv->receiver);
+ tp_clear_pointer (&priv->timestamp, g_date_time_unref);
+ tp_clear_pointer (&priv->timer, g_timer_destroy);
+ tp_clear_object (&priv->end_actor);
+ tp_clear_pointer (&priv->detailed_end_reason, g_free);
+
+ G_OBJECT_CLASS (_tpl_call_channel_parent_class)->dispose (obj);
+}
+
+
+static void
+tpl_call_channel_finalize (GObject *obj)
+{
+ PATH_DEBUG (obj, "finalizing channel %p", obj);
+
+ G_OBJECT_CLASS (_tpl_call_channel_parent_class)->finalize (obj);
+}
+
+
+static void
+_tpl_call_channel_class_init (TplCallChannelClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ TpProxyClass *proxy_class = (TpProxyClass *) klass;
+
+ object_class->dispose = tpl_call_channel_dispose;
+ object_class->finalize = tpl_call_channel_finalize;
+
+ proxy_class->list_features = tpl_call_channel_list_features;
+
+ g_type_class_add_private (object_class, sizeof (TplCallChannelPriv));
+
+ dbus_g_object_register_marshaller (tpl_marshal_VOID__UINT_UINT_BOXED_BOXED,
+ G_TYPE_NONE,
+ G_TYPE_UINT, G_TYPE_UINT, G_TYPE_BOXED, G_TYPE_BOXED,
+ G_TYPE_INVALID);
+
+ dbus_g_object_register_marshaller (tpl_marshal_VOID__BOXED_BOXED,
+ G_TYPE_NONE,
+ G_TYPE_BOXED, G_TYPE_BOXED,
+ G_TYPE_INVALID);
+}
+
+
+static void
+_tpl_call_channel_init (TplCallChannel *self)
+{
+ gchar *date;
+
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TPL_TYPE_CALL_CHANNEL, TplCallChannelPriv);
+
+ self->priv->timestamp = g_date_time_new_now_utc ();
+ self->priv->timer = g_timer_new ();
+
+ date = g_date_time_format (self->priv->timestamp, "%Y-%m-%d %H:%M:%S");
+ DEBUG ("New call, timestamp=%s UTC", date);
+ g_free (date);
+
+ self->priv->entities = g_hash_table_new_full (NULL, NULL, NULL,
+ (GDestroyNotify) g_object_unref);
+}
+
+
+/**
+ * _tpl_call_channel_new:
+ * @conn: TpConnection instance owning the channel
+ * @object_path: the channel's DBus path
+ * @tp_chan_props: channel's immutable properties, obtained for example by
+ * %tp_channel_borrow_immutable_properties()
+ * @error: location of the GError, used in case a problem is raised while
+ * creating the channel
+ *
+ * Convenience function to create a new TPL Call Channel proxy.
+ * The returned #TplCallChannel is not guaranteed to be ready
+ * at the point of return.
+ *
+ * TplCallChannel is actually a subclass of #TpChannel implementing
+ * interface #TplChannel. Use #TpChannel methods, casting the #TplCallChannel
+ * instance to a TpChannel, to access TpChannel data/methods from it.
+ *
+ * TplCallChannel is usually created using
+ * #tpl_channel_factory_build, from within a #TplObserver singleton,
+ * when its Observer_Channel method is called by the Channel Dispatcher.
+ *
+ * Returns: the TplCallChannel instance or %NULL if
+ * @object_path is not valid.
+ */
+TplCallChannel *
+_tpl_call_channel_new (TpConnection *conn,
+ const gchar *object_path,
+ GHashTable *tp_chan_props,
+ GError **error)
+{
+ return _tpl_call_channel_new_with_factory (NULL, conn, object_path,
+ tp_chan_props, error);
+}
+
+TplCallChannel *
+_tpl_call_channel_new_with_factory (TpClientFactory *factory,
+ TpConnection *conn,
+ const gchar *object_path,
+ const GHashTable *tp_chan_props,
+ GError **error)
+{
+ TplCallChannel *self;
+
+ /* Do what tpl_channel_new does + set TplCallChannel
+ * specific properties */
+
+ g_return_val_if_fail (TP_IS_CONNECTION (conn), NULL);
+ g_return_val_if_fail (!TPL_STR_EMPTY (object_path), NULL);
+ g_return_val_if_fail (tp_chan_props != NULL, NULL);
+
+ if (!tp_dbus_check_valid_object_path (object_path, error))
+ return NULL;
+
+ self = g_object_new (TPL_TYPE_CALL_CHANNEL,
+ "factory", factory,
+ "connection", conn,
+ "dbus-daemon", tp_proxy_get_dbus_daemon (conn),
+ "bus-name", tp_proxy_get_bus_name (conn),
+ "object-path", object_path,
+ "handle-type", (guint) TP_UNKNOWN_HANDLE_TYPE,
+ "channel-properties", tp_chan_props,
+ NULL);
+
+ self->priv->account = g_object_ref (tp_connection_get_account (conn));
+
+ return self;
+}
diff --git a/telepathy-logger/call-event-internal.h b/telepathy-logger/call-event-internal.h
new file mode 100644
index 000000000..11cf81071
--- /dev/null
+++ b/telepathy-logger/call-event-internal.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Nicolas Dufresne <nicolas.dufresne@collabora.co.uk>
+ */
+
+#ifndef __TPL_CALL_EVENT_INTERNAL_H__
+#define __TPL_CALL_EVENT_INTERNAL_H__
+
+#include <telepathy-logger/call-event.h>
+#include <telepathy-logger/event-internal.h>
+
+G_BEGIN_DECLS
+
+struct _TplCallEvent
+{
+ TplEvent parent;
+
+ /* Private */
+ TplCallEventPriv *priv;
+};
+
+struct _TplCallEventClass
+{
+ TplEventClass parent_class;
+};
+
+const gchar * _tpl_call_event_end_reason_to_str (TpCallStateChangeReason reason);
+TpCallStateChangeReason _tpl_call_event_str_to_end_reason (const gchar *str);
+
+
+G_END_DECLS
+#endif // __TPL_CALL_EVENT_INTERNAL_H__
diff --git a/telepathy-logger/call-event.c b/telepathy-logger/call-event.c
new file mode 100644
index 000000000..68c0d86d4
--- /dev/null
+++ b/telepathy-logger/call-event.c
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Nicolas Dufresne <nicolas.dufresne@collabora.co.uk>
+ */
+
+#include "config.h"
+#include "call-event.h"
+#include "call-event-internal.h"
+
+#include <glib-object.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "entity.h"
+#include "event.h"
+#include "event-internal.h"
+#include "util-internal.h"
+
+#define DEBUG_FLAG TPL_DEBUG_LOG_EVENT
+#include "debug-internal.h"
+
+/**
+ * SECTION:call-event
+ * @title: TplCallEvent
+ * @short_description: Representation of a call log event
+ *
+ * A subclass of #TplEvent representing a call log event.
+ */
+
+/**
+ * TplCallEvent:
+ *
+ * An object representing a call log event.
+ */
+
+G_DEFINE_TYPE (TplCallEvent, tpl_call_event, TPL_TYPE_EVENT)
+
+struct _TplCallEventPriv
+{
+ GTimeSpan duration;
+ TplEntity *end_actor;
+ TpCallStateChangeReason end_reason;
+ gchar *detailed_end_reason;
+};
+
+enum
+{
+ PROP_DURATION = 1,
+ PROP_END_ACTOR,
+ PROP_END_REASON,
+ PROP_DETAILED_END_REASON
+};
+
+static const gchar* end_reasons[] = {
+ "unknown",
+ "progress-made",
+ "user-requested",
+ "forwared",
+ "rejected",
+ "no-answer",
+ "invalid-contact",
+ "permission-denied",
+ "busy",
+ "internal-error",
+ "service-error",
+ "network-error",
+ "media-error",
+ "connectivity-error"
+};
+
+
+static void
+tpl_call_event_dispose (GObject *object)
+{
+ TplCallEventPriv *priv = TPL_CALL_EVENT (object)->priv;
+
+ tp_clear_object (&priv->end_actor);
+ tp_clear_pointer (&priv->detailed_end_reason, g_free);
+
+ G_OBJECT_CLASS (tpl_call_event_parent_class)->dispose (object);
+}
+
+
+static void
+tpl_call_event_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TplCallEventPriv *priv = TPL_CALL_EVENT (object)->priv;
+
+ switch (param_id)
+ {
+ case PROP_DURATION:
+ g_value_set_int64 (value, priv->duration);
+ break;
+ case PROP_END_ACTOR:
+ g_value_set_object (value, priv->end_actor);
+ break;
+ case PROP_END_REASON:
+ g_value_set_int (value, priv->end_reason);
+ break;
+ case PROP_DETAILED_END_REASON:
+ g_value_set_string (value, priv->detailed_end_reason);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+
+static void
+tpl_call_event_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TplCallEventPriv *priv = TPL_CALL_EVENT (object)->priv;
+
+ switch (param_id)
+ {
+ case PROP_DURATION:
+ priv->duration = g_value_get_int64 (value);
+ break;
+ case PROP_END_ACTOR:
+ priv->end_actor = g_value_dup_object (value);
+ break;
+ case PROP_END_REASON:
+ priv->end_reason = g_value_get_int (value);
+ break;
+ case PROP_DETAILED_END_REASON:
+ priv->detailed_end_reason = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+static void tpl_call_event_class_init (TplCallEventClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GParamSpec *param_spec;
+
+ object_class->dispose = tpl_call_event_dispose;
+ object_class->get_property = tpl_call_event_get_property;
+ object_class->set_property = tpl_call_event_set_property;
+
+ param_spec = g_param_spec_int64 ("duration",
+ "Duration",
+ "The call duration in seconds",
+ -1, G_MAXINT64, 0,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DURATION, param_spec);
+
+ param_spec = g_param_spec_object ("end-actor",
+ "End Actor",
+ "Actor (a #TplEntity) that caused the call to end",
+ TPL_TYPE_ENTITY,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_END_ACTOR, param_spec);
+
+ param_spec = g_param_spec_int ("end-reason",
+ "End Reason",
+ "Reason for wich this call was ended",
+ 0, TP_NUM_CALL_STATE_CHANGE_REASONS, TP_CALL_STATE_CHANGE_REASON_UNKNOWN,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_END_REASON, param_spec);
+
+ param_spec = g_param_spec_string ("detailed-end-reason",
+ "Detailed End Reason",
+ "A string representing a D-Bus error that gives more details about the end reason",
+ "",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DETAILED_END_REASON, param_spec);
+
+ g_type_class_add_private (object_class, sizeof (TplCallEventPriv));
+}
+
+
+static void
+tpl_call_event_init (TplCallEvent *self)
+{
+ TplCallEventPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TPL_TYPE_CALL_EVENT, TplCallEventPriv);
+ self->priv = priv;
+}
+
+
+/**
+ * tpl_call_event_get_duration:
+ * @self: a #TplCallEvent
+ *
+ * Returns: the same duration as the #TplCallEvent:duration property
+ */
+GTimeSpan
+tpl_call_event_get_duration (TplCallEvent *self)
+{
+ g_return_val_if_fail (TPL_IS_CALL_EVENT (self), 0);
+
+ return self->priv->duration;
+}
+
+/**
+ * tpl_call_event_get_end_actor:
+ * @self: a #TplCallEvent
+ *
+ * Returns: (transfer none): the same #TplEntity
+ * as #TplCallEvent:end-actor property
+ */
+TplEntity *
+tpl_call_event_get_end_actor (TplCallEvent *self)
+{
+ g_return_val_if_fail (TPL_IS_CALL_EVENT (self), NULL);
+
+ return self->priv->end_actor;
+}
+
+
+/**
+ * tpl_call_event_get_end_reason:
+ * @self: a #TplCallEvent
+ *
+ * Returns: the same #TpCallStateChangeReason as #TplCallEvent:end-reason property
+ */
+TpCallStateChangeReason
+tpl_call_event_get_end_reason (TplCallEvent *self)
+{
+ g_return_val_if_fail (TPL_IS_CALL_EVENT (self),
+ TP_CALL_STATE_CHANGE_REASON_UNKNOWN);
+
+ return self->priv->end_reason;
+}
+
+
+/**
+ * tpl_call_event_get_detailed_end_reason:
+ * @self: a #TplCallEvent
+ *
+ * Returns: (transfer none): the same string as the
+ * #TplCallEvent:detailed-end-reason property
+ */
+const gchar *
+tpl_call_event_get_detailed_end_reason (TplCallEvent *self)
+{
+ g_return_val_if_fail (TPL_IS_CALL_EVENT (self), "");
+
+ return self->priv->detailed_end_reason;
+}
+
+
+const gchar *
+_tpl_call_event_end_reason_to_str (TpCallStateChangeReason reason)
+{
+ g_return_val_if_fail (reason < G_N_ELEMENTS (end_reasons), end_reasons[0]);
+ return end_reasons[reason];
+}
+
+
+TpCallStateChangeReason
+_tpl_call_event_str_to_end_reason (const gchar *str)
+{
+ guint i;
+ for (i = 0; i < G_N_ELEMENTS (end_reasons); i++)
+ if (g_strcmp0 (str, end_reasons[i]) == 0)
+ return i;
+
+ return TP_CALL_STATE_CHANGE_REASON_UNKNOWN;
+}
diff --git a/telepathy-logger/call-event.h b/telepathy-logger/call-event.h
new file mode 100644
index 000000000..85dd8905d
--- /dev/null
+++ b/telepathy-logger/call-event.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Nicolas Dufresne <nicolas.dufresne@collabora.co.uk>
+ */
+
+#ifndef __TPL_CALL_EVENT_H__
+#define __TPL_CALL_EVENT_H__
+
+#include <glib-object.h>
+
+#include <telepathy-logger/event.h>
+
+G_BEGIN_DECLS
+#define TPL_TYPE_CALL_EVENT (tpl_call_event_get_type ())
+#define TPL_CALL_EVENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TPL_TYPE_CALL_EVENT, TplCallEvent))
+#define TPL_CALL_EVENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TPL_TYPE_CALL_EVENT, TplCallEventClass))
+#define TPL_IS_CALL_EVENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TPL_TYPE_CALL_EVENT))
+#define TPL_IS_CALL_EVENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TPL_TYPE_CALL_EVENT))
+#define TPL_CALL_EVENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TPL_TYPE_CALL_EVENT, TplCallEventClass))
+
+typedef struct _TplCallEvent TplCallEvent;
+typedef struct _TplCallEventClass TplCallEventClass;
+typedef struct _TplCallEventPriv TplCallEventPriv;
+
+GType tpl_call_event_get_type (void);
+
+GTimeSpan tpl_call_event_get_duration (TplCallEvent *self);
+TplEntity * tpl_call_event_get_end_actor (TplCallEvent *self);
+TpCallStateChangeReason tpl_call_event_get_end_reason (TplCallEvent *self);
+const gchar * tpl_call_event_get_detailed_end_reason (TplCallEvent *self);
+
+
+G_END_DECLS
+#endif // __TPL_CALL_EVENT_H__
diff --git a/telepathy-logger/client-factory-internal.h b/telepathy-logger/client-factory-internal.h
new file mode 100644
index 000000000..f678d64da
--- /dev/null
+++ b/telepathy-logger/client-factory-internal.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2012 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Xavier Claessens <xavier.claessens@collabora.co.uk>
+ */
+
+#ifndef __TPL_CLIENT_FACTORY_H__
+#define __TPL_CLIENT_FACTORY_H__
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/telepathy-glib-dbus.h>
+
+typedef struct _TplClientFactory TplClientFactory;
+typedef struct _TplClientFactoryClass TplClientFactoryClass;
+
+struct _TplClientFactoryClass {
+ /*<public>*/
+ TpAutomaticClientFactoryClass parent_class;
+};
+
+struct _TplClientFactory {
+ /*<private>*/
+ TpAutomaticClientFactory parent;
+};
+
+GType _tpl_client_factory_get_type (void);
+
+#define TPL_TYPE_CLIENT_FACTORY \
+ (_tpl_client_factory_get_type ())
+#define TPL_CLIENT_FACTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TPL_TYPE_CLIENT_FACTORY, \
+ TplClientFactory))
+#define TPL_CLIENT_FACTORY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TPL_TYPE_CLIENT_FACTORY, \
+ TplClientFactoryClass))
+#define TPL_IS_CLIENT_FACTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TPL_TYPE_CLIENT_FACTORY))
+#define TPL_IS_CLIENT_FACTORY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TPL_TYPE_CLIENT_FACTORY))
+#define TPL_CLIENT_FACTORY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TPL_TYPE_CLIENT_FACTORY, \
+ TplClientFactoryClass))
+
+TpClientFactory *_tpl_client_factory_dup (TpDBusDaemon *dbus);
+
+#endif /* __TPL_CLIENT_FACTORY_H__ */
diff --git a/telepathy-logger/client-factory.c b/telepathy-logger/client-factory.c
new file mode 100644
index 000000000..cedd1898f
--- /dev/null
+++ b/telepathy-logger/client-factory.c
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2012 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Xavier Claessens <xavier.claessens@collabora.co.uk>
+ */
+
+#include "config.h"
+#include "client-factory-internal.h"
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include <telepathy-logger/text-channel-internal.h>
+#include <telepathy-logger/call-channel-internal.h>
+
+G_DEFINE_TYPE (TplClientFactory, _tpl_client_factory,
+ TP_TYPE_AUTOMATIC_CLIENT_FACTORY)
+
+#define chainup ((TpClientFactoryClass *) \
+ _tpl_client_factory_parent_class)
+
+static TpChannel *
+create_channel_impl (TpClientFactory *self,
+ TpConnection *conn,
+ const gchar *object_path,
+ const GHashTable *properties,
+ GError **error)
+{
+ const gchar *chan_type;
+
+ chan_type = tp_asv_get_string (properties, TP_PROP_CHANNEL_CHANNEL_TYPE);
+
+ if (!tp_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_TEXT))
+ {
+ return (TpChannel *) _tpl_text_channel_new_with_factory (self, conn,
+ object_path, properties, error);
+ }
+ else if (!tp_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_CALL1))
+ {
+ return (TpChannel *) _tpl_call_channel_new_with_factory (self, conn,
+ object_path, properties, error);
+ }
+
+ return chainup->create_channel (self, conn, object_path, properties, error);
+}
+
+static GArray *
+dup_channel_features_impl (TpClientFactory *self,
+ TpChannel *channel)
+{
+ GArray *features;
+ GQuark f;
+
+ features = chainup->dup_channel_features (self, channel);
+
+ if (TPL_IS_CALL_CHANNEL (channel))
+ {
+ f = TPL_CALL_CHANNEL_FEATURE_CORE;
+ g_array_append_val (features, f);
+ }
+ else if (TPL_IS_TEXT_CHANNEL (channel))
+ {
+ f = TPL_TEXT_CHANNEL_FEATURE_CORE;
+ g_array_append_val (features, f);
+ }
+
+ return features;
+}
+
+static void
+_tpl_client_factory_init (TplClientFactory *self)
+{
+}
+
+static void
+_tpl_client_factory_class_init (TplClientFactoryClass *cls)
+{
+ TpClientFactoryClass *simple_class = (TpClientFactoryClass *) cls;
+
+ simple_class->create_channel = create_channel_impl;
+ simple_class->dup_channel_features = dup_channel_features_impl;
+}
+
+
+static TpClientFactory *
+_tpl_client_factory_new (TpDBusDaemon *dbus)
+{
+ return g_object_new (TPL_TYPE_CLIENT_FACTORY,
+ "dbus-daemon", dbus,
+ NULL);
+}
+
+TpClientFactory *
+_tpl_client_factory_dup (TpDBusDaemon *dbus)
+{
+ static TpClientFactory *singleton = NULL;
+
+ if (singleton != NULL)
+ return g_object_ref (singleton);
+
+ singleton = _tpl_client_factory_new (dbus);
+
+ g_object_add_weak_pointer (G_OBJECT (singleton), (gpointer) &singleton);
+
+ return singleton;
+}
diff --git a/telepathy-logger/conf-internal.h b/telepathy-logger/conf-internal.h
new file mode 100644
index 000000000..f88aa3392
--- /dev/null
+++ b/telepathy-logger/conf-internal.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2009 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ */
+
+#ifndef __TPL_CONF_H__
+#define __TPL_CONF_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define TPL_TYPE_CONF (_tpl_conf_get_type ())
+#define TPL_CONF(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TPL_TYPE_CONF, TplConf))
+#define TPL_CONF_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TPL_TYPE_CONF, TplConfClass))
+#define TPL_IS_CONF(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TPL_TYPE_CONF))
+#define TPL_IS_CONF_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TPL_TYPE_CONF))
+#define TPL_CONF_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TPL_TYPE_CONF, TplConfClass))
+
+typedef struct
+{
+ GObject parent;
+
+ /* private */
+ gpointer priv;
+} TplConf;
+
+typedef struct
+{
+ GObjectClass parent_class;
+} TplConfClass;
+
+GType _tpl_conf_get_type (void);
+TplConf *_tpl_conf_dup (void);
+
+gboolean _tpl_conf_is_globally_enabled (TplConf *self);
+const gchar **_tpl_conf_get_ignorelist (TplConf *self);
+
+void _tpl_conf_globally_enable (TplConf *self, gboolean enable);
+void _tpl_conf_set_ignorelist (TplConf *self, const gchar **newlist);
+G_END_DECLS
+
+#endif // __TPL_CONF_H__
diff --git a/telepathy-logger/conf.c b/telepathy-logger/conf.c
new file mode 100644
index 000000000..888114dea
--- /dev/null
+++ b/telepathy-logger/conf.c
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2009-2010 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ * Danielle Madeley <danielle.madeley@collabora.co.uk>
+ */
+
+#include "config.h"
+#include "conf-internal.h"
+
+#include <glib.h>
+#include <gio/gio.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+#define DEBUG_FLAG TPL_DEBUG_CONF
+#include <telepathy-logger/debug-internal.h>
+#include <telepathy-logger/util-internal.h>
+
+#define GET_PRIV(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TPL_TYPE_CONF, TplConfPriv))
+
+#define GSETTINGS_SCHEMA "org.freedesktop.Telepathy.Logger"
+#define KEY_ENABLED "enabled"
+
+G_DEFINE_TYPE (TplConf, _tpl_conf, G_TYPE_OBJECT)
+
+static TplConf *conf_singleton = NULL;
+
+typedef struct
+{
+ gboolean test_mode;
+ gchar **ignore_list;
+ GSettings *gsettings;
+} TplConfPriv;
+
+
+enum /* properties */
+{
+ PROP_0,
+ PROP_GLOBALLY_ENABLED,
+ PROP_IGNORE_LIST,
+};
+
+
+static void
+_notify_globally_enable (GSettings *gsettings,
+ const gchar *key,
+ GObject *self)
+{
+ g_object_notify (self, "globally-enabled");
+}
+
+
+static void
+tpl_conf_get_property (GObject *self,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (prop_id)
+ {
+ case PROP_GLOBALLY_ENABLED:
+ g_value_set_boolean (value,
+ _tpl_conf_is_globally_enabled (TPL_CONF (self)));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (self, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+tpl_conf_set_property (GObject *self,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (prop_id)
+ {
+ case PROP_GLOBALLY_ENABLED:
+ _tpl_conf_globally_enable (TPL_CONF (self),
+ g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (self, prop_id, pspec);
+ break;
+ }
+}
+
+
+static void
+tpl_conf_finalize (GObject *obj)
+{
+ TplConfPriv *priv;
+
+ priv = GET_PRIV (obj);
+
+ g_strfreev (priv->ignore_list);
+ priv->ignore_list = NULL;
+
+ if (priv->gsettings != NULL)
+ {
+ g_object_unref (priv->gsettings);
+ priv->gsettings = NULL;
+ }
+
+ G_OBJECT_CLASS (_tpl_conf_parent_class)->finalize (obj);
+}
+
+
+static GObject *
+tpl_conf_constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *retval;
+
+ if (conf_singleton != NULL)
+ {
+ retval = g_object_ref (conf_singleton);
+ }
+ else
+ {
+ retval = G_OBJECT_CLASS (_tpl_conf_parent_class)->constructor (type,
+ n_props, props);
+ conf_singleton = TPL_CONF (retval);
+ g_object_add_weak_pointer (retval, (gpointer *) &conf_singleton);
+ }
+ return retval;
+}
+
+
+static void
+_tpl_conf_class_init (TplConfClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = tpl_conf_get_property;
+ object_class->set_property = tpl_conf_set_property;
+ object_class->finalize = tpl_conf_finalize;
+ object_class->constructor = tpl_conf_constructor;
+
+ g_object_class_install_property (object_class, PROP_GLOBALLY_ENABLED,
+ g_param_spec_boolean ("globally-enabled",
+ "Globally Enabled",
+ "TRUE if logging is enabled (may still be disabled for specific users)",
+ TRUE,
+ G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_IGNORE_LIST,
+ g_param_spec_pointer ("ignore-list",
+ "Ignore List",
+ "List of TplEntities with which not to log conversations.",
+ G_PARAM_READWRITE));
+
+ g_type_class_add_private (object_class, sizeof (TplConfPriv));
+}
+
+
+static void
+_tpl_conf_init (TplConf *self)
+{
+ TplConfPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TPL_TYPE_CONF, TplConfPriv);
+
+ if (g_getenv ("TPL_TEST_MODE") != NULL)
+ {
+ priv->test_mode = TRUE;
+ }
+ else
+ {
+ priv->gsettings = g_settings_new (GSETTINGS_SCHEMA);
+
+ g_signal_connect (priv->gsettings, "changed::" KEY_ENABLED,
+ G_CALLBACK (_notify_globally_enable), self);
+ }
+
+ priv->ignore_list = NULL;
+}
+
+
+/**
+ * _tpl_conf_dup:
+ *
+ * Convenience function to obtain a TPL Configuration object, which is a
+ * singleton.
+ *
+ * Returns: a TplConf signleton instance with its reference counter
+ * incremented. Remember to unref the counter.
+ */
+TplConf *
+_tpl_conf_dup (void)
+{
+ return g_object_new (TPL_TYPE_CONF, NULL);
+}
+
+
+/**
+ * _tpl_conf_is_globally_enabled:
+ * @self: a TplConf instance
+ *
+ * Whether TPL is globally enabled or not. If it's not globally enabled, no
+ * signals will be logged at all.
+ * To enable/disable a single account use _tpl_conf_set_accounts_ignorelist()
+ *
+ * Returns: %TRUE if TPL logging is globally enabled, otherwise returns
+ * %FALSE.
+ */
+gboolean
+_tpl_conf_is_globally_enabled (TplConf *self)
+{
+ g_return_val_if_fail (TPL_IS_CONF (self), FALSE);
+
+ if (GET_PRIV (self)->test_mode)
+ return TRUE;
+ else
+ return g_settings_get_boolean (GET_PRIV (self)->gsettings, KEY_ENABLED);
+}
+
+
+/**
+ * _tpl_conf_globally_enable:
+ * @self: a TplConf instance
+ * @enable: wether to globally enable or globally disable logging.
+ *
+ * Globally enables or disables logging for TPL. If it's globally disabled, no
+ * signals will be logged at all.
+ * Note that this will change the global TPL configuration, affecting all the
+ * TPL instances, including the TPL logging process and all the clients using
+ * libtelepathy-logger.
+ */
+void
+_tpl_conf_globally_enable (TplConf *self,
+ gboolean enable)
+{
+ g_return_if_fail (TPL_IS_CONF (self));
+
+ if (GET_PRIV (self)->test_mode)
+ return;
+
+ g_settings_set_boolean (GET_PRIV (self)->gsettings,
+ KEY_ENABLED, enable);
+}
+
+/**
+ * _tpl_conf_set_accounts_ignorelist:
+ * @self: a TplConf instance
+ * @newlist: a NULL-terminated list of account/entity IDs that should not be logged
+ */
+void
+_tpl_conf_set_ignorelist (TplConf *self,
+ const gchar **newlist)
+{
+ TplConfPriv *priv;
+
+ g_return_if_fail (TPL_IS_CONF (self));
+
+ priv = GET_PRIV (self);
+
+ if (!priv->test_mode) {
+ g_settings_set_strv (GET_PRIV (self)->gsettings, "ignorelist", newlist);
+ }
+
+ g_strfreev (priv->ignore_list);
+ priv->ignore_list = g_strdupv ((gchar **) newlist);
+
+ g_object_notify (G_OBJECT (self), "ignore-list");
+}
+
+/**
+ * _tpl_conf_get_accounts_ignorelist:
+ * @self: a TplConf instance
+ *
+ * Provides list of IDs in "account_id/entity_id" format. Events from or to
+ * this entities should not be logged.
+ *
+ * Returns: (transfer none) NULL-terminated list of contact IDs.
+ */
+const gchar **
+_tpl_conf_get_ignorelist (TplConf *self)
+{
+ TplConfPriv *priv;
+
+ g_return_val_if_fail (TPL_IS_CONF (self), NULL);
+
+ priv = GET_PRIV (self);
+
+ if ((priv->ignore_list == NULL) && (!priv->test_mode)) {
+ priv->ignore_list = g_settings_get_strv (priv->gsettings, "ignorelist");
+ }
+
+ return (const gchar **) priv->ignore_list;
+}
diff --git a/telepathy-logger/dbus-service-internal.h b/telepathy-logger/dbus-service-internal.h
new file mode 100644
index 000000000..b2dc657a5
--- /dev/null
+++ b/telepathy-logger/dbus-service-internal.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2009 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ */
+
+#ifndef __TPL_DBUS_SERVICE_H__
+#define __TPL_DBUS_SERVICE_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+#include <telepathy-logger/log-manager.h>
+
+#define TPL_DBUS_SRV_WELL_KNOWN_BUS_NAME \
+ "im.telepathy1.Logger"
+#define TPL_DBUS_SRV_OBJECT_PATH \
+ "/im.telepathy1/Logger"
+
+G_BEGIN_DECLS
+
+#define TPL_TYPE_DBUS_SERVICE (_tpl_dbus_service_get_type ())
+#define TPL_DBUS_SERVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TPL_TYPE_DBUS_SERVICE, TplDBusService))
+#define TPL_DBUS_SERVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TPL_TYPE_DBUS_SERVICE, TplDBusServiceClass))
+#define TPL_IS_DBUS_SERVICE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TPL_TYPE_DBUS_SERVICE))
+#define TPL_IS_DBUS_SERVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TPL_TYPE_DBUS_SERVICE))
+#define TPL_DBUS_SERVICE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TPL_TYPE_DBUS_SERVICE, TplDBusServiceClass))
+
+#define TPL_DBUS_SERVICE_ERROR g_quark_from_string ( \
+ "tpl-dbus-service-error-quark")
+typedef enum
+{
+ TPL_DBUS_SERVICE_ERROR_FAILED,
+ /* >= 1 argument(s) is/are invalid */
+ TPL_DBUS_SERVICE_ERROR_INVALID_ARGS,
+ TPL_DBUS_SERVICE_ERROR_NOT_READY,
+} TplDBusServiceError;
+
+typedef struct _TplDBusServicePriv TplDBusServicePriv;
+typedef struct
+{
+ GObject parent;
+ /* Private */
+ TplDBusServicePriv *priv;
+} TplDBusService;
+
+
+typedef struct
+{
+ GObjectClass parent_class;
+} TplDBusServiceClass;
+
+typedef struct
+{
+ long unsigned timestamp;
+ gchar *sender;
+ gchar *message;
+} TplDBusServiceChatMessage;
+
+GType _tpl_dbus_service_get_type (void);
+
+TplDBusService * _tpl_dbus_service_new (void);
+
+G_END_DECLS
+
+#endif
diff --git a/telepathy-logger/dbus-service.c b/telepathy-logger/dbus-service.c
new file mode 100644
index 000000000..1e0d4ff5d
--- /dev/null
+++ b/telepathy-logger/dbus-service.c
@@ -0,0 +1,871 @@
+/*
+ * Copyright (C) 2009-2011 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ */
+
+#include "config.h"
+#include "dbus-service-internal.h"
+
+#include <string.h>
+#include <sys/stat.h>
+
+#include <glib.h>
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/telepathy-glib-dbus.h>
+
+#include <telepathy-logger/event-internal.h>
+#include <telepathy-logger/text-event.h>
+#include <telepathy-logger/log-manager.h>
+#include <telepathy-logger/log-manager-internal.h>
+#include <telepathy-logger/client-factory-internal.h>
+
+#include "telepathy-logger/extensions/extensions.h"
+
+#define DEBUG_FLAG TPL_DEBUG_DBUS_SERVICE
+#include <telepathy-logger/action-chain-internal.h>
+#include <telepathy-logger/debug-internal.h>
+#include <telepathy-logger/util-internal.h>
+
+#define FAVOURITE_CONTACTS_FILENAME "favourite-contacts.txt"
+
+static void tpl_logger_iface_init (gpointer iface, gpointer iface_data);
+
+struct _TplDBusServicePriv
+{
+ TplLogManager *manager;
+ /* map of (string) account name -> (string set) contact ID */
+ /* (the set is implemented as a hash table) */
+ GHashTable *accounts_contacts_map;
+ TplActionChain *favourite_contacts_actions;
+};
+
+G_DEFINE_TYPE_WITH_CODE (TplDBusService, _tpl_dbus_service, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TPL_TYPE_SVC_LOGGER, tpl_logger_iface_init));
+
+typedef struct _FavouriteContactClosure FavouriteContactClosure;
+typedef void (*FavouriteContactCallback) (gboolean success,
+ FavouriteContactClosure *closure);
+
+
+struct _FavouriteContactClosure {
+ TplDBusService *service;
+ gchar *account;
+ gchar *contact_id;
+ gchar *file_contents;
+ DBusGMethodInvocation *context;
+ FavouriteContactCallback cb;
+};
+
+
+static void
+favourite_contact_closure_free (FavouriteContactClosure *closure)
+{
+ if (closure == NULL)
+ return;
+
+ if (closure->service != NULL)
+ g_object_unref (closure->service);
+
+ g_free (closure->account);
+ g_free (closure->contact_id);
+ g_free (closure->file_contents);
+ g_slice_free (FavouriteContactClosure, closure);
+}
+
+
+static FavouriteContactClosure *
+favourite_contact_closure_new (TplDBusService *self,
+ const gchar *account,
+ const gchar *contact_id,
+ DBusGMethodInvocation *context)
+{
+ FavouriteContactClosure *closure;
+
+ closure = g_slice_new0 (FavouriteContactClosure);
+ closure->service = g_object_ref (G_OBJECT (self));
+ closure->account = g_strdup (account);
+ closure->contact_id = g_strdup (contact_id);
+ /* XXX: ideally we'd up the ref count or duplicate this */
+ closure->context = context;
+
+ return closure;
+}
+
+
+static gboolean
+favourite_contacts_add_event (TplDBusService *self,
+ const gchar *account,
+ const gchar *contact_id)
+{
+ GHashTable *contacts;
+ gboolean new_event = FALSE;
+ TplDBusServicePriv *priv;
+
+ g_return_val_if_fail (TPL_IS_DBUS_SERVICE (self), FALSE);
+ g_return_val_if_fail (account != NULL, FALSE);
+ g_return_val_if_fail (contact_id != NULL, FALSE);
+
+ priv = self->priv;
+
+ DEBUG ("adding favourite contact: account '%s', ID '%s'",
+ account, contact_id);
+
+ contacts = g_hash_table_lookup (priv->accounts_contacts_map, account);
+ if (contacts == NULL)
+ {
+ contacts = g_hash_table_new_full (g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free, NULL);
+ g_hash_table_insert (priv->accounts_contacts_map, g_strdup (account),
+ contacts);
+ new_event = TRUE;
+ }
+ else if (g_hash_table_lookup (contacts, contact_id) == NULL)
+ {
+ new_event = TRUE;
+ }
+
+ if (new_event)
+ {
+ /* add dummy string for the value just for the convenience of looking up
+ * whether the key already exists */
+ g_hash_table_insert (contacts, g_strdup (contact_id),
+ GINT_TO_POINTER (TRUE));
+ }
+
+ return new_event;
+}
+
+
+static const gchar *
+favourite_contacts_get_filename (void)
+{
+ static gchar *filename = NULL;
+
+ if (filename == NULL)
+ {
+ filename = g_build_filename (g_get_user_data_dir (), TPL_DATA_DIR,
+ FAVOURITE_CONTACTS_FILENAME, NULL);
+ }
+
+ return filename;
+}
+
+
+static gboolean
+favourite_contacts_parse_line (TplDBusService *self,
+ const gchar *line)
+{
+ gboolean success = TRUE;
+ gchar **elements;
+
+ if (line == NULL || line[0] == '\0')
+ return TRUE;
+
+ /* this works on the assumption that account names can't have spaces in them
+ */
+ elements = g_strsplit (line, " ", 2);
+ if (g_strv_length (elements) < 2)
+ {
+ DEBUG ("invalid number of elements on favourite contacts file line:\n"
+ "%s\n", line);
+ success = FALSE;
+ }
+ else
+ favourite_contacts_add_event (self, elements[0], elements[1]);
+
+ g_strfreev (elements);
+
+ return success;
+}
+
+
+static void
+favourite_contacts_file_read_line_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GDataInputStream *data_stream = G_DATA_INPUT_STREAM (object);
+ TplActionChain *action_chain = (TplActionChain *) (user_data);
+ TplDBusService *self = _tpl_action_chain_get_object (action_chain);
+ gchar *line;
+ GError *error = NULL;
+
+ line = g_data_input_stream_read_line_finish (data_stream, result, NULL, &error);
+
+ if (error != NULL)
+ {
+ g_prefix_error (&error, "failed to open favourite contacts file: ");
+ _tpl_action_chain_terminate (action_chain, error);
+ g_clear_error (&error);
+ }
+ else if (line != NULL)
+ {
+ favourite_contacts_parse_line (self, line);
+
+ g_data_input_stream_read_line_async (data_stream, G_PRIORITY_DEFAULT,
+ NULL, favourite_contacts_file_read_line_cb, action_chain);
+ }
+ else
+ _tpl_action_chain_continue (action_chain);
+}
+
+
+static void
+favourite_contacts_file_open_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GFile *file = G_FILE (object);
+ TplActionChain *action_chain = (TplActionChain *) user_data;
+ GFileInputStream *stream;
+ GError *error = NULL;
+
+ if ((stream = g_file_read_finish (file, result, &error)))
+ {
+ GDataInputStream *data_stream = g_data_input_stream_new (
+ G_INPUT_STREAM (stream));
+
+ g_data_input_stream_read_line_async (data_stream, G_PRIORITY_DEFAULT,
+ NULL, favourite_contacts_file_read_line_cb, action_chain);
+
+ g_object_unref (stream);
+ }
+ else if (error->code == G_IO_ERROR_NOT_FOUND)
+ {
+ DEBUG ("Favourite contacts file doesn't exist yet. Will create as "
+ "necessary.");
+
+ g_clear_error (&error);
+ _tpl_action_chain_continue (action_chain);
+ }
+ else
+ {
+ g_prefix_error (&error, "Failed to open the favourite contacts file: ");
+ _tpl_action_chain_terminate (action_chain, error);
+ g_clear_error (&error);
+ }
+}
+
+
+static void
+pendingproc_favourite_contacts_file_open (TplActionChain *action_chain,
+ gpointer user_data)
+{
+ const gchar *filename;
+ GFile *file;
+
+ filename = favourite_contacts_get_filename ();
+ file = g_file_new_for_path (filename);
+
+ g_file_read_async (file, G_PRIORITY_DEFAULT, NULL,
+ favourite_contacts_file_open_cb, action_chain);
+
+ g_object_unref (G_OBJECT (file));
+}
+
+
+static void
+tpl_dbus_service_dispose (GObject *obj)
+{
+ TplDBusServicePriv *priv = TPL_DBUS_SERVICE (obj)->priv;
+
+ if (priv->accounts_contacts_map != NULL)
+ {
+ g_hash_table_unref (priv->accounts_contacts_map);
+ priv->accounts_contacts_map = NULL;
+ }
+
+ if (priv->favourite_contacts_actions != NULL)
+ priv->favourite_contacts_actions = NULL;
+
+ G_OBJECT_CLASS (_tpl_dbus_service_parent_class)->dispose (obj);
+}
+
+
+static void
+favourite_contacts_file_parsed_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TplDBusService *self = TPL_DBUS_SERVICE (object);
+ TplDBusServicePriv *priv = self->priv;
+ GError *error = NULL;
+
+ if (!_tpl_action_chain_new_finish (object, result, &error))
+ {
+ DEBUG ("Failed to parse the favourite contacts file and/or execute "
+ "subsequent queued method calls: %s", error->message);
+ g_error_free (error);
+ }
+
+ priv->favourite_contacts_actions = NULL;
+}
+
+
+static void
+tpl_dbus_service_constructed (GObject *object)
+{
+ TplDBusServicePriv *priv = TPL_DBUS_SERVICE (object)->priv;
+
+ priv->favourite_contacts_actions = _tpl_action_chain_new_async (object,
+ favourite_contacts_file_parsed_cb, object);
+
+ _tpl_action_chain_append (priv->favourite_contacts_actions,
+ pendingproc_favourite_contacts_file_open, NULL);
+ _tpl_action_chain_continue (priv->favourite_contacts_actions);
+}
+
+
+static void
+_tpl_dbus_service_class_init (TplDBusServiceClass *klass)
+{
+ GObjectClass* object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = tpl_dbus_service_constructed;
+ object_class->dispose = tpl_dbus_service_dispose;
+
+ g_type_class_add_private (object_class, sizeof (TplDBusServicePriv));
+}
+
+
+static void
+_tpl_dbus_service_init (TplDBusService *self)
+{
+ TplDBusServicePriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TPL_TYPE_DBUS_SERVICE, TplDBusServicePriv);
+
+ g_return_if_fail (TPL_IS_DBUS_SERVICE (self));
+
+ self->priv = priv;
+ priv->manager = tpl_log_manager_dup_singleton ();
+ priv->accounts_contacts_map = g_hash_table_new_full (g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free, (GDestroyNotify) g_hash_table_unref);
+ priv->favourite_contacts_actions = NULL;
+}
+
+
+TplDBusService *
+_tpl_dbus_service_new (void)
+{
+ return g_object_new (TPL_TYPE_DBUS_SERVICE, NULL);
+}
+
+
+static void
+append_favourite_contacts_account_and_contacts (const gchar *account,
+ GHashTable *contacts,
+ GPtrArray *packed)
+{
+ GList *l;
+ gchar **contact_ids;
+ gint i;
+
+ /* this case shouldn't happen, but this is just some basic sanity checking */
+ if (g_hash_table_size (contacts) < 1)
+ return;
+
+ /* includes room for the terminal NULL */
+ contact_ids = g_new0 (gchar *, g_hash_table_size (contacts)+1);
+
+ for (i = 0, l = g_hash_table_get_keys (contacts);
+ l;
+ i++, l = g_list_delete_link (l, l))
+ {
+ contact_ids[i] = l->data;
+ }
+
+ g_ptr_array_add (packed, tp_value_array_build (2,
+ DBUS_TYPE_G_OBJECT_PATH, account,
+ G_TYPE_STRV, contact_ids,
+ G_TYPE_INVALID));
+
+ g_free (contact_ids);
+}
+
+
+static void
+pendingproc_get_favourite_contacts (TplActionChain *action_chain,
+ gpointer user_data)
+{
+ FavouriteContactClosure *closure = user_data;
+ TplDBusServicePriv *priv;
+ GPtrArray *packed;
+
+ g_return_if_fail (closure);
+ g_return_if_fail (TPL_IS_DBUS_SERVICE (closure->service));
+ g_return_if_fail (closure->context != NULL);
+
+ priv = closure->service->priv;
+
+ packed = g_ptr_array_new_with_free_func ((GDestroyNotify) tp_value_array_free);
+
+ g_hash_table_foreach (priv->accounts_contacts_map,
+ (GHFunc) append_favourite_contacts_account_and_contacts, packed);
+
+ tpl_svc_logger_return_from_get_favourite_contacts (closure->context, packed);
+
+ g_ptr_array_unref (packed);
+ favourite_contact_closure_free (closure);
+
+ if (action_chain != NULL)
+ _tpl_action_chain_continue (action_chain);
+}
+
+
+static void
+tpl_dbus_service_get_favourite_contacts (TplSvcLogger *logger,
+ DBusGMethodInvocation *context)
+{
+ TplDBusService *self;
+ TplDBusServicePriv *priv;
+ FavouriteContactClosure *closure;
+
+ g_return_if_fail (TPL_IS_DBUS_SERVICE (logger));
+ g_return_if_fail (context != NULL);
+
+ self = TPL_DBUS_SERVICE (logger);
+ priv = self->priv;
+
+ closure = favourite_contact_closure_new (self, NULL, NULL, context);
+
+ /* If we're still waiting on the contacts to finish being parsed from disk,
+ * queue this action */
+ if (priv->favourite_contacts_actions != NULL)
+ {
+ _tpl_action_chain_append (priv->favourite_contacts_actions,
+ pendingproc_get_favourite_contacts, closure);
+ }
+ else
+ pendingproc_get_favourite_contacts (NULL, closure);
+}
+
+
+static void
+append_favourite_contacts_file_entries (const gchar *account,
+ GHashTable *contacts,
+ GString *string)
+{
+ GList *l;
+
+ for (l = g_hash_table_get_keys (contacts); l; l = g_list_delete_link (l, l))
+ g_string_append_printf (string, "%s %s\n", account, (const gchar*) l->data);
+}
+
+
+static gchar *
+favourite_contacts_to_string (TplDBusService *self)
+{
+ TplDBusServicePriv *priv = self->priv;
+ GString *string;
+
+ string = g_string_new ("");
+
+ g_hash_table_foreach (priv->accounts_contacts_map,
+ (GHFunc) append_favourite_contacts_file_entries, string);
+
+ return g_string_free (string, FALSE);
+}
+
+
+static void
+favourite_contacts_file_replace_contents_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GFile *file = G_FILE (object);
+ GError *error = NULL;
+ FavouriteContactClosure *closure = user_data;
+ gboolean success;
+
+ if (g_file_replace_contents_finish (file, result, NULL, &error))
+ {
+ success = TRUE;
+ }
+ else
+ {
+ DEBUG ("Failed to save favourite contacts file: %s", error->message);
+ success = FALSE;
+ g_clear_error (&error);
+ }
+
+ ((FavouriteContactCallback) closure->cb) (success, closure);
+}
+
+
+static void
+favourite_contacts_file_save_async (TplDBusService *self,
+ FavouriteContactClosure *closure)
+{
+ gchar *dir;
+ const gchar *filename;
+ GFile *file;
+ gchar *file_contents;
+
+ g_return_if_fail (closure != NULL);
+
+ filename = favourite_contacts_get_filename ();
+ dir = g_path_get_dirname (filename);
+ g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR);
+ g_free (dir);
+
+ file = g_file_new_for_path (filename);
+
+ file_contents = favourite_contacts_to_string (self);
+
+ closure->file_contents = file_contents;
+
+ g_file_replace_contents_async (file,
+ file_contents, strlen (file_contents), NULL, FALSE,
+ G_FILE_CREATE_REPLACE_DESTINATION, NULL,
+ favourite_contacts_file_replace_contents_cb, closure);
+
+ g_object_unref (file);
+}
+
+
+static void
+add_favourite_contact_file_save_cb (gboolean added_favourite,
+ FavouriteContactClosure *closure)
+{
+ TplDBusServicePriv *priv = closure->service->priv;
+ TplActionChain *action_chain = priv->favourite_contacts_actions;
+
+ if (added_favourite)
+ {
+ const gchar *added[] = { NULL, NULL };
+ const gchar *removed[] = { NULL };
+
+ added[0] = closure->contact_id;
+
+ tpl_svc_logger_emit_favourite_contacts_changed (closure->service,
+ closure->account, added, removed);
+ }
+
+ tpl_svc_logger_return_from_add_favourite_contact (closure->context);
+
+ favourite_contact_closure_free (closure);
+ if (action_chain != NULL)
+ _tpl_action_chain_continue (action_chain);
+}
+
+
+static void
+pendingproc_add_favourite_contact (TplActionChain *action_chain,
+ gpointer user_data)
+{
+ FavouriteContactClosure *closure = user_data;
+ gboolean should_add = FALSE;
+ GError *error = NULL;
+
+ g_return_if_fail (closure);
+ g_return_if_fail (TPL_IS_DBUS_SERVICE (closure->service));
+ g_return_if_fail (closure->context != NULL);
+
+ if (!tp_dbus_check_valid_object_path (closure->account, &error))
+ {
+ dbus_g_method_return_error (closure->context, error);
+
+ goto pendingproc_add_favourite_contact_ERROR;
+ }
+
+ should_add = favourite_contacts_add_event (closure->service, closure->account,
+ closure->contact_id);
+
+ closure->cb = add_favourite_contact_file_save_cb;
+
+ if (should_add)
+ favourite_contacts_file_save_async (closure->service, closure);
+ else
+ add_favourite_contact_file_save_cb (FALSE, closure);
+
+ return;
+
+pendingproc_add_favourite_contact_ERROR:
+ if (action_chain != NULL)
+ _tpl_action_chain_terminate (action_chain, error);
+
+ g_clear_error (&error);
+}
+
+
+static void
+tpl_dbus_service_add_favourite_contact (TplSvcLogger *logger,
+ const gchar *account,
+ const gchar *contact_id,
+ DBusGMethodInvocation *context)
+{
+ TplDBusService *self = TPL_DBUS_SERVICE (logger);
+ TplDBusServicePriv *priv;
+ FavouriteContactClosure *closure;
+
+ g_return_if_fail (TPL_IS_DBUS_SERVICE (self));
+ g_return_if_fail (context != NULL);
+
+ priv = self->priv;
+
+ closure = favourite_contact_closure_new (self, account, contact_id, context);
+
+ /* If we're still waiting on the contacts to finish being parsed from disk,
+ * queue this action */
+ if (priv->favourite_contacts_actions != NULL)
+ {
+ _tpl_action_chain_append (priv->favourite_contacts_actions,
+ pendingproc_add_favourite_contact, closure);
+ }
+ else
+ pendingproc_add_favourite_contact (NULL, closure);
+}
+
+static void
+remove_favourite_contact_file_save_cb (gboolean removed_favourite,
+ FavouriteContactClosure *closure)
+{
+ TplDBusServicePriv *priv = closure->service->priv;
+ TplActionChain *action_chain = priv->favourite_contacts_actions;
+
+ if (removed_favourite)
+ {
+ const gchar *added[] = { NULL };
+ const gchar *removed[] = { NULL, NULL };
+
+ removed[0] = closure->contact_id;
+
+ tpl_svc_logger_emit_favourite_contacts_changed (closure->service,
+ closure->account, added, removed);
+ }
+
+ tpl_svc_logger_return_from_remove_favourite_contact (closure->context);
+
+ favourite_contact_closure_free (closure);
+ if (action_chain != NULL)
+ _tpl_action_chain_continue (action_chain);
+}
+
+
+static void
+pendingproc_remove_favourite_contact (TplActionChain *action_chain,
+ gpointer user_data)
+{
+ FavouriteContactClosure *closure = user_data;
+ GHashTable *contacts;
+ gboolean removed = FALSE;
+ GError *error = NULL;
+ TplDBusServicePriv *priv;
+
+ g_return_if_fail (closure != NULL);
+ g_return_if_fail (TPL_IS_DBUS_SERVICE (closure->service));
+ g_return_if_fail (closure->context != NULL);
+
+ priv = closure->service->priv;
+
+ if (!tp_dbus_check_valid_object_path (closure->account, &error))
+ {
+ dbus_g_method_return_error (closure->context, error);
+
+ goto pendingproc_remove_favourite_contact_ERROR;
+ }
+
+ DEBUG ("removing favourite contact: account '%s', ID '%s'",
+ closure->account, closure->contact_id);
+
+ contacts = g_hash_table_lookup (priv->accounts_contacts_map,
+ closure->account);
+ if (contacts != NULL && g_hash_table_remove (contacts, closure->contact_id))
+ removed = TRUE;
+
+ closure->cb = remove_favourite_contact_file_save_cb;
+
+ if (removed)
+ favourite_contacts_file_save_async (closure->service, closure);
+ else
+ remove_favourite_contact_file_save_cb (FALSE, closure);
+
+ return;
+
+pendingproc_remove_favourite_contact_ERROR:
+ if (action_chain != NULL)
+ _tpl_action_chain_terminate (action_chain, error);
+
+ g_clear_error (&error);
+}
+
+static void
+tpl_dbus_service_remove_favourite_contact (TplSvcLogger *logger,
+ const gchar *account,
+ const gchar *contact_id,
+ DBusGMethodInvocation *context)
+{
+ TplDBusService *self = TPL_DBUS_SERVICE (logger);
+ TplDBusServicePriv *priv;
+ FavouriteContactClosure *closure;
+
+ g_return_if_fail (TPL_IS_DBUS_SERVICE (self));
+ g_return_if_fail (context != NULL);
+
+ priv = self->priv;
+
+ closure = favourite_contact_closure_new (self, account, contact_id, context);
+
+ /* If we're still waiting on the contacts to finish being parsed from disk,
+ * queue this action */
+ if (priv->favourite_contacts_actions != NULL)
+ {
+ _tpl_action_chain_append (priv->favourite_contacts_actions,
+ pendingproc_remove_favourite_contact, closure);
+ }
+ else
+ pendingproc_remove_favourite_contact (NULL, closure);
+}
+
+
+static void
+tpl_dbus_service_clear (TplSvcLogger *logger,
+ DBusGMethodInvocation *context)
+{
+ TplDBusService *self = TPL_DBUS_SERVICE (logger);
+
+ g_return_if_fail (TPL_IS_DBUS_SERVICE (self));
+ g_return_if_fail (context != NULL);
+
+ /* We want to clear synchronously to avoid concurent write */
+ _tpl_log_manager_clear (self->priv->manager);
+
+ tpl_svc_logger_return_from_clear (context);
+}
+
+
+static void
+tpl_dbus_service_clear_account (TplSvcLogger *logger,
+ const gchar *account_path,
+ DBusGMethodInvocation *context)
+{
+ TplDBusService *self = TPL_DBUS_SERVICE (logger);
+ TpDBusDaemon *bus;
+ TpAccount *account;
+ GError *error = NULL;
+ TpClientFactory *factory = NULL;
+
+ g_return_if_fail (TPL_IS_DBUS_SERVICE (self));
+ g_return_if_fail (context != NULL);
+
+ bus = tp_dbus_daemon_dup (&error);
+ if (bus == NULL)
+ {
+ DEBUG ("Unable to acquire the bus daemon: %s", error->message);
+ dbus_g_method_return_error (context, error);
+ goto out;
+ }
+
+ factory = _tpl_client_factory_dup (bus);
+
+ account = tp_client_factory_ensure_account (factory, account_path,
+ NULL, &error);
+ if (account == NULL)
+ {
+ DEBUG ("Unable to acquire the account for %s: %s", account_path,
+ error->message);
+ dbus_g_method_return_error (context, error);
+ goto out;
+ }
+
+ /* We want to clear synchronously to avoid concurent write */
+ _tpl_log_manager_clear_account (self->priv->manager, account);
+ g_object_unref (account);
+
+ tpl_svc_logger_return_from_clear_account (context);
+
+out:
+ if (bus != NULL)
+ g_object_unref (bus);
+
+ g_clear_error (&error);
+ g_clear_object (&factory);
+}
+
+
+static void
+tpl_dbus_service_clear_entity (TplSvcLogger *logger,
+ const gchar *account_path,
+ const gchar *identifier,
+ gint type,
+ DBusGMethodInvocation *context)
+{
+ TplDBusService *self = TPL_DBUS_SERVICE (logger);
+ TpDBusDaemon *bus;
+ TpAccount *account;
+ TplEntity *entity;
+ GError *error = NULL;
+ TpClientFactory *factory = NULL;
+
+ g_return_if_fail (TPL_IS_DBUS_SERVICE (self));
+ g_return_if_fail (context != NULL);
+ g_return_if_fail (!TPL_STR_EMPTY (identifier));
+
+ bus = tp_dbus_daemon_dup (&error);
+ if (bus == NULL)
+ {
+ DEBUG ("Unable to acquire the bus daemon: %s", error->message);
+ dbus_g_method_return_error (context, error);
+ goto out;
+ }
+
+ factory = _tpl_client_factory_dup (bus);
+
+ account = tp_client_factory_ensure_account (factory, account_path,
+ NULL, &error);
+ if (account == NULL)
+ {
+ DEBUG ("Unable to acquire the account for %s: %s", account_path,
+ error->message);
+ dbus_g_method_return_error (context, error);
+ goto out;
+ }
+
+ entity = tpl_entity_new (identifier, type, NULL, NULL);
+
+ /* We want to clear synchronously to avoid concurent write */
+ _tpl_log_manager_clear_entity (self->priv->manager, account, entity);
+
+ g_object_unref (account);
+ g_object_unref (entity);
+ g_clear_object (&factory);
+
+ tpl_svc_logger_return_from_clear_account (context);
+
+out:
+ if (bus != NULL)
+ g_object_unref (bus);
+
+ g_clear_error (&error);
+}
+
+static void
+tpl_logger_iface_init (gpointer iface,
+ gpointer iface_data)
+{
+ TplSvcLoggerClass *klass = (TplSvcLoggerClass *) iface;
+
+#define IMPLEMENT(x) tpl_svc_logger_implement_##x (klass, tpl_dbus_service_##x)
+ IMPLEMENT (get_favourite_contacts);
+ IMPLEMENT (add_favourite_contact);
+ IMPLEMENT (remove_favourite_contact);
+ IMPLEMENT (clear);
+ IMPLEMENT (clear_account);
+ IMPLEMENT (clear_entity);
+#undef IMPLEMENT
+}
diff --git a/telepathy-logger/debug-internal.h b/telepathy-logger/debug-internal.h
new file mode 100644
index 000000000..8d39bcccd
--- /dev/null
+++ b/telepathy-logger/debug-internal.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2009 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ */
+
+#ifndef __TPL_DEBUG_H__
+#define __TPL_DEBUG_H__
+
+#include "config.h"
+
+#include <string.h>
+
+#include <glib.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+#ifdef ENABLE_DEBUG
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+ TPL_DEBUG_ACTION_CHAIN = 1 << 0,
+ TPL_DEBUG_CONF = 1 << 1,
+ TPL_DEBUG_ENTITY = 1 << 2,
+ TPL_DEBUG_CHANNEL = 1 << 3,
+ TPL_DEBUG_DBUS_SERVICE = 1 << 4,
+ TPL_DEBUG_LOG_EVENT = 1 << 5,
+ TPL_DEBUG_LOG_MANAGER = 1 << 6,
+ TPL_DEBUG_LOG_STORE = 1 << 7,
+ TPL_DEBUG_MAIN = 1 << 8,
+ TPL_DEBUG_OBSERVER = 1 << 9,
+ TPL_DEBUG_TESTSUITE = 1 << 10
+} TplDebugFlags;
+
+void _tpl_debug_set_flags_from_env (void);
+void _tpl_debug_set_flags (TplDebugFlags flags);
+gboolean _tpl_debug_flag_is_set (TplDebugFlags flag);
+void _tpl_debug_free (void);
+void _tpl_debug (TplDebugFlags flag, const gchar *format, ...)
+ G_GNUC_PRINTF (2, 3);
+void _tpl_critical (TplDebugFlags flag, const gchar *format, ...)
+ G_GNUC_PRINTF (2, 3);
+
+
+G_END_DECLS
+
+/* CRITICAL/PATH_CRITICAL needs to be always defined */
+#define CRITICAL(format, ...) \
+ _tpl_critical (DEBUG_FLAG, "%s: " format, G_STRFUNC, ##__VA_ARGS__)
+#define PATH_CRITICAL(_proxy, _format, ...) \
+G_STMT_START { \
+ const gchar *_path; \
+ g_assert (TP_IS_PROXY (_proxy)); \
+ _path = tp_proxy_get_object_path (TP_PROXY (_proxy)); \
+ if (TP_IS_CHANNEL (_proxy)) \
+ _path += strlen (TP_CONN_OBJECT_PATH_BASE); \
+ else if (TP_IS_ACCOUNT (_proxy)) \
+ _path += strlen (TP_ACCOUNT_OBJECT_PATH_BASE); \
+ CRITICAL (" %s: " _format, _path, ##__VA_ARGS__); \
+} G_STMT_END
+
+#ifdef DEBUG_FLAG
+
+#define DEBUG(format, ...) \
+ _tpl_debug (DEBUG_FLAG, "%s: " format, G_STRFUNC, ##__VA_ARGS__)
+
+#define DEBUGGING _tpl_debug_flag_is_set (DEBUG_FLAG)
+
+/* The same of DEBUG, printing also the object-path property for the TpProxy,
+ * passed as first arg. prepending '_' to avoid shadowing local variables */
+#define PATH_DEBUG(_proxy, _format, ...) \
+G_STMT_START { \
+ const gchar *_path; \
+ g_assert (TP_IS_PROXY (_proxy)); \
+ _path = tp_proxy_get_object_path (TP_PROXY (_proxy)); \
+ if (TP_IS_CHANNEL (_proxy)) \
+ _path += strlen (TP_CONN_OBJECT_PATH_BASE); \
+ else if (TP_IS_ACCOUNT (_proxy)) \
+ _path += strlen (TP_ACCOUNT_OBJECT_PATH_BASE); \
+ DEBUG (" %s: " _format, _path, ##__VA_ARGS__); \
+} G_STMT_END
+
+#endif /* DEBUG_FLAG */
+
+#else /* ENABLE_DEBUG */
+
+#ifdef DEBUG_FLAG
+
+#define DEBUG(format, ...) G_STMT_START { } G_STMT_END
+#define DEBUGGING 0
+#define PATH_DEBUG(chan, format, ...) G_STMT_START { } G_STMT_END
+
+#endif /* DEBUG_FLAG */
+
+#define _tpl_debug_free() G_STMT_START { } G_STMT_END
+
+#endif /* ENABLE_DEBUG */
+
+#endif /* __TPL_DEBUG_H__ */
diff --git a/telepathy-logger/debug.c b/telepathy-logger/debug.c
new file mode 100644
index 000000000..7b38564ff
--- /dev/null
+++ b/telepathy-logger/debug.c
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2009 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ */
+
+#include "config.h"
+#include "telepathy-logger/debug-internal.h"
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#ifdef ENABLE_DEBUG
+
+static TplDebugFlags flags = 0;
+
+static GDebugKey keys[] = {
+ { "action-chain", TPL_DEBUG_ACTION_CHAIN },
+ { "channel", TPL_DEBUG_CHANNEL },
+ { "conf", TPL_DEBUG_CONF },
+ { "entity", TPL_DEBUG_ENTITY },
+ { "dbus-service", TPL_DEBUG_DBUS_SERVICE },
+ { "log-event", TPL_DEBUG_LOG_EVENT },
+ { "log-manager", TPL_DEBUG_LOG_MANAGER },
+ { "log-store", TPL_DEBUG_LOG_STORE },
+ { "main", TPL_DEBUG_MAIN },
+ { "observer", TPL_DEBUG_OBSERVER },
+ { "testsuite", TPL_DEBUG_TESTSUITE },
+ { 0, },
+};
+
+void
+_tpl_debug_set_flags_from_env (void)
+{
+ guint nkeys;
+ const gchar *flags_string;
+
+ for (nkeys = 0; keys[nkeys].value; nkeys++);
+
+ flags_string = g_getenv ("TPL_DEBUG");
+
+ if (flags_string != NULL)
+ _tpl_debug_set_flags (g_parse_debug_string (flags_string, keys, nkeys));
+
+ tp_debug_set_flags (g_getenv ("TP_DEBUG"));
+}
+
+
+void
+_tpl_debug_set_flags (TplDebugFlags new_flags)
+{
+ flags |= new_flags;
+}
+
+
+gboolean
+_tpl_debug_flag_is_set (TplDebugFlags flag)
+{
+ return flag & flags;
+}
+
+GHashTable *flag_to_domains = NULL;
+
+
+void
+_tpl_debug_free (void)
+{
+ if (flag_to_domains == NULL)
+ return;
+
+ g_hash_table_unref (flag_to_domains);
+ flag_to_domains = NULL;
+}
+
+
+void _tpl_debug (TplDebugFlags flag,
+ const gchar *format,
+ ...)
+{
+ gchar *message;
+ va_list args;
+
+ va_start (args, format);
+ message = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ if (flag & flags)
+ g_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "%s", message);
+
+ g_free (message);
+}
+
+#endif /* ENABLE_DEBUG */
+
+/* The following function has to be always define or CRITICAL messages won't
+ * be shown */
+
+void _tpl_critical (TplDebugFlags flag,
+ const gchar *format,
+ ...)
+{
+ gchar *message;
+ va_list args;
+
+ va_start (args, format);
+ message = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ if (flag & flags)
+ g_log (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, "%s", message);
+
+ g_free (message);
+}
diff --git a/telepathy-logger/entity-internal.h b/telepathy-logger/entity-internal.h
new file mode 100644
index 000000000..d9c6eef60
--- /dev/null
+++ b/telepathy-logger/entity-internal.h
@@ -0,0 +1,44 @@
+/*
+ *Copyright (C) 2009-2010 Collabora Ltd.
+ *
+ *This library 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 library is distributed in the hope that it will be useful,
+ *but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *Lesser General Public License for more details.
+ *
+ *You should have received a copy of the GNU Lesser General Public
+ *License along with this library; if not, write to the Free Software
+ *Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ *Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ */
+
+#ifndef __TPL_ENTITY_INTERNAL_H__
+#define __TPL_ENTITY_INTERNAL_H__
+
+#include <telepathy-logger/entity.h>
+
+#include <glib-object.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+G_BEGIN_DECLS
+#define TPL_ENTITY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TPL_TYPE_ENTITY, TplEntityClass))
+#define TPL_IS_ENTITY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TPL_TYPE_ENTITY))
+#define TPL_ENTITY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TPL_TYPE_ENTITY, TplEntityClass))
+
+typedef struct
+{
+ GObjectClass parent_class;
+} TplEntityClass;
+
+gint _tpl_entity_compare (TplEntity *e1, TplEntity *e2);
+TplEntityType _tpl_entity_type_from_str (const gchar *type_str);
+const gchar * _tpl_entity_type_to_str (TplEntityType type);
+
+G_END_DECLS
+#endif // __TPL_ENTITY_INTERNAL_H__
diff --git a/telepathy-logger/entity.c b/telepathy-logger/entity.c
new file mode 100644
index 000000000..e2e3240ce
--- /dev/null
+++ b/telepathy-logger/entity.c
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2009-2010 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ */
+
+#include "config.h"
+#include "entity.h"
+#include "entity-internal.h"
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#define DEBUG_FLAG TPL_DEBUG_ENTITY
+#include <telepathy-logger/debug-internal.h>
+#include <telepathy-logger/util-internal.h>
+
+/**
+ * SECTION:entity
+ * @title: TplEntity
+ * @short_description: Representation of a contact or room
+ *
+ * An object representing a contact or room.
+ */
+
+/**
+ * TplEntity:
+ *
+ * An object representing a contact or room.
+ */
+
+G_DEFINE_TYPE (TplEntity, tpl_entity, G_TYPE_OBJECT)
+
+struct _TplEntityPriv
+{
+ TplEntityType type;
+ gchar *alias;
+ gchar *identifier;
+ gchar *avatar_token;
+};
+
+enum
+{
+ PROP0,
+ PROP_TYPE,
+ PROP_IDENTIFIER,
+ PROP_ALIAS,
+ PROP_AVATAR_TOKEN
+};
+
+static const gchar * entity_types[] = {
+ "unknown",
+ "contact",
+ "room",
+ "self"
+};
+
+
+static void
+tpl_entity_finalize (GObject *obj)
+{
+ TplEntity *self = TPL_ENTITY (obj);
+ TplEntityPriv *priv = self->priv;
+
+ tp_clear_pointer (&priv->alias, g_free);
+ tp_clear_pointer (&priv->identifier, g_free);
+ tp_clear_pointer (&priv->avatar_token, g_free);
+
+ G_OBJECT_CLASS (tpl_entity_parent_class)->finalize (obj);
+}
+
+
+static void
+tpl_entity_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TplEntityPriv *priv = TPL_ENTITY (object)->priv;
+
+ switch (param_id)
+ {
+ case PROP_TYPE:
+ g_value_set_int (value, priv->type);
+ break;
+ case PROP_IDENTIFIER:
+ g_value_set_string (value, priv->identifier);
+ break;
+ case PROP_ALIAS:
+ g_value_set_string (value, priv->alias);
+ break;
+ case PROP_AVATAR_TOKEN:
+ g_value_set_string (value, priv->avatar_token);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+
+static void
+tpl_entity_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TplEntityPriv *priv = TPL_ENTITY (object)->priv;
+
+ switch (param_id)
+ {
+ case PROP_TYPE:
+ priv->type = g_value_get_int (value);
+ break;
+ case PROP_IDENTIFIER:
+ g_assert (priv->identifier == NULL);
+ priv->identifier = g_value_dup_string (value);
+ break;
+ case PROP_ALIAS:
+ g_assert (priv->alias == NULL);
+ priv->alias = g_value_dup_string (value);
+ break;
+ case PROP_AVATAR_TOKEN:
+ g_assert (priv->avatar_token == NULL);
+ priv->avatar_token = g_value_dup_string (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+
+}
+
+
+static void tpl_entity_class_init (TplEntityClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GParamSpec *param_spec;
+
+ object_class->finalize = tpl_entity_finalize;
+ object_class->get_property = tpl_entity_get_property;
+ object_class->set_property = tpl_entity_set_property;
+
+ /**
+ * TplEntity:type:
+ *
+ * The entity's type (see #TplEntityType).
+ */
+ param_spec = g_param_spec_int ("type",
+ "Type",
+ "The entity's type",
+ TPL_ENTITY_UNKNOWN,
+ TPL_ENTITY_SELF,
+ TPL_ENTITY_UNKNOWN,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TYPE, param_spec);
+
+ /**
+ * TplEntity:identifier:
+ *
+ * The entity's identifier
+ */
+ param_spec = g_param_spec_string ("identifier",
+ "Identifier",
+ "The entity's identifier",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_IDENTIFIER, param_spec);
+
+ /**
+ * TplEntity:alias:
+ *
+ * The entity's alias
+ */
+ param_spec = g_param_spec_string ("alias",
+ "Alias",
+ "The entity's alias",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_ALIAS, param_spec);
+
+ /**
+ * TplEntity:avatar-token:
+ *
+ * The entity's avatar token
+ */
+ param_spec = g_param_spec_string ("avatar-token",
+ "AvatarToken",
+ "The entity's avatar's token",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_AVATAR_TOKEN, param_spec);
+
+ g_type_class_add_private (object_class, sizeof (TplEntityPriv));
+}
+
+
+static void
+tpl_entity_init (TplEntity *self)
+{
+ TplEntityPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TPL_TYPE_ENTITY, TplEntityPriv);
+
+ self->priv = priv;
+}
+
+TplEntity *
+tpl_entity_new (const gchar *id,
+ TplEntityType type,
+ const gchar *alias,
+ const gchar *avatar_token)
+{
+ TplEntity *ret;
+
+ g_return_val_if_fail (!TPL_STR_EMPTY (id), NULL);
+
+ ret = g_object_new (TPL_TYPE_ENTITY,
+ "identifier", id,
+ "type", type,
+ "alias", alias == NULL ? id : alias,
+ "avatar-token", avatar_token == NULL ? "" : avatar_token,
+ NULL);
+
+ switch (type)
+ {
+ case TPL_ENTITY_ROOM:
+ DEBUG ("Room id: %s", id);
+ break;
+ case TPL_ENTITY_CONTACT:
+ DEBUG ("Contact id: %s, tok: %s", id, avatar_token);
+ break;
+ case TPL_ENTITY_SELF:
+ DEBUG ("Self id: %s, tok: %s", id, avatar_token);
+ break;
+ case TPL_ENTITY_UNKNOWN:
+ DEBUG ("Unknown entity.");
+ break;
+ default:
+ g_warning ("Unknown entity type %i", type);
+ g_object_unref (ret);
+ ret = NULL;
+ }
+
+ return ret;
+}
+
+/**
+ * tpl_entity_new_from_room_id:
+ * @room_id: the room id which will be the identifier for the entity
+ *
+ * Returns: a TplEntity instance with identifier, alias copied from
+ * @room_id. It also sets %TPL_ENTITY_ROOM as type for
+ * the #TplEntity returned.
+ */
+TplEntity *
+tpl_entity_new_from_room_id (const gchar *room_id)
+{
+ return tpl_entity_new (room_id, TPL_ENTITY_ROOM, NULL, NULL);
+}
+
+
+/**
+ * tpl_entity_new_from_tp_contact:
+ * @contact: the TpContact instance to create the TplEntity from
+ * @type: the #TplEntity type
+ *
+ * Returns: a TplEntity instance with identifier, alias and
+ * avatar's token copied. Type parameter is useful to differentiate between
+ * normal contact and self contact, thus only %TPL_ENTITY_CONTACT and
+ * %TPL_ENTITY_SELF are accepted. If contact is %NULL, an entity of type
+ * %TPL_ENTITY_UNKNOWN with id set to "unknown" is returned.
+ */
+TplEntity *
+tpl_entity_new_from_tp_contact (TpContact *contact,
+ TplEntityType type)
+{
+ g_return_val_if_fail (contact == NULL || TP_IS_CONTACT (contact), NULL);
+ g_return_val_if_fail (type == TPL_ENTITY_CONTACT || type == TPL_ENTITY_SELF,
+ NULL);
+
+ if (contact != NULL)
+ return tpl_entity_new (tp_contact_get_identifier (contact),
+ type,
+ tp_contact_get_alias (contact),
+ tp_contact_get_avatar_token (contact));
+ else
+ return tpl_entity_new ("unknown", TPL_ENTITY_UNKNOWN, NULL, NULL);
+}
+
+
+/**
+ * tpl_entity_get_alias:
+ * @self: a #TplEntity
+ *
+ * Returns: the alias of the entity, or %NULL
+ */
+const gchar *
+tpl_entity_get_alias (TplEntity *self)
+{
+ g_return_val_if_fail (TPL_IS_ENTITY (self), NULL);
+
+ return self->priv->alias;
+}
+
+
+/**
+ * tpl_entity_get_identifier:
+ * @self: a #TplEntity
+ *
+ * Returns: the identifier of the entity
+ */
+const gchar *
+tpl_entity_get_identifier (TplEntity *self)
+{
+ g_return_val_if_fail (TPL_IS_ENTITY (self), NULL);
+
+ return self->priv->identifier;
+}
+
+
+/**
+ * tpl_entity_get_entity_type:
+ * @self: a #TplEntity
+ *
+ * Returns: the type of the entity
+ */
+TplEntityType
+tpl_entity_get_entity_type (TplEntity *self)
+{
+ g_return_val_if_fail (TPL_IS_ENTITY (self), TPL_ENTITY_UNKNOWN);
+
+ return self->priv->type;
+}
+
+
+/**
+ * tpl_entity_get_avatar_token:
+ * @self: a #TplEntity
+ *
+ * Returns: a token representing the avatar of the token, or %NULL
+ */
+const gchar *
+tpl_entity_get_avatar_token (TplEntity *self)
+{
+ g_return_val_if_fail (TPL_IS_ENTITY (self), NULL);
+
+ return self->priv->avatar_token;
+}
+
+
+/*
+ * _tpl_entity_compare:
+ * @a: a #TplEntity
+ * @b: a #TplEntity
+ *
+ * Compares @a and @b.
+ *
+ * Returns: 0 if a == b, -1 if a < b, 1 otherwise.
+ */
+gint
+_tpl_entity_compare (TplEntity *a,
+ TplEntity *b)
+{
+ g_return_val_if_fail (TPL_IS_ENTITY (a), TPL_IS_ENTITY (b) ? -1 : 0);
+ g_return_val_if_fail (TPL_IS_ENTITY (b), 1);
+
+ if (tpl_entity_get_entity_type (a) == tpl_entity_get_entity_type (b))
+ return g_strcmp0 (tpl_entity_get_identifier (a),
+ tpl_entity_get_identifier (b));
+ else if (tpl_entity_get_entity_type (a) < tpl_entity_get_entity_type (b))
+ return -1;
+ else
+ return 1;
+}
+
+
+TplEntityType
+_tpl_entity_type_from_str (const gchar *type_str)
+{
+ guint i;
+ for (i = 0; i < G_N_ELEMENTS (entity_types); ++i)
+ if (!tp_strdiff (type_str, entity_types[i]))
+ return (TplEntityType) i;
+
+ /* default case */
+ return TPL_ENTITY_UNKNOWN;
+}
+
+
+const gchar *
+_tpl_entity_type_to_str (TplEntityType type)
+{
+ g_return_val_if_fail (G_N_ELEMENTS (entity_types) >= type, "unknown");
+ return entity_types[type];
+}
diff --git a/telepathy-logger/entity.h b/telepathy-logger/entity.h
new file mode 100644
index 000000000..5f61b9977
--- /dev/null
+++ b/telepathy-logger/entity.h
@@ -0,0 +1,80 @@
+/*
+ *Copyright (C) 2009-2010 Collabora Ltd.
+ *
+ *This library 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 library is distributed in the hope that it will be useful,
+ *but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ *Lesser General Public License for more details.
+ *
+ *You should have received a copy of the GNU Lesser General Public
+ *License along with this library; if not, write to the Free Software
+ *Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ *Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ */
+
+#ifndef __TPL_ENTITY_H__
+#define __TPL_ENTITY_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+G_BEGIN_DECLS
+#define TPL_TYPE_ENTITY (tpl_entity_get_type ())
+#define TPL_ENTITY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TPL_TYPE_ENTITY, TplEntity))
+#define TPL_ENTITY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TPL_TYPE_ENTITY, TplEntityClass))
+#define TPL_IS_ENTITY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TPL_TYPE_ENTITY))
+#define TPL_IS_ENTITY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TPL_TYPE_ENTITY))
+#define TPL_ENTITY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TPL_TYPE_ENTITY, TplEntityClass))
+
+/**
+ * TplEntityType:
+ * @TPL_ENTITY_UNKNOWN: the current contact's type is unknown
+ * @TPL_ENTITY_CONTACT: the contact's type represents a user (buddy), but not
+ * the account's owner for which @TPL_ENTITY_SELF is used
+ * @TPL_ENTITY_ROOM: a named room (#TP_HANDLE_TYPE_ROOM)
+ * @TPL_ENTITY_SELF: the contact's type represents the owner of the account
+ * whose channel has been logged, as opposed to @TPL_ENTITY_CONTACT which
+ * represents any other user
+ */
+typedef enum
+{
+ TPL_ENTITY_UNKNOWN,
+ TPL_ENTITY_CONTACT,
+ TPL_ENTITY_ROOM,
+ TPL_ENTITY_SELF
+} TplEntityType;
+
+typedef struct _TplEntity TplEntity;
+typedef struct _TplEntityPriv TplEntityPriv;
+
+struct _TplEntity
+{
+ GObject parent;
+
+ /*Private */
+ TplEntityPriv *priv;
+};
+
+
+GType tpl_entity_get_type (void);
+
+TplEntity *tpl_entity_new (const gchar *id,
+ TplEntityType type,
+ const gchar *alias,
+ const gchar *avatar_token);
+TplEntity *tpl_entity_new_from_tp_contact (TpContact *contact, TplEntityType type);
+TplEntity *tpl_entity_new_from_room_id (const gchar *room_id);
+
+const gchar *tpl_entity_get_alias (TplEntity *self);
+const gchar *tpl_entity_get_identifier (TplEntity *self);
+TplEntityType tpl_entity_get_entity_type (TplEntity *self);
+const gchar *tpl_entity_get_avatar_token (TplEntity *self);
+
+G_END_DECLS
+#endif // __TPL_ENTITY_H__
diff --git a/telepathy-logger/event-internal.h b/telepathy-logger/event-internal.h
new file mode 100644
index 000000000..57bb9ad3b
--- /dev/null
+++ b/telepathy-logger/event-internal.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2009 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ */
+
+#ifndef __TPL_EVENT_INTERNAL_H__
+#define __TPL_EVENT_INTERNAL_H__
+
+#include <telepathy-logger/event.h>
+
+G_BEGIN_DECLS
+
+struct _TplEvent
+{
+ GObject parent;
+
+ /* Private */
+ TplEventPriv *priv;
+};
+
+struct _TplEventClass {
+ GObjectClass parent_class;
+
+ gboolean (*equal) (TplEvent *event1, TplEvent *event2);
+};
+
+TplEntity * _tpl_event_get_target (TplEvent *self);
+
+const gchar * _tpl_event_get_target_id (TplEvent * self);
+
+gboolean _tpl_event_target_is_room (TplEvent *self);
+
+const gchar * _tpl_event_get_channel_path (TplEvent *self);
+
+G_END_DECLS
+#endif // __TPL_EVENT_INTERNAL_H__
diff --git a/telepathy-logger/event.c b/telepathy-logger/event.c
new file mode 100644
index 000000000..f061ce0bd
--- /dev/null
+++ b/telepathy-logger/event.c
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2009 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ */
+
+#include "config.h"
+#include "event.h"
+#include "event-internal.h"
+
+#include <glib.h>
+#include "entity-internal.h"
+
+#define DEBUG_FLAG TPL_DEBUG_EVENT
+#include <telepathy-logger/debug-internal.h>
+#include <telepathy-logger/util-internal.h>
+
+/**
+ * SECTION:event
+ * @title: TplEvent
+ * @short_description: Abstract representation of a log event
+ * @see_also: #TplTextEvent and other subclasses when they'll exist
+ *
+ * The TPLogger log event represents a generic log event, which will be
+ * specialized by subclasses of #TplEvent.
+ */
+
+/**
+ * TplEvent:
+ *
+ * An object representing a generic log event.
+ */
+
+G_DEFINE_ABSTRACT_TYPE (TplEvent, tpl_event, G_TYPE_OBJECT)
+
+struct _TplEventPriv
+{
+ gint64 timestamp;
+ TpAccount *account;
+ gchar *channel_path;
+
+ /* message and receiver may be NULL depending on the signal. ie. status
+ * changed signals set only the sender */
+ TplEntity *sender;
+ TplEntity *receiver;
+};
+
+enum {
+ PROP_TIMESTAMP = 1,
+ PROP_TARGET_ID,
+ PROP_ACCOUNT,
+ PROP_ACCOUNT_PATH,
+ PROP_CHANNEL_PATH,
+ PROP_SENDER,
+ PROP_RECEIVER
+};
+
+
+static void
+tpl_event_finalize (GObject *obj)
+{
+ TplEvent *self = TPL_EVENT (obj);
+ TplEventPriv *priv = self->priv;
+
+ tp_clear_pointer (&priv->channel_path, g_free);
+
+ G_OBJECT_CLASS (tpl_event_parent_class)->finalize (obj);
+}
+
+
+static void
+tpl_event_dispose (GObject *obj)
+{
+ TplEvent *self = TPL_EVENT (obj);
+ TplEventPriv *priv = self->priv;
+
+ tp_clear_object (&priv->account);
+ tp_clear_object (&priv->sender);
+ tp_clear_object (&priv->receiver);
+
+ G_OBJECT_CLASS (tpl_event_parent_class)->dispose (obj);
+}
+
+
+static void
+tpl_event_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TplEvent *self = TPL_EVENT (object);
+ TplEventPriv *priv = self->priv;
+
+ switch (param_id)
+ {
+ case PROP_TIMESTAMP:
+ g_value_set_int64 (value, priv->timestamp);
+ break;
+ case PROP_ACCOUNT:
+ g_value_set_object (value, priv->account);
+ break;
+ case PROP_ACCOUNT_PATH:
+ g_value_set_string (value, tpl_event_get_account_path (self));
+ break;
+ case PROP_CHANNEL_PATH:
+ g_value_set_string (value, priv->channel_path);
+ break;
+ case PROP_SENDER:
+ g_value_set_object (value, priv->sender);
+ break;
+ case PROP_RECEIVER:
+ g_value_set_object (value, priv->receiver);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+
+static void
+tpl_event_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TplEvent *self = TPL_EVENT (object);
+ TplEventPriv *priv = self->priv;
+
+ switch (param_id) {
+ case PROP_TIMESTAMP:
+ g_assert (priv->timestamp == 0);
+ priv->timestamp = g_value_get_int64 (value);
+ break;
+ case PROP_ACCOUNT:
+ g_assert (priv->account == NULL);
+ priv->account = g_value_dup_object (value);
+ break;
+ case PROP_CHANNEL_PATH:
+ g_assert (priv->channel_path == NULL);
+ priv->channel_path = g_value_dup_string (value);
+ break;
+ case PROP_SENDER:
+ g_assert (priv->sender == NULL);
+ g_return_if_fail (TPL_IS_ENTITY (g_value_get_object (value)));
+ priv->sender = g_value_dup_object (value);
+ break;
+ case PROP_RECEIVER:
+ g_assert (priv->receiver == NULL);
+ /* can be NULL */
+ priv->receiver = g_value_dup_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+static inline gboolean
+account_equal (TpAccount *account1, TpAccount *account2)
+{
+ g_return_val_if_fail (TP_IS_PROXY (account1), FALSE);
+ g_return_val_if_fail (TP_IS_PROXY (account2), FALSE);
+
+ return !tp_strdiff (tp_proxy_get_object_path (TP_PROXY (account1)),
+ tp_proxy_get_object_path (TP_PROXY (account2)));
+}
+
+
+static gboolean
+tpl_event_equal_default (TplEvent *message1,
+ TplEvent *message2)
+{
+ g_return_val_if_fail (TPL_IS_EVENT (message1), FALSE);
+ g_return_val_if_fail (TPL_IS_EVENT (message2), FALSE);
+
+ return message1->priv->timestamp == message2->priv->timestamp
+ && account_equal (message1->priv->account, message2->priv->account)
+ && _tpl_entity_compare (message1->priv->sender, message2->priv->sender)
+ && _tpl_entity_compare (message1->priv->receiver, message2->priv->receiver);
+}
+
+
+static void
+tpl_event_class_init (TplEventClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GParamSpec *param_spec;
+
+ /* to be used by subclasses */
+ object_class->finalize = tpl_event_finalize;
+ object_class->dispose = tpl_event_dispose;
+ object_class->get_property = tpl_event_get_property;
+ object_class->set_property = tpl_event_set_property;
+
+ klass->equal = tpl_event_equal_default;
+
+ param_spec = g_param_spec_int64 ("timestamp",
+ "Timestamp",
+ "The timestamp (gint64) for the log event",
+ G_MININT64, G_MAXINT64, 0,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TIMESTAMP, param_spec);
+
+ param_spec = g_param_spec_object ("account",
+ "TpAccount",
+ "The TpAccount to which the log event is related",
+ TP_TYPE_ACCOUNT,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_ACCOUNT, param_spec);
+
+ param_spec = g_param_spec_string ("account-path",
+ "AccountPath",
+ "The account path of the TpAccount to which the log event is related",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_ACCOUNT_PATH, param_spec);
+
+ param_spec = g_param_spec_string ("channel-path",
+ "ChannelPath",
+ "The channel path of the TpChannel to which the log event is related",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CHANNEL_PATH, param_spec);
+
+ param_spec = g_param_spec_object ("sender",
+ "Sender",
+ "TplEntity instance who originated the log event",
+ TPL_TYPE_ENTITY,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SENDER, param_spec);
+
+ param_spec = g_param_spec_object ("receiver",
+ "Receiver",
+ "TplEntity instance destination for the log event "
+ "(may be NULL with some log stores)",
+ TPL_TYPE_ENTITY,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_RECEIVER, param_spec);
+
+ g_type_class_add_private (object_class, sizeof (TplEventPriv));
+}
+
+
+static void
+tpl_event_init (TplEvent *self)
+{
+ TplEventPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TPL_TYPE_EVENT, TplEventPriv);
+ self->priv = priv;
+}
+
+/**
+ * tpl_event_get_timestamp:
+ * @self: a #TplEvent
+ *
+ * Returns: the same timestamp as the #TplEvent:timestamp property
+ */
+gint64
+tpl_event_get_timestamp (TplEvent *self)
+{
+ g_return_val_if_fail (TPL_IS_EVENT (self), -1);
+
+ return self->priv->timestamp;
+}
+
+
+/**
+ * tpl_event_get_sender:
+ * @self: a #TplEvent
+ *
+ * Returns: (transfer none): the same #TplEntity as the #TplEvent:sender property
+ */
+TplEntity *
+tpl_event_get_sender (TplEvent *self)
+{
+ g_return_val_if_fail (TPL_IS_EVENT (self), NULL);
+
+ return self->priv->sender;
+}
+
+/**
+ * tpl_event_get_receiver:
+ * @self: a #TplEvent
+ *
+ * Returns: (transfer none): the same #TplEntity as the #TplEvent:receiver property
+ */
+TplEntity *
+tpl_event_get_receiver (TplEvent *self)
+{
+ g_return_val_if_fail (TPL_IS_EVENT (self), NULL);
+
+ return self->priv->receiver;
+}
+
+
+TplEntity *
+_tpl_event_get_target (TplEvent *self)
+{
+ g_return_val_if_fail (TPL_IS_EVENT (self), NULL);
+
+ if (_tpl_event_target_is_room (self)
+ || tpl_entity_get_entity_type (self->priv->sender) == TPL_ENTITY_SELF)
+ return self->priv->receiver;
+ else
+ return self->priv->sender;
+}
+
+
+const gchar *
+_tpl_event_get_target_id (TplEvent *self)
+{
+ g_return_val_if_fail (TPL_IS_EVENT (self), NULL);
+
+ return tpl_entity_get_identifier (_tpl_event_get_target (self));
+}
+
+gboolean
+_tpl_event_target_is_room (TplEvent *self)
+{
+ /* Some log-store like Pidgin text mode does not know about receiver, so
+ * having a NULL receiver is fine. */
+ if (self->priv->receiver == NULL)
+ return FALSE;
+
+ return (tpl_entity_get_entity_type (self->priv->receiver) == TPL_ENTITY_ROOM);
+}
+
+
+/**
+ * tpl_event_get_account_path:
+ * @self: a #TplEvent
+ *
+ * <!-- no more to say -->
+ *
+ * Returns: the path as the #TplEvent:account property
+ */
+const gchar *
+tpl_event_get_account_path (TplEvent *self)
+{
+ g_return_val_if_fail (TPL_IS_EVENT (self), NULL);
+ g_return_val_if_fail (TP_IS_ACCOUNT (self->priv->account), NULL);
+
+ return tp_proxy_get_object_path (self->priv->account);
+}
+
+
+const gchar *
+_tpl_event_get_channel_path (TplEvent *self)
+{
+ g_return_val_if_fail (TPL_IS_EVENT (self), NULL);
+
+ return self->priv->channel_path;
+}
+
+
+/**
+ * tpl_event_equal:
+ * @self: TplEvent subclass instance
+ * @data: an instance of the same TplEvent subclass of @self
+ *
+ * Checks if two instances of TplEvent represent the same data
+ *
+ * Returns: %TRUE if @data is the same type of @self and they hold the same
+ * data, %FALSE otherwise
+ */
+gboolean
+tpl_event_equal (TplEvent *self,
+ TplEvent *data)
+{
+ g_return_val_if_fail (TPL_IS_EVENT (self), FALSE);
+ g_return_val_if_fail (TPL_IS_EVENT (data), FALSE);
+
+ return TPL_EVENT_GET_CLASS (self)->equal (self, data);
+}
+
+/**
+ * tpl_event_get_account:
+ * @self: a #TplEvent
+ *
+ * <!-- no more to say -->
+ *
+ * Returns: (transfer none): the same account as the #TplEvent:account property
+ */
+TpAccount *
+tpl_event_get_account (TplEvent *self)
+{
+ return self->priv->account;
+}
diff --git a/telepathy-logger/event.h b/telepathy-logger/event.h
new file mode 100644
index 000000000..8e0301e71
--- /dev/null
+++ b/telepathy-logger/event.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2009 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ */
+
+#ifndef __TPL_EVENT_H__
+#define __TPL_EVENT_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include <telepathy-logger/entity.h>
+
+G_BEGIN_DECLS
+#define TPL_TYPE_EVENT (tpl_event_get_type ())
+#define TPL_EVENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TPL_TYPE_EVENT, TplEvent))
+#define TPL_EVENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TPL_TYPE_EVENT, TplEventClass))
+#define TPL_IS_EVENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TPL_TYPE_EVENT))
+#define TPL_IS_EVENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TPL_TYPE_EVENT))
+#define TPL_EVENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TPL_TYPE_EVENT, TplEventClass))
+
+typedef struct _TplEvent TplEvent;
+typedef struct _TplEventClass TplEventClass;
+typedef struct _TplEventPriv TplEventPriv;
+
+GType tpl_event_get_type (void);
+
+gint64 tpl_event_get_timestamp (TplEvent *self);
+
+const gchar *tpl_event_get_account_path (TplEvent *self);
+TpAccount * tpl_event_get_account (TplEvent *self);
+
+TplEntity * tpl_event_get_sender (TplEvent *self);
+TplEntity * tpl_event_get_receiver (TplEvent *self);
+
+gboolean tpl_event_equal (TplEvent *self, TplEvent *data);
+
+G_END_DECLS
+#endif // __TPL_EVENT_H__
diff --git a/telepathy-logger/extensions/Logger.xml b/telepathy-logger/extensions/Logger.xml
new file mode 100644
index 000000000..fcfd36de7
--- /dev/null
+++ b/telepathy-logger/extensions/Logger.xml
@@ -0,0 +1,153 @@
+<?xml version="1.0" ?>
+<node name="/Logger"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009-2011 Collabora Ltd.</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library 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.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="im.telepathy1.Logger1"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.3">(as a draft)</tp:added>
+
+ <tp:docstring>
+ An interface for requesting information from the Telepathy Logger
+ service.
+ </tp:docstring>
+
+ <method name="GetFavouriteContacts"
+ tp:name-for-bindings="Get_Favourite_Contacts">
+ <arg direction="out" name="Favourite_Contacts" type="a(oas)">
+ <tp:docstring>
+ The favourite contacts, as an array of TpAccounts and their contact
+ identifiers.
+ </tp:docstring>
+ </arg>
+
+ <tp:docstring>
+ Returns the favourite contacts.
+ </tp:docstring>
+ </method>
+
+ <method name="AddFavouriteContact"
+ tp:name-for-bindings="Add_Favourite_Contact">
+ <arg direction="in" name="Account" type="o" tp:type="Account">
+ <tp:docstring>
+ The object path for the TpAccount to which the contact belongs
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Identifier" type="s">
+ <tp:docstring>
+ The favourite contact's identifier
+ </tp:docstring>
+ </arg>
+
+ <tp:docstring>
+ Add a contact's designation as a favourite.
+ </tp:docstring>
+ </method>
+
+ <method name="RemoveFavouriteContact"
+ tp:name-for-bindings="Remove_Favourite_Contact">
+ <arg direction="in" name="Account" type="o" tp:type="Account">
+ <tp:docstring>
+ The object path for the TpAccount to which the contact belongs
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Identifier" type="s">
+ <tp:docstring>
+ The favourite contact's identifier
+ </tp:docstring>
+ </arg>
+
+ <tp:docstring>
+ Remove a contact's designation as a favourite.
+ </tp:docstring>
+ </method>
+
+ <method name="Clear"
+ tp:name-for-bindings="Clear">
+ <tp:docstring>
+ Clear all the logs. This will not erase the favourite contacts.
+ </tp:docstring>
+ </method>
+
+ <method name="ClearAccount"
+ tp:name-for-bindings="Clear_Account">
+ <arg direction="in" name="Account" type="o" tp:type="Account">
+ <tp:docstring>
+ The object path for the TpAccount in which logs will be cleared.
+ </tp:docstring>
+ </arg>
+
+ <tp:docstring>
+ Clear all logs stored for specified account.
+ </tp:docstring>
+ </method>
+
+ <method name="ClearEntity"
+ tp:name-for-bindings="Clear_Entity">
+ <arg direction="in" name="Account" type="o" tp:type="Account">
+ <tp:docstring>
+ The object path for the TpAccount in which logs will be cleared.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Identifier" type="s">
+ <tp:docstring>
+ The entity identifier.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Type" type="i">
+ <tp:docstring>
+ The entity type, should be one of TPL_ENTITY_CONTACT (1) or TPL_ENTITY_ROOM (2).
+ </tp:docstring>
+ </arg>
+
+ <tp:docstring>
+ Clear all logs stored for discussions with entity in account.
+ </tp:docstring>
+ </method>
+
+ <signal name="FavouriteContactsChanged"
+ tp:name-for-bindings="Favourite_Contacts_Changed">
+ <tp:docstring>
+ The set of favourite contacts has changed.
+ </tp:docstring>
+
+ <arg name="Account" type="o" tp:type="Account">
+ <tp:docstring>
+ An account associated with the contact.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Added" type="as">
+ <tp:docstring>
+ List of contact identifiers of contacts which are now favourites.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Removed" type="as">
+ <tp:docstring>
+ List of contact identifiers of contacts which are no longer favourites.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/telepathy-logger/extensions/Makefile.am b/telepathy-logger/extensions/Makefile.am
new file mode 100644
index 000000000..bc5bc7f29
--- /dev/null
+++ b/telepathy-logger/extensions/Makefile.am
@@ -0,0 +1,173 @@
+tools_dir = $(top_srcdir)/tools
+
+pkgconfigdir = $(libdir)/pkgconfig
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir) \
+ -I$(top_builddir) \
+ $(NULL)
+
+pkgconfig_DATA =
+if ENABLE_PUBLIC_EXTENSIONS
+pkgconfig_DATA += tpl-extensions.pc
+endif # ENABLE_PUBLIC_EXTENSIONS
+
+$(pkgconfig_DATA): $(top_builddir)/config.status
+
+EXTRA_DIST = \
+ all.xml \
+ misc.xml \
+ Logger.xml \
+ $(pkgconfig_DATA:.pc=.pc.in) \
+ $(NULL)
+
+noinst_LTLIBRARIES = libtpl-extensions-convenience.la
+libtpl_extensions_convenience_la_LIBADD = \
+ $(top_builddir)/telepathy-glib/libtelepathy-glib-1.la \
+ $(top_builddir)/telepathy-glib/libtelepathy-glib-1-dbus.la \
+ $(top_builddir)/telepathy-glib/libtelepathy-glib-1-core.la \
+ @TPL_LIBS@ \
+ $(NULL)
+
+gen_headers = \
+ _gen/cli-misc.h \
+ _gen/enums.h \
+ _gen/gtypes.h \
+ _gen/interfaces.h \
+ _gen/svc-misc.h \
+ $(NULL)
+
+nodist_libtpl_extensions_convenience_la_SOURCES = \
+ _gen/cli-misc-body.h \
+ _gen/gtypes-body.h \
+ _gen/interfaces-body.h \
+ _gen/register-dbus-glib-marshallers-body.h \
+ _gen/svc-misc.c \
+ $(gen_headers) \
+ $(NULL)
+
+extension_headers = \
+ extensions.h \
+ $(NULL)
+
+libtpl_extensions_convenience_la_SOURCES = \
+ extensions.c \
+ extensions-cli.c \
+ $(extension_headers) \
+ $(NULL)
+
+BUILT_SOURCES = \
+ _gen/all.xml \
+ _gen/misc.xml \
+ $(nodist_libtpl_extensions_convenience_la_SOURCES) \
+ $(gen_headers) \
+ $(NULL)
+
+CLEANFILES = \
+ $(BUILT_SOURCES) \
+ $(gen_headers:.h=-gtk-doc.h) \
+ $(pkgconfig_DATA) \
+ $(NULL)
+
+if ENABLE_PUBLIC_EXTENSIONS
+tplincludedir=$(includedir)/tpl-extensions
+genincludedir=$(tplincludedir)/_gen
+
+tplinclude_HEADERS = $(extension_headers)
+geninclude_HEADERS = $(gen_headers)
+
+# copy the core library and make it installable
+lib_LTLIBRARIES = libtpl-extensions.la
+
+libtpl_extensions_la_SOURCES =
+libtpl_extensions_la_LIBADD = libtpl-extensions-convenience.la
+libtpl_extensions_la_LDFLAGS = \
+ -version-info "$(TPL_EXT_LT_CURRENT)":"$(TPL_EXT_LT_REVISION)":"$(TPL_EXT_LT_AGE)"
+endif # ENABLE_PUBLIC_EXTENSIONS
+
+AM_CFLAGS = \
+ $(ERROR_CFLAGS) \
+ @TPL_CFLAGS@ \
+ -DTP_DISABLE_SINGLE_INCLUDE \
+ $(NULL)
+
+# Generated stuff
+
+DROP_NAMESPACE = sed -e 's@xmlns:tp="http://telepathy\.freedesktop\.org/wiki/DbusSpec.extensions-v0"@@g'
+XSLTPROCFLAGS = --nonet --novalid
+
+# Generated files which can be generated for all categories simultaneously
+
+_gen/all.xml: all.xml $(wildcard *.xml) $(tools_dir)/xincludator.py
+ $(MKDIR_P) _gen
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/xincludator.py $< > $@
+
+doc/index.html: _gen/all.xml $(tools_dir)/doc-generator.xsl
+ $(AM_V_GEN)$(XSLTPROC) $(XSLTPROCFLAGS) \
+ $(tools_dir)/doc-generator.xsl \
+ $< > $@
+
+# do nothing, output as a side-effect
+_gen/gtypes.h: _gen/gtypes-body.h
+ @:
+
+_gen/gtypes-body.h: _gen/all.xml \
+ $(top_srcdir)/tools/glib-gtypes-generator.py
+ $(AM_V_GEN)$(PYTHON) $(top_srcdir)/tools/glib-gtypes-generator.py \
+ $< _gen/gtypes Tpl
+
+_gen/register-dbus-glib-marshallers-body.h: _gen/all.xml \
+ $(tools_dir)/glib-client-marshaller-gen.py
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-client-marshaller-gen.py $< \
+ _tpl_ext > $@
+
+_gen/enums.h: _gen/all.xml \
+ $(tools_dir)/c-constants-gen.py
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/c-constants-gen.py \
+ Tpl \
+ $< _gen/enums
+
+# do nothing, output as a side-effect
+_gen/interfaces-body.h: _gen/interfaces.h
+ @:
+
+_gen/interfaces.h: _gen/all.xml \
+ $(tools_dir)/glib-interfaces-gen.py
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-interfaces-gen.py \
+ Tpl _gen/interfaces-body.h _gen/interfaces.h $<
+
+# Generated files which must be generated per "category". Each TpProxy
+# subclass you want to use with --subclass will need to have its own category,
+# although you can subdivide further if you want.
+
+_gen/misc.xml: misc.xml $(wildcard *.xml) $(tools_dir)/xincludator.py
+ $(MKDIR_P) _gen
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/xincludator.py $< > $@
+
+# do nothing, output as a side-effect
+_gen/cli-misc.h: _gen/cli-misc-body.h
+ @:
+
+_gen/cli-misc-body.h: _gen/misc.xml \
+ $(tools_dir)/glib-client-gen.py Makefile.am
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-client-gen.py \
+ --group=misc \
+ --subclass=TpProxy \
+ --subclass-assert=TP_IS_PROXY \
+ --iface-quark-prefix=TPL_IFACE_QUARK \
+ --tp-proxy-api=0.10.0 \
+ $< Tpl_Cli _gen/cli-misc
+
+# do nothing, output as a side-effect
+_gen/svc-misc.h: _gen/svc-misc.c
+ @:
+
+_gen/svc-misc.c: _gen/misc.xml \
+ $(tools_dir)/glib-ginterface-gen.py
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-ginterface-gen.py \
+ --filename=_gen/svc-misc \
+ --signal-marshal-prefix=_tpl_ext \
+ --include='<telepathy-glib/telepathy-glib.h>' \
+ --not-implemented-func='tp_dbus_g_method_return_not_implemented' \
+ --allow-unstable \
+ $< Tpl_Svc_
diff --git a/telepathy-logger/extensions/all.xml b/telepathy-logger/extensions/all.xml
new file mode 100644
index 000000000..efe90b06c
--- /dev/null
+++ b/telepathy-logger/extensions/all.xml
@@ -0,0 +1,34 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>TelepathyLogger-specific extensions to the Telepathy interfaces</tp:title>
+
+<tp:copyright>Copyright (C) 2009-2010 Collabora Limited</tp:copyright>
+
+<tp:license xmlns="http://www.w3.org/1999/xhtml">
+<p>This library 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.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA</p>
+</tp:license>
+
+<tp:generic-types>
+ <tp:external-type name="Account" type="o"
+ from="Telepathy specification"/>
+ <tp:external-type name="Unix_Timestamp64" type="x"
+ from="Telepathy specification"/>
+</tp:generic-types>
+
+<xi:include href="misc.xml"/>
+
+</tp:spec>
diff --git a/telepathy-logger/extensions/extensions-cli.c b/telepathy-logger/extensions/extensions-cli.c
new file mode 100644
index 000000000..62e06bdea
--- /dev/null
+++ b/telepathy-logger/extensions/extensions-cli.c
@@ -0,0 +1,33 @@
+#include "config.h"
+
+#include "telepathy-logger/extensions/extensions.h"
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/proxy-subclass.h>
+
+static void _tpl_ext_register_dbus_glib_marshallers (void);
+
+/* include auto-generated stubs for client-specific code */
+#include "_gen/cli-misc-body.h"
+#include "_gen/register-dbus-glib-marshallers-body.h"
+
+static gpointer
+tpl_cli_once (gpointer data)
+{
+ _tpl_ext_register_dbus_glib_marshallers ();
+
+ tp_proxy_init_known_interfaces ();
+
+ tp_proxy_or_subclass_hook_on_interface_add (TP_TYPE_PROXY,
+ tpl_cli_misc_add_signals);
+
+ return NULL;
+}
+
+void
+tpl_cli_init (void)
+{
+ static GOnce once = G_ONCE_INIT;
+
+ g_once (&once, tpl_cli_once, NULL);
+}
diff --git a/telepathy-logger/extensions/extensions.c b/telepathy-logger/extensions/extensions.c
new file mode 100644
index 000000000..f5a276f2d
--- /dev/null
+++ b/telepathy-logger/extensions/extensions.c
@@ -0,0 +1,9 @@
+#include "config.h"
+
+#include "telepathy-logger/extensions/extensions.h"
+
+#include <telepathy-glib/proxy-subclass.h>
+
+/* auto-generated stubs */
+#include "_gen/gtypes-body.h"
+#include "_gen/interfaces-body.h"
diff --git a/telepathy-logger/extensions/extensions.h b/telepathy-logger/extensions/extensions.h
new file mode 100644
index 000000000..c80827dd0
--- /dev/null
+++ b/telepathy-logger/extensions/extensions.h
@@ -0,0 +1,20 @@
+#ifndef _TPL_EXTENSIONS_H
+#define _TPL_EXTENSIONS_H
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "telepathy-logger/extensions/_gen/enums.h"
+#include "telepathy-logger/extensions/_gen/cli-misc.h"
+#include "telepathy-logger/extensions/_gen/svc-misc.h"
+
+G_BEGIN_DECLS
+
+#include "telepathy-logger/extensions/_gen/gtypes.h"
+#include "telepathy-logger/extensions/_gen/interfaces.h"
+
+G_END_DECLS
+
+void tpl_cli_init (void);
+
+#endif /* _TPL_EXTENSIONS_H */
+
diff --git a/telepathy-logger/extensions/misc.xml b/telepathy-logger/extensions/misc.xml
new file mode 100644
index 000000000..3b3bc0ea6
--- /dev/null
+++ b/telepathy-logger/extensions/misc.xml
@@ -0,0 +1,10 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Misc extensions for the logger (which will eventually be moved into their own interfaces)</tp:title>
+
+<xi:include href="Logger.xml" />
+
+</tp:spec>
+
diff --git a/telepathy-logger/extensions/tpl-extensions.pc.in b/telepathy-logger/extensions/tpl-extensions.pc.in
new file mode 100644
index 000000000..a24903c6f
--- /dev/null
+++ b/telepathy-logger/extensions/tpl-extensions.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=${prefix}
+libdir=${exec_prefix}/lib
+includedir=${prefix}/include
+
+Name: tpl-extensions
+Description: Telepathy Logger GLib convenience library
+Version: @VERSION@
+Libs: -L${libdir} -ltpl-extensions
+Cflags: -I${includedir}
+Requires: @LIBTPL_MODULES@
diff --git a/telepathy-logger/log-iter-internal.h b/telepathy-logger/log-iter-internal.h
new file mode 100644
index 000000000..ec5589fd5
--- /dev/null
+++ b/telepathy-logger/log-iter-internal.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Debarshi Ray <debarshir@freedesktop.org>
+ */
+
+#ifndef __TPL_LOG_ITER_H__
+#define __TPL_LOG_ITER_H__
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define TPL_TYPE_LOG_ITER (tpl_log_iter_get_type ())
+
+#define TPL_LOG_ITER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ TPL_TYPE_LOG_ITER, TplLogIter))
+
+#define TPL_LOG_ITER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ TPL_TYPE_LOG_ITER, TplLogIterClass))
+
+#define TPL_IS_LOG_ITER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ TPL_TYPE_LOG_ITER))
+
+#define TPL_IS_LOG_ITER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ TPL_TYPE_LOG_ITER))
+
+#define TPL_LOG_ITER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ TPL_TYPE_LOG_ITER, TplLogIterClass))
+
+typedef struct _TplLogIter TplLogIter;
+typedef struct _TplLogIterClass TplLogIterClass;
+
+struct _TplLogIter
+{
+ GObject parent_instance;
+};
+
+struct _TplLogIterClass
+{
+ GObjectClass parent_class;
+
+ GList * (*get_events) (TplLogIter *self, guint num_events, GError **error);
+ void (*rewind) (TplLogIter *self, guint num_events, GError **error);
+};
+
+GType tpl_log_iter_get_type (void) G_GNUC_CONST;
+
+GList *tpl_log_iter_get_events (TplLogIter *self,
+ guint num_events,
+ GError **error);
+
+void tpl_log_iter_rewind (TplLogIter *self,
+ guint num_events,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* __TPL_LOG_ITER_H__ */
diff --git a/telepathy-logger/log-iter-pidgin-internal.h b/telepathy-logger/log-iter-pidgin-internal.h
new file mode 100644
index 000000000..230a57edb
--- /dev/null
+++ b/telepathy-logger/log-iter-pidgin-internal.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Debarshi Ray <debarshir@freedesktop.org>
+ */
+
+#ifndef __TPL_LOG_ITER_PIDGIN_H__
+#define __TPL_LOG_ITER_PIDGIN_H__
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include <telepathy-logger/entity.h>
+#include <telepathy-logger/log-iter-internal.h>
+#include <telepathy-logger/log-store-internal.h>
+
+G_BEGIN_DECLS
+
+#define TPL_TYPE_LOG_ITER_PIDGIN (tpl_log_iter_pidgin_get_type ())
+
+#define TPL_LOG_ITER_PIDGIN(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ TPL_TYPE_LOG_ITER_PIDGIN, TplLogIterPidgin))
+
+#define TPL_LOG_ITER_PIDGIN_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ TPL_TYPE_LOG_ITER_PIDGIN, TplLogIterPidginClass))
+
+#define TPL_IS_LOG_ITER_PIDGIN(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ TPL_TYPE_LOG_ITER_PIDGIN))
+
+#define TPL_IS_LOG_ITER_PIDGIN_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ TPL_TYPE_LOG_ITER_PIDGIN))
+
+#define TPL_LOG_ITER_PIDGIN_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ TPL_TYPE_LOG_ITER_PIDGIN, TplLogIterPidginClass))
+
+typedef struct _TplLogIterPidgin TplLogIterPidgin;
+typedef struct _TplLogIterPidginClass TplLogIterPidginClass;
+typedef struct _TplLogIterPidginPriv TplLogIterPidginPriv;
+
+struct _TplLogIterPidgin
+{
+ TplLogIter parent_instance;
+ TplLogIterPidginPriv *priv;
+};
+
+struct _TplLogIterPidginClass
+{
+ TplLogIterClass parent_class;
+};
+
+GType tpl_log_iter_pidgin_get_type (void) G_GNUC_CONST;
+
+TplLogIter *tpl_log_iter_pidgin_new (TplLogStore *store,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask);
+
+G_END_DECLS
+
+#endif /* __TPL_LOG_ITER_PIDGIN_H__ */
diff --git a/telepathy-logger/log-iter-pidgin.c b/telepathy-logger/log-iter-pidgin.c
new file mode 100644
index 000000000..a9263d48c
--- /dev/null
+++ b/telepathy-logger/log-iter-pidgin.c
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Debarshi Ray <debarshir@freedesktop.org>
+ */
+
+#include "config.h"
+#include "log-iter-pidgin-internal.h"
+
+
+struct _TplLogIterPidginPriv
+{
+ GList *dates;
+ GList *events;
+ GList *next_date;
+ GList *next_event;
+ TpAccount *account;
+ TplEntity *target;
+ TplLogStore *store;
+ gint type_mask;
+};
+
+enum
+{
+ PROP_ACCOUNT = 1,
+ PROP_STORE,
+ PROP_TARGET,
+ PROP_TYPE_MASK
+};
+
+
+G_DEFINE_TYPE (TplLogIterPidgin, tpl_log_iter_pidgin, TPL_TYPE_LOG_ITER);
+
+
+static GList *
+tpl_log_iter_pidgin_get_events (TplLogIter *iter,
+ guint num_events,
+ GError **error)
+{
+ TplLogIterPidginPriv *priv;
+ GList *events;
+ guint i;
+
+ priv = TPL_LOG_ITER_PIDGIN (iter)->priv;
+ events = NULL;
+
+ if (priv->dates == NULL)
+ {
+ priv->dates = _tpl_log_store_get_dates (priv->store, priv->account,
+ priv->target, priv->type_mask);
+ priv->next_date = g_list_last (priv->dates);
+ }
+
+ i = 0;
+ while (i < num_events)
+ {
+ TplEvent *event;
+
+ if (priv->next_event == NULL)
+ {
+ if (priv->next_date == NULL)
+ break;
+
+ g_list_free_full (priv->events, g_object_unref);
+ priv->events = _tpl_log_store_get_events_for_date (priv->store,
+ priv->account, priv->target, priv->type_mask,
+ (GDate *) priv->next_date->data);
+
+ priv->next_event = g_list_last (priv->events);
+ priv->next_date = g_list_previous (priv->next_date);
+ }
+
+ event = TPL_EVENT (priv->next_event->data);
+ events = g_list_prepend (events, g_object_ref (event));
+ i++;
+
+ priv->next_event = g_list_previous (priv->next_event);
+ }
+
+ return events;
+}
+
+
+static void
+tpl_log_iter_pidgin_rewind (TplLogIter *iter,
+ guint num_events,
+ GError **error)
+{
+ GList *e;
+ TplLogIterPidginPriv *priv;
+ guint i;
+
+ priv = TPL_LOG_ITER_PIDGIN (iter)->priv;
+ e = NULL;
+
+ /* Set e to the last event that was returned */
+ if (priv->next_event == NULL)
+ e = priv->events;
+ else
+ e = g_list_next (priv->next_event);
+
+ i = 0;
+ while (i < num_events)
+ {
+ if (e == NULL)
+ {
+ GList *d;
+
+ if (priv->next_date == NULL)
+ d = priv->dates;
+ else
+ d = g_list_next (priv->next_date);
+
+ /* This can happen if get_events was never called or called
+ * with num_events == 0
+ */
+ if (d == NULL)
+ break;
+
+ g_list_free_full (priv->events, g_object_unref);
+ priv->events = NULL;
+ priv->next_event = NULL;
+
+ /* Rollback the priv->next_date */
+ priv->next_date = d;
+
+ /* Rollback the current date (ie. d) */
+ d = g_list_next (d);
+ if (d == NULL)
+ break;
+
+ priv->events = _tpl_log_store_get_events_for_date (priv->store,
+ priv->account, priv->target, priv->type_mask,
+ (GDate *) d->data);
+ e = priv->events;
+ }
+
+ priv->next_event = e;
+ e = g_list_next (e);
+ i++;
+ }
+}
+
+
+static void
+tpl_log_iter_pidgin_dispose (GObject *object)
+{
+ TplLogIterPidginPriv *priv;
+
+ priv = TPL_LOG_ITER_PIDGIN (object)->priv;
+
+ g_list_free_full (priv->dates, (GDestroyNotify) g_date_free);
+ priv->dates = NULL;
+
+ g_list_free_full (priv->events, g_object_unref);
+ priv->events = NULL;
+
+ g_clear_object (&priv->account);
+ g_clear_object (&priv->store);
+ g_clear_object (&priv->target);
+
+ G_OBJECT_CLASS (tpl_log_iter_pidgin_parent_class)->dispose (object);
+}
+
+
+static void
+tpl_log_iter_pidgin_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (tpl_log_iter_pidgin_parent_class)->finalize (object);
+}
+
+
+static void
+tpl_log_iter_pidgin_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TplLogIterPidginPriv *priv;
+
+ priv = TPL_LOG_ITER_PIDGIN (object)->priv;
+
+ switch (param_id)
+ {
+ case PROP_ACCOUNT:
+ g_value_set_object (value, priv->account);
+ break;
+
+ case PROP_STORE:
+ g_value_set_object (value, priv->store);
+ break;
+
+ case PROP_TARGET:
+ g_value_set_object (value, priv->target);
+ break;
+
+ case PROP_TYPE_MASK:
+ g_value_set_int (value, priv->type_mask);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+
+static void
+tpl_log_iter_pidgin_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TplLogIterPidginPriv *priv;
+
+ priv = TPL_LOG_ITER_PIDGIN (object)->priv;
+
+ switch (param_id)
+ {
+ case PROP_ACCOUNT:
+ priv->account = g_value_dup_object (value);
+ break;
+
+ case PROP_STORE:
+ priv->store = g_value_dup_object (value);
+ break;
+
+ case PROP_TARGET:
+ priv->target = g_value_dup_object (value);
+ break;
+
+ case PROP_TYPE_MASK:
+ priv->type_mask = g_value_get_int (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+
+static void
+tpl_log_iter_pidgin_init (TplLogIterPidgin *iter)
+{
+ iter->priv = G_TYPE_INSTANCE_GET_PRIVATE (iter, TPL_TYPE_LOG_ITER_PIDGIN,
+ TplLogIterPidginPriv);
+}
+
+
+static void
+tpl_log_iter_pidgin_class_init (TplLogIterPidginClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ TplLogIterClass *log_iter_class = TPL_LOG_ITER_CLASS (klass);
+ GParamSpec *param_spec;
+
+ object_class->dispose = tpl_log_iter_pidgin_dispose;
+ object_class->finalize = tpl_log_iter_pidgin_finalize;
+ object_class->get_property = tpl_log_iter_pidgin_get_property;
+ object_class->set_property = tpl_log_iter_pidgin_set_property;
+ log_iter_class->get_events = tpl_log_iter_pidgin_get_events;
+ log_iter_class->rewind = tpl_log_iter_pidgin_rewind;
+
+ param_spec = g_param_spec_object ("account",
+ "Account",
+ "The account whose logs are to be traversed",
+ TP_TYPE_ACCOUNT,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_ACCOUNT, param_spec);
+
+ param_spec = g_param_spec_object ("store",
+ "Store",
+ "The storage backend from which the logs are to be retrieved",
+ TPL_TYPE_LOG_STORE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_STORE, param_spec);
+
+ param_spec = g_param_spec_object ("target",
+ "Target",
+ "The target entity with which the account interacted",
+ TPL_TYPE_ENTITY,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TARGET, param_spec);
+
+ param_spec = g_param_spec_int ("type-mask",
+ "Type Mask",
+ "A bitmask to filter the events to be retrieved",
+ 1,
+ 0xffff,
+ TPL_EVENT_MASK_ANY,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TYPE_MASK, param_spec);
+
+ g_type_class_add_private (klass, sizeof (TplLogIterPidginPriv));
+}
+
+
+TplLogIter *
+tpl_log_iter_pidgin_new (TplLogStore *store,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask)
+{
+ return g_object_new (TPL_TYPE_LOG_ITER_PIDGIN,
+ "store", store,
+ "account", account,
+ "target", target,
+ "type-mask", type_mask,
+ NULL);
+}
diff --git a/telepathy-logger/log-iter-xml-internal.h b/telepathy-logger/log-iter-xml-internal.h
new file mode 100644
index 000000000..789c91f0c
--- /dev/null
+++ b/telepathy-logger/log-iter-xml-internal.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Debarshi Ray <debarshir@freedesktop.org>
+ */
+
+#ifndef __TPL_LOG_ITER_XML_H__
+#define __TPL_LOG_ITER_XML_H__
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include <telepathy-logger/entity.h>
+#include <telepathy-logger/log-iter-internal.h>
+#include <telepathy-logger/log-manager.h>
+#include <telepathy-logger/log-store-internal.h>
+
+G_BEGIN_DECLS
+
+#define TPL_TYPE_LOG_ITER_XML (tpl_log_iter_xml_get_type ())
+
+#define TPL_LOG_ITER_XML(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ TPL_TYPE_LOG_ITER_XML, TplLogIterXml))
+
+#define TPL_LOG_ITER_XML_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ TPL_TYPE_LOG_ITER_XML, TplLogIterXmlClass))
+
+#define TPL_IS_LOG_ITER_XML(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ TPL_TYPE_LOG_ITER_XML))
+
+#define TPL_IS_LOG_ITER_XML_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ TPL_TYPE_LOG_ITER_XML))
+
+#define TPL_LOG_ITER_XML_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ TPL_TYPE_LOG_ITER_XML, TplLogIterXmlClass))
+
+typedef struct _TplLogIterXml TplLogIterXml;
+typedef struct _TplLogIterXmlClass TplLogIterXmlClass;
+typedef struct _TplLogIterXmlPriv TplLogIterXmlPriv;
+
+struct _TplLogIterXml
+{
+ TplLogIter parent_instance;
+ TplLogIterXmlPriv *priv;
+};
+
+struct _TplLogIterXmlClass
+{
+ TplLogIterClass parent_class;
+};
+
+GType tpl_log_iter_xml_get_type (void) G_GNUC_CONST;
+
+TplLogIter *tpl_log_iter_xml_new (TplLogStore *store,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask);
+
+G_END_DECLS
+
+#endif /* __TPL_LOG_ITER_XML_H__ */
diff --git a/telepathy-logger/log-iter-xml.c b/telepathy-logger/log-iter-xml.c
new file mode 100644
index 000000000..1684a7410
--- /dev/null
+++ b/telepathy-logger/log-iter-xml.c
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Debarshi Ray <debarshir@freedesktop.org>
+ */
+
+#include "config.h"
+#include "log-iter-xml-internal.h"
+
+
+struct _TplLogIterXmlPriv
+{
+ GList *dates;
+ GList *events;
+ GList *next_date;
+ GList *next_event;
+ TpAccount *account;
+ TplEntity *target;
+ TplLogStore *store;
+ gint type_mask;
+
+};
+
+enum
+{
+ PROP_ACCOUNT = 1,
+ PROP_STORE,
+ PROP_TARGET,
+ PROP_TYPE_MASK
+};
+
+
+G_DEFINE_TYPE (TplLogIterXml, tpl_log_iter_xml, TPL_TYPE_LOG_ITER);
+
+
+static GList *
+tpl_log_iter_xml_get_events (TplLogIter *iter,
+ guint num_events,
+ GError **error)
+{
+ TplLogIterXmlPriv *priv;
+ GList *events;
+ guint i;
+
+ priv = TPL_LOG_ITER_XML (iter)->priv;
+ events = NULL;
+
+ if (priv->dates == NULL)
+ {
+ priv->dates = _tpl_log_store_get_dates (priv->store, priv->account,
+ priv->target, priv->type_mask);
+ priv->next_date = g_list_last (priv->dates);
+ }
+
+ i = 0;
+ while (i < num_events)
+ {
+ TplEvent *event;
+
+ if (priv->next_event == NULL)
+ {
+ if (priv->next_date == NULL)
+ break;
+
+ g_list_free_full (priv->events, g_object_unref);
+ priv->events = _tpl_log_store_get_events_for_date (priv->store,
+ priv->account, priv->target, priv->type_mask,
+ (GDate *) priv->next_date->data);
+
+ priv->next_event = g_list_last (priv->events);
+ priv->next_date = g_list_previous (priv->next_date);
+ }
+
+ event = TPL_EVENT (priv->next_event->data);
+ events = g_list_prepend (events, g_object_ref (event));
+ i++;
+
+ priv->next_event = g_list_previous (priv->next_event);
+ }
+
+ return events;
+}
+
+
+static void
+tpl_log_iter_xml_rewind (TplLogIter *iter,
+ guint num_events,
+ GError **error)
+{
+ GList *e;
+ TplLogIterXmlPriv *priv;
+ guint i;
+
+ priv = TPL_LOG_ITER_XML (iter)->priv;
+ e = NULL;
+
+ /* Set e to the last event that was returned */
+ if (priv->next_event == NULL)
+ e = priv->events;
+ else
+ e = g_list_next (priv->next_event);
+
+ i = 0;
+ while (i < num_events)
+ {
+ if (e == NULL)
+ {
+ GList *d;
+
+ if (priv->next_date == NULL)
+ d = priv->dates;
+ else
+ d = g_list_next (priv->next_date);
+
+ /* This can happen if get_events was never called or called
+ * with num_events == 0
+ */
+ if (d == NULL)
+ break;
+
+ g_list_free_full (priv->events, g_object_unref);
+ priv->events = NULL;
+ priv->next_event = NULL;
+
+ /* Rollback the priv->next_date */
+ priv->next_date = d;
+
+ /* Rollback the current date (ie. d) */
+ d = g_list_next (d);
+ if (d == NULL)
+ break;
+
+ priv->events = _tpl_log_store_get_events_for_date (priv->store,
+ priv->account, priv->target, priv->type_mask,
+ (GDate *) d->data);
+ e = priv->events;
+ }
+
+ priv->next_event = e;
+ e = g_list_next (e);
+ i++;
+ }
+}
+
+
+static void
+tpl_log_iter_xml_dispose (GObject *object)
+{
+ TplLogIterXmlPriv *priv;
+
+ priv = TPL_LOG_ITER_XML (object)->priv;
+
+ g_list_free_full (priv->dates, (GDestroyNotify) g_date_free);
+ priv->dates = NULL;
+
+ g_list_free_full (priv->events, g_object_unref);
+ priv->events = NULL;
+
+ g_clear_object (&priv->account);
+ g_clear_object (&priv->store);
+ g_clear_object (&priv->target);
+
+ G_OBJECT_CLASS (tpl_log_iter_xml_parent_class)->dispose (object);
+}
+
+
+static void
+tpl_log_iter_xml_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (tpl_log_iter_xml_parent_class)->finalize (object);
+}
+
+
+static void
+tpl_log_iter_xml_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TplLogIterXmlPriv *priv;
+
+ priv = TPL_LOG_ITER_XML (object)->priv;
+
+ switch (param_id)
+ {
+ case PROP_ACCOUNT:
+ g_value_set_object (value, priv->account);
+ break;
+
+ case PROP_STORE:
+ g_value_set_object (value, priv->store);
+ break;
+
+ case PROP_TARGET:
+ g_value_set_object (value, priv->target);
+ break;
+
+ case PROP_TYPE_MASK:
+ g_value_set_int (value, priv->type_mask);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+
+static void
+tpl_log_iter_xml_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TplLogIterXmlPriv *priv;
+
+ priv = TPL_LOG_ITER_XML (object)->priv;
+
+ switch (param_id)
+ {
+ case PROP_ACCOUNT:
+ priv->account = g_value_dup_object (value);
+ break;
+
+ case PROP_STORE:
+ priv->store = g_value_dup_object (value);
+ break;
+
+ case PROP_TARGET:
+ priv->target = g_value_dup_object (value);
+ break;
+
+ case PROP_TYPE_MASK:
+ priv->type_mask = g_value_get_int (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+
+static void
+tpl_log_iter_xml_init (TplLogIterXml *iter)
+{
+ iter->priv = G_TYPE_INSTANCE_GET_PRIVATE (iter, TPL_TYPE_LOG_ITER_XML,
+ TplLogIterXmlPriv);
+}
+
+
+static void
+tpl_log_iter_xml_class_init (TplLogIterXmlClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ TplLogIterClass *log_iter_class = TPL_LOG_ITER_CLASS (klass);
+ GParamSpec *param_spec;
+
+ object_class->dispose = tpl_log_iter_xml_dispose;
+ object_class->finalize = tpl_log_iter_xml_finalize;
+ object_class->get_property = tpl_log_iter_xml_get_property;
+ object_class->set_property = tpl_log_iter_xml_set_property;
+ log_iter_class->get_events = tpl_log_iter_xml_get_events;
+ log_iter_class->rewind = tpl_log_iter_xml_rewind;
+
+ param_spec = g_param_spec_object ("account",
+ "Account",
+ "The account whose logs are to be traversed",
+ TP_TYPE_ACCOUNT,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_ACCOUNT, param_spec);
+
+ param_spec = g_param_spec_object ("store",
+ "Store",
+ "The storage backend from which the logs are to be retrieved",
+ TPL_TYPE_LOG_STORE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_STORE, param_spec);
+
+ param_spec = g_param_spec_object ("target",
+ "Target",
+ "The target entity with which the account interacted",
+ TPL_TYPE_ENTITY,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TARGET, param_spec);
+
+ param_spec = g_param_spec_int ("type-mask",
+ "Type Mask",
+ "A bitmask to filter the events to be retrieved",
+ 1,
+ 0xffff,
+ TPL_EVENT_MASK_ANY,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TYPE_MASK, param_spec);
+
+ g_type_class_add_private (klass, sizeof (TplLogIterXmlPriv));
+}
+
+
+TplLogIter *
+tpl_log_iter_xml_new (TplLogStore *store,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask)
+{
+ return g_object_new (TPL_TYPE_LOG_ITER_XML,
+ "store", store,
+ "account", account,
+ "target", target,
+ "type-mask", type_mask,
+ NULL);
+}
diff --git a/telepathy-logger/log-iter.c b/telepathy-logger/log-iter.c
new file mode 100644
index 000000000..5ad1f5378
--- /dev/null
+++ b/telepathy-logger/log-iter.c
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Debarshi Ray <debarshir@freedesktop.org>
+ */
+
+#include "config.h"
+#include "log-iter-internal.h"
+
+
+G_DEFINE_TYPE (TplLogIter, tpl_log_iter, G_TYPE_OBJECT);
+
+
+static void
+tpl_log_iter_dispose (GObject *object)
+{
+ G_OBJECT_CLASS (tpl_log_iter_parent_class)->dispose (object);
+}
+
+
+static void
+tpl_log_iter_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (tpl_log_iter_parent_class)->finalize (object);
+}
+
+
+static void
+tpl_log_iter_init (TplLogIter *self)
+{
+}
+
+
+static void
+tpl_log_iter_class_init (TplLogIterClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ object_class->dispose = tpl_log_iter_dispose;
+ object_class->finalize = tpl_log_iter_finalize;
+}
+
+
+GList *
+tpl_log_iter_get_events (TplLogIter *self,
+ guint num_events,
+ GError **error)
+{
+ TplLogIterClass *log_iter_class;
+
+ g_return_val_if_fail (TPL_IS_LOG_ITER (self), NULL);
+
+ log_iter_class = TPL_LOG_ITER_GET_CLASS (self);
+
+ if (log_iter_class->get_events == NULL)
+ return NULL;
+
+ return log_iter_class->get_events (self, num_events, error);
+}
+
+
+void
+tpl_log_iter_rewind (TplLogIter *self,
+ guint num_events,
+ GError **error)
+{
+ TplLogIterClass *log_iter_class;
+
+ g_return_if_fail (TPL_IS_LOG_ITER (self));
+
+ log_iter_class = TPL_LOG_ITER_GET_CLASS (self);
+
+ if (log_iter_class->rewind == NULL)
+ return;
+
+ log_iter_class->rewind (self, num_events, error);
+}
diff --git a/telepathy-logger/log-manager-internal.h b/telepathy-logger/log-manager-internal.h
new file mode 100644
index 000000000..56650deb3
--- /dev/null
+++ b/telepathy-logger/log-manager-internal.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2003-2007 Imendio AB
+ * Copyright (C) 2007-2011 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#ifndef __TPL_LOG_MANAGER_PRIV_H__
+#define __TPL_LOG_MANAGER_PRIV_H__
+
+#include <telepathy-logger/log-manager.h>
+#include <telepathy-logger/log-store-factory-internal.h>
+#include <telepathy-logger/log-store-internal.h>
+
+#define TPL_TYPE_LOG_SEARCH_HIT (_tpl_log_manager_search_hit_get_type ())
+
+gboolean _tpl_log_manager_add_event (TplLogManager *manager,
+ TplEvent *event,
+ GError **error);
+
+gboolean _tpl_log_manager_register_log_store (TplLogManager *self,
+ TplLogStore *logstore);
+
+GList * _tpl_log_manager_get_dates (TplLogManager *manager,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask);
+
+GList * _tpl_log_manager_get_events_for_date (TplLogManager *manager,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask,
+ const GDate *date);
+
+GList * _tpl_log_manager_get_filtered_events (TplLogManager *manager,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask,
+ guint num_events,
+ TplLogEventFilter filter,
+ gpointer user_data);
+
+GList * _tpl_log_manager_get_entities (TplLogManager *manager,
+ TpAccount *account);
+
+GList * _tpl_log_manager_search (TplLogManager *manager,
+ const gchar *text,
+ gint type_mask);
+
+void _tpl_log_manager_clear (TplLogManager *self);
+
+void _tpl_log_manager_clear_account (TplLogManager *self, TpAccount *account);
+
+void _tpl_log_manager_clear_entity (TplLogManager *self, TpAccount *account,
+ TplEntity *entity);
+
+GType _tpl_log_manager_search_hit_get_type (void);
+
+TplLogSearchHit * _tpl_log_manager_search_hit_new (TpAccount *account,
+ TplEntity *target,
+ GDate *date);
+
+void _tpl_log_manager_search_hit_free (TplLogSearchHit *hit);
+
+TplLogSearchHit * _tpl_log_manager_search_hit_copy (TplLogSearchHit *hit);
+
+#endif /* __TPL_LOG_MANAGER_PRIV_H__ */
diff --git a/telepathy-logger/log-manager.c b/telepathy-logger/log-manager.c
new file mode 100644
index 000000000..b19d3b213
--- /dev/null
+++ b/telepathy-logger/log-manager.c
@@ -0,0 +1,1682 @@
+/*
+ * Copyright (C) 2003-2007 Imendio AB
+ * Copyright (C) 2007-2011 Collabora Ltd.
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ * Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ */
+
+#include "config.h"
+#include "log-manager.h"
+#include "log-manager-internal.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <gio/gio.h>
+#include <glib/gstdio.h>
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/telepathy-glib-dbus.h>
+
+#include <telepathy-logger/conf-internal.h>
+#include <telepathy-logger/entity-internal.h>
+#include <telepathy-logger/event.h>
+#include <telepathy-logger/event-internal.h>
+#include <telepathy-logger/log-store-internal.h>
+#include <telepathy-logger/log-store-empathy-internal.h>
+#include <telepathy-logger/log-store-xml-internal.h>
+#include <telepathy-logger/log-store-pidgin-internal.h>
+#include <telepathy-logger/log-store-sqlite-internal.h>
+
+#define DEBUG_FLAG TPL_DEBUG_LOG_MANAGER
+#include <telepathy-logger/debug-internal.h>
+#include <telepathy-logger/log-walker-internal.h>
+#include <telepathy-logger/util-internal.h>
+
+/**
+ * SECTION:log-manager
+ * @title: TplLogManager
+ * @short_description: Fetch and search through logs
+ *
+ * The #TplLogManager object allows user to fetch logs and make searches.
+ */
+
+/**
+ * TplLogManager:
+ *
+ * An object used to access logs
+ */
+
+/**
+ * TplLogEventFilter:
+ * @event: the #TplEvent to filter
+ * @user_data: user-supplied data
+ *
+ * Returns: %TRUE if @event should appear in the result
+ */
+
+/**
+ * TPL_LOG_MANAGER_ERROR:
+ *
+ * The error domain for the #TplLogManager.
+ */
+
+/* This macro is used to check if a list has been taken by a _finish()
+ * function call. It detects the marker set by _take_list() method. Those
+ * are used to avoid copying the full list on every call. */
+#define _LIST_TAKEN(l) ((l) != NULL && (l)->data == NULL)
+
+typedef struct
+{
+ TplConf *conf;
+
+ GList *stores;
+ GList *writable_stores;
+ GList *readable_stores;
+} TplLogManagerPriv;
+
+
+typedef void (*TplLogManagerFreeFunc) (gpointer *data);
+
+
+typedef struct
+{
+ TpAccount *account;
+ TplEntity *target;
+ gint type_mask;
+ GDate *date;
+ guint num_events;
+ TplLogEventFilter filter;
+ gchar *search_text;
+ gpointer user_data;
+ TplEvent *logevent;
+} TplLogManagerEventInfo;
+
+
+typedef struct
+{
+ TplLogManager *manager;
+ TplLogManagerEventInfo *request;
+ TplLogManagerFreeFunc request_free;
+ GAsyncReadyCallback cb;
+ gpointer user_data;
+} TplLogManagerAsyncData;
+
+
+G_DEFINE_TYPE (TplLogManager, tpl_log_manager, G_TYPE_OBJECT);
+
+G_DEFINE_BOXED_TYPE (TplLogSearchHit,
+ _tpl_log_manager_search_hit,
+ _tpl_log_manager_search_hit_copy,
+ _tpl_log_manager_search_hit_free);
+
+static TplLogManager *manager_singleton = NULL;
+
+
+static void
+log_manager_finalize (GObject *object)
+{
+ TplLogManagerPriv *priv;
+
+ priv = TPL_LOG_MANAGER (object)->priv;
+
+ g_object_unref (priv->conf);
+
+ g_list_foreach (priv->stores, (GFunc) g_object_unref, NULL);
+ g_list_free (priv->stores);
+ /* no unref needed here, the only reference kept is in priv->stores */
+ g_list_free (priv->writable_stores);
+ g_list_free (priv->readable_stores);
+
+ G_OBJECT_CLASS (tpl_log_manager_parent_class)->finalize (object);
+}
+
+
+/*
+ * - Singleton LogManager constructor -
+ * Initialises LogStores with LogStoreEmpathy instance
+ */
+static GObject *
+log_manager_constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *retval = NULL;
+
+ if (G_LIKELY (manager_singleton))
+ retval = g_object_ref (manager_singleton);
+ else
+ {
+ retval = G_OBJECT_CLASS (tpl_log_manager_parent_class)->constructor (
+ type, n_props, props);
+ if (retval == NULL)
+ return NULL;
+
+ manager_singleton = TPL_LOG_MANAGER (retval);
+ g_object_add_weak_pointer (retval, (gpointer *) &manager_singleton);
+ }
+
+ return retval;
+}
+
+
+static void
+tpl_log_manager_class_init (TplLogManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructor = log_manager_constructor;
+ object_class->finalize = log_manager_finalize;
+
+ g_type_class_add_private (object_class, sizeof (TplLogManagerPriv));
+}
+
+
+static void
+add_log_store (TplLogManager *self,
+ TplLogStore *store)
+{
+ g_return_if_fail (TPL_IS_LOG_STORE (store));
+
+ /* set the log store in "testmode" if it supports it and the environment is
+ * currently in test mode */
+ if (g_object_class_find_property (G_OBJECT_GET_CLASS (store), "testmode"))
+ g_object_set (store,
+ "testmode", (g_getenv ("TPL_TEST_MODE") != NULL),
+ NULL);
+
+ if (!_tpl_log_manager_register_log_store (self, store))
+ CRITICAL ("Failed to register store name=%s",
+ _tpl_log_store_get_name (store));
+
+ /* drop the initial ref */
+ g_object_unref (store);
+}
+
+
+static void
+_globally_enabled_changed (TplConf *conf,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ DEBUG ("Logging has been globally %s",
+ _tpl_conf_is_globally_enabled (conf) ? "enabled" : "disabled");
+}
+
+
+static GList *
+_take_list (GList *list)
+{
+ GList *copy = NULL;
+
+ if (list != NULL)
+ {
+ copy = g_list_alloc ();
+ memcpy (copy, list, sizeof (GList));
+ memset (list, 0, sizeof (GList));
+ }
+
+ return copy;
+}
+
+
+static void
+_list_of_object_free (gpointer data)
+{
+ GList *lst = data; /* list of GObject */
+
+ if (!_LIST_TAKEN (lst))
+ g_list_foreach (lst, (GFunc) g_object_unref, NULL);
+
+ g_list_free (lst);
+}
+
+
+static void
+_list_of_date_free (gpointer data)
+{
+ GList *lst = data; /* list of (GDate *) */
+
+ if (!_LIST_TAKEN (lst))
+ g_list_foreach (lst, (GFunc) g_date_free, NULL);
+
+ g_list_free (lst);
+}
+
+
+static void
+tpl_log_manager_init (TplLogManager *self)
+{
+ TplLogManagerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TPL_TYPE_LOG_MANAGER, TplLogManagerPriv);
+
+ self->priv = priv;
+
+ DEBUG ("Initialising the Log Manager");
+
+ priv->conf = _tpl_conf_dup ();
+
+ g_signal_connect (priv->conf, "notify::globally-enabled",
+ G_CALLBACK (_globally_enabled_changed), NULL);
+
+ /* The TPL's default read-write logstore */
+ add_log_store (self,
+ g_object_new (TPL_TYPE_LOG_STORE_XML,
+ NULL));
+
+ /* Load by default the Empathy's legacy 'past coversations' LogStore */
+ add_log_store (self,
+ g_object_new (TPL_TYPE_LOG_STORE_EMPATHY,
+ NULL));
+
+ add_log_store (self,
+ g_object_new (TPL_TYPE_LOG_STORE_PIDGIN,
+ NULL));
+
+ /* Load the event counting cache */
+ add_log_store (self,
+ g_object_new (TPL_TYPE_LOG_STORE_SQLITE,
+ NULL));
+
+ DEBUG ("Log Manager initialised");
+}
+
+
+/**
+ * tpl_log_manager_dup_singleton:
+ *
+ * Returns: (transfer full): a new reference on the log manager
+ */
+TplLogManager *
+tpl_log_manager_dup_singleton (void)
+{
+ return g_object_new (TPL_TYPE_LOG_MANAGER, NULL);
+}
+
+/*
+ * _tpl_log_manager_add_event:
+ * @manager: the log manager
+ * @event: a TplEvent subclass's instance
+ * @error: the memory location of GError, filled if an error occurs
+ *
+ * It stores @event, sending it to all the writable registered #TplLogStore objects.
+ * (Every TplLogManager is guaranteed to have at least one writable log store.)
+ *
+ * Returns: %TRUE if the event has been successfully added, otherwise %FALSE.
+ */
+gboolean
+_tpl_log_manager_add_event (TplLogManager *manager,
+ TplEvent *event,
+ GError **error)
+{
+ TplLogManagerPriv *priv;
+ GList *l;
+ gboolean retval = FALSE;
+
+ TplEntity *target;
+ TpAccount *account;
+
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+ g_return_val_if_fail (TPL_IS_LOG_MANAGER (manager), FALSE);
+ g_return_val_if_fail (TPL_IS_EVENT (event), FALSE);
+
+ priv = manager->priv;
+
+ if (!_tpl_conf_is_globally_enabled (priv->conf))
+ {
+ /* ignore event, logging is globally disabled */
+ return FALSE;
+ }
+
+ account = tpl_event_get_account (event);
+
+ /* check whether receiver is in the list of contacts to ignore */
+ target = tpl_event_get_receiver (event);
+ if (tpl_log_manager_is_disabled_for_entity (manager, account, target))
+ return FALSE;
+
+ /* check whether sender is in the list of contacts to ignore */
+ target = tpl_event_get_sender (event);
+ if (tpl_log_manager_is_disabled_for_entity (manager, account, target))
+ return FALSE;
+
+ /* send the event to any writable log store */
+ for (l = priv->writable_stores; l != NULL; l = g_list_next (l))
+ {
+ GError *loc_error = NULL;
+ TplLogStore *store = l->data;
+ gboolean result;
+
+ result = _tpl_log_store_add_event (store, event, &loc_error);
+ if (!result)
+ {
+ CRITICAL ("logstore name=%s: %s. "
+ "Event may not be logged properly.",
+ _tpl_log_store_get_name (store),
+ loc_error != NULL ? loc_error->message : "no error message");
+ g_clear_error (&loc_error);
+ }
+ /* TRUE if at least one LogStore succeeds */
+ retval = result || retval;
+ }
+ if (!retval)
+ {
+ CRITICAL ("Failed to write event to all writable LogStores.");
+ g_set_error_literal (error, TPL_LOG_MANAGER_ERROR,
+ TPL_LOG_MANAGER_ERROR_ADD_EVENT,
+ "Non recoverable error occurred during log manager's "
+ "add_event() execution");
+ }
+ return retval;
+}
+
+
+/*
+ * _tpl_log_manager_register_log_store:
+ * @self: the log manager
+ * @logstore: a TplLogStore interface implementation
+ *
+ * It registers @logstore into @manager, the log store has to be an
+ * implementation of the TplLogStore interface.
+ *
+ * @logstore has to properly implement all the search/query methods if the
+ * TplLogStore:readable is set to %TRUE.
+ */
+gboolean
+_tpl_log_manager_register_log_store (TplLogManager *self,
+ TplLogStore *logstore)
+{
+ TplLogManagerPriv *priv = self->priv;
+ const gchar *name = _tpl_log_store_get_name (logstore);
+ GList *l;
+
+ g_return_val_if_fail (TPL_IS_LOG_MANAGER (self), FALSE);
+ g_return_val_if_fail (TPL_IS_LOG_STORE (logstore), FALSE);
+
+ /* check that the logstore name is not already used */
+ for (l = priv->stores; l != NULL; l = g_list_next (l))
+ {
+ TplLogStore *store = l->data;
+
+ if (!tp_strdiff (name, _tpl_log_store_get_name (store)))
+ {
+ DEBUG ("name=%s: already registered", name);
+ return FALSE;
+ }
+ }
+
+ if (_tpl_log_store_is_readable (logstore))
+ priv->readable_stores = g_list_prepend (priv->readable_stores, logstore);
+
+ if (_tpl_log_store_is_writable (logstore))
+ priv->writable_stores = g_list_prepend (priv->writable_stores, logstore);
+
+ /* reference just once, writable/readable lists are kept in sync with the
+ * general list and never written separately */
+ priv->stores = g_list_prepend (priv->stores, g_object_ref (logstore));
+ DEBUG ("LogStore name=%s registered", _tpl_log_store_get_name (logstore));
+
+ return TRUE;
+}
+
+
+/**
+ * tpl_log_manager_exists:
+ * @manager: TplLogManager
+ * @account: TpAccount
+ * @target: a non-NULL #TplEntity
+ * @type_mask: event type filter see #TplEventTypeMask
+ *
+ * Checks if logs exist for @target.
+ *
+ * It applies for any registered TplLogStore with the TplLogStore:readable
+ * property %TRUE.
+
+ * Returns: %TRUE logs exist for @target, otherwise %FALSE
+ */
+gboolean
+tpl_log_manager_exists (TplLogManager *manager,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask)
+{
+ GList *l;
+ TplLogManagerPriv *priv;
+
+ g_return_val_if_fail (TPL_IS_LOG_MANAGER (manager), FALSE);
+ g_return_val_if_fail (TPL_IS_ENTITY (target), FALSE);
+
+ priv = manager->priv;
+
+ for (l = priv->readable_stores; l != NULL; l = g_list_next (l))
+ {
+ if (_tpl_log_store_exists (TPL_LOG_STORE (l->data), account, target,
+ type_mask))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+/*
+ * _tpl_log_manager_get_dates:
+ * @manager: a #TplLogManager
+ * @account: a #TpAccount
+ * @target: a non-NULL #TplEntity
+ *
+ * Retrieves a list of #GDate corresponding to each day
+ * at least an event exist for @target_id.
+ *
+ * It applies for any registered TplLogStore with the TplLogStore:readable
+ * property %TRUE.
+ *
+ * Returns: a GList of (GDate *), to be freed using something like
+ * g_list_free_full (lst, g_date_free);
+ */
+GList *
+_tpl_log_manager_get_dates (TplLogManager *manager,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask)
+{
+ GList *l, *out = NULL;
+ TplLogManagerPriv *priv;
+
+ g_return_val_if_fail (TPL_IS_LOG_MANAGER (manager), NULL);
+ g_return_val_if_fail (TPL_IS_ENTITY (target), NULL);
+
+ priv = manager->priv;
+
+ for (l = priv->readable_stores; l != NULL; l = g_list_next (l))
+ {
+ TplLogStore *store = TPL_LOG_STORE (l->data);
+ GList *new;
+
+ /* Insert dates of each store in the out list. Keep the out list sorted
+ * and avoid to insert dups. */
+ new = _tpl_log_store_get_dates (store, account, target, type_mask);
+ while (new)
+ {
+ if (g_list_find_custom (out, new->data,
+ (GCompareFunc) g_date_compare))
+ g_date_free (new->data);
+ else
+ out =
+ g_list_insert_sorted (out, new->data,
+ (GCompareFunc) g_date_compare);
+
+ new = g_list_delete_link (new, new);
+ }
+ }
+
+ return out;
+}
+
+
+GList *
+_tpl_log_manager_get_events_for_date (TplLogManager *manager,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask,
+ const GDate *date)
+{
+ GList *l, *out = NULL;
+ TplLogManagerPriv *priv;
+
+ g_return_val_if_fail (TPL_IS_LOG_MANAGER (manager), NULL);
+ g_return_val_if_fail (TPL_IS_ENTITY (target), NULL);
+
+ priv = manager->priv;
+
+ for (l = priv->readable_stores; l != NULL; l = g_list_next (l))
+ {
+ TplLogStore *store = TPL_LOG_STORE (l->data);
+
+ out = g_list_concat (out, _tpl_log_store_get_events_for_date (store,
+ account, target, type_mask, date));
+ }
+
+ return out;
+}
+
+
+GList *
+_tpl_log_manager_get_filtered_events (TplLogManager *manager,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask,
+ guint num_events,
+ TplLogEventFilter filter,
+ gpointer user_data)
+{
+ TplLogManagerPriv *priv;
+ GQueue out = G_QUEUE_INIT;
+ GList *l;
+
+ g_return_val_if_fail (TPL_IS_LOG_MANAGER (manager), NULL);
+ g_return_val_if_fail (TPL_IS_ENTITY (target), NULL);
+
+ priv = manager->priv;
+
+ /* Get num_events from each log store and keep only the
+ * newest ones in the out list. Keep that list sorted: olders first. */
+ for (l = priv->readable_stores; l != NULL; l = g_list_next (l))
+ {
+ TplLogStore *store = TPL_LOG_STORE (l->data);
+ GList *new, *index = NULL;
+
+ new = _tpl_log_store_get_filtered_events (store, account, target,
+ type_mask, num_events, filter, user_data);
+
+ while (new != NULL)
+ {
+ index = _tpl_event_queue_insert_sorted_after (&out, index, new->data);
+
+ if (out.length > num_events)
+ {
+ /* We have too many elements. Remove the oldest event. */
+ g_object_unref (g_queue_pop_head (&out));
+ }
+
+ new = g_list_delete_link (new, new);
+ }
+ }
+
+ return out.head;
+}
+
+
+/*
+ * _tpl_log_manager_get_entities:
+ * @manager: the log manager
+ * @account: a TpAccount the query will return data related to
+ *
+ * It queries the readable TplLogStores in @manager for all the buddies the
+ * log store has at least a conversation stored originated using @account.
+ *
+ * Returns: a list of pointer to #TplEntity, to be freed using something like
+ * g_list_free_full (lst, g_object_unref)
+ */
+GList *
+_tpl_log_manager_get_entities (TplLogManager *manager,
+ TpAccount *account)
+{
+ GList *l, *out = NULL;
+ TplLogManagerPriv *priv;
+
+ g_return_val_if_fail (TPL_IS_LOG_MANAGER (manager), NULL);
+ g_return_val_if_fail (TP_IS_ACCOUNT (account), NULL);
+
+ priv = manager->priv;
+
+ for (l = priv->readable_stores; l != NULL; l = g_list_next (l))
+ {
+ TplLogStore *store = TPL_LOG_STORE (l->data);
+ GList *in, *j;
+
+ in = _tpl_log_store_get_entities (store, account);
+ /* merge the lists avoiding duplicates */
+ for (j = in; j != NULL; j = g_list_next (j))
+ {
+ TplEntity *entity = TPL_ENTITY (j->data);
+
+ if (g_list_find_custom (out, entity,
+ (GCompareFunc) _tpl_entity_compare) == NULL)
+ {
+ /* add data if not already present */
+ out = g_list_prepend (out, entity);
+ }
+ else
+ /* free hit if already present in out */
+ g_object_unref (entity);
+ }
+ g_list_free (in);
+ }
+
+ return out;
+}
+
+
+GList *
+_tpl_log_manager_search (TplLogManager *manager,
+ const gchar *text,
+ gint type_mask)
+{
+ GList *l, *out = NULL;
+ TplLogManagerPriv *priv;
+
+ g_return_val_if_fail (TPL_IS_LOG_MANAGER (manager), NULL);
+ g_return_val_if_fail (!TPL_STR_EMPTY (text), NULL);
+
+ priv = manager->priv;
+
+ for (l = priv->readable_stores; l != NULL; l = g_list_next (l))
+ {
+ TplLogStore *store = TPL_LOG_STORE (l->data);
+
+ out = g_list_concat (out, _tpl_log_store_search_new (store, text,
+ type_mask));
+ }
+
+ return out;
+}
+
+
+TplLogSearchHit *
+_tpl_log_manager_search_hit_new (TpAccount *account,
+ TplEntity *target,
+ GDate *date)
+{
+ TplLogSearchHit *hit = g_slice_new0 (TplLogSearchHit);
+
+ g_return_val_if_fail (TPL_IS_ENTITY (target), NULL);
+
+ if (account != NULL)
+ hit->account = g_object_ref (account);
+
+ hit->target = g_object_ref (target);
+
+ if (date != NULL)
+ hit->date = g_date_new_dmy (g_date_get_day (date), g_date_get_month (date),
+ g_date_get_year (date));
+
+ return hit;
+}
+
+void
+_tpl_log_manager_search_hit_free (TplLogSearchHit *hit)
+{
+ if (hit->account != NULL)
+ g_object_unref (hit->account);
+
+ if (hit->date != NULL)
+ g_date_free (hit->date);
+
+ if (hit->target != NULL)
+ g_object_unref (hit->target);
+
+ g_slice_free (TplLogSearchHit, hit);
+}
+
+
+/**
+ * tpl_log_manager_search_free: (skip)
+ * @hits: a #GList of #TplLogSearchHit
+ *
+ * Free @hits and its content.
+ */
+void
+tpl_log_manager_search_free (GList *hits)
+{
+ GList *l;
+
+ for (l = hits; l != NULL; l = g_list_next (l))
+ {
+ if (l->data != NULL)
+ _tpl_log_manager_search_hit_free (l->data);
+ }
+
+ g_list_free (hits);
+}
+
+
+/* start of Async definitions */
+static TplLogManagerAsyncData *
+tpl_log_manager_async_data_new (void)
+{
+ return g_slice_new0 (TplLogManagerAsyncData);
+}
+
+
+static void
+tpl_log_manager_async_data_free (TplLogManagerAsyncData *data)
+{
+ if (data->manager != NULL)
+ g_object_unref (data->manager);
+ data->request_free ((gpointer) data->request);
+ g_slice_free (TplLogManagerAsyncData, data);
+}
+
+
+static TplLogManagerEventInfo *
+tpl_log_manager_event_info_new (void)
+{
+ return g_slice_new0 (TplLogManagerEventInfo);
+}
+
+
+static void
+tpl_log_manager_event_info_free (TplLogManagerEventInfo *data)
+{
+ tp_clear_object (&data->account);
+ tp_clear_object (&data->logevent);
+ tp_clear_object (&data->target);
+
+ tp_clear_pointer (&data->date, g_date_free);
+ tp_clear_pointer (&data->search_text, g_free);
+ g_slice_free (TplLogManagerEventInfo, data);
+}
+
+
+static void
+_tpl_log_manager_async_operation_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TplLogManagerAsyncData *async_data = (TplLogManagerAsyncData *) user_data;
+
+ if (async_data->cb)
+ async_data->cb (G_OBJECT (async_data->manager), result,
+ async_data->user_data);
+
+ tpl_log_manager_async_data_free (async_data);
+}
+
+
+void
+_tpl_log_manager_clear (TplLogManager *self)
+{
+ GList *l;
+ TplLogManagerPriv *priv;
+
+ g_return_if_fail (TPL_IS_LOG_MANAGER (self));
+
+ priv = self->priv;
+
+ for (l = priv->stores; l != NULL; l = g_list_next (l))
+ {
+ _tpl_log_store_clear (TPL_LOG_STORE (l->data));
+ }
+}
+
+
+void
+_tpl_log_manager_clear_account (TplLogManager *self,
+ TpAccount *account)
+{
+ GList *l;
+ TplLogManagerPriv *priv;
+
+ g_return_if_fail (TPL_IS_LOG_MANAGER (self));
+
+ priv = self->priv;
+
+ for (l = priv->stores; l != NULL; l = g_list_next (l))
+ {
+ _tpl_log_store_clear_account (TPL_LOG_STORE (l->data), account);
+ }
+}
+
+
+void
+_tpl_log_manager_clear_entity (TplLogManager *self,
+ TpAccount *account,
+ TplEntity *entity)
+{
+ GList *l;
+ TplLogManagerPriv *priv;
+
+ g_return_if_fail (TPL_IS_LOG_MANAGER (self));
+
+ priv = self->priv;
+
+ for (l = priv->stores; l != NULL; l = g_list_next (l))
+ {
+ _tpl_log_store_clear_entity (TPL_LOG_STORE (l->data), account, entity);
+ }
+}
+
+
+/* There is no g_date_copy() */
+static GDate *
+copy_date (const GDate *date)
+{
+ return g_date_new_julian (g_date_get_julian (date));
+}
+
+
+static void
+_get_dates_async_thread (GSimpleAsyncResult *simple,
+ GObject *object,
+ GCancellable *cancellable)
+{
+ TplLogManagerAsyncData *async_data;
+ TplLogManagerEventInfo *event_info;
+ GList *lst = NULL;
+
+ async_data = g_async_result_get_user_data (G_ASYNC_RESULT (simple));
+ event_info = async_data->request;
+
+ lst = _tpl_log_manager_get_dates (async_data->manager,
+ event_info->account, event_info->target, event_info->type_mask);
+
+ g_simple_async_result_set_op_res_gpointer (simple, lst,
+ _list_of_date_free);
+}
+
+typedef struct
+{
+ GSimpleAsyncResult *result;
+ GSimpleAsyncThreadFunc func;
+} AsyncOpData;
+
+static AsyncOpData *
+async_op_data_new (GSimpleAsyncResult *result,
+ GSimpleAsyncThreadFunc func)
+{
+ AsyncOpData *data = g_slice_new (AsyncOpData);
+
+ data->result = g_object_ref (result);
+ data->func = func;
+ return data;
+}
+
+static void
+async_op_data_free (AsyncOpData *data)
+{
+ g_object_unref (data->result);
+ g_slice_free (AsyncOpData, data);
+}
+
+static void
+account_prepared_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ AsyncOpData *data = user_data;
+ GError *error = NULL;
+
+ if (!tp_proxy_prepare_finish (source, result, &error))
+ {
+ g_simple_async_result_take_error (data->result, error);
+ g_simple_async_result_complete (data->result);
+ }
+ else
+ {
+ g_simple_async_result_run_in_thread (data->result, data->func, 0, NULL);
+ }
+
+ async_op_data_free (data);
+}
+
+static void
+start_async_op_in_thread (TpAccount *account,
+ GSimpleAsyncResult *result,
+ GSimpleAsyncThreadFunc func)
+{
+ if (account != NULL)
+ {
+ GQuark features[] = { TP_ACCOUNT_FEATURE_CORE, 0 };
+
+ /* Most APIs rely on TpAccount being prepared, so make sure
+ * it is. telepathy-glib is not thread-safe, so we must do
+ * this in the main thread, before starting the actual
+ * operation in the other thread. */
+ tp_proxy_prepare_async (account, features, account_prepared_cb,
+ async_op_data_new (result, func));
+ }
+ else
+ {
+ g_simple_async_result_run_in_thread (result, func, 0, NULL);
+ }
+}
+
+/**
+ * tpl_log_manager_get_dates_async:
+ * @manager: a #TplLogManager
+ * @account: a #TpAccount
+ * @target: a non-NULL #TplEntity
+ * @type_mask: event type filter see #TplEventTypeMask
+ * @callback: a callback to call when the request is satisfied
+ * @user_data: data to pass to @callback
+ *
+ * Retrieves a list of #GDate corresponding to each day where
+ * at least one event exist for @target.
+ *
+ * It applies for any registered TplLogStore with the TplLogStore:readable
+ * property %TRUE.
+ */
+void
+tpl_log_manager_get_dates_async (TplLogManager *manager,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TplLogManagerEventInfo *event_info = tpl_log_manager_event_info_new ();
+ TplLogManagerAsyncData *async_data = tpl_log_manager_async_data_new ();
+ GSimpleAsyncResult *simple;
+
+ g_return_if_fail (TPL_IS_LOG_MANAGER (manager));
+ g_return_if_fail (TP_IS_ACCOUNT (account));
+ g_return_if_fail (TPL_IS_ENTITY (target));
+
+ event_info->account = g_object_ref (account);
+ event_info->target = g_object_ref (target);
+ event_info->type_mask = type_mask;
+
+ async_data->manager = g_object_ref (manager);
+ async_data->request = event_info;
+ async_data->request_free =
+ (TplLogManagerFreeFunc) tpl_log_manager_event_info_free;
+ async_data->cb = callback;
+ async_data->user_data = user_data;
+
+ simple = g_simple_async_result_new (G_OBJECT (manager),
+ _tpl_log_manager_async_operation_cb, async_data,
+ tpl_log_manager_get_dates_async);
+
+ start_async_op_in_thread (account, simple, _get_dates_async_thread);
+
+ g_object_unref (simple);
+}
+
+
+/**
+ * tpl_log_manager_get_dates_finish:
+ * @self: a #TplLogManager
+ * @result: a #GAsyncResult
+ * @dates: (out) (transfer full) (element-type GLib.Date): a pointer to a
+ * #GList used to return the list of #GDate
+ * @error: a #GError to fill
+ *
+ * Returns: #TRUE if the operation was successful, otherwise #FALSE
+ */
+gboolean
+tpl_log_manager_get_dates_finish (TplLogManager *self,
+ GAsyncResult *result,
+ GList **dates,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (TPL_IS_LOG_MANAGER (self), FALSE);
+ g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE);
+ g_return_val_if_fail (g_simple_async_result_is_valid (result,
+ G_OBJECT (self), tpl_log_manager_get_dates_async), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return FALSE;
+
+ if (dates != NULL)
+ *dates = _take_list (g_simple_async_result_get_op_res_gpointer (simple));
+
+ return TRUE;
+}
+
+
+static void
+_get_events_for_date_async_thread (GSimpleAsyncResult *simple,
+ GObject *object,
+ GCancellable *cancellable)
+{
+ TplLogManagerAsyncData *async_data;
+ TplLogManagerEventInfo *event_info;
+ GList *lst;
+
+ async_data = g_async_result_get_user_data (G_ASYNC_RESULT (simple));
+ event_info = async_data->request;
+
+ lst = _tpl_log_manager_get_events_for_date (async_data->manager,
+ event_info->account,
+ event_info->target,
+ event_info->type_mask,
+ event_info->date);
+
+ g_simple_async_result_set_op_res_gpointer (simple, lst,
+ _list_of_object_free);
+}
+
+
+/**
+ * tpl_log_manager_get_events_for_date_async:
+ * @manager: a #TplLogManager
+ * @account: a #TpAccount
+ * @target: a non-NULL #TplEntity
+ * @type_mask: event type filter see #TplEventTypeMask
+ * @date: a #GDate
+ * @callback: a callback to call when the request is satisfied
+ * @user_data: data to pass to @callback
+ *
+ * Retrieve a list of #TplEvent at @date with @target.
+ */
+void
+tpl_log_manager_get_events_for_date_async (TplLogManager *manager,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask,
+ const GDate *date,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TplLogManagerEventInfo *event_info = tpl_log_manager_event_info_new ();
+ TplLogManagerAsyncData *async_data = tpl_log_manager_async_data_new ();
+ GSimpleAsyncResult *simple;
+
+ g_return_if_fail (TPL_IS_LOG_MANAGER (manager));
+ g_return_if_fail (TP_IS_ACCOUNT (account));
+ g_return_if_fail (TPL_IS_ENTITY (target));
+ g_return_if_fail (date != NULL);
+
+ event_info->account = g_object_ref (account);
+ event_info->target = g_object_ref (target);
+ event_info->type_mask = type_mask;
+ event_info->date = copy_date (date);
+
+ async_data->manager = g_object_ref (manager);
+ async_data->request = event_info;
+ async_data->request_free =
+ (TplLogManagerFreeFunc) tpl_log_manager_event_info_free;
+ async_data->cb = callback;
+ async_data->user_data = user_data;
+
+ simple = g_simple_async_result_new (G_OBJECT (manager),
+ _tpl_log_manager_async_operation_cb, async_data,
+ tpl_log_manager_get_events_for_date_async);
+
+ start_async_op_in_thread (account, simple, _get_events_for_date_async_thread);
+
+ g_object_unref (simple);
+}
+
+
+/**
+ * tpl_log_manager_get_events_for_date_finish:
+ * @self: a #TplLogManager
+ * @result: a #GAsyncResult
+ * @events: (out) (transfer full) (element-type TelepathyLogger1.Event): a
+ * pointer to a #GList used to return the list of #TplEvent
+ * @error: a #GError to fill
+ *
+ * Returns: #TRUE if the operation was successful, otherwise #FALSE
+ */
+gboolean
+tpl_log_manager_get_events_for_date_finish (TplLogManager *self,
+ GAsyncResult *result,
+ GList **events,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (TPL_IS_LOG_MANAGER (self), FALSE);
+ g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE);
+ g_return_val_if_fail (g_simple_async_result_is_valid (result,
+ G_OBJECT (self), tpl_log_manager_get_events_for_date_async), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return FALSE;
+
+ if (events != NULL)
+ *events = _take_list (g_simple_async_result_get_op_res_gpointer (simple));
+
+ return TRUE;
+}
+
+
+static void
+_get_filtered_events_async_thread (GSimpleAsyncResult *simple,
+ GObject *object,
+ GCancellable *cancellable)
+{
+ TplLogManagerAsyncData *async_data;
+ TplLogManagerEventInfo *event_info;
+ GList *lst;
+
+ async_data = g_async_result_get_user_data (G_ASYNC_RESULT (simple));
+ event_info = async_data->request;
+
+ lst = _tpl_log_manager_get_filtered_events (async_data->manager,
+ event_info->account, event_info->target,
+ event_info->type_mask, event_info->num_events,
+ event_info->filter, event_info->user_data);
+
+ g_simple_async_result_set_op_res_gpointer (simple, lst,
+ _list_of_object_free);
+}
+
+
+/**
+ * tpl_log_manager_get_filtered_events_async:
+ * @manager: a #TplLogManager
+ * @account: a #TpAccount
+ * @target: a non-NULL #TplEntity
+ * @type_mask: event type filter see #TplEventTypeMask
+ * @num_events: number of maximum events to fetch
+ * @filter: (scope call) (allow-none): an optional filter function
+ * @filter_user_data: user data to pass to @filter
+ * @callback: (scope async) (allow-none): a callback to call when
+ * the request is satisfied
+ * @user_data: data to pass to @callback
+ *
+ * Retrieve the most recent @num_event events exchanged with @target.
+ */
+void
+tpl_log_manager_get_filtered_events_async (TplLogManager *manager,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask,
+ guint num_events,
+ TplLogEventFilter filter,
+ gpointer filter_user_data,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TplLogManagerEventInfo *event_info = tpl_log_manager_event_info_new ();
+ TplLogManagerAsyncData *async_data = tpl_log_manager_async_data_new ();
+ GSimpleAsyncResult *simple;
+
+ g_return_if_fail (TPL_IS_LOG_MANAGER (manager));
+ g_return_if_fail (TP_IS_ACCOUNT (account));
+ g_return_if_fail (TPL_IS_ENTITY (target));
+ g_return_if_fail (num_events > 0);
+
+ event_info->account = g_object_ref (account);
+ event_info->target = g_object_ref (target);
+ event_info->type_mask = type_mask;
+ event_info->num_events = num_events;
+ event_info->filter = filter;
+ event_info->user_data = filter_user_data;
+
+ async_data->manager = g_object_ref (manager);
+ async_data->request = event_info;
+ async_data->request_free =
+ (TplLogManagerFreeFunc) tpl_log_manager_event_info_free;
+ async_data->cb = callback;
+ async_data->user_data = user_data;
+
+ simple = g_simple_async_result_new (G_OBJECT (manager),
+ _tpl_log_manager_async_operation_cb, async_data,
+ tpl_log_manager_get_filtered_events_async);
+
+ start_async_op_in_thread (account, simple, _get_filtered_events_async_thread);
+
+ g_object_unref (simple);
+}
+
+
+/**
+ * tpl_log_manager_get_filtered_events_finish:
+ * @self: a #TplLogManager
+ * @result: a #GAsyncResult
+ * @events: (out) (transfer full) (element-type TelepathyLogger1.Event):
+ * a pointer to a #GList used to return the list #TplEvent
+ * @error: a #GError to fill
+ *
+ * Returns: #TRUE if the operation was successful, otherwise #FALSE.
+ */
+gboolean
+tpl_log_manager_get_filtered_events_finish (TplLogManager *self,
+ GAsyncResult *result,
+ GList **events,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (TPL_IS_LOG_MANAGER (self), FALSE);
+ g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE);
+ g_return_val_if_fail (g_simple_async_result_is_valid (result,
+ G_OBJECT (self), tpl_log_manager_get_filtered_events_async), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return FALSE;
+
+ if (events != NULL)
+ *events = _take_list (g_simple_async_result_get_op_res_gpointer (simple));
+
+ return TRUE;
+}
+
+
+/**
+ * tpl_log_manager_walk_filtered_events:
+ * @manager: a #TplLogManager
+ * @account: a #TpAccount
+ * @target: a non-NULL #TplEntity
+ * @type_mask: event type filter see #TplEventTypeMask
+ * @filter: (scope call) (allow-none): an optional filter function
+ * @filter_data: user data to pass to @filter
+ *
+ * Create a #TplLogWalker to traverse all the events exchanged with @target.
+
+ * Returns: (transfer full): a #TplLogWalker
+ */
+TplLogWalker *
+tpl_log_manager_walk_filtered_events (TplLogManager *manager,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask,
+ TplLogEventFilter filter,
+ gpointer filter_data)
+{
+ TplLogManagerPriv *priv;
+ TplLogWalker *walker;
+ GList *l;
+
+ g_return_val_if_fail (TPL_IS_LOG_MANAGER (manager), NULL);
+ g_return_val_if_fail (TPL_IS_ENTITY (target), NULL);
+
+ priv = manager->priv;
+ walker = tpl_log_walker_new (filter, filter_data);
+
+ for (l = priv->readable_stores; l != NULL; l = g_list_next (l))
+ {
+ TplLogStore *store = TPL_LOG_STORE (l->data);
+ TplLogIter *iter;
+
+ iter = _tpl_log_store_create_iter (store, account, target, type_mask);
+ if (iter != NULL)
+ tpl_log_walker_add_iter (walker, iter);
+ }
+
+ return walker;
+}
+
+
+static void
+_get_entities_async_thread (GSimpleAsyncResult *simple,
+ GObject *object,
+ GCancellable *cancellable)
+{
+ TplLogManagerAsyncData *async_data;
+ TplLogManagerEventInfo *event_info;
+ GList *lst;
+
+ async_data = g_async_result_get_user_data (G_ASYNC_RESULT (simple));
+ event_info = async_data->request;
+
+ lst = _tpl_log_manager_get_entities (async_data->manager, event_info->account);
+
+ g_simple_async_result_set_op_res_gpointer (simple, lst,
+ _list_of_object_free);
+}
+
+
+/**
+ * tpl_log_manager_get_entities_async:
+ * @self: a #TplLogManager
+ * @account: a #TpAccount
+ * @callback: a callback to call when the request is satisfied
+ * @user_data: data to pass to @callback
+ *
+ * Start a query looking for all entities for which you have logs in the @account.
+ */
+void
+tpl_log_manager_get_entities_async (TplLogManager *self,
+ TpAccount *account,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TplLogManagerEventInfo *event_info = tpl_log_manager_event_info_new ();
+ TplLogManagerAsyncData *async_data = tpl_log_manager_async_data_new ();
+ GSimpleAsyncResult *simple;
+
+ g_return_if_fail (TPL_IS_LOG_MANAGER (self));
+ g_return_if_fail (TP_IS_ACCOUNT (account));
+
+ event_info->account = g_object_ref (account);
+
+ async_data->manager = g_object_ref (self);
+ async_data->request = event_info;
+ async_data->request_free =
+ (TplLogManagerFreeFunc) tpl_log_manager_event_info_free;
+ async_data->cb = callback;
+ async_data->user_data = user_data;
+
+ simple = g_simple_async_result_new (G_OBJECT (self),
+ _tpl_log_manager_async_operation_cb, async_data,
+ tpl_log_manager_get_entities_async);
+
+ start_async_op_in_thread (account, simple, _get_entities_async_thread);
+
+ g_object_unref (simple);
+}
+
+
+/**
+ * tpl_log_manager_get_entities_finish:
+ * @self: a #TplLogManager
+ * @result: a #GAsyncResult
+ * @entities: (out) (transfer full) (element-type TelepathyLogger1.Entity): a
+ * pointer to a #GList used to return the list of #TplEntity, to be freed
+ * using something like g_list_free_full (lst, g_object_unref)
+ * @error: a #GError to fill
+ *
+ * Returns: #TRUE if the operation was successful, otherwise #FALSE
+ */
+gboolean
+tpl_log_manager_get_entities_finish (TplLogManager *self,
+ GAsyncResult *result,
+ GList **entities,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (TPL_IS_LOG_MANAGER (self), FALSE);
+ g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE);
+ g_return_val_if_fail (g_simple_async_result_is_valid (result,
+ G_OBJECT (self), tpl_log_manager_get_entities_async), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return FALSE;
+
+ if (entities != NULL)
+ *entities = _take_list (g_simple_async_result_get_op_res_gpointer (simple));
+
+ return TRUE;
+}
+
+
+static void
+_search_async_thread (GSimpleAsyncResult *simple,
+ GObject *object,
+ GCancellable *cancellable)
+{
+ TplLogManagerAsyncData *async_data;
+ TplLogManagerEventInfo *event_info;
+ GList *lst;
+
+ async_data = g_async_result_get_user_data (G_ASYNC_RESULT (simple));
+ event_info = async_data->request;
+
+ lst = _tpl_log_manager_search (async_data->manager,
+ event_info->search_text, event_info->type_mask);
+
+ g_simple_async_result_set_op_res_gpointer (simple, lst,
+ (GDestroyNotify) tpl_log_manager_search_free);
+}
+
+
+/**
+ * tpl_log_manager_search_async:
+ * @manager: a #TplLogManager
+ * @text: the pattern to search
+ * @type_mask: event type filter see #TplEventTypeMask
+ * @callback: a callback to call when the request is satisfied
+ * @user_data: data to pass to @callback
+ *
+ * Search for all the conversations containing @text.
+ */
+void
+tpl_log_manager_search_async (TplLogManager *manager,
+ const gchar *text,
+ gint type_mask,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TplLogManagerEventInfo *event_info = tpl_log_manager_event_info_new ();
+ TplLogManagerAsyncData *async_data = tpl_log_manager_async_data_new ();
+ GSimpleAsyncResult *simple;
+
+ g_return_if_fail (TPL_IS_LOG_MANAGER (manager));
+
+ event_info->search_text = g_strdup (text);
+ event_info->type_mask = type_mask;
+
+ async_data->manager = g_object_ref (manager);
+ async_data->request = event_info;
+ async_data->request_free =
+ (TplLogManagerFreeFunc) tpl_log_manager_event_info_free;
+ async_data->cb = callback;
+ async_data->user_data = user_data;
+
+ simple = g_simple_async_result_new (G_OBJECT (manager),
+ _tpl_log_manager_async_operation_cb, async_data,
+ tpl_log_manager_search_async);
+
+ start_async_op_in_thread (NULL, simple, _search_async_thread);
+
+ g_object_unref (simple);
+}
+
+
+/**
+ * tpl_log_manager_search_finish:
+ * @self: a #TplLogManager
+ * @result: a #GAsyncResult
+ * @hits: (out) (transfer full) (element-type TelepathyLogger1.LogSearchHit): a
+ * pointer to a #GList used to return the list of #TplLogSearchHit
+ * @error: a #GError to fill
+ *
+ * Returns: #TRUE if the operation was successful, otherwise #FALSE
+ */
+gboolean
+tpl_log_manager_search_finish (TplLogManager *self,
+ GAsyncResult *result,
+ GList **hits,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (TPL_IS_LOG_MANAGER (self), FALSE);
+ g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE);
+ g_return_val_if_fail (g_simple_async_result_is_valid (result,
+ G_OBJECT (self), tpl_log_manager_search_async), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return FALSE;
+
+ if (hits != NULL)
+ *hits = _take_list (g_simple_async_result_get_op_res_gpointer (simple));
+ return TRUE;
+}
+
+
+/**
+ * tpl_log_manager_errors_quark:
+ *
+ * Returns: the #GQuark associated with the error domain of #TplLogManager
+ */
+GQuark
+tpl_log_manager_errors_quark (void)
+{
+ static gsize quark = 0;
+
+ if (g_once_init_enter (&quark))
+ {
+ GQuark domain = g_quark_from_static_string (
+ "tpl_log_manager_errors");
+
+ g_once_init_leave (&quark, domain);
+ }
+
+ return (GQuark) quark;
+}
+
+
+TplLogSearchHit *
+_tpl_log_manager_search_hit_copy (TplLogSearchHit *hit)
+{
+ return _tpl_log_manager_search_hit_new (hit->account, hit->target,
+ hit->date);
+}
+
+static gchar *
+_tpl_log_manager_build_identifier (TpAccount *account,
+ TplEntity *entity)
+{
+ gchar *identifier;
+ const gchar *acc_name = tp_proxy_get_object_path (account);
+ if (g_str_has_prefix (acc_name, TP_ACCOUNT_OBJECT_PATH_BASE))
+ acc_name += strlen (TP_ACCOUNT_OBJECT_PATH_BASE);
+
+ identifier = g_strconcat (acc_name, "/", tpl_entity_get_identifier (entity), NULL);
+
+ return identifier;
+}
+
+static gboolean
+_tpl_log_manager_is_disabled_for_entity (TplLogManager *self,
+ const gchar *identifier)
+{
+ gint i;
+ TplLogManagerPriv *priv = self->priv;
+ const gchar **ignorelist;
+
+ priv = self->priv;
+ ignorelist = _tpl_conf_get_ignorelist (priv->conf);
+
+ for (i = 0; ignorelist && ignorelist[i]; i++)
+ {
+ if (g_strcmp0 (ignorelist[i], identifier) == 0)
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/**
+ * tpl_log_manager_disable_for_entity:
+ * @self: the log manager
+ * @entity a TplEntity
+ *
+ * Disables logging of events for given entity. By default logging is enabled
+ * for all entities.
+ */
+void
+tpl_log_manager_disable_for_entity (TplLogManager *self,
+ TpAccount *account,
+ TplEntity *entity)
+{
+ TplLogManagerPriv *priv;
+ gchar *identifier;
+
+ g_return_if_fail (TPL_IS_LOG_MANAGER (self));
+ g_return_if_fail (TP_IS_ACCOUNT (account));
+ g_return_if_fail (TPL_IS_ENTITY (entity));
+
+ priv = self->priv;
+ identifier = _tpl_log_manager_build_identifier (account, entity);
+ if (!_tpl_log_manager_is_disabled_for_entity (self, identifier))
+ {
+ const gchar **ignorelist = _tpl_conf_get_ignorelist (priv->conf);
+ gchar **newlist;
+ if (ignorelist)
+ {
+ gint newlen;
+ newlist = g_strdupv ((gchar **) ignorelist);
+ newlen = g_strv_length (newlist) + 1;
+ newlist = g_realloc (newlist, sizeof (gchar *) * newlen );
+ newlist[newlen - 1] = g_strdup (identifier);
+ }
+ else
+ {
+ newlist = g_malloc0_n (2, sizeof (gchar *));
+ newlist[0] = g_strdup (identifier);
+ }
+
+ _tpl_conf_set_ignorelist (priv->conf, (const gchar **) newlist);
+ g_strfreev (newlist);
+ }
+
+ g_free (identifier);
+}
+
+/**
+ * tpl_log_manager_enable_for_entity:
+ * @self: the log manager
+ * @entity: a TplEntity
+ *
+ * Re-enables logging of events for entity previously disabled by
+ * tpl_log_manager_disable_for_entity(). By default logging is enabled for all
+ * entities.
+ */
+void
+tpl_log_manager_enable_for_entity (TplLogManager *self,
+ TpAccount *account,
+ TplEntity *entity)
+{
+ TplLogManagerPriv *priv;
+ gchar *identifier;
+
+ g_return_if_fail (TPL_IS_LOG_MANAGER (self));
+ g_return_if_fail (TP_IS_ACCOUNT (account));
+ g_return_if_fail (TPL_IS_ENTITY (entity));
+
+ priv = self->priv;
+ identifier = _tpl_log_manager_build_identifier (account, entity);
+ if (_tpl_log_manager_is_disabled_for_entity (self, identifier))
+ {
+ gint i, j;
+ const gchar **ignorelist = _tpl_conf_get_ignorelist (priv->conf);
+ gchar **newlist;
+
+ if (!ignorelist)
+ return;
+
+ newlist = g_malloc0_n (g_strv_length ((gchar **) ignorelist) - 1,
+ sizeof (gchar *));
+ j = 0;
+ for (i = 0; ignorelist && ignorelist[i]; i++)
+ {
+ if (g_strcmp0 (ignorelist[i], identifier) != 0)
+ {
+ newlist[j] = g_strdup (ignorelist[i]);
+ j++;
+ }
+ }
+
+ _tpl_conf_set_ignorelist (priv->conf, (const gchar **) newlist);
+ g_strfreev (newlist);
+ }
+
+ g_free (identifier);
+}
+
+/**
+ * tpl_log_manager_is_disabled_for_entity:
+ * @self: the log manager
+ * @entity: a TplEntity
+ *
+ * Checks, whether logging is disabled for given entity. By default, logging
+ * is enabled for all entities.
+ *
+ * Returns: %TRUE if logging for the entity has been disabled, %FALSE otherwise.
+ */
+gboolean
+tpl_log_manager_is_disabled_for_entity (TplLogManager *self,
+ TpAccount *account,
+ TplEntity *entity)
+{
+ gboolean is_disabled;
+ gchar *identifier;
+
+ g_return_val_if_fail (TPL_IS_LOG_MANAGER (self), FALSE);
+ g_return_val_if_fail (TP_IS_ACCOUNT (account), FALSE);
+ g_return_val_if_fail (TPL_IS_ENTITY (entity), FALSE);
+
+ identifier = _tpl_log_manager_build_identifier (account, entity);
+ is_disabled = _tpl_log_manager_is_disabled_for_entity (self, identifier);
+ g_free (identifier);
+
+ return is_disabled;
+}
diff --git a/telepathy-logger/log-manager.h b/telepathy-logger/log-manager.h
new file mode 100644
index 000000000..df8c0bc6f
--- /dev/null
+++ b/telepathy-logger/log-manager.h
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2003-2007 Imendio AB
+ * Copyright (C) 2007-2011 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#ifndef __TPL_LOG_MANAGER_H__
+#define __TPL_LOG_MANAGER_H__
+
+#include <gio/gio.h>
+#include <glib-object.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+#include <telepathy-logger/event.h>
+#include <telepathy-logger/log-walker.h>
+
+G_BEGIN_DECLS
+#define TPL_TYPE_LOG_MANAGER (tpl_log_manager_get_type ())
+#define TPL_LOG_MANAGER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TPL_TYPE_LOG_MANAGER, TplLogManager))
+#define TPL_LOG_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), TPL_TYPE_LOG_MANAGER, TplLogManagerClass))
+#define TPL_IS_LOG_MANAGER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TPL_TYPE_LOG_MANAGER))
+#define TPL_IS_LOG_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), TPL_TYPE_LOG_MANAGER))
+#define TPL_LOG_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TPL_TYPE_LOG_MANAGER, TplLogManagerClass))
+
+#define TPL_LOG_MANAGER_ERROR tpl_log_manager_errors_quark()
+
+GQuark tpl_log_manager_errors_quark (void);
+
+/**
+ * TplLogManagerError:
+ * @TPL_LOG_MANAGER_ERROR_ADD_EVENT: Error return when adding logs fails
+ */
+typedef enum
+{
+ TPL_LOG_MANAGER_ERROR_ADD_EVENT
+} TplLogManagerError;
+
+typedef struct _TplLogManager TplLogManager;
+
+struct _TplLogManager
+{
+ GObject parent;
+
+ gpointer priv;
+};
+
+typedef struct
+{
+ GObjectClass parent_class;
+} TplLogManagerClass;
+
+/**
+ * TplEventTypeMask:
+ * @TPL_EVENT_MASK_TEXT: Mask to #TplTextEvent
+ * @TPL_EVENT_MASK_CALL: Mask to #TplCallEvent
+ * @TPL_EVENT_MASK_ANY: Special value to select all type of #TplEvent
+ *
+ * Mask used to filter type of #TplEvent returned.
+ */
+typedef enum
+{
+ TPL_EVENT_MASK_TEXT = 1 << 0,
+ TPL_EVENT_MASK_CALL = 1 << 1,
+ TPL_EVENT_MASK_ANY = 0xffff
+} TplEventTypeMask;
+
+/**
+ * TplLogSearchHit:
+ * @account: the #TpAccount
+ * @target: the #TplEntity
+ * @date: the #GDate
+ *
+ * Represent the context where the search has results.
+ */
+typedef struct _TplLogSearchHit TplLogSearchHit;
+struct _TplLogSearchHit
+{
+ TpAccount *account;
+ TplEntity *target;
+ GDate *date;
+};
+
+typedef gboolean (*TplLogEventFilter) (TplEvent *event,
+ gpointer user_data);
+
+GType tpl_log_manager_get_type (void);
+
+TplLogManager *tpl_log_manager_dup_singleton (void);
+
+gboolean tpl_log_manager_exists (TplLogManager *manager,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask);
+
+void tpl_log_manager_get_dates_async (TplLogManager *manager,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean tpl_log_manager_get_dates_finish (TplLogManager *self,
+ GAsyncResult *result,
+ GList **dates,
+ GError **error);
+
+void tpl_log_manager_get_events_for_date_async (TplLogManager *manager,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask,
+ const GDate *date,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean tpl_log_manager_get_events_for_date_finish (TplLogManager *self,
+ GAsyncResult *result,
+ GList **events,
+ GError **error);
+
+void tpl_log_manager_get_filtered_events_async (TplLogManager *manager,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask,
+ guint num_events,
+ TplLogEventFilter filter,
+ gpointer filter_user_data,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean tpl_log_manager_get_filtered_events_finish (TplLogManager *self,
+ GAsyncResult *result,
+ GList **events,
+ GError **error);
+
+TplLogWalker *tpl_log_manager_walk_filtered_events (TplLogManager *manager,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask,
+ TplLogEventFilter filter,
+ gpointer filter_data);
+
+void tpl_log_manager_get_entities_async (TplLogManager *self,
+ TpAccount *account,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean tpl_log_manager_get_entities_finish (TplLogManager *self,
+ GAsyncResult *result,
+ GList **entities,
+ GError **error);
+
+void tpl_log_manager_search_async (TplLogManager *manager,
+ const gchar *text,
+ gint type_mask,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean tpl_log_manager_search_finish (TplLogManager *self,
+ GAsyncResult *result,
+ GList **hits,
+ GError **error);
+
+void tpl_log_manager_disable_for_entity (TplLogManager *self,
+ TpAccount *account,
+ TplEntity *entity);
+
+void tpl_log_manager_enable_for_entity (TplLogManager *self,
+ TpAccount *account,
+ TplEntity *entity);
+
+gboolean tpl_log_manager_is_disabled_for_entity (TplLogManager *self,
+ TpAccount *account,
+ TplEntity *entity);
+
+void tpl_log_manager_search_free (GList *hits);
+
+G_END_DECLS
+#endif /* __TPL_LOG_MANAGER_H__ */
diff --git a/telepathy-logger/log-store-empathy-internal.h b/telepathy-logger/log-store-empathy-internal.h
new file mode 100644
index 000000000..e4645bdd7
--- /dev/null
+++ b/telepathy-logger/log-store-empathy-internal.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright © 2013 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef TPL_LOG_STORE_EMPATHY_H
+#define TPL_LOG_STORE_EMPATHY_H
+
+#include "log-store-xml-internal.h"
+
+typedef struct _TplLogStoreEmpathy TplLogStoreEmpathy;
+typedef struct _TplLogStoreEmpathyClass TplLogStoreEmpathyClass;
+
+struct _TplLogStoreEmpathyClass {
+ /*< private >*/
+ TplLogStoreXmlClass parent_class;
+};
+
+struct _TplLogStoreEmpathy {
+ TplLogStoreXml parent;
+};
+
+GType _tpl_log_store_empathy_get_type (void);
+
+/* TYPE MACROS */
+#define TPL_TYPE_LOG_STORE_EMPATHY \
+ (_tpl_log_store_empathy_get_type ())
+#define TPL_LOG_STORE_EMPATHY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), TPL_TYPE_LOG_STORE_EMPATHY, TplLogStoreEmpathy))
+#define TPL_LOG_STORE_EMPATHY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), TPL_TYPE_LOG_STORE_EMPATHY,\
+ TplLogStoreEmpathyClass))
+#define TPL_IS_LOG_STORE_EMPATHY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TPL_TYPE_LOG_STORE_EMPATHY))
+#define TPL_IS_LOG_STORE_EMPATHY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TPL_TYPE_LOG_STORE_EMPATHY))
+#define TPL_LOG_STORE_EMPATHY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TPL_TYPE_LOG_STORE_EMPATHY, \
+ TplLogStoreEmpathyClass))
+
+#endif /* TPL_LOG_STORE_EMPATHY_H */
diff --git a/telepathy-logger/log-store-empathy.c b/telepathy-logger/log-store-empathy.c
new file mode 100644
index 000000000..651c72192
--- /dev/null
+++ b/telepathy-logger/log-store-empathy.c
@@ -0,0 +1,67 @@
+/*
+ * Copyright ©2013 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * This is a subclass of TplLogStoreXml to read logs from the directory Empathy
+ * used to store them it. It disables writing to that legacy location.
+ */
+
+#include "config.h"
+#include "log-store-empathy-internal.h"
+
+#include "telepathy-logger/log-store-internal.h"
+
+static void log_store_iface_init (gpointer g_iface, gpointer iface_data);
+
+G_DEFINE_TYPE_WITH_CODE (TplLogStoreEmpathy, _tpl_log_store_empathy,
+ TPL_TYPE_LOG_STORE_XML,
+ G_IMPLEMENT_INTERFACE (TPL_TYPE_LOG_STORE, log_store_iface_init))
+
+static void
+_tpl_log_store_empathy_init (TplLogStoreEmpathy *self)
+{
+}
+
+static void
+_tpl_log_store_empathy_class_init (TplLogStoreEmpathyClass *klass)
+{
+}
+
+
+static const gchar *
+log_store_empathy_get_name (TplLogStore *store)
+{
+ TplLogStoreXml *self = (TplLogStoreXml *) store;
+
+ g_return_val_if_fail (TPL_IS_LOG_STORE_EMPATHY (self), NULL);
+
+ return "Empathy";
+}
+
+static void
+log_store_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ TplLogStoreInterface *iface = (TplLogStoreInterface *) g_iface;
+
+ iface->get_name = log_store_empathy_get_name;
+
+ /* We don't want to store new logs in Empathy's directory, just read the old
+ * ones. */
+ iface->add_event = NULL;
+}
diff --git a/telepathy-logger/log-store-factory-internal.h b/telepathy-logger/log-store-factory-internal.h
new file mode 100644
index 000000000..84708f36f
--- /dev/null
+++ b/telepathy-logger/log-store-factory-internal.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2009 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ */
+
+#ifndef __TPL_LOG_STORE_FACTORY_H__
+#define __TPL_LOG_STORE_FACTORY_H__
+
+#include <glib-object.h>
+
+#include <telepathy-logger/log-store-internal.h>
+
+typedef TplLogStore* (*TplLogStoreConstructor) (const gchar *name,
+ gboolean write_access, gboolean read_access);
+typedef TplLogStore* (*TplLogStoreFactory) (const gchar *logstore_type,
+ const gchar *name, gboolean write_access, gboolean read_access);
+
+void _tpl_log_store_factory_init (void);
+void _tpl_log_store_factory_deinit (void);
+void _tpl_log_store_factory_add (const gchar *logstore_type,
+ TplLogStoreConstructor constructor);
+TplLogStoreConstructor _tpl_log_store_factory_lookup (const gchar *logstore_type);
+TplLogStore * _tpl_log_store_factory_build (const gchar *logstore_type,
+ const gchar *name, gboolean write_access, gboolean read_access);
+
+#endif /* __TPL_LOG_STORE_FACTORY_H__ */
diff --git a/telepathy-logger/log-store-factory.c b/telepathy-logger/log-store-factory.c
new file mode 100644
index 000000000..894677a00
--- /dev/null
+++ b/telepathy-logger/log-store-factory.c
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2009 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ */
+
+#include "config.h"
+#include "log-store-factory-internal.h"
+
+#define DEBUG_FLAG TPL_DEBUG_LOG_STORE
+#include <telepathy-logger/debug-internal.h>
+#include <telepathy-logger/util-internal.h>
+
+static GHashTable *logstores_table = NULL;
+
+void
+_tpl_log_store_factory_init (void)
+{
+ g_return_if_fail (logstores_table == NULL);
+
+ logstores_table = g_hash_table_new_full (g_str_hash,
+ (GEqualFunc) g_str_equal, g_free, NULL);
+}
+
+
+void
+_tpl_log_store_factory_add (const gchar *logstore_type,
+ TplLogStoreConstructor constructor)
+{
+ gchar *key;
+
+ g_return_if_fail (!TPL_STR_EMPTY (logstore_type));
+ g_return_if_fail (constructor != NULL);
+ g_return_if_fail (logstores_table != NULL);
+
+ key = g_strdup (logstore_type);
+
+ if (g_hash_table_lookup (logstores_table, logstore_type) != NULL)
+ {
+ g_warning ("Type %s already mapped. replacing constructor.",
+ logstore_type);
+ g_hash_table_replace (logstores_table, key, constructor);
+ }
+ else
+ g_hash_table_insert (logstores_table, key, constructor);
+}
+
+
+TplLogStoreConstructor
+_tpl_log_store_factory_lookup (const gchar *logstore_type)
+{
+ g_return_val_if_fail (!TPL_STR_EMPTY (logstore_type), NULL);
+ g_return_val_if_fail (logstores_table != NULL, NULL);
+
+ return g_hash_table_lookup (logstores_table, logstore_type);
+}
+
+void
+_tpl_log_store_factory_deinit (void)
+{
+ g_return_if_fail (logstores_table != NULL);
+
+ g_hash_table_unref (logstores_table);
+ logstores_table = NULL;
+}
+
+TplLogStore *
+_tpl_log_store_factory_build (const gchar *logstore_type,
+ const gchar *name,
+ gboolean write_access,
+ gboolean read_access)
+{
+ TplLogStoreConstructor constructor;
+
+ g_return_val_if_fail (logstores_table != NULL, NULL);
+
+ constructor = _tpl_log_store_factory_lookup (logstore_type);
+ if (constructor == NULL)
+ {
+ DEBUG ("%s: log store type not handled by this logger", logstore_type);
+ return NULL;
+ }
+
+ return constructor (name, write_access, read_access);
+}
diff --git a/telepathy-logger/log-store-internal.h b/telepathy-logger/log-store-internal.h
new file mode 100644
index 000000000..9b0a66695
--- /dev/null
+++ b/telepathy-logger/log-store-internal.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2008-2011 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Jonny Lamb <jonny.lamb@collabora.co.uk>
+ * Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ */
+
+#ifndef __TPL_LOG_STORE_H__
+#define __TPL_LOG_STORE_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+#include <telepathy-logger/event.h>
+#include <telepathy-logger/log-iter-internal.h>
+#include <telepathy-logger/log-manager.h>
+
+G_BEGIN_DECLS
+
+#define TPL_TYPE_LOG_STORE (_tpl_log_store_get_type ())
+#define TPL_LOG_STORE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ TPL_TYPE_LOG_STORE, TplLogStore))
+#define TPL_IS_LOG_STORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ TPL_TYPE_LOG_STORE))
+#define TPL_LOG_STORE_GET_INTERFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ( \
+ (inst), TPL_TYPE_LOG_STORE, TplLogStoreInterface))
+
+#define TPL_LOG_STORE_ERROR g_quark_from_static_string ("tpl-log-store-error-quark")
+typedef enum
+{
+ /* generic error */
+ TPL_LOG_STORE_ERROR_FAILED,
+ /* generic failure for add_event() method, when nothing else applies */
+ TPL_LOG_STORE_ERROR_ADD_EVENT,
+ /* data is already present in the LogStore */
+ TPL_LOG_STORE_ERROR_PRESENT,
+ /* data is not present in the LogStore */
+ TPL_LOG_STORE_ERROR_NOT_PRESENT,
+ /* to be used in TplLogStoreIndexError as first value, so that value won't
+ * overlap */
+ TPL_LOG_STORE_ERROR_LAST
+} TplLogStoreError;
+
+typedef struct _TplLogStore TplLogStore; /*dummy object */
+
+typedef struct
+{
+ GTypeInterface parent;
+
+ const gchar * (*get_name) (TplLogStore *self);
+ gboolean (*exists) (TplLogStore *self, TpAccount *account,
+ TplEntity *target, gint type_mask);
+ gboolean (*add_event) (TplLogStore *self, TplEvent *event,
+ GError **error);
+ GList * (*get_dates) (TplLogStore *self, TpAccount *account,
+ TplEntity *target, gint type_mask);
+ GList * (*get_events_for_date) (TplLogStore *self, TpAccount *account,
+ TplEntity *target, gint type_mask, const GDate *date);
+ GList * (*get_recent_events) (TplLogStore *self, TpAccount *account,
+ TplEntity *target, gint type_mask);
+ GList * (*get_entities) (TplLogStore *self, TpAccount *account);
+ GList * (*search_new) (TplLogStore *self, const gchar *text, gint type_mask);
+ GList * (*get_filtered_events) (TplLogStore *self, TpAccount *account,
+ TplEntity *target, gint type_mask, guint num_events,
+ TplLogEventFilter filter, gpointer user_data);
+ void (*clear) (TplLogStore *self);
+ void (*clear_account) (TplLogStore *self, TpAccount *account);
+ void (*clear_entity) (TplLogStore *self, TpAccount *account,
+ TplEntity *entity);
+ TplLogIter * (*create_iter) (TplLogStore *self, TpAccount *account,
+ TplEntity *target, gint type_mask);
+} TplLogStoreInterface;
+
+GType _tpl_log_store_get_type (void);
+
+const gchar * _tpl_log_store_get_name (TplLogStore *self);
+gboolean _tpl_log_store_exists (TplLogStore *self, TpAccount *account,
+ TplEntity *target, gint type_mask);
+gboolean _tpl_log_store_add_event (TplLogStore *self, TplEvent *event,
+ GError **error);
+GList * _tpl_log_store_get_dates (TplLogStore *self, TpAccount *account,
+ TplEntity *target, gint type_mask);
+GList * _tpl_log_store_get_events_for_date (TplLogStore *self,
+ TpAccount *account, TplEntity *target, gint type_mask, const GDate *date);
+GList * _tpl_log_store_get_recent_events (TplLogStore *self,
+ TpAccount *account, TplEntity *target, gint type_mask);
+GList * _tpl_log_store_get_entities (TplLogStore *self, TpAccount *account);
+GList * _tpl_log_store_search_new (TplLogStore *self, const gchar *text,
+ gint type_mask);
+GList * _tpl_log_store_get_filtered_events (TplLogStore *self,
+ TpAccount *account, TplEntity *target, gint type_mask, guint num_events,
+ TplLogEventFilter filter, gpointer user_data);
+void _tpl_log_store_clear (TplLogStore *self);
+void _tpl_log_store_clear_account (TplLogStore *self, TpAccount *account);
+void _tpl_log_store_clear_entity (TplLogStore *self, TpAccount *account,
+ TplEntity *entity);
+TplLogIter * _tpl_log_store_create_iter (TplLogStore *self,
+ TpAccount *account, TplEntity *target, gint type_mask);
+gboolean _tpl_log_store_is_writable (TplLogStore *self);
+gboolean _tpl_log_store_is_readable (TplLogStore *self);
+
+G_END_DECLS
+
+#endif /*__TPL_LOG_STORE_H__ */
diff --git a/telepathy-logger/log-store-pidgin-internal.h b/telepathy-logger/log-store-pidgin-internal.h
new file mode 100644
index 000000000..abe214e50
--- /dev/null
+++ b/telepathy-logger/log-store-pidgin-internal.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2008-2011 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Jonny Lamb <jonny.lamb@collabora.co.uk>
+ * Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ */
+
+#ifndef __TPL_LOG_STORE_PIDGIN_H__
+#define __TPL_LOG_STORE_PIDGIN_H__
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define TPL_TYPE_LOG_STORE_PIDGIN \
+ (tpl_log_store_pidgin_get_type ())
+#define TPL_LOG_STORE_PIDGIN(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TPL_TYPE_LOG_STORE_PIDGIN, \
+ TplLogStorePidgin))
+#define TPL_LOG_STORE_PIDGIN_CLASS(vtable) \
+ (G_TYPE_CHECK_CLASS_CAST ((vtable), TPL_TYPE_LOG_STORE_PIDGIN, \
+ TplLogStorePidginClass))
+#define TPL_IS_LOG_STORE_PIDGIN(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TPL_TYPE_LOG_STORE_PIDGIN))
+#define TPL_IS_LOG_STORE_PIDGIN_CLASS(vtable) \
+ (G_TYPE_CHECK_CLASS_TYPE ((vtable), TPL_TYPE_LOG_STORE_PIDGIN))
+#define TPL_LOG_STORE_PIDGIN_GET_CLASS(inst) \
+ (G_TYPE_INSTANCE_GET_CLASS ((inst), TPL_TYPE_LOG_STORE_PIDGIN, \
+ TplLogStorePidginClass))
+
+typedef struct _TplLogStorePidginPriv TplLogStorePidginPriv;
+
+typedef struct
+{
+ GObject parent;
+ TplLogStorePidginPriv *priv;
+} TplLogStorePidgin;
+
+typedef struct
+{
+ GObjectClass parent;
+} TplLogStorePidginClass;
+
+GType tpl_log_store_pidgin_get_type (void);
+
+G_END_DECLS
+
+#endif /* __TPL_LOG_STORE_PIDGIN_H__ */
diff --git a/telepathy-logger/log-store-pidgin.c b/telepathy-logger/log-store-pidgin.c
new file mode 100644
index 000000000..c2c12cff4
--- /dev/null
+++ b/telepathy-logger/log-store-pidgin.c
@@ -0,0 +1,1161 @@
+/*
+ * Copyright (C) 2008-2011 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Jonny Lamb <jonny.lamb@collabora.co.uk>
+ * Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ */
+
+#include <config.h>
+
+#define _XOPEN_SOURCE
+#include <time.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "log-iter-pidgin-internal.h"
+#include "log-store-internal.h"
+#include "log-store-pidgin-internal.h"
+#include "log-manager-internal.h"
+#include "text-event-internal.h"
+#include "entity-internal.h"
+#include "util-internal.h"
+
+#define DEBUG_FLAG TPL_DEBUG_LOG_STORE
+#include "debug-internal.h"
+
+#define TPL_LOG_STORE_PIDGIN_NAME "Pidgin"
+
+#define TXT_LOG_FILENAME_SUFFIX ".txt"
+#define HTML_LOG_FILENAME_SUFFIX ".html"
+
+struct _TplLogStorePidginPriv
+{
+ gboolean test_mode;
+ TpAccountManager *account_manager;
+
+ gchar *basedir;
+};
+
+enum {
+ PROP_0,
+ PROP_READABLE,
+ PROP_BASEDIR,
+ PROP_TESTMODE,
+};
+
+
+
+static void log_store_iface_init (gpointer g_iface, gpointer iface_data);
+static void tpl_log_store_pidgin_get_property (GObject *object, guint param_id, GValue *value,
+ GParamSpec *pspec);
+static void tpl_log_store_pidgin_set_property (GObject *object, guint param_id, const GValue *value,
+ GParamSpec *pspec);
+static const gchar *log_store_pidgin_get_name (TplLogStore *store);
+static const gchar *log_store_pidgin_get_basedir (TplLogStorePidgin *self);
+static void log_store_pidgin_set_basedir (TplLogStorePidgin *self,
+ const gchar *data);
+
+
+G_DEFINE_TYPE_WITH_CODE (TplLogStorePidgin, tpl_log_store_pidgin,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TPL_TYPE_LOG_STORE, log_store_iface_init));
+
+static void
+tpl_log_store_pidgin_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TplLogStorePidginPriv *priv = TPL_LOG_STORE_PIDGIN (object)->priv;
+
+ switch (param_id)
+ {
+ case PROP_READABLE:
+ g_value_set_boolean (value, TRUE);
+ break;
+ case PROP_BASEDIR:
+ g_value_set_string (value, priv->basedir);
+ break;
+ case PROP_TESTMODE:
+ g_value_set_boolean (value, priv->test_mode);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+
+static void
+tpl_log_store_pidgin_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TplLogStorePidgin *self = TPL_LOG_STORE_PIDGIN (object);
+
+ switch (param_id)
+ {
+ case PROP_BASEDIR:
+ log_store_pidgin_set_basedir (self, g_value_get_string (value));
+ break;
+ case PROP_TESTMODE:
+ self->priv->test_mode = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+
+static void
+tpl_log_store_pidgin_dispose (GObject *self)
+{
+ TplLogStorePidginPriv *priv = TPL_LOG_STORE_PIDGIN (self)->priv;
+
+ g_clear_object (&priv->account_manager);
+ g_free (priv->basedir);
+ priv->basedir = NULL;
+
+ G_OBJECT_CLASS (tpl_log_store_pidgin_parent_class)->dispose (self);
+}
+
+
+static void
+tpl_log_store_pidgin_class_init (TplLogStorePidginClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GParamSpec *param_spec;
+
+ object_class->get_property = tpl_log_store_pidgin_get_property;
+ object_class->set_property = tpl_log_store_pidgin_set_property;
+ object_class->dispose = tpl_log_store_pidgin_dispose;
+
+ g_object_class_override_property (object_class, PROP_READABLE, "readable");
+
+ /**
+ * TplLogStorePidgin:basedir:
+ *
+ * The log store's basedir.
+ */
+ param_spec = g_param_spec_string ("basedir",
+ "Basedir",
+ "The directory where the LogStore will look for data",
+ NULL, G_PARAM_READABLE | G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_BASEDIR, param_spec);
+
+
+ param_spec = g_param_spec_boolean ("testmode",
+ "TestMode",
+ "Whether the logstore is in testmode, for testsuite use only",
+ FALSE, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TESTMODE, param_spec);
+
+
+ g_type_class_add_private (object_class, sizeof (TplLogStorePidginPriv));
+}
+
+
+static void
+tpl_log_store_pidgin_init (TplLogStorePidgin *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TPL_TYPE_LOG_STORE_PIDGIN, TplLogStorePidginPriv);
+
+ self->priv->account_manager = tp_account_manager_dup ();
+}
+
+
+static const gchar *
+log_store_pidgin_get_name (TplLogStore *store)
+{
+ TplLogStorePidgin *self = (TplLogStorePidgin *) store;
+
+ g_return_val_if_fail (TPL_IS_LOG_STORE_PIDGIN (self), NULL);
+
+ return TPL_LOG_STORE_PIDGIN_NAME;
+}
+
+
+/* returns an absolute path for the base directory of LogStore */
+static const gchar *
+log_store_pidgin_get_basedir (TplLogStorePidgin *self)
+{
+ g_return_val_if_fail (TPL_IS_LOG_STORE_PIDGIN (self), NULL);
+
+ /* If basedir isn't yet set (defaults to NULL), use the libpurple default
+ * location, useful for testing logstore with a different basedir */
+ if (self->priv->basedir == NULL)
+ {
+ gchar *dir;
+
+ if (self->priv->test_mode && g_getenv ("TPL_TEST_LOG_DIR") != NULL)
+ dir = g_build_path (G_DIR_SEPARATOR_S, g_getenv ("TPL_TEST_LOG_DIR"),
+ "purple", NULL);
+ else
+ dir = g_build_path (G_DIR_SEPARATOR_S, g_get_home_dir (), ".purple",
+ "logs", NULL);
+ log_store_pidgin_set_basedir (self, dir);
+
+ g_free (dir);
+ }
+
+ return self->priv->basedir;
+}
+
+
+static void
+log_store_pidgin_set_basedir (TplLogStorePidgin *self,
+ const gchar *data)
+{
+ g_return_if_fail (TPL_IS_LOG_STORE_PIDGIN (self));
+ g_return_if_fail (self->priv->basedir == NULL);
+ /* data may be NULL when the class is initialized and the default value is
+ * set */
+
+ self->priv->basedir = g_strdup (data);
+
+ /* at install_spec time, default value is set to NULL, ignore it */
+ if (self->priv->basedir != NULL)
+ DEBUG ("logstore set to dir: %s", data);
+}
+
+
+/* internal: get the full name of the storing directory, including protocol
+ * and id */
+static gchar *
+log_store_pidgin_get_dir (TplLogStore *self,
+ TpAccount *account,
+ TplEntity *target)
+{
+ const gchar *protocol;
+ gchar *basedir;
+ gchar *username, *normalized, *tmp;
+ gchar *id = NULL; /* if not NULL, it contains a modified version of
+ target id, to be g_free'd */
+ const GHashTable *params;
+
+ params = tp_account_get_parameters (account);
+ protocol = tp_account_get_protocol_name (account);
+
+ if (tp_strdiff (protocol, "irc") == 0)
+ {
+ const gchar *account_param, *server;
+
+ account_param = tp_asv_get_string (params, "account");
+ server = tp_asv_get_string (params, "server");
+
+ username = g_strdup_printf ("%s@%s", account_param, server);
+ }
+ else
+ {
+ username = g_strdup (tp_asv_get_string (params, "account"));
+ }
+
+ if (username == NULL)
+ {
+ DEBUG ("Failed to get account");
+ return NULL;
+ }
+
+ normalized = g_utf8_normalize (username, -1, G_NORMALIZE_DEFAULT);
+ g_free (username);
+
+ if (target != NULL)
+ {
+ const gchar *orig_id = tpl_entity_get_identifier (target);
+
+ if (tpl_entity_get_entity_type (target) == TPL_ENTITY_ROOM)
+ id = g_strdup_printf ("%s.chat", orig_id);
+ else if (g_str_has_suffix (orig_id, "#1"))
+ /* Small butterfly workaround */
+ id = g_strndup (orig_id, strlen (orig_id) - 2);
+ else
+ id = g_strdup (orig_id);
+ }
+
+ tmp = g_uri_escape_string (normalized, "#@", TRUE);
+ g_free (normalized);
+ normalized = tmp; /* now normalized and escaped */
+
+ /* purple basedir + protocol name + account name + recipient id */
+ basedir = g_build_path (G_DIR_SEPARATOR_S,
+ log_store_pidgin_get_basedir (TPL_LOG_STORE_PIDGIN (self)),
+ protocol,
+ normalized,
+ id,
+ NULL);
+
+ g_free (id);
+ g_free (normalized);
+
+ return basedir;
+}
+
+
+/* public: returns whether some data for @id exist in @account */
+static gboolean
+log_store_pidgin_exists (TplLogStore *self,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask)
+{
+ gchar *dir;
+ gboolean exists;
+
+ if (!(type_mask & TPL_EVENT_MASK_TEXT))
+ return FALSE;
+
+ dir = log_store_pidgin_get_dir (self, account, target);
+
+ if (dir != NULL)
+ exists = g_file_test (dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR);
+ else
+ exists = FALSE;
+
+ g_free (dir);
+
+ return exists;
+}
+
+
+/* internal */
+static GDate *
+log_store_pidgin_get_time (const gchar *filename)
+{
+ gchar *date;
+ GDate *retval = NULL;
+ const gchar *p;
+
+ gint year;
+ gint month;
+ gint day;
+
+ if (filename == NULL)
+ return NULL;
+
+ if (g_str_has_suffix (filename, TXT_LOG_FILENAME_SUFFIX))
+ {
+ p = strstr (filename, TXT_LOG_FILENAME_SUFFIX);
+ date = g_strndup (filename, p - filename);
+ }
+ else if (g_str_has_suffix (filename, HTML_LOG_FILENAME_SUFFIX))
+ {
+ p = strstr (filename, HTML_LOG_FILENAME_SUFFIX);
+ date = g_strndup (filename, p - filename);
+ }
+ else
+ {
+ date = g_strdup (filename);
+ }
+
+ sscanf (date, "%4d-%2d-%2d.*s", &year, &month, &day);
+
+ DEBUG ("date is %s", date);
+ retval = g_date_new_dmy (day, month, year);
+ g_free (date);
+
+ return retval;
+}
+
+
+static GList *
+log_store_pidgin_get_dates (TplLogStore *self,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask)
+{
+ GList *dates = NULL;
+ gchar *directory;
+ GDir *dir;
+ const gchar *filename;
+
+ g_return_val_if_fail (TPL_IS_LOG_STORE_PIDGIN (self), NULL);
+ g_return_val_if_fail (TP_IS_ACCOUNT (account), NULL);
+ g_return_val_if_fail (TPL_IS_ENTITY (target), NULL);
+
+ if (!(type_mask & TPL_EVENT_MASK_TEXT))
+ return NULL;
+
+ directory = log_store_pidgin_get_dir (self, account, target);
+
+ if (directory == NULL)
+ return NULL;
+
+ dir = g_dir_open (directory, 0, NULL);
+ if (dir == NULL)
+ {
+ DEBUG ("Could not open directory:'%s'", directory);
+ g_free (directory);
+ return NULL;
+ }
+
+ DEBUG ("Collating a list of dates in: '%s'", directory);
+
+ while ((filename = g_dir_read_name (dir)) != NULL)
+ {
+ GDate *date;
+
+ if (!g_str_has_suffix (filename, TXT_LOG_FILENAME_SUFFIX)
+ && !g_str_has_suffix (filename, HTML_LOG_FILENAME_SUFFIX))
+ continue;
+ DEBUG ("%s: %s %s\n", G_STRFUNC, directory, filename);
+
+ date = log_store_pidgin_get_time (filename);
+ dates = g_list_insert_sorted (dates, date, (GCompareFunc) g_date_compare);
+ }
+
+ g_free (directory);
+ g_dir_close (dir);
+
+ DEBUG ("Parsed %d dates", g_list_length (dates));
+
+ return dates;
+}
+
+
+static GList *
+log_store_pidgin_get_filenames_for_date (TplLogStore *self,
+ TpAccount *account,
+ TplEntity *target,
+ const GDate *date)
+{
+ gchar *basedir;
+ gchar timestamp[11];
+ GList *filenames = NULL;
+ GDir *dir;
+ const gchar *dirfile;
+
+ basedir = log_store_pidgin_get_dir (self, account, target);
+
+ if (basedir == NULL)
+ return NULL;
+
+ dir = g_dir_open (basedir, 0, NULL);
+ if (dir == NULL)
+ {
+ g_free (basedir);
+ return NULL;
+ }
+
+ g_date_strftime (timestamp, 11, "%F", date);
+
+ while ((dirfile = g_dir_read_name (dir)) != NULL)
+ {
+ if (!g_str_has_suffix (dirfile, TXT_LOG_FILENAME_SUFFIX)
+ && !g_str_has_suffix (dirfile, HTML_LOG_FILENAME_SUFFIX))
+ continue;
+
+ if (g_str_has_prefix (dirfile, timestamp))
+ {
+ filenames = g_list_insert_sorted (filenames,
+ g_build_filename (basedir, dirfile, NULL),
+ (GCompareFunc) g_strcmp0);
+ }
+ }
+
+ g_dir_close (dir);
+
+ g_free (basedir);
+
+ return filenames;
+}
+
+
+static TpAccount *
+log_store_pidgin_dup_account (TplLogStorePidgin *self,
+ const gchar *filename)
+{
+ GList *accounts, *l;
+ TpAccount *account = NULL;
+ gchar **strv;
+ guint len;
+ gchar *protocol, *username, *server = NULL, *tmp;
+ gboolean is_irc;
+
+ accounts = tp_account_manager_dup_usable_accounts (
+ self->priv->account_manager);
+
+ strv = g_strsplit (filename, G_DIR_SEPARATOR_S, -1);
+ len = g_strv_length (strv);
+
+ protocol = strv[len - 4];
+ tmp = strchr (strv[len - 3], '@');
+ is_irc = !tp_strdiff (protocol, "irc");
+
+ if (is_irc && tmp != NULL)
+ {
+ username = g_strndup (strv[len - 3], tmp - strv[len - 3]);
+ server = g_strdup (strv[len - 3] + (tmp - strv[len - 3]) + 1);
+ }
+ else
+ {
+ username = g_strdup (strv[len - 3]);
+ }
+
+ /* You can have multiple accounts with the same username so we have to
+ * look at all the accounts to find the right one going on the username and
+ * protocol. */
+ for (l = accounts; l != NULL; l = l->next)
+ {
+ TpAccount *acc = (TpAccount *) l->data;
+ const GHashTable *params;
+
+ if (tp_strdiff (tp_account_get_protocol_name (acc), protocol))
+ continue;
+
+ params = tp_account_get_parameters (acc);
+
+ if (!tp_strdiff (username, tp_asv_get_string (params, "account")))
+ {
+ if (is_irc && tp_strdiff (server, tp_asv_get_string (params, "server")))
+ continue;
+
+ account = g_object_ref (acc);
+ break;
+ }
+ }
+
+ g_free (username);
+ g_free (server);
+ g_list_free_full (accounts, g_object_unref);
+ g_strfreev (strv);
+
+ return account;
+}
+
+
+static TplLogSearchHit *
+log_store_pidgin_search_hit_new (TplLogStore *self,
+ const gchar *filename)
+{
+ TplLogSearchHit *hit;
+ gchar **strv;
+ guint len;
+ TplEntityType type;
+ gchar *id;
+
+ if (!g_str_has_suffix (filename, TXT_LOG_FILENAME_SUFFIX)
+ && !g_str_has_suffix (filename, HTML_LOG_FILENAME_SUFFIX))
+ return NULL;
+
+ strv = g_strsplit (filename, G_DIR_SEPARATOR_S, -1);
+ len = g_strv_length (strv);
+
+ hit = g_slice_new0 (TplLogSearchHit);
+ hit->date = log_store_pidgin_get_time (strv[len-1]);
+
+ type = g_str_has_suffix (strv[len-2], ".chat")
+ ? TPL_ENTITY_ROOM : TPL_ENTITY_CONTACT;
+
+ /* Remove ".chat" suffix. */
+ if (type == TPL_ENTITY_ROOM)
+ id = g_strndup (strv[len-2], (strlen (strv[len-2]) - 5));
+ else
+ id = g_strdup (strv[len-2]);
+
+ hit->target = tpl_entity_new (id, type, NULL, NULL);
+
+ g_free (id);
+
+ hit->account = log_store_pidgin_dup_account (TPL_LOG_STORE_PIDGIN (self),
+ filename);
+
+ g_strfreev (strv);
+
+ return hit;
+}
+
+
+static GList *
+log_store_pidgin_get_events_for_files (TplLogStore *self,
+ TpAccount *account,
+ const GList *filenames)
+{
+ GList *events = NULL;
+ const GList *l;
+
+ g_return_val_if_fail (TPL_IS_LOG_STORE_PIDGIN (self), NULL);
+ g_return_val_if_fail (filenames != NULL, NULL);
+
+ for (l = filenames; l != NULL; l = l->next)
+ {
+ const gchar *filename;
+
+ gchar *target_id = NULL;
+ gchar *date = NULL;
+ gchar *own_user = NULL;
+ gchar *protocol = NULL;
+ gboolean is_room;
+ gchar *dirname;
+ gchar *date_str;
+ gchar *basename;
+ gchar **split;
+
+ gchar *buffer;
+ GError *error = NULL;
+ gchar **lines;
+ int i;
+
+ GRegex *regex;
+ GMatchInfo *match_info;
+ gchar **hits = NULL;
+ gboolean is_html = FALSE;
+
+ filename = (gchar *) l->data;
+
+ DEBUG ("Attempting to parse filename:'%s'...", filename);
+
+ if (!g_file_test (filename, G_FILE_TEST_EXISTS))
+ {
+ DEBUG ("Filename:'%s' does not exist", filename);
+ continue;
+ }
+
+ if (!g_file_get_contents (filename, &buffer, NULL, &error))
+ {
+ DEBUG ("Failed to read file: %s",
+ error ? error->message : "no event");
+ g_error_free (error);
+ continue;
+ }
+
+ dirname = g_path_get_dirname (filename);
+ is_room = g_str_has_suffix (dirname, ".chat");
+ g_free (dirname);
+
+ basename = g_path_get_basename (filename);
+ split = g_strsplit_set (basename, "-.", 4);
+
+ if (g_strv_length (split) < 3)
+ {
+ DEBUG ("Unexpected filename: %s (expected YYYY-MM-DD ...)",
+ basename);
+ g_strfreev (split);
+ g_free (basename);
+ g_free (buffer);
+ continue;
+ }
+
+ date_str = g_strdup_printf ("%s%s%sT", split[0], split[1], split[2]);
+ g_free (basename);
+ g_strfreev (split);
+
+ lines = g_strsplit (buffer, "\n", -1);
+
+ g_free (buffer);
+
+ is_html = g_str_has_suffix (filename, HTML_LOG_FILENAME_SUFFIX);
+
+ if (is_html)
+ {
+ regex = g_regex_new ("<h3>Conversation with (.+) at (.+) on (.+) \\((.+)\\)</h3>",
+ 0, 0, NULL);
+ }
+ else
+ {
+ regex = g_regex_new ("Conversation with (.+) at (.+) on (.+) \\((.+)\\)",
+ 0, 0, NULL);
+ }
+
+ if (lines[0] != NULL)
+ {
+ g_regex_match (regex, lines[0], 0, &match_info);
+ hits = g_match_info_fetch_all (match_info);
+
+ g_match_info_free (match_info);
+ }
+
+ g_regex_unref (regex);
+
+ if (hits == NULL)
+ {
+ g_strfreev (lines);
+ continue;
+ }
+
+ if (g_strv_length (hits) != 5)
+ {
+ g_strfreev (lines);
+ g_strfreev (hits);
+ continue;
+ }
+
+ target_id = g_strdup (hits[1]);
+ own_user = g_strdup (hits[3]);
+ protocol = g_strdup (hits[4]);
+
+ g_strfreev (hits);
+
+ for (i = 1; lines[i] != NULL; i++)
+ {
+ TplTextEvent *event;
+ TplEntity *sender;
+ TplEntity *receiver = NULL;
+ gchar *sender_name = NULL;
+ gchar *time_str = NULL;
+ gchar *timestamp_str = NULL;
+ gchar *body = NULL;
+ int j = i + 1;
+ gboolean is_user = FALSE;
+ gint64 timestamp;
+
+ if (is_html)
+ {
+ if (!tp_strdiff (lines[i], "</body></html>"))
+ break;
+
+ regex = g_regex_new (
+ "<font size=\"2\">\\((.+)\\)</font> <b>(.+):</b></font> (<body>|)(.*)(</body>|)<br/>$",
+ G_REGEX_UNGREEDY, 0, NULL);
+ }
+ else
+ {
+ regex = g_regex_new ("^\\((.+)\\) (.+): (.+)", 0, 0, NULL);
+ }
+
+ g_regex_match (regex, lines[i], 0, &match_info);
+ hits = g_match_info_fetch_all (match_info);
+
+ g_match_info_free (match_info);
+ g_regex_unref (regex);
+
+ if (hits == NULL
+ || (is_html && g_strv_length (hits) < 5)
+ || (g_strv_length (hits) < 4))
+ {
+ g_strfreev (hits);
+ continue;
+ }
+
+ time_str = g_strdup (hits[1]);
+ sender_name = g_strdup (hits[2]);
+
+ if (is_html)
+ {
+ GRegex *r;
+
+ r = g_regex_new ("<br/>", 0, 0, NULL);
+ body = g_regex_replace (r, hits[4], -1, 0, "\n", 0, NULL);
+ g_regex_unref (r);
+
+ is_user = strstr (lines[i], "16569E") != NULL;
+ }
+ else
+ {
+ body = g_strdup (hits[3]);
+ }
+
+ g_strfreev (hits);
+
+ /* time_str -> "%H:%M:%S" */
+ timestamp_str = g_strdup_printf ("%s%s", date_str, time_str);
+ timestamp = _tpl_time_parse (timestamp_str);
+ g_free (timestamp_str);
+
+ /* Unfortunately, there's no way to tell which user is you in plain
+ * text logs as they appear like this:
+ *
+ * Conversation with contacts@jid at date on my@jid (protocol)
+ * (10:17:18) Some Person: hello
+ * (10:17:19) Another person: hey
+ *
+ * We can hack around it in the HTML logs because we know what
+ * colour the local user will be displayed as. sigh.
+ */
+
+ /* FIXME: in text format (is_html==FALSE) there is no actual way to
+ * understand what type the entity is, it might lead to inaccuracy,
+ * as is_user will be always FALSE */
+ sender = tpl_entity_new (
+ is_user ? own_user : sender_name,
+ is_user ? TPL_ENTITY_SELF : TPL_ENTITY_CONTACT,
+ sender_name, NULL);
+
+ /* FIXME: in text format it's not possible to guess who is the
+ * receiver (unless we are in a room). In this case the receiver will
+ * be left to NULL in the generated event. */
+ if (is_html || is_room)
+ {
+ const gchar *receiver_id;
+ TplEntityType receiver_type;
+
+ /* In chatrooms, the receiver is always the room */
+ if (is_room)
+ {
+ receiver_id = target_id;
+ receiver_type = TPL_ENTITY_ROOM;
+ }
+ else if (is_user)
+ {
+ receiver_id = target_id;
+ receiver_type = TPL_ENTITY_CONTACT;
+ }
+ else
+ {
+ receiver_id = own_user;
+ receiver_type = TPL_ENTITY_SELF;
+ }
+
+ receiver = tpl_entity_new (receiver_id, receiver_type,
+ NULL, NULL);
+ }
+
+ event = g_object_new (TPL_TYPE_TEXT_EVENT,
+ /* TplEvent */
+ "account", account,
+ /* MISSING: "channel-path", channel_path, */
+ "receiver", receiver,
+ "sender", sender,
+ "timestamp", timestamp,
+ /* TplTextEvent */
+ "message-type", TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+ "message", body,
+ NULL);
+
+ /* prepend and then reverse is better than append */
+ events = g_list_prepend (events, event);
+
+ g_free (sender_name);
+ g_free (time_str);
+ g_object_unref (sender);
+
+ i = j - 1;
+ }
+ events = g_list_reverse (events);
+
+ g_free (target_id);
+ g_free (own_user);
+ g_free (date);
+ g_free (protocol);
+
+ g_strfreev (lines);
+ }
+
+ DEBUG ("Parsed %d events", g_list_length (events));
+
+ return events;
+}
+
+
+/* internal: return a GList of file names (char *) which need to be freed with
+ * g_free */
+static GList *
+log_store_pidgin_get_all_files (TplLogStore *self,
+ const gchar *dir)
+{
+ GDir *gdir;
+ GList *files = NULL;
+ const gchar *name;
+ const gchar *basedir;
+
+
+ basedir = (dir != NULL) ?
+ dir : log_store_pidgin_get_basedir (TPL_LOG_STORE_PIDGIN (self));
+
+ gdir = g_dir_open (basedir, 0, NULL);
+ if (gdir == NULL)
+ return NULL;
+
+ while ((name = g_dir_read_name (gdir)) != NULL)
+ {
+ gchar *filename;
+
+ filename = g_build_filename (basedir, name, NULL);
+ if (g_str_has_suffix (filename, TXT_LOG_FILENAME_SUFFIX)
+ || g_str_has_suffix (filename, HTML_LOG_FILENAME_SUFFIX))
+ {
+ files = g_list_prepend (files, filename);
+ continue;
+ }
+
+ if (g_file_test (filename, G_FILE_TEST_IS_DIR))
+ {
+ files = g_list_concat (files,
+ log_store_pidgin_get_all_files (self, filename));
+ }
+
+ g_free (filename);
+ }
+
+ g_dir_close (gdir);
+
+ return files;
+}
+
+
+static GList *
+_log_store_pidgin_search_in_files (TplLogStorePidgin *self,
+ const gchar *text,
+ GList *files)
+{
+ GList *l;
+ GList *hits = NULL;
+ gchar *text_casefold;
+
+ text_casefold = g_utf8_casefold (text, -1);
+
+ for (l = files; l != NULL; l = l->next)
+ {
+ gchar *filename;
+ GMappedFile *file;
+ gsize length;
+ gchar *contents;
+ gchar *contents_casefold = NULL;
+
+ filename = l->data;
+
+ file = g_mapped_file_new (filename, FALSE, NULL);
+ if (file == NULL)
+ continue;
+
+ length = g_mapped_file_get_length (file);
+ contents = g_mapped_file_get_contents (file);
+
+ if (contents != NULL)
+ contents_casefold = g_utf8_casefold (contents, length);
+
+ g_mapped_file_unref (file);
+
+ if (contents_casefold == NULL)
+ continue;
+
+ if (strstr (contents_casefold, text_casefold))
+ {
+ TplLogSearchHit *hit;
+
+ hit = log_store_pidgin_search_hit_new (TPL_LOG_STORE (self),
+ filename);
+
+ if (hit != NULL)
+ {
+ hits = g_list_prepend (hits, hit);
+ DEBUG ("Found text:'%s' in file:'%s' on date:'%04u-%02u-%02u'",
+ text_casefold, filename, g_date_get_year (hit->date),
+ g_date_get_month (hit->date), g_date_get_day (hit->date));
+ }
+ }
+
+ g_free (contents_casefold);
+ }
+
+ g_free (text_casefold);
+
+ return hits;
+}
+
+
+static GList *
+log_store_pidgin_search_new (TplLogStore *self,
+ const gchar *text,
+ gint type_mask)
+{
+ GList *files;
+ GList *retval;
+
+ g_return_val_if_fail (TPL_IS_LOG_STORE_PIDGIN (self), NULL);
+ g_return_val_if_fail (!tp_str_empty (text), NULL);
+
+ if (!(type_mask & TPL_EVENT_MASK_TEXT))
+ return NULL;
+
+ files = log_store_pidgin_get_all_files (self, NULL);
+ DEBUG ("Found %d log files in total", g_list_length (files));
+
+ retval = _log_store_pidgin_search_in_files (TPL_LOG_STORE_PIDGIN (self),
+ text, files);
+
+ g_list_foreach (files, (GFunc) g_free, NULL);
+ g_list_free (files);
+
+ return retval;
+}
+
+
+static GList *
+log_store_pidgin_get_entities_for_dir (TplLogStore *self,
+ const gchar *dir)
+{
+ GDir *gdir;
+ GList *entities = NULL;
+ const gchar *name;
+
+ gdir = g_dir_open (dir, 0, NULL);
+ if (gdir == NULL)
+ return NULL;
+
+ while ((name = g_dir_read_name (gdir)) != NULL)
+ {
+ TplEntity *entity;
+
+ /* pidgin internal ".system" directory is not a target ID */
+ if (g_strcmp0 (name, ".system") == 0)
+ continue;
+
+ /* Check if it's a chatroom */
+ if (g_str_has_suffix (name, ".chat"))
+ {
+ gchar *id = g_strndup (name, strlen (name) - 5);
+ entity = tpl_entity_new_from_room_id (id);
+ g_free (id);
+ }
+ else
+ entity = tpl_entity_new (name, TPL_ENTITY_CONTACT, NULL, NULL);
+
+ entities = g_list_prepend (entities, entity);
+ }
+
+ g_dir_close (gdir);
+
+ return entities;
+}
+
+
+static GList *
+log_store_pidgin_get_events_for_date (TplLogStore *self,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask,
+ const GDate *date)
+{
+ GList *events, *filenames;
+
+ g_return_val_if_fail (TPL_IS_LOG_STORE_PIDGIN (self), NULL);
+ g_return_val_if_fail (TP_IS_ACCOUNT (account), NULL);
+ g_return_val_if_fail (TPL_IS_ENTITY (target), NULL);
+
+ if (!(type_mask & TPL_EVENT_MASK_TEXT))
+ return NULL;
+
+ /* pidgin stores multiple files related to the same date */
+ filenames = log_store_pidgin_get_filenames_for_date (self, account,
+ target, date);
+
+ if (filenames == NULL)
+ return NULL;
+
+ events = log_store_pidgin_get_events_for_files (self, account, filenames);
+
+ g_list_foreach (filenames, (GFunc) g_free, NULL);
+ g_list_free (filenames);
+
+ return events;
+}
+
+
+static GList *
+log_store_pidgin_get_entities (TplLogStore *self,
+ TpAccount *account)
+{
+ gchar *dir;
+ GList *hits;
+
+ dir = log_store_pidgin_get_dir (self, account, NULL);
+
+ if (dir != NULL)
+ hits = log_store_pidgin_get_entities_for_dir (self, dir);
+ else
+ hits = NULL;
+
+ g_free (dir);
+
+ return hits;
+}
+
+
+static GList *
+log_store_pidgin_get_filtered_events (TplLogStore *self,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask,
+ guint num_events,
+ TplLogEventFilter filter,
+ gpointer user_data)
+{
+ GList *dates, *l, *events = NULL;
+ guint i = 0;
+
+ dates = log_store_pidgin_get_dates (self, account, target, type_mask);
+
+ for (l = g_list_last (dates); l != NULL && i < num_events; l = l->prev)
+ {
+ GList *new_events, *n, *next;
+
+ /* FIXME: We should really restrict the event parsing to get only
+ * the newest num_events. */
+ new_events = log_store_pidgin_get_events_for_date (self, account,
+ target, type_mask, l->data);
+
+ n = new_events;
+ while (n != NULL)
+ {
+ next = n->next;
+ if (filter != NULL && !filter (n->data, user_data))
+ {
+ g_object_unref (n->data);
+ new_events = g_list_delete_link (new_events, n);
+ }
+ else
+ {
+ i++;
+ }
+ n = next;
+ }
+ events = g_list_concat (events, new_events);
+ }
+
+ g_list_foreach (dates, (GFunc) g_free, NULL);
+ g_list_free (dates);
+
+ return events;
+}
+
+
+static TplLogIter *
+log_store_pidgin_create_iter (TplLogStore *store,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask)
+{
+ g_return_val_if_fail (TPL_IS_LOG_STORE_PIDGIN (store), NULL);
+ g_return_val_if_fail (TP_IS_ACCOUNT (account), NULL);
+ g_return_val_if_fail (TPL_IS_ENTITY (target), NULL);
+
+ return tpl_log_iter_pidgin_new (store, account, target, type_mask);
+}
+
+
+static void
+log_store_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ TplLogStoreInterface *iface = (TplLogStoreInterface *) g_iface;
+
+ iface->get_name = log_store_pidgin_get_name;
+ iface->exists = log_store_pidgin_exists;
+ iface->add_event = NULL;
+ iface->get_dates = log_store_pidgin_get_dates;
+ iface->get_events_for_date = log_store_pidgin_get_events_for_date;
+ iface->get_entities = log_store_pidgin_get_entities;
+ iface->search_new = log_store_pidgin_search_new;
+ iface->get_filtered_events = log_store_pidgin_get_filtered_events;
+ iface->create_iter = log_store_pidgin_create_iter;
+}
diff --git a/telepathy-logger/log-store-sqlite-internal.h b/telepathy-logger/log-store-sqlite-internal.h
new file mode 100644
index 000000000..026a1239e
--- /dev/null
+++ b/telepathy-logger/log-store-sqlite-internal.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2010 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Danielle Madeley <danielle.madeley@collabora.co.uk>
+ */
+
+#ifndef __TPL_LOG_STORE_SQLITE_H__
+#define __TPL_LOG_STORE_SQLITE_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include <telepathy-logger/log-store-internal.h>
+
+G_BEGIN_DECLS
+
+#define TPL_TYPE_LOG_STORE_SQLITE \
+ (_tpl_log_store_sqlite_get_type ())
+#define TPL_LOG_STORE_SQLITE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TPL_TYPE_LOG_STORE_SQLITE, \
+ TplLogStoreSqlite))
+#define TPL_LOG_STORE_SQLITE_CLASS(obj) \
+ (G_TYPE_CHECK_CLASS_CAST ((obj), TPL_TYPE_LOG_STORE_SQLITE, \
+ TplLogStoreSqliteClass))
+#define TPL_IS_LOG_STORE_SQLITE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TPL_TYPE_LOG_STORE_SQLITE))
+#define TPL_IS_LOG_STORE_SQLITE_CLASS(obj) \
+ (G_TYPE_CHECK_CLASS_TYPE ((obj), TPL_TYPE_LOG_STORE_SQLITE))
+#define TPL_LOG_STORE_SQLITE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TPL_TYPE_LOG_STORE_SQLITE, \
+ TplLogStoreSqliteClass))
+
+#define TPL_LOG_STORE_SQLITE_CLEANUP_DELTA_LIMIT (60*60)
+#define TPL_LOG_STORE_SQLITE_TIMESTAMP_FORMAT "%Y-%m-%d %H:%M:%S"
+#define TPL_LOG_STORE_SQLITE_ERROR g_quark_from_static_string ( \
+ "tpl-log-store-index-error-quark")
+typedef enum
+{
+ /* generic error, avoids clashing with TPL_LOG_STORE_ERROR using its last
+ * value */
+ TPL_LOG_STORE_SQLITE_ERROR_FAILED = TPL_LOG_STORE_ERROR_LAST,
+ /* generic _tpl_log_store_sqlite_get_pending_messages() error, to be used when
+ * any other code cannot be use, including TPL_LOG_STORE_ERROR ones */
+ TPL_LOG_STORE_SQLITE_ERROR_GET_PENDING_MESSAGES,
+ TPL_LOG_STORE_SQLITE_ERROR_REMOVE_PENDING_MESSAGES,
+ TPL_LOG_STORE_SQLITE_ERROR_ADD_PENDING_MESSAGE
+} TplLogStoreSqliteError;
+
+typedef struct _TplLogStoreSqlite TplLogStoreSqlite;
+typedef struct _TplLogStoreSqlitePrivate TplLogStoreSqlitePrivate;
+typedef struct _TplLogStoreSqliteClass TplLogStoreSqliteClass;
+typedef struct _TplPendingMessage TplPendingMessage;
+
+struct _TplLogStoreSqlite
+{
+ GObject parent;
+ TplLogStoreSqlitePrivate *priv;
+};
+
+struct _TplLogStoreSqliteClass
+{
+ GObjectClass parent_class;
+};
+
+struct _TplPendingMessage
+{
+ guint id;
+ gint64 timestamp;
+};
+
+GType _tpl_log_store_sqlite_get_type (void);
+TplLogStore * _tpl_log_store_sqlite_dup (void);
+
+GList * _tpl_log_store_sqlite_get_pending_messages (TplLogStore *self,
+ TpChannel *channel, GError **error);
+gboolean _tpl_log_store_sqlite_remove_pending_messages (TplLogStore *self,
+ TpChannel *channel, GList *log_ids, GError **error);
+gboolean _tpl_log_store_sqlite_add_pending_message (TplLogStore *self,
+ TpChannel *channel, guint id, gint64 timestamp, GError **error);
+
+gint64 _tpl_log_store_sqlite_get_most_recent (TplLogStoreSqlite *self,
+ TpAccount *account, const char *identifier);
+double _tpl_log_store_sqlite_get_frequency (TplLogStoreSqlite *self,
+ TpAccount *account, const char *identifier);
+
+G_END_DECLS
+
+#endif
diff --git a/telepathy-logger/log-store-sqlite.c b/telepathy-logger/log-store-sqlite.c
new file mode 100644
index 000000000..c0b00dc20
--- /dev/null
+++ b/telepathy-logger/log-store-sqlite.c
@@ -0,0 +1,992 @@
+/*
+ * Copyright (C) 2010-2011 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Danielle Madeley <danielle.madeley@collabora.co.uk>
+ * Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ * Nicolas Dufresne <nicolas.dufresne@collabora.co.uk>
+ */
+
+#include <config.h>
+#include "log-store-sqlite-internal.h"
+
+#include <string.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/telepathy-glib-dbus.h>
+#include <sqlite3.h>
+
+#include "event-internal.h"
+#include "text-event.h"
+#include "text-event-internal.h"
+#include "entity-internal.h"
+#include "log-manager-internal.h"
+
+#define DEBUG_FLAG TPL_DEBUG_LOG_STORE
+#include "debug-internal.h"
+#include "util-internal.h"
+
+#define TPL_LOG_STORE_SQLITE_NAME "Sqlite"
+
+static void log_store_iface_init (TplLogStoreInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (TplLogStoreSqlite, _tpl_log_store_sqlite,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TPL_TYPE_LOG_STORE, log_store_iface_init));
+
+enum /* properties */
+{
+ PROP_0,
+ PROP_READABLE,
+};
+
+struct _TplLogStoreSqlitePrivate
+{
+ sqlite3 *db;
+};
+
+static GObject *singleton = NULL;
+
+
+static GObject *
+tpl_log_store_sqlite_constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ if (singleton != NULL)
+ g_object_ref (singleton);
+ else
+ {
+ singleton =
+ G_OBJECT_CLASS (_tpl_log_store_sqlite_parent_class)->constructor (
+ type, n_props, props);
+
+ if (singleton == NULL)
+ return NULL;
+
+ g_object_add_weak_pointer (singleton, (gpointer *) &singleton);
+ }
+
+ return singleton;
+}
+
+
+static char *
+get_db_filename (void)
+{
+ return g_build_filename (g_get_user_cache_dir (),
+ "telepathy",
+ "logger",
+ "sqlite-data",
+ NULL);
+}
+
+
+static void
+tpl_log_store_sqlite_get_property (GObject *self,
+ guint id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (id)
+ {
+ case PROP_READABLE:
+ /* this store should never be queried by the LogManager */
+ g_value_set_boolean (value, FALSE);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (self, id, pspec);
+ break;
+ }
+}
+
+
+static void
+purge_pending_messages (TplLogStoreSqlitePrivate *priv,
+ GTimeSpan delta,
+ GError **error)
+{
+ sqlite3_stmt *sql = NULL;
+ GDateTime *now;
+ GDateTime *timestamp;
+ gchar *date;
+ int e;
+
+ g_return_if_fail (error == NULL || *error == NULL);
+
+ now = g_date_time_new_now_utc ();
+ timestamp = g_date_time_add (now, -(delta * G_TIME_SPAN_SECOND));
+
+ date = g_date_time_format (timestamp,
+ TPL_LOG_STORE_SQLITE_TIMESTAMP_FORMAT);
+
+ g_date_time_unref (now);
+
+ DEBUG ("Purging entries older than %s (%u seconds ago)", date, (guint) delta);
+
+ e = sqlite3_prepare_v2 (priv->db,
+ "DELETE FROM pending_messages WHERE timestamp<?",
+ -1, &sql, NULL);
+
+ if (e != SQLITE_OK)
+ {
+ g_set_error (error, TPL_LOG_STORE_ERROR,
+ TPL_LOG_STORE_ERROR_ADD_EVENT,
+ "SQL Error preparing statement in %s: %s", G_STRFUNC,
+ sqlite3_errmsg (priv->db));
+
+ goto out;
+ }
+
+ sqlite3_bind_int64 (sql, 1, g_date_time_to_unix (timestamp));
+
+ e = sqlite3_step (sql);
+ if (e != SQLITE_DONE)
+ {
+ g_set_error (error, TPL_LOG_STORE_ERROR,
+ TPL_LOG_STORE_ERROR_ADD_EVENT,
+ "SQL Error in %s: %s", G_STRFUNC, sqlite3_errmsg (priv->db));
+ }
+
+out:
+ g_date_time_unref (timestamp);
+
+ if (sql != NULL)
+ sqlite3_finalize (sql);
+
+ g_free (date);
+}
+
+
+static void
+_tpl_log_store_sqlite_init (TplLogStoreSqlite *self)
+{
+ TplLogStoreSqlitePrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TPL_TYPE_LOG_STORE_SQLITE, TplLogStoreSqlitePrivate);
+ char *filename = get_db_filename ();
+ int e;
+ char *errmsg = NULL;
+ GError *error = NULL;
+
+ self->priv = priv;
+
+ DEBUG ("cache file is '%s'", filename);
+
+ /* counter & cache tables - common part */
+ /* check to see if the sqlite db exists */
+ if (!g_file_test (filename, G_FILE_TEST_EXISTS))
+ {
+ char *dirname = g_path_get_dirname (filename);
+
+ DEBUG ("Creating cache");
+
+ g_mkdir_with_parents (dirname, 0700);
+ g_free (dirname);
+ }
+
+ e = sqlite3_open_v2 (filename, &priv->db,
+ SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
+ NULL);
+ if (e != SQLITE_OK)
+ {
+ CRITICAL ("Failed to open Sqlite3 DB: %s\n",
+ sqlite3_errmsg (priv->db));
+ goto out;
+ }
+ /* end of common part */
+
+ /* start of cache table init */
+
+ /* drop deprecated table (since 0.2.6) */
+ sqlite3_exec (priv->db, "DROP TABLE IF EXISTS message_cache",
+ NULL, NULL, &errmsg);
+ if (errmsg != NULL)
+ {
+ CRITICAL ("Failed to drop deprecated message_cache table: %s\n", errmsg);
+ sqlite3_free (errmsg);
+ goto out;
+ }
+
+ sqlite3_exec (priv->db, "CREATE TABLE IF NOT EXISTS pending_messages ( "
+ "channel TEXT NOT NULL, "
+ "id INTEGER, "
+ "timestamp INTEGER)",
+ NULL, NULL, &errmsg);
+ if (errmsg != NULL)
+ {
+ CRITICAL ("Failed to create table pending_messages: %s\n", errmsg);
+ sqlite3_free (errmsg);
+ goto out;
+ }
+
+ /* purge old entries */
+ purge_pending_messages (priv,
+ TPL_LOG_STORE_SQLITE_CLEANUP_DELTA_LIMIT, &error);
+ if (error != NULL)
+ {
+ CRITICAL ("Failed to purge pending messages: %s", error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ /* end of cache table init */
+
+ /* start of counter table init */
+ sqlite3_exec (priv->db,
+ "CREATE TABLE IF NOT EXISTS messagecounts ("
+ "account TEXT, "
+ "identifier TEXT, "
+ "chatroom BOOLEAN, "
+ "date DATE, "
+ "messages INTEGER)",
+ NULL,
+ NULL,
+ &errmsg);
+ if (errmsg != NULL)
+ {
+ CRITICAL ("Failed to create table messagecounts: %s\n", errmsg);
+ sqlite3_free (errmsg);
+ goto out;
+ }
+ /* end of counter table init */
+
+out:
+ g_free (filename);
+}
+
+
+static void
+tpl_log_store_sqlite_dispose (GObject *self)
+{
+ TplLogStoreSqlitePrivate *priv = TPL_LOG_STORE_SQLITE (self)->priv;
+
+ if (priv->db != NULL)
+ {
+ sqlite3_close (priv->db);
+ priv->db = NULL;
+ }
+
+ G_OBJECT_CLASS (_tpl_log_store_sqlite_parent_class)->dispose (self);
+}
+
+
+static void
+_tpl_log_store_sqlite_class_init (TplLogStoreSqliteClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->constructor = tpl_log_store_sqlite_constructor;
+ gobject_class->get_property = tpl_log_store_sqlite_get_property;
+ gobject_class->dispose = tpl_log_store_sqlite_dispose;
+
+ g_object_class_override_property (gobject_class, PROP_READABLE, "readable");
+
+ g_type_class_add_private (gobject_class, sizeof (TplLogStoreSqlitePrivate));
+}
+
+
+static const char *
+get_account_name (TpAccount *account)
+{
+ return tp_proxy_get_object_path (account) +
+ strlen (TP_ACCOUNT_OBJECT_PATH_BASE);
+}
+
+
+static const char *
+get_account_name_from_event (TplEvent *event)
+{
+ return tpl_event_get_account_path (event) +
+ strlen (TP_ACCOUNT_OBJECT_PATH_BASE);
+}
+
+
+static const char *
+get_channel_name (TpChannel *chan)
+{
+ return tp_proxy_get_object_path (chan) +
+ strlen (TP_CONN_OBJECT_PATH_BASE);
+}
+
+
+static char *
+get_date (TplEvent *event)
+{
+ GDateTime *ts;
+ gchar *date;
+
+ ts = g_date_time_new_from_unix_utc (tpl_event_get_timestamp (event));
+ g_return_val_if_fail (ts != NULL, NULL);
+ date = g_date_time_format (ts, "%Y-%m-%d");
+
+ g_date_time_unref (ts);
+
+ return date;
+}
+
+
+static char *
+get_datetime (gint64 timestamp)
+{
+ GDateTime *ts;
+ gchar *date;
+
+ ts = g_date_time_new_from_unix_utc (timestamp);
+ date = g_date_time_format (ts, TPL_LOG_STORE_SQLITE_TIMESTAMP_FORMAT);
+
+ g_date_time_unref (ts);
+
+ return date;
+}
+
+
+static const char *
+tpl_log_store_sqlite_get_name (TplLogStore *self)
+{
+ return TPL_LOG_STORE_SQLITE_NAME;
+}
+
+
+static gboolean
+tpl_log_store_sqlite_add_message_counter (TplLogStore *self,
+ TplEvent *message,
+ GError **error)
+{
+ TplLogStoreSqlitePrivate *priv = TPL_LOG_STORE_SQLITE (self)->priv;
+ const char *account, *identifier;
+ gboolean chatroom;
+ char *date = NULL;
+ int count = 0;
+ sqlite3_stmt *sql = NULL;
+ gboolean retval = FALSE;
+ gboolean insert = FALSE;
+ int e;
+
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ if (TPL_IS_TEXT_EVENT (message) == FALSE)
+ {
+ DEBUG ("ignoring non-text event not intersting for message-counter");
+ retval = TRUE;
+ goto out;
+ }
+
+ DEBUG ("message received");
+
+ account = get_account_name_from_event (message);
+ identifier = _tpl_event_get_target_id (message);
+ chatroom = _tpl_event_target_is_room (message);
+ date = get_date (message);
+
+ DEBUG ("account = %s", account);
+ DEBUG ("identifier = %s", identifier);
+ DEBUG ("chatroom = %i", chatroom);
+ DEBUG ("date = %s", date);
+
+ /* get the existing row */
+ e = sqlite3_prepare_v2 (priv->db,
+ "SELECT messages FROM messagecounts WHERE "
+ "account=? AND "
+ "identifier=? AND "
+ "chatroom=? AND "
+ "date=date(?)",
+ -1, &sql, NULL);
+ if (e != SQLITE_OK)
+ {
+ g_set_error (error, TPL_LOG_STORE_ERROR,
+ TPL_LOG_STORE_ERROR_ADD_EVENT,
+ "SQL Error checking current counter in %s: %s", G_STRFUNC,
+ sqlite3_errmsg (priv->db));
+
+ goto out;
+ }
+
+ sqlite3_bind_text (sql, 1, account, -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text (sql, 2, identifier, -1, SQLITE_TRANSIENT);
+ sqlite3_bind_int (sql, 3, chatroom);
+ sqlite3_bind_text (sql, 4, date, -1, SQLITE_TRANSIENT);
+
+ e = sqlite3_step (sql);
+ if (e == SQLITE_DONE)
+ {
+ DEBUG ("no rows, insert");
+ insert = TRUE;
+ }
+ else if (e == SQLITE_ROW)
+ {
+ count = sqlite3_column_int (sql, 0);
+ DEBUG ("got row, count = %i", count);
+ }
+ else
+ {
+ g_set_error (error, TPL_LOG_STORE_ERROR,
+ TPL_LOG_STORE_ERROR_ADD_EVENT,
+ "SQL Error binding counter checking query in %s: %s", G_STRFUNC,
+ sqlite3_errmsg (priv->db));
+
+ goto out;
+ }
+
+ sqlite3_finalize (sql);
+ sql = NULL;
+
+ /* increment the message count */
+ count++;
+
+ DEBUG ("new count = %i, insert = %i", count, insert);
+
+ /* update table with new message count */
+ if (insert)
+ e = sqlite3_prepare_v2 (priv->db,
+ "INSERT INTO messagecounts "
+ "(messages, account, identifier, chatroom, date) "
+ "VALUES (?, ?, ?, ?, date(?))",
+ -1, &sql, NULL);
+ else
+ e = sqlite3_prepare_v2 (priv->db,
+ "UPDATE messagecounts SET messages=? WHERE "
+ "account=? AND "
+ "identifier=? AND "
+ "chatroom=? AND "
+ "date=date(?)",
+ -1, &sql, NULL);
+
+ if (e != SQLITE_OK)
+ {
+ g_set_error (error, TPL_LOG_STORE_ERROR,
+ TPL_LOG_STORE_ERROR_ADD_EVENT,
+ "SQL Error preparing query in %s: %s", G_STRFUNC,
+ sqlite3_errmsg (priv->db));
+
+ goto out;
+ }
+
+ sqlite3_bind_int (sql, 1, count);
+ sqlite3_bind_text (sql, 2, account, -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text (sql, 3, identifier, -1, SQLITE_TRANSIENT);
+ sqlite3_bind_int (sql, 4, chatroom);
+ sqlite3_bind_text (sql, 5, date, -1, SQLITE_TRANSIENT);
+
+ e = sqlite3_step (sql);
+ if (e != SQLITE_DONE)
+ {
+ g_set_error (error, TPL_LOG_STORE_ERROR,
+ TPL_LOG_STORE_ERROR_ADD_EVENT,
+ "SQL Error %s counter in %s: %s",
+ (insert ? "inserting new" : "updating"),
+ G_STRFUNC, sqlite3_errmsg (priv->db));
+
+ goto out;
+ }
+
+ retval = TRUE;
+
+out:
+ g_free (date);
+
+ if (sql != NULL)
+ sqlite3_finalize (sql);
+
+ /* check that we set an error if appropriate */
+ g_assert ((retval == TRUE && *error == NULL) ||
+ (retval == FALSE && *error != NULL));
+
+ return retval;
+}
+
+
+/**
+ * tpl_log_store_sqlite_add_event:
+ * @self: TplLogstoreSqlite instance
+ * @message: a TplEvent instance
+ * @error: memory pointer use in case of error
+ *
+ * Text messages will be accounted for statistics purpose.
+ *
+ * Returns: %TRUE if @self was able to store, %FALSE with @error set if an error occurred.
+ */
+static gboolean
+tpl_log_store_sqlite_add_event (TplLogStore *self,
+ TplEvent *message,
+ GError **error)
+{
+ gboolean retval = FALSE;
+
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ if (!TPL_IS_LOG_STORE_SQLITE (self))
+ {
+ g_set_error (error, TPL_LOG_STORE_ERROR,
+ TPL_LOG_STORE_ERROR_ADD_EVENT,
+ "TplLogStoreSqlite intance needed");
+ goto out;
+ }
+
+ if (!TPL_IS_EVENT (message))
+ {
+ g_set_error (error, TPL_LOG_STORE_ERROR,
+ TPL_LOG_STORE_ERROR_ADD_EVENT, "TplEvent instance needed");
+ goto out;
+ }
+
+ retval = tpl_log_store_sqlite_add_message_counter (self, message, error);
+
+out:
+ /* check that we set an error if appropriate */
+ g_assert ((retval == TRUE && *error == NULL) ||
+ (retval == FALSE && *error != NULL));
+
+ DEBUG ("returning with %d", retval);
+ return retval;
+}
+
+
+static GList *
+tpl_log_store_sqlite_get_entities (TplLogStore *self,
+ TpAccount *account)
+{
+ TplLogStoreSqlitePrivate *priv = TPL_LOG_STORE_SQLITE (self)->priv;
+ sqlite3_stmt *sql = NULL;
+ int e;
+ GList *list = NULL;
+ const char *account_name = get_account_name (account);
+
+ DEBUG ("account = %s", account_name);
+
+ /* list all the identifiers known to the database */
+ e = sqlite3_prepare_v2 (priv->db,
+ "SELECT DISTINCT identifier, chatroom FROM messagecounts WHERE "
+ "account=?",
+ -1, &sql, NULL);
+ if (e != SQLITE_OK)
+ {
+ DEBUG ("Failed to prepare SQL: %s",
+ sqlite3_errmsg (priv->db));
+
+ goto out;
+ }
+
+ sqlite3_bind_text (sql, 1, account_name, -1, SQLITE_TRANSIENT);
+
+ while ((e = sqlite3_step (sql)) == SQLITE_ROW)
+ {
+ TplEntity *entity;
+ const char *identifier;
+ gboolean chatroom;
+ TplEntityType type;
+
+ /* for some reason this returns unsigned char */
+ identifier = (const char *) sqlite3_column_text (sql, 0);
+ chatroom = sqlite3_column_int (sql, 1);
+ type = chatroom ? TPL_ENTITY_ROOM : TPL_ENTITY_CONTACT;
+
+ DEBUG ("identifier = %s, chatroom = %i", identifier, chatroom);
+
+ entity = tpl_entity_new (identifier, type, NULL, NULL);
+
+ list = g_list_prepend (list, entity);
+ }
+ if (e != SQLITE_DONE)
+ {
+ DEBUG ("Failed to execute SQL: %s",
+ sqlite3_errmsg (priv->db));
+ goto out;
+ }
+
+out:
+ if (sql != NULL)
+ sqlite3_finalize (sql);
+
+ return list;
+}
+
+static void
+log_store_iface_init (TplLogStoreInterface *iface)
+{
+ iface->get_name = tpl_log_store_sqlite_get_name;
+ iface->add_event = tpl_log_store_sqlite_add_event;
+ iface->get_entities = tpl_log_store_sqlite_get_entities;
+}
+
+TplLogStore *
+_tpl_log_store_sqlite_dup (void)
+{
+ return g_object_new (TPL_TYPE_LOG_STORE_SQLITE, NULL);
+}
+
+
+/**
+ * _tpl_log_store_sqlite_get_pending_messages:
+ * @self: a TplLogStoreSqlite instance
+ * @channel: a pointer to a TpChannel
+ * @error: set if an error occurs
+ *
+ * Returns the list of pending message IDs and timestamp for this @channel.
+ * Note that those message might not be valid anymore, check timestamp match
+ * before assuming a message is not new.
+ *
+ * Returns: (transfer full): a #GList of #TplLogStoreSqlitePendingMessage
+ */
+GList *
+_tpl_log_store_sqlite_get_pending_messages (TplLogStore *self,
+ TpChannel *channel,
+ GError **error)
+{
+ TplLogStoreSqlitePrivate *priv = TPL_LOG_STORE_SQLITE (self)->priv;
+ sqlite3_stmt *sql = NULL;
+ GList *retval = NULL;
+ int e;
+
+ g_return_val_if_fail (TPL_IS_LOG_STORE_SQLITE (self), NULL);
+ g_return_val_if_fail (TP_IS_CHANNEL (channel), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ DEBUG ("Listing pending messages for channel %s",
+ get_channel_name (channel));
+
+ e = sqlite3_prepare_v2 (priv->db, "SELECT id,timestamp "
+ "FROM pending_messages "
+ "WHERE channel=? "
+ "ORDER BY id ASC",
+ -1, &sql, NULL);
+
+ if (e != SQLITE_OK)
+ {
+ CRITICAL ("Error preparing SQL for pending messages list: %s",
+ sqlite3_errmsg (priv->db));
+ g_set_error (error, TPL_LOG_STORE_SQLITE_ERROR,
+ TPL_LOG_STORE_SQLITE_ERROR_GET_PENDING_MESSAGES,
+ "SQL Error in %s: %s", G_STRFUNC, sqlite3_errmsg (priv->db));
+ goto out;
+ }
+
+ sqlite3_bind_text (sql, 1, get_channel_name (channel), -1,
+ SQLITE_TRANSIENT);
+
+ while (SQLITE_ROW == (e = sqlite3_step (sql)))
+ {
+ /* create the pending messages list */
+ TplPendingMessage *pending;
+
+ pending = g_new (TplPendingMessage, 1);
+
+ pending->id = (guint) sqlite3_column_int64 (sql, 0);
+ pending->timestamp = sqlite3_column_int64 (sql, 1);
+
+ DEBUG (" - pending id=%u timestamp=%"G_GINT64_FORMAT,
+ pending->id, pending->timestamp);
+
+ retval = g_list_prepend (retval, pending);
+ }
+
+ if (e != SQLITE_DONE)
+ {
+ g_set_error (error, TPL_LOG_STORE_SQLITE_ERROR,
+ TPL_LOG_STORE_SQLITE_ERROR_GET_PENDING_MESSAGES,
+ "SQL Error in %s: %s", G_STRFUNC, sqlite3_errmsg (priv->db));
+
+ /* free partial result, which might be misleading */
+ g_list_foreach (retval, (GFunc) g_free, NULL);
+ g_list_free (retval);
+ retval = NULL;
+ }
+
+out:
+ if (sql != NULL)
+ sqlite3_finalize (sql);
+
+ /* check that we set an error if appropriate
+ * NOTE: retval == NULL && *error !=
+ * NULL doesn't apply to this method, since NULL is also for an empty list */
+ g_assert ((retval != NULL && *error == NULL) || retval == NULL);
+
+ return retval;
+}
+
+
+/**
+ *_tpl_log_store_sqlite_remove_pending_messages:
+ * @self: a #TplLogStore
+ * @channel: a #TpAccount
+ * @pending_ids: a #GList of pending message IDs (guint)
+ * @error: a #GError to be set on error, or NULL
+ *
+ * Removes listed pending IDs for @channel.
+ *
+ * Returns: #TRUE on success, #FALSE on error with @error set
+ */
+gboolean
+_tpl_log_store_sqlite_remove_pending_messages (TplLogStore *self,
+ TpChannel *channel,
+ GList *pending_ids,
+ GError **error)
+{
+ TplLogStoreSqlitePrivate *priv = TPL_LOG_STORE_SQLITE (self)->priv;
+ gboolean retval = TRUE;
+ GString *query = NULL;
+ GList *it;
+ sqlite3_stmt *sql = NULL;
+
+ g_return_val_if_fail (TPL_IS_LOG_STORE_SQLITE (self), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+ g_return_val_if_fail (pending_ids != NULL, FALSE);
+
+ DEBUG ("Removing pending messages for channel %s",
+ get_channel_name (channel));
+
+ query = g_string_new ("DELETE FROM pending_messages WHERE ");
+
+ g_string_append_printf (query, "channel='%s' AND id IN (%u",
+ get_channel_name (channel), GPOINTER_TO_UINT (pending_ids->data));
+
+ DEBUG (" - pending_id: %u", GPOINTER_TO_UINT (pending_ids->data));
+
+ for (it = g_list_next (pending_ids); it != NULL; it = g_list_next (it))
+ {
+ DEBUG (" - pending_id: %u", GPOINTER_TO_UINT (it->data));
+ g_string_append_printf (query, ",%u", GPOINTER_TO_UINT (it->data));
+ }
+
+ g_string_append (query, ")");
+
+ if (sqlite3_prepare_v2 (priv->db, query->str, -1, &sql, NULL) != SQLITE_OK)
+ {
+ g_set_error (error, TPL_LOG_STORE_SQLITE_ERROR,
+ TPL_LOG_STORE_SQLITE_ERROR_REMOVE_PENDING_MESSAGES,
+ "SQL Error in %s: %s", G_STRFUNC, sqlite3_errmsg (priv->db));
+ retval = FALSE;
+ goto out;
+ }
+
+ if (sqlite3_step (sql) != SQLITE_DONE)
+ {
+ g_set_error (error, TPL_LOG_STORE_SQLITE_ERROR,
+ TPL_LOG_STORE_SQLITE_ERROR_REMOVE_PENDING_MESSAGES,
+ "SQL Error in %s: %s", G_STRFUNC, sqlite3_errmsg (priv->db));
+ retval = FALSE;
+ goto out;
+ }
+
+out:
+ if (query != NULL)
+ g_string_free (query, TRUE);
+
+ if (sql != NULL)
+ sqlite3_finalize (sql);
+
+ return retval;
+}
+
+/**
+ *_tpl_log_store_sqlite_add_pending_message:
+ * @self: a #TplLogStore
+ * @channel: a #TpChannel
+ * @pending_msg_id: the pending message ID
+ * @timestamp: a unix utc timestamp
+ * @error: a #GError to be set on error, or NULL
+ *
+ * Add an entry to the list of pending message.
+ *
+ * Returns: #TRUE on success, #FALSE on error with @error set
+ */
+gboolean
+_tpl_log_store_sqlite_add_pending_message (TplLogStore *self,
+ TpChannel *channel,
+ guint id,
+ gint64 timestamp,
+ GError **error)
+{
+ TplLogStoreSqlitePrivate *priv = TPL_LOG_STORE_SQLITE (self)->priv;
+ gboolean retval = FALSE;
+ const gchar *channel_path;
+ gchar *date = NULL;
+ sqlite3_stmt *sql = NULL;
+ int e;
+
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ channel_path = get_channel_name (channel);
+ date = get_datetime (timestamp);
+
+ DEBUG ("Caching pending message %u", id);
+ DEBUG (" - channel = %s", channel_path);
+ DEBUG (" - date = %s", date);
+
+ if (TPL_STR_EMPTY (channel_path)
+ || timestamp <= 0)
+ {
+ g_set_error_literal (error, TPL_LOG_STORE_ERROR,
+ TPL_LOG_STORE_SQLITE_ERROR_ADD_PENDING_MESSAGE,
+ "passed LogStore has at least one of the needed properties unset: "
+ "channel-path, timestamp");
+ goto out;
+ }
+
+ e = sqlite3_prepare_v2 (priv->db,
+ "INSERT INTO pending_messages (channel, id, timestamp) VALUES (?, ?, ?)",
+ -1, &sql, NULL);
+ if (e != SQLITE_OK)
+ {
+ g_set_error (error, TPL_LOG_STORE_ERROR,
+ TPL_LOG_STORE_SQLITE_ERROR_ADD_PENDING_MESSAGE,
+ "SQL Error in %s: %s", G_STRFUNC, sqlite3_errmsg (priv->db));
+ goto out;
+ }
+
+ sqlite3_bind_text (sql, 1, channel_path, -1, SQLITE_TRANSIENT);
+ sqlite3_bind_int (sql, 2, (gint) id);
+ sqlite3_bind_int64 (sql, 3, timestamp);
+
+ e = sqlite3_step (sql);
+ if (e != SQLITE_DONE)
+ {
+ g_set_error (error, TPL_LOG_STORE_ERROR,
+ TPL_LOG_STORE_SQLITE_ERROR_ADD_PENDING_MESSAGE,
+ "SQL Error bind in %s: %s", G_STRFUNC, sqlite3_errmsg (priv->db));
+ goto out;
+ }
+
+ retval = TRUE;
+
+out:
+ g_free (date);
+
+ if (sql != NULL)
+ sqlite3_finalize (sql);
+
+ /* check that we set an error if appropriate */
+ g_assert ((retval == TRUE && *error == NULL) ||
+ (retval == FALSE && *error != NULL));
+
+ return retval;
+}
+
+
+gint64
+_tpl_log_store_sqlite_get_most_recent (TplLogStoreSqlite *self,
+ TpAccount *account,
+ const char *identifier)
+{
+ TplLogStoreSqlitePrivate *priv = TPL_LOG_STORE_SQLITE (self)->priv;
+ sqlite3_stmt *sql = NULL;
+ int e;
+ gint64 date = -1;
+ const char *account_name;
+
+ account_name = get_account_name (account);
+
+ /* this SQL gets this most recent date for a single identifier */
+ e = sqlite3_prepare_v2 (priv->db,
+ "SELECT STRFTIME('%s', date) FROM messagecounts WHERE "
+ "account=? AND "
+ "identifier=? "
+ "ORDER BY date DESC LIMIT 1",
+ -1, &sql, NULL);
+ if (e != SQLITE_OK)
+ {
+ DEBUG ("Failed to prepare SQL: %s",
+ sqlite3_errmsg (priv->db));
+
+ goto out;
+ }
+
+ sqlite3_bind_text (sql, 1, account_name, -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text (sql, 2, identifier, -1, SQLITE_TRANSIENT);
+
+ e = sqlite3_step (sql);
+ if (e == SQLITE_DONE)
+ {
+ DEBUG ("no rows (account identifer doesn't exist?)");
+ }
+ else if (e == SQLITE_ROW)
+ {
+ date = sqlite3_column_int64 (sql, 0);
+ DEBUG ("got row, date = %" G_GINT64_FORMAT, date);
+ }
+ else
+ {
+ DEBUG ("Failed to execute SQL: %s",
+ sqlite3_errmsg (priv->db));
+
+ goto out;
+ }
+
+out:
+
+ if (sql != NULL)
+ sqlite3_finalize (sql);
+
+ return date;
+}
+
+
+double
+_tpl_log_store_sqlite_get_frequency (TplLogStoreSqlite *self,
+ TpAccount *account,
+ const char *identifier)
+{
+ TplLogStoreSqlitePrivate *priv = TPL_LOG_STORE_SQLITE (self)->priv;
+ sqlite3_stmt *sql = NULL;
+ int e;
+ double freq = -1.;
+ const char *account_name;
+
+ account_name = get_account_name (account);
+
+ /* this SQL query builds the frequency for a single identifier */
+ e = sqlite3_prepare_v2 (priv->db,
+ "SELECT SUM(messages / ROUND(JULIANDAY('now') - JULIANDAY(date) + 1)) "
+ "FROM messagecounts WHERE "
+ "account=? AND "
+ "identifier=?",
+ -1, &sql, NULL);
+ if (e != SQLITE_OK)
+ {
+ DEBUG ("Failed to prepare SQL: %s",
+ sqlite3_errmsg (priv->db));
+
+ goto out;
+ }
+
+ sqlite3_bind_text (sql, 1, account_name, -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text (sql, 2, identifier, -1, SQLITE_TRANSIENT);
+
+ e = sqlite3_step (sql);
+ if (e == SQLITE_DONE)
+ {
+ DEBUG ("no rows (account identifer doesn't exist?)");
+ }
+ else if (e == SQLITE_ROW)
+ {
+ freq = sqlite3_column_double (sql, 0);
+ DEBUG ("got row, freq = %g", freq);
+ }
+ else
+ {
+ DEBUG ("Failed to execute SQL: %s",
+ sqlite3_errmsg (priv->db));
+
+ goto out;
+ }
+
+out:
+
+ if (sql != NULL)
+ sqlite3_finalize (sql);
+
+ return freq;
+}
diff --git a/telepathy-logger/log-store-xml-internal.h b/telepathy-logger/log-store-xml-internal.h
new file mode 100644
index 000000000..4351936d0
--- /dev/null
+++ b/telepathy-logger/log-store-xml-internal.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2003-2007 Imendio AB
+ * Copyright (C) 2007-2010 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ * Jonny Lamb <jonny.lamb@collabora.co.uk>
+ */
+
+#ifndef __TPL_LOG_STORE_XML_H__
+#define __TPL_LOG_STORE_XML_H__
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+#define TPL_TYPE_LOG_STORE_XML \
+ (_tpl_log_store_xml_get_type ())
+#define TPL_LOG_STORE_XML(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TPL_TYPE_LOG_STORE_XML, \
+ TplLogStoreXml))
+#define TPL_LOG_STORE_XML_CLASS(vtable) \
+ (G_TYPE_CHECK_CLASS_CAST ((vtable), TPL_TYPE_LOG_STORE_XML, \
+ TplLogStoreXmlClass))
+#define TPL_IS_LOG_STORE_XML(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TPL_TYPE_LOG_STORE_XML))
+#define TPL_IS_LOG_STORE_XML_CLASS(vtable) \
+ (G_TYPE_CHECK_CLASS_TYPE ((vtable), TPL_TYPE_LOG_STORE_XML))
+#define TPL_LOG_STORE_XML_GET_CLASS(inst) \
+ (G_TYPE_INSTANCE_GET_CLASS ((inst), TPL_TYPE_LOG_STORE_XML, \
+ TplLogStoreXmlClass))
+
+typedef struct _TplLogStoreXmlPriv TplLogStoreXmlPriv;
+
+typedef struct TplLogStoreXml
+{
+ GObject parent;
+ TplLogStoreXmlPriv *priv;
+} TplLogStoreXml;
+
+typedef struct
+{
+ GObjectClass parent;
+} TplLogStoreXmlClass;
+
+GType _tpl_log_store_xml_get_type (void);
+
+G_END_DECLS
+#endif /* __TPL_LOG_STORE_XML_H__ */
diff --git a/telepathy-logger/log-store-xml.c b/telepathy-logger/log-store-xml.c
new file mode 100644
index 000000000..9d5690061
--- /dev/null
+++ b/telepathy-logger/log-store-xml.c
@@ -0,0 +1,1939 @@
+/*
+ * Copyright (C) 2003-2007 Imendio AB
+ * Copyright (C) 2007-2013 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ * Jonny Lamb <jonny.lamb@collabora.co.uk>
+ * Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ */
+
+#include "config.h"
+#include "log-store-xml-internal.h"
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <glib/gstdio.h>
+
+#include <glib-object.h>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/telepathy-glib-dbus.h>
+
+#include "telepathy-logger/call-event.h"
+#include "telepathy-logger/call-event-internal.h"
+#include "telepathy-logger/entity-internal.h"
+#include "telepathy-logger/event-internal.h"
+#include "telepathy-logger/text-event.h"
+#include "telepathy-logger/text-event-internal.h"
+#include "telepathy-logger/log-iter-xml-internal.h"
+#include "telepathy-logger/log-manager.h"
+#include "telepathy-logger/log-store-internal.h"
+#include "telepathy-logger/log-manager-internal.h"
+#include "telepathy-logger/util-internal.h"
+
+#define DEBUG_FLAG TPL_DEBUG_LOG_STORE
+#include "telepathy-logger/debug-internal.h"
+
+#define LOG_DIR_CREATE_MODE (S_IRUSR | S_IWUSR | S_IXUSR)
+#define LOG_FILE_CREATE_MODE (S_IRUSR | S_IWUSR)
+#define LOG_DIR_CHATROOMS "chatrooms"
+#define LOG_FILENAME_SUFFIX ".log"
+#define LOG_FILENAME_CALL_TAG ".call"
+#define LOG_FILENAME_CALL_SUFFIX LOG_FILENAME_CALL_TAG LOG_FILENAME_SUFFIX
+#define LOG_DATE_PATTERN "[0-9]{8,}"
+#define LOG_FILENAME_PATTERN "^" LOG_DATE_PATTERN "\\" LOG_FILENAME_SUFFIX "$"
+#define LOG_FILENAME_CALL_PATTERN "^" LOG_DATE_PATTERN "\\" LOG_FILENAME_CALL_TAG "\\" LOG_FILENAME_SUFFIX "$"
+
+#define LOG_TIME_FORMAT_FULL "%Y%m%dT%H:%M:%S"
+#define LOG_TIME_FORMAT "%Y%m%d"
+#define LOG_HEADER \
+ "<?xml version='1.0' encoding='utf-8'?>\n" \
+ "<?xml-stylesheet type=\"text/xsl\" href=\"log-store-xml.xsl\"?>\n" \
+ "<log>\n"
+
+#define LOG_FOOTER \
+ "</log>\n"
+
+#define ALL_SUPPORTED_TYPES (TPL_EVENT_MASK_TEXT | TPL_EVENT_MASK_CALL)
+#define CONTAINS_ALL_SUPPORTED_TYPES(type_mask) \
+ (((type_mask) & ALL_SUPPORTED_TYPES) == ALL_SUPPORTED_TYPES)
+
+
+struct _TplLogStoreXmlPriv
+{
+ gchar *basedir;
+ gboolean test_mode;
+ TpAccountManager *account_manager;
+};
+
+enum {
+ PROP_0,
+ PROP_READABLE,
+ PROP_BASEDIR,
+ PROP_TESTMODE
+};
+
+static void log_store_iface_init (gpointer g_iface, gpointer iface_data);
+static void tpl_log_store_xml_get_property (GObject *object, guint param_id, GValue *value,
+ GParamSpec *pspec);
+static void tpl_log_store_xml_set_property (GObject *object, guint param_id, const GValue *value,
+ GParamSpec *pspec);
+static const gchar *log_store_xml_get_basedir (TplLogStoreXml *self);
+static void log_store_xml_set_basedir (TplLogStoreXml *self,
+ const gchar *data);
+
+
+G_DEFINE_TYPE_WITH_CODE (TplLogStoreXml, _tpl_log_store_xml,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TPL_TYPE_LOG_STORE, log_store_iface_init))
+
+
+static void
+log_store_xml_dispose (GObject *object)
+{
+ TplLogStoreXml *self = TPL_LOG_STORE_XML (object);
+ TplLogStoreXmlPriv *priv = self->priv;
+
+ /* FIXME See TP-bug #25569, when dispose a non prepared TP_AM, it
+ might segfault.
+ To avoid it, a *klduge*, a reference in the TplObserver to
+ the TplLogManager is kept, so that until TplObserver is instanced,
+ there will always be a TpLogManager reference and it won't be
+ diposed */
+ if (priv->account_manager != NULL)
+ {
+ g_object_unref (priv->account_manager);
+ priv->account_manager = NULL;
+ }
+
+ G_OBJECT_CLASS (_tpl_log_store_xml_parent_class)->dispose (object);
+}
+
+
+static void
+log_store_xml_finalize (GObject *object)
+{
+ TplLogStoreXml *self = TPL_LOG_STORE_XML (object);
+ TplLogStoreXmlPriv *priv = self->priv;
+
+ if (priv->basedir != NULL)
+ {
+ g_free (priv->basedir);
+ priv->basedir = NULL;
+ }
+}
+
+
+static void
+tpl_log_store_xml_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TplLogStoreXmlPriv *priv = TPL_LOG_STORE_XML (object)->priv;
+
+ switch (param_id)
+ {
+ case PROP_READABLE:
+ g_value_set_boolean (value, TRUE);
+ break;
+ case PROP_BASEDIR:
+ g_value_set_string (value, priv->basedir);
+ break;
+ case PROP_TESTMODE:
+ g_value_set_boolean (value, priv->test_mode);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+
+static void
+tpl_log_store_xml_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TplLogStoreXml *self = TPL_LOG_STORE_XML (object);
+
+ switch (param_id)
+ {
+ case PROP_BASEDIR:
+ log_store_xml_set_basedir (self, g_value_get_string (value));
+ break;
+ case PROP_TESTMODE:
+ self->priv->test_mode = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+
+static void
+_tpl_log_store_xml_class_init (TplLogStoreXmlClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GParamSpec *param_spec;
+
+ object_class->finalize = log_store_xml_finalize;
+ object_class->dispose = log_store_xml_dispose;
+ object_class->get_property = tpl_log_store_xml_get_property;
+ object_class->set_property = tpl_log_store_xml_set_property;
+
+ g_object_class_override_property (object_class, PROP_READABLE, "readable");
+
+ /**
+ * TplLogStoreXml:basedir:
+ *
+ * The log store's basedir.
+ */
+ param_spec = g_param_spec_string ("basedir",
+ "Basedir",
+ "The TplLogStore implementation's name",
+ NULL, G_PARAM_READABLE | G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_BASEDIR, param_spec);
+
+ param_spec = g_param_spec_boolean ("testmode",
+ "TestMode",
+ "Whether the logstore is in testmode, for testsuite use only",
+ FALSE, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TESTMODE, param_spec);
+
+ g_type_class_add_private (object_class, sizeof (TplLogStoreXmlPriv));
+}
+
+
+static void
+_tpl_log_store_xml_init (TplLogStoreXml *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TPL_TYPE_LOG_STORE_XML, TplLogStoreXmlPriv);
+ self->priv->account_manager = tp_account_manager_dup ();
+}
+
+
+static gchar *
+log_store_account_to_dirname (TpAccount *account)
+{
+ const gchar *name;
+
+ g_return_val_if_fail (TP_IS_ACCOUNT (account), NULL);
+
+ name = tp_proxy_get_object_path (account);
+ if (g_str_has_prefix (name, TP_ACCOUNT_OBJECT_PATH_BASE))
+ name += strlen (TP_ACCOUNT_OBJECT_PATH_BASE);
+
+ return g_strdelimit (g_strdup (name), "/", '_');
+}
+
+
+/* id can be NULL, but if present have to be a non zero-lenght string.
+ * If NULL, the returned dir will be composed until the account part.
+ * If non-NULL, the returned dir will be composed until the id part */
+static gchar *
+log_store_xml_get_dir (TplLogStoreXml *self,
+ TpAccount *account,
+ TplEntity *target)
+{
+ gchar *basedir;
+ gchar *escaped_account;
+ gchar *escaped_id = NULL;
+
+ g_return_val_if_fail (TPL_IS_LOG_STORE_XML (self), NULL);
+ g_return_val_if_fail (TP_IS_ACCOUNT (account), NULL);
+
+ escaped_account = log_store_account_to_dirname (account);
+
+ if (target != NULL)
+ {
+ /* FIXME This may be source of bug (does that case still exist ?)
+ * avoid that 1-1 conversation generated from a chatroom, having id similar
+ * to room@conference.domain/My_Alias (in XMPP) are treated as a directory
+ * path, creating My_Alias as a subdirectory of room@conference.domain */
+ escaped_id = g_strdelimit (
+ g_strdup (tpl_entity_get_identifier (target)),
+ "/", '_');
+ }
+
+ if (target != NULL
+ && tpl_entity_get_entity_type (target) == TPL_ENTITY_ROOM)
+ basedir = g_build_path (G_DIR_SEPARATOR_S,
+ log_store_xml_get_basedir (self), escaped_account, LOG_DIR_CHATROOMS,
+ escaped_id, NULL);
+ else
+ basedir = g_build_path (G_DIR_SEPARATOR_S,
+ log_store_xml_get_basedir (self), escaped_account, escaped_id, NULL);
+
+ g_free (escaped_account);
+ g_free (escaped_id);
+
+ return basedir;
+}
+
+
+static const gchar *
+log_store_xml_get_file_suffix (GType type)
+{
+ if (type == TPL_TYPE_TEXT_EVENT)
+ return LOG_FILENAME_SUFFIX;
+ else if (type == TPL_TYPE_CALL_EVENT)
+ return LOG_FILENAME_CALL_SUFFIX;
+ else
+ g_return_val_if_reached (NULL);
+}
+
+
+static gchar *
+log_store_xml_get_timestamp_filename (GType type,
+ gint64 timestamp)
+{
+ gchar *date_str;
+ gchar *filename;
+ GDateTime *date;
+
+ date = g_date_time_new_from_unix_utc (timestamp);
+ date_str = g_date_time_format (date, LOG_TIME_FORMAT);
+ filename = g_strconcat (date_str, log_store_xml_get_file_suffix (type),
+ NULL);
+
+ g_date_time_unref (date);
+ g_free (date_str);
+
+ return filename;
+}
+
+
+static gchar *
+log_store_xml_format_timestamp (gint64 timestamp)
+{
+ GDateTime *ts;
+ gchar *ts_str;
+
+ ts = g_date_time_new_from_unix_utc (timestamp);
+ ts_str = g_date_time_format (ts, LOG_TIME_FORMAT_FULL);
+
+ g_date_time_unref (ts);
+
+ return ts_str;
+}
+
+
+static gchar *
+log_store_xml_get_timestamp_from_event (TplEvent *event)
+{
+ return log_store_xml_format_timestamp (tpl_event_get_timestamp (event));
+}
+
+
+static gchar *
+log_store_xml_get_filename (TplLogStoreXml *self,
+ TpAccount *account,
+ TplEntity *target,
+ GType type,
+ gint64 timestamp)
+{
+ gchar *id_dir;
+ gchar *timestamp_str;
+ gchar *filename;
+
+ id_dir = log_store_xml_get_dir (self, account, target);
+ timestamp_str = log_store_xml_get_timestamp_filename (type, timestamp);
+ filename = g_build_filename (id_dir, timestamp_str, NULL);
+
+ g_free (id_dir);
+ g_free (timestamp_str);
+
+ return filename;
+}
+
+
+/* this is a method used at the end of the add_event process, used by any
+ * Event<Type> instance. it should the only method allowed to write to the
+ * store */
+static gboolean
+_log_store_xml_write_to_store (TplLogStoreXml *self,
+ TpAccount *account,
+ TplEntity *target,
+ const gchar *event,
+ GType type,
+ gint64 timestamp,
+ GError **error)
+{
+ FILE *file;
+ gchar *filename;
+ gchar *basedir;
+ gboolean ret = TRUE;
+
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+ g_return_val_if_fail (TPL_IS_LOG_STORE_XML (self), FALSE);
+ g_return_val_if_fail (TP_IS_ACCOUNT (account), FALSE);
+ g_return_val_if_fail (TPL_IS_ENTITY (target), FALSE);
+
+
+ filename = log_store_xml_get_filename (self, account, target, type, timestamp);
+ basedir = g_path_get_dirname (filename);
+
+ if (!g_file_test (basedir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
+ {
+ DEBUG ("Creating directory: '%s'", basedir);
+ g_mkdir_with_parents (basedir, LOG_DIR_CREATE_MODE);
+ }
+
+ g_free (basedir);
+
+ if (!g_file_test (filename, G_FILE_TEST_EXISTS))
+ {
+ file = g_fopen (filename, "w+");
+ if (file != NULL)
+ g_fprintf (file, LOG_HEADER);
+
+ g_chmod (filename, LOG_FILE_CREATE_MODE);
+ }
+ else
+ {
+ file = g_fopen (filename, "r+");
+ if (file != NULL)
+ fseek (file, -strlen (LOG_FOOTER), SEEK_END);
+ }
+
+ if (file == NULL)
+ {
+ g_set_error (error, TPL_LOG_STORE_ERROR,
+ TPL_LOG_STORE_ERROR_FAILED,
+ "Couldn't open log file: %s", filename);
+ ret = FALSE;
+ goto out;
+ }
+
+ g_fprintf (file, "%s", event);
+ DEBUG ("%s: written: %s", filename, event);
+
+ fclose (file);
+ out:
+ g_free (filename);
+ return ret;
+}
+
+
+static gboolean
+add_text_event (TplLogStoreXml *self,
+ TplTextEvent *message,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gint64 timestamp;
+ TpDBusDaemon *bus_daemon;
+ TpAccount *account;
+ TplEntity *sender;
+ const gchar *body_str;
+ const gchar *token_str;
+ gchar *avatar_token = NULL;
+ gchar *body = NULL;
+ gchar *time_str = NULL;
+ gchar *contact_name = NULL;
+ gchar *contact_id = NULL;
+ GString *event = NULL;
+ TpChannelTextMessageType msg_type;
+
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+ g_return_val_if_fail (TPL_IS_LOG_STORE_XML (self), FALSE);
+ g_return_val_if_fail (TPL_IS_TEXT_EVENT (message), FALSE);
+
+ bus_daemon = tp_dbus_daemon_dup (error);
+ if (bus_daemon == NULL)
+ {
+ DEBUG ("Error acquiring bus daemon: %s", (*error)->message);
+ goto out;
+ }
+
+ account = tpl_event_get_account (TPL_EVENT (message));
+
+ body_str = tpl_text_event_get_message (message);
+ if (TPL_STR_EMPTY (body_str))
+ {
+ g_set_error (error, TPL_LOG_STORE_ERROR,
+ TPL_LOG_STORE_ERROR_FAILED,
+ "The message body is empty or NULL");
+ goto out;
+ }
+
+ body = g_markup_escape_text (body_str, -1);
+ msg_type = tpl_text_event_get_message_type (message);
+ time_str = log_store_xml_get_timestamp_from_event (
+ TPL_EVENT (message));
+
+ sender = tpl_event_get_sender (TPL_EVENT (message));
+
+ if (sender != NULL)
+ {
+ contact_id = g_markup_escape_text (tpl_entity_get_identifier (sender), -1);
+ contact_name = g_markup_escape_text (tpl_entity_get_alias (sender), -1);
+ avatar_token = g_markup_escape_text (tpl_entity_get_avatar_token (sender),
+ -1);
+ }
+
+ event = g_string_new (NULL);
+ g_string_printf (event, "<message time='%s' id='%s' name='%s' "
+ "token='%s' isuser='%s' type='%s'",
+ time_str,
+ contact_id ? contact_id : "",
+ contact_name ? contact_name : "",
+ avatar_token ? avatar_token : "",
+ (sender && tpl_entity_get_entity_type (sender)
+ == TPL_ENTITY_SELF) ? "true" : "false",
+ _tpl_text_event_message_type_to_str (msg_type));
+
+ token_str = tpl_text_event_get_message_token (message);
+ if (!TPL_STR_EMPTY (token_str))
+ {
+ gchar *message_token = g_markup_escape_text (token_str, -1);
+ g_string_append_printf (event, " message-token='%s'", message_token);
+ g_free (message_token);
+
+ token_str = tpl_text_event_get_supersedes_token (message);
+ if (!TPL_STR_EMPTY (token_str))
+ {
+ gchar *supersedes_token = g_markup_escape_text (token_str, -1);
+ guint edit_timestamp;
+ g_string_append_printf (event, " supersedes-token='%s'",
+ supersedes_token);
+
+ edit_timestamp = tpl_text_event_get_edit_timestamp (message);
+ if (edit_timestamp != 0)
+ {
+ gchar *edit_timestamp_str =
+ log_store_xml_format_timestamp (edit_timestamp);
+ g_string_append_printf (event, " edit-timestamp='%s'",
+ edit_timestamp_str);
+ g_free (edit_timestamp_str);
+ }
+ }
+
+ }
+
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (message));
+
+ g_string_append_printf (event, ">%s</message>\n" LOG_FOOTER, body);
+
+ DEBUG ("writing text event from %s (ts %s)",
+ contact_id, time_str);
+
+ ret = _log_store_xml_write_to_store (self, account,
+ _tpl_event_get_target (TPL_EVENT (message)), event->str,
+ TPL_TYPE_TEXT_EVENT, timestamp, error);
+
+out:
+ g_free (contact_id);
+ g_free (contact_name);
+ g_free (time_str);
+ g_free (body);
+ g_string_free (event, TRUE);
+ g_free (avatar_token);
+
+ if (bus_daemon != NULL)
+ g_object_unref (bus_daemon);
+
+ return ret;
+}
+
+
+static gboolean
+add_call_event (TplLogStoreXml *self,
+ TplCallEvent *event,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ TpDBusDaemon *bus_daemon;
+ TpAccount *account;
+ TplEntity *sender;
+ TplEntity *actor;
+ TplEntity *target;
+ gchar *time_str = NULL;
+ gchar *sender_avatar = NULL;
+ gchar *sender_name = NULL;
+ gchar *sender_id = NULL;
+ gchar *actor_name = NULL;
+ gchar *actor_avatar = NULL;
+ gchar *actor_id = NULL;
+ gchar *log_str = NULL;
+ TpCallStateChangeReason reason;
+
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+ g_return_val_if_fail (TPL_IS_LOG_STORE_XML (self), FALSE);
+ g_return_val_if_fail (TPL_IS_CALL_EVENT (event), FALSE);
+
+ bus_daemon = tp_dbus_daemon_dup (error);
+ if (bus_daemon == NULL)
+ {
+ DEBUG ("Error acquiring bus daemon: %s", (*error)->message);
+ goto out;
+ }
+
+ account = tpl_event_get_account (TPL_EVENT (event));
+
+ time_str = log_store_xml_get_timestamp_from_event (
+ TPL_EVENT (event));
+ reason = tpl_call_event_get_end_reason (event);
+
+ sender = tpl_event_get_sender (TPL_EVENT (event));
+ actor = tpl_call_event_get_end_actor (event);
+ target = _tpl_event_get_target (TPL_EVENT (event));
+
+ if (sender != NULL)
+ {
+ sender_id = g_markup_escape_text (tpl_entity_get_identifier (sender), -1);
+ sender_name = g_markup_escape_text (tpl_entity_get_alias (sender), -1);
+ sender_avatar = g_markup_escape_text (tpl_entity_get_avatar_token (sender),
+ -1);
+ }
+
+ if (actor != NULL)
+ {
+ actor_id = g_markup_escape_text (tpl_entity_get_identifier (actor), -1);
+ actor_name = g_markup_escape_text (tpl_entity_get_alias (actor), -1);
+ actor_avatar = g_markup_escape_text (tpl_entity_get_avatar_token (actor),
+ -1);
+ }
+
+
+ log_str = g_strdup_printf ("<call time='%s' "
+ "id='%s' name='%s' isuser='%s' token='%s' "
+ "duration='%" G_GINT64_FORMAT "' "
+ "actor='%s' actortype='%s' "
+ "actorname='%s' actortoken='%s' "
+ "reason='%s' detail='%s'/>\n"
+ LOG_FOOTER,
+ time_str,
+ sender_id ? sender_id : "",
+ sender_name ? sender_name : "",
+ (sender && tpl_entity_get_entity_type (sender) ==
+ TPL_ENTITY_SELF) ? "true" : "false",
+ sender_avatar ? sender_avatar : "",
+ tpl_call_event_get_duration (event),
+ actor_id ? actor_id : "",
+ actor ? _tpl_entity_type_to_str (tpl_entity_get_entity_type (actor)) : "",
+ actor_name ? actor_name : "",
+ actor_avatar ? actor_avatar : "",
+ _tpl_call_event_end_reason_to_str (reason),
+ tpl_call_event_get_detailed_end_reason (event));
+
+ DEBUG ("writing call event from %s (ts %s)",
+ tpl_entity_get_identifier (target),
+ time_str);
+
+ ret = _log_store_xml_write_to_store (self, account, target, log_str,
+ TPL_TYPE_CALL_EVENT, tpl_event_get_timestamp (TPL_EVENT (event)),
+ error);
+
+out:
+ g_free (sender_id);
+ g_free (sender_name);
+ g_free (sender_avatar);
+ g_free (actor_id);
+ g_free (actor_name);
+ g_free (actor_avatar);
+ g_free (time_str);
+ g_free (log_str);
+
+ if (bus_daemon != NULL)
+ g_object_unref (bus_daemon);
+
+ return ret;
+}
+
+
+/* First of two phases selection: understand the type Event */
+static gboolean
+log_store_xml_add_event (TplLogStore *store,
+ TplEvent *event,
+ GError **error)
+{
+ TplLogStoreXml *self = TPL_LOG_STORE_XML (store);
+
+ g_return_val_if_fail (TPL_IS_EVENT (event), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ if (TPL_IS_TEXT_EVENT (event))
+ return add_text_event (self, TPL_TEXT_EVENT (event), error);
+ else if (TPL_IS_CALL_EVENT (event))
+ return add_call_event (self, TPL_CALL_EVENT (event), error);
+
+ DEBUG ("TplEntry not handled by this LogStore (%s). "
+ "Ignoring Event", _tpl_log_store_get_name (store));
+ /* do not consider it an error, this LogStore simply do not want/need
+ * this Event */
+ return TRUE;
+}
+
+
+static gboolean
+log_store_xml_exists_in_directory (const gchar *dirname,
+ GRegex *regex,
+ gint type_mask,
+ gboolean recursive)
+{
+ gboolean exists;
+ const gchar *basename;
+ GDir *dir;
+
+ DEBUG ("Looking in directory '%s' %s",
+ dirname, recursive ? "resursively" : "");
+
+ dir = g_dir_open (dirname, 0, NULL);
+ exists = (dir != NULL);
+
+ if (CONTAINS_ALL_SUPPORTED_TYPES (type_mask) || !exists)
+ goto out;
+
+ exists = FALSE;
+ while ((basename = g_dir_read_name (dir)) != NULL)
+ {
+ gchar *filename;
+
+ filename = g_build_filename (dirname, basename, NULL);
+
+ DEBUG ("Matching with filename '%s'", basename);
+
+ if (recursive && g_file_test (filename, G_FILE_TEST_IS_DIR))
+ exists = log_store_xml_exists_in_directory (filename, regex, type_mask,
+ !tp_strdiff (basename, LOG_DIR_CHATROOMS));
+ else if (g_file_test (filename, G_FILE_TEST_IS_REGULAR))
+ exists = g_regex_match (regex, basename, 0, 0);
+
+ g_free (filename);
+
+ if (exists)
+ break;
+ }
+
+out:
+ if (dir != NULL)
+ g_dir_close (dir);
+
+ return exists;
+}
+
+
+static GRegex *
+log_store_xml_create_filename_regex (gint type_mask)
+{
+ GString *pattern;
+ GRegex *regex = NULL;
+ GError *error = NULL;
+
+ pattern = g_string_new ("");
+
+ if (type_mask & TPL_EVENT_MASK_TEXT)
+ g_string_append (pattern, LOG_FILENAME_PATTERN);
+
+ if (type_mask & TPL_EVENT_MASK_CALL)
+ g_string_append_printf (pattern,
+ "%s" LOG_FILENAME_CALL_PATTERN,
+ pattern->len == 0 ? "" : "|");
+
+ if (pattern->len == 0)
+ goto out;
+
+ DEBUG ("Pattern is '%s'", pattern->str);
+
+ regex = g_regex_new (pattern->str, G_REGEX_OPTIMIZE, 0, &error);
+
+ if (regex == NULL)
+ {
+ DEBUG ("Failed to create regex: %s", error->message);
+ g_error_free (error);
+ }
+
+out:
+ g_string_free (pattern, TRUE);
+
+ return regex;
+}
+
+
+static gboolean
+log_store_xml_exists (TplLogStore *store,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask)
+{
+ TplLogStoreXml *self = (TplLogStoreXml *) store;
+ gchar *dirname;
+ GRegex *regex;
+ gboolean exists = FALSE;
+
+ g_return_val_if_fail (TPL_IS_LOG_STORE_XML (self), FALSE);
+ g_return_val_if_fail (TP_IS_ACCOUNT (account), FALSE);
+ g_return_val_if_fail (target == NULL || TPL_IS_ENTITY (target), FALSE);
+
+ dirname = log_store_xml_get_dir (self, account, target);
+ regex = log_store_xml_create_filename_regex (type_mask);
+
+ if (regex != NULL)
+ exists = log_store_xml_exists_in_directory (dirname, regex, type_mask,
+ target == NULL);
+
+ g_free (dirname);
+
+ if (regex != NULL)
+ g_regex_unref (regex);
+
+ return exists;
+}
+
+static GDate *
+create_date_from_string (const gchar *str)
+{
+ GDate *date;
+ guint u;
+ guint day, month, year;
+
+ if (sscanf (str, "%u", &u) != 1)
+ return NULL;
+
+ day = (u % 100);
+ month = ((u / 100) % 100);
+ year = (u / 10000);
+
+ if (!g_date_valid_dmy (day, month, year))
+ return NULL;
+
+ date = g_date_new_dmy (day, month, year);
+
+ return date;
+}
+
+
+static gboolean
+log_store_xml_match_in_file (const gchar *filename,
+ GRegex *regex)
+{
+ gboolean retval = FALSE;
+ GMappedFile *file;
+ gsize length;
+ gchar *contents = NULL;
+
+ file = g_mapped_file_new (filename, FALSE, NULL);
+ if (file == NULL)
+ goto out;
+
+ length = g_mapped_file_get_length (file);
+ contents = g_mapped_file_get_contents (file);
+
+ if (length == 0 || contents == NULL)
+ goto out;
+
+ retval = g_regex_match_full (regex, contents, length, 0, 0, NULL, NULL);
+
+ DEBUG ("%s pattern '%s' in file '%s'",
+ retval ? "Matched" : "Not matched",
+ g_regex_get_pattern (regex),
+ filename);
+
+out:
+ if (file != NULL)
+ g_mapped_file_unref (file);
+
+ return retval;
+}
+
+
+static GList *
+log_store_xml_get_dates (TplLogStore *store,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask)
+{
+ TplLogStoreXml *self = (TplLogStoreXml *) store;
+ GList *dates = NULL;
+ GList *l;
+ gchar *directory = NULL;
+ GDir *dir = NULL;
+ GString *pattern = NULL;
+ GRegex *regex = NULL;
+ const gchar *basename;
+
+ g_return_val_if_fail (TPL_IS_LOG_STORE_XML (self), NULL);
+ g_return_val_if_fail (TP_IS_ACCOUNT (account), NULL);
+ g_return_val_if_fail (TPL_IS_ENTITY (target), NULL);
+
+ directory = log_store_xml_get_dir (self, account, target);
+ dir = g_dir_open (directory, 0, NULL);
+ if (!dir)
+ {
+ DEBUG ("Could not open directory:'%s'", directory);
+ goto out;
+ }
+
+ DEBUG ("Collating a list of dates in:'%s'", directory);
+ regex = log_store_xml_create_filename_regex (type_mask);
+
+ if (regex == NULL)
+ goto out;
+
+ while ((basename = g_dir_read_name (dir)) != NULL)
+ {
+ const gchar *p;
+ gchar *str;
+ GDate *date;
+
+ if (!g_regex_match (regex, basename, 0, NULL))
+ continue;
+
+ p = strstr (basename, LOG_FILENAME_CALL_SUFFIX);
+
+ if (p == NULL)
+ p = strstr (basename, LOG_FILENAME_SUFFIX);
+
+ str = g_strndup (basename, p - basename);
+
+ if (str == NULL)
+ continue;
+
+ date = create_date_from_string (str);
+
+ if (date != NULL)
+ dates = g_list_insert_sorted (dates, date,
+ (GCompareFunc) g_date_compare);
+
+ g_free (str);
+ }
+
+ /* Filter out duplicate dates in-place */
+ for (l = dates; g_list_next (l) != NULL; l = g_list_next (l))
+ {
+ GList *next = g_list_next (l);
+
+ if (g_date_compare ((GDate *) next->data, (GDate *) l->data) == 0)
+ {
+ g_date_free ((GDate *) next->data);
+ l = g_list_delete_link (l, next);
+ }
+ }
+
+out:
+ g_free (directory);
+
+ if (dir != NULL)
+ g_dir_close (dir);
+
+ if (pattern != NULL)
+ g_string_free (pattern, TRUE);
+
+ if (regex != NULL)
+ g_regex_unref (regex);
+
+ DEBUG ("Parsed %d dates", g_list_length (dates));
+
+ return dates;
+}
+
+
+static gchar *
+log_store_xml_get_filename_for_date (TplLogStoreXml *self,
+ TpAccount *account,
+ TplEntity *target,
+ const GDate *date,
+ GType type)
+{
+ gchar *basedir;
+ gchar *timestamp;
+ gchar *filename;
+ gchar str[9];
+
+ g_return_val_if_fail (TPL_IS_LOG_STORE_XML (self), NULL);
+ g_return_val_if_fail (TP_IS_ACCOUNT (account), NULL);
+ g_return_val_if_fail (TPL_IS_ENTITY (target), NULL);
+ g_return_val_if_fail (date != NULL, NULL);
+
+ g_date_strftime (str, 9, "%Y%m%d", date);
+
+ basedir = log_store_xml_get_dir (self, account, target);
+ timestamp = g_strconcat (str, log_store_xml_get_file_suffix (type), NULL);
+ filename = g_build_filename (basedir, timestamp, NULL);
+
+ g_free (basedir);
+ g_free (timestamp);
+
+ return filename;
+}
+
+
+static TplLogSearchHit *
+log_store_xml_search_hit_new (TplLogStoreXml *self,
+ const gchar *filename)
+{
+ TplLogSearchHit *hit;
+ gchar *account_name;
+ const gchar *end;
+ gchar **strv;
+ guint len;
+ GList *accounts, *l;
+ gchar *tmp;
+ TpAccount *account = NULL;
+ GDate *date;
+ const gchar *chat_id;
+ gboolean is_chatroom;
+ TplEntity *target;
+
+ g_return_val_if_fail (TPL_IS_LOG_STORE_XML (self), NULL);
+ g_return_val_if_fail (!TPL_STR_EMPTY (filename), NULL);
+ g_return_val_if_fail (g_str_has_suffix (filename, LOG_FILENAME_SUFFIX),
+ NULL);
+
+ strv = g_strsplit (filename, G_DIR_SEPARATOR_S, -1);
+ len = g_strv_length (strv);
+
+ end = strstr (strv[len - 1], LOG_FILENAME_SUFFIX);
+ tmp = g_strndup (strv[len - 1], end - strv[len - 1]);
+ date = create_date_from_string (tmp);
+ g_free (tmp);
+ chat_id = strv[len - 2];
+ is_chatroom = (strcmp (strv[len - 3], LOG_DIR_CHATROOMS) == 0);
+
+ if (is_chatroom)
+ account_name = strv[len - 4];
+ else
+ account_name = strv[len - 3];
+
+ /* FIXME: This assumes the account manager is prepared, but the
+ * synchronous API forces this. See bug #599189. */
+ accounts = tp_account_manager_dup_usable_accounts (
+ self->priv->account_manager);
+
+ for (l = accounts; l != NULL && account == NULL; l = g_list_next (l))
+ {
+ TpAccount *acc = TP_ACCOUNT (l->data);
+ gchar *name;
+
+ name = log_store_account_to_dirname (acc);
+ if (!tp_strdiff (name, account_name))
+ account = acc;
+ g_free (name);
+ }
+ g_list_free_full (accounts, g_object_unref);
+
+ if (is_chatroom)
+ target = tpl_entity_new_from_room_id (chat_id);
+ else
+ target = tpl_entity_new (chat_id, TPL_ENTITY_CONTACT, NULL, NULL);
+
+ hit = _tpl_log_manager_search_hit_new (account, target, date);
+
+ g_strfreev (strv);
+ g_date_free (date);
+ g_object_unref (target);
+
+ return hit;
+}
+
+
+static TplEvent *
+parse_text_node (TplLogStoreXml *self,
+ xmlNodePtr node,
+ gboolean is_room,
+ const gchar *target_id,
+ TpAccount *account)
+{
+ TplEvent *event;
+ TplEntity *sender;
+ TplEntity *receiver;
+ gchar *time_str;
+ gint64 timestamp;
+ gchar *edit_time_str;
+ gint64 edit_timestamp = 0;
+ gchar *sender_id;
+ gchar *sender_name;
+ gchar *sender_avatar_token;
+ gchar *body;
+ gchar *message_token;
+ gchar *supersedes_token;
+ gchar *is_user_str;
+ gboolean is_user = FALSE;
+ gchar *msg_type_str;
+ TpChannelTextMessageType msg_type = TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL;
+
+ body = (gchar *) xmlNodeGetContent (node);
+ time_str = (gchar *) xmlGetProp (node, (const xmlChar *) "time");
+ edit_time_str = (gchar *) xmlGetProp (node,
+ (const xmlChar *) "edit-timestamp");
+ sender_id = (gchar *) xmlGetProp (node, (const xmlChar *) "id");
+ sender_name = (gchar *) xmlGetProp (node, (const xmlChar *) "name");
+ sender_avatar_token = (gchar *) xmlGetProp (node,
+ (const xmlChar *) "token");
+ message_token = (gchar *) xmlGetProp (node,
+ (const xmlChar *) "message-token");
+ supersedes_token = (gchar *) xmlGetProp (node,
+ (const xmlChar *) "supersedes-token");
+ is_user_str = (gchar *) xmlGetProp (node, (const xmlChar *) "isuser");
+ msg_type_str = (gchar *) xmlGetProp (node, (const xmlChar *) "type");
+
+ if (is_user_str != NULL)
+ is_user = (!tp_strdiff (is_user_str, "true"));
+
+ if (msg_type_str != NULL)
+ msg_type = _tpl_text_event_message_type_from_str (msg_type_str);
+
+ timestamp = _tpl_time_parse (time_str);
+
+ if (supersedes_token != NULL && edit_time_str != NULL)
+ {
+ edit_timestamp = _tpl_time_parse (edit_time_str);
+ }
+
+ if (is_room)
+ receiver = tpl_entity_new_from_room_id (target_id);
+ else if (is_user)
+ receiver = tpl_entity_new (target_id, TPL_ENTITY_CONTACT, NULL, NULL);
+ else
+ receiver = tpl_entity_new (tp_account_get_normalized_name (account),
+ TPL_ENTITY_SELF, tp_account_get_nickname (account), NULL);
+
+ sender = tpl_entity_new (sender_id,
+ is_user ? TPL_ENTITY_SELF : TPL_ENTITY_CONTACT,
+ sender_name, sender_avatar_token);
+
+ event = g_object_new (TPL_TYPE_TEXT_EVENT,
+ /* TplEvent */
+ "account", account,
+ /* MISSING: "channel-path", channel_path, */
+ "receiver", receiver,
+ "sender", sender,
+ "timestamp", timestamp,
+ /* TplTextEvent */
+ "message-type", msg_type,
+ "message", body,
+ "message-token", message_token,
+ "supersedes-token", supersedes_token,
+ "edit-timestamp", edit_timestamp,
+ NULL);
+
+ g_object_unref (sender);
+ g_object_unref (receiver);
+ xmlFree (time_str);
+ xmlFree (edit_time_str);
+ xmlFree (sender_id);
+ xmlFree (sender_name);
+ xmlFree (body);
+ xmlFree (message_token);
+ xmlFree (supersedes_token);
+ xmlFree (is_user_str);
+ xmlFree (msg_type_str);
+ xmlFree (sender_avatar_token);
+
+ return event;
+}
+
+static gchar *
+dup_detailed_reason (xmlNodePtr node)
+{
+ gchar *d, *result;
+ const gchar *old_tp_prefix = "org.freedesktop.Telepathy.Error";
+
+ d = (char *) xmlGetProp (node, (const xmlChar *) "detail");
+
+ /* Ensure log backward compatiblity if the reason is using the old (pre 1.0)
+ * Telepathy prefix. */
+ if (!g_str_has_prefix (d, old_tp_prefix))
+ {
+ result = g_strdup (d);
+ xmlFree (d);
+ return result;
+ }
+
+ result = g_strdup_printf ("%s.%s", TP_ERROR_PREFIX,
+ d + strlen (old_tp_prefix) + 1);
+
+ xmlFree (d);
+ return result;
+}
+
+static TplEvent *
+parse_call_node (TplLogStoreXml *self,
+ xmlNodePtr node,
+ gboolean is_room,
+ const gchar *target_id,
+ TpAccount *account)
+{
+ TplEvent *event;
+ TplEntity *sender;
+ TplEntity *receiver;
+ TplEntity *actor;
+ gchar *time_str;
+ gint64 timestamp;
+ gchar *sender_id;
+ gchar *sender_name;
+ gchar *sender_avatar_token;
+ gchar *is_user_str;
+ gboolean is_user = FALSE;
+ gchar *actor_id;
+ gchar *actor_name;
+ gchar *actor_type;
+ gchar *actor_avatar_token;
+ gchar *duration_str;
+ gint64 duration = -1;
+ gchar *reason_str;
+ TpCallStateChangeReason reason = TP_CALL_STATE_CHANGE_REASON_UNKNOWN;
+ gchar *detailed_reason;
+
+ time_str = (gchar *) xmlGetProp (node, (const xmlChar *) "time");
+ sender_id = (gchar *) xmlGetProp (node, (const xmlChar *) "id");
+ sender_name = (gchar *) xmlGetProp (node, (const xmlChar *) "name");
+ sender_avatar_token = (gchar *) xmlGetProp (node,
+ (const xmlChar *) "token");
+ is_user_str = (gchar *) xmlGetProp (node, (const xmlChar *) "isuser");
+ duration_str = (char *) xmlGetProp (node, (const xmlChar*) "duration");
+ actor_id = (char *) xmlGetProp (node, (const xmlChar *) "actor");
+ actor_name = (char *) xmlGetProp (node, (const xmlChar *) "actorname");
+ actor_type = (char *) xmlGetProp (node, (const xmlChar *) "actortype");
+ actor_avatar_token = (char *) xmlGetProp (node,
+ (const xmlChar *) "actortoken");
+ reason_str = (char *) xmlGetProp (node, (const xmlChar *) "reason");
+ detailed_reason = dup_detailed_reason (node);
+
+ if (is_user_str != NULL)
+ is_user = (!tp_strdiff (is_user_str, "true"));
+
+ if (reason_str != NULL)
+ reason = _tpl_call_event_str_to_end_reason (reason_str);
+
+ timestamp = _tpl_time_parse (time_str);
+
+ if (is_room)
+ receiver = tpl_entity_new_from_room_id (target_id);
+ else if (is_user)
+ receiver = tpl_entity_new (target_id, TPL_ENTITY_CONTACT, NULL, NULL);
+ else
+ receiver = tpl_entity_new (tp_account_get_normalized_name (account),
+ TPL_ENTITY_SELF, tp_account_get_nickname (account), NULL);
+
+ sender = tpl_entity_new (sender_id,
+ is_user ? TPL_ENTITY_SELF : TPL_ENTITY_CONTACT,
+ sender_name, sender_avatar_token);
+
+ actor = tpl_entity_new (actor_id,
+ _tpl_entity_type_from_str (actor_type),
+ actor_name, actor_avatar_token);
+
+ if (duration_str != NULL)
+ duration = atoll (duration_str);
+
+ event = g_object_new (TPL_TYPE_CALL_EVENT,
+ /* TplEvent */
+ "account", account,
+ /* MISSING: "channel-path", channel_path, */
+ "receiver", receiver,
+ "sender", sender,
+ "timestamp", timestamp,
+ /* TplCallEvent */
+ "duration", duration,
+ "end-actor", actor,
+ "end-reason", reason,
+ "detailed-end-reason", detailed_reason,
+ NULL);
+
+ g_object_unref (sender);
+ g_object_unref (receiver);
+ g_object_unref (actor);
+ xmlFree (time_str);
+ xmlFree (sender_id);
+ xmlFree (sender_name);
+ xmlFree (sender_avatar_token);
+ xmlFree (is_user_str);
+ xmlFree (actor_id);
+ xmlFree (actor_name);
+ xmlFree (actor_type);
+ xmlFree (actor_avatar_token);
+ xmlFree (duration_str);
+ xmlFree (reason_str);
+ g_free (detailed_reason);
+
+ return event;
+}
+
+
+static void
+event_queue_replace_and_supersede (GQueue *events,
+ GList *index,
+ GHashTable *superseded_links,
+ TplTextEvent *event)
+{
+ _tpl_text_event_add_supersedes (event, index->data);
+ g_hash_table_insert (superseded_links,
+ (gpointer) tpl_text_event_get_message_token (index->data), index);
+ g_object_unref (index->data);
+ index->data = event;
+}
+
+
+static GList *
+event_queue_add_text_event (GQueue *events,
+ GList *index,
+ GHashTable *superseded_links,
+ TplTextEvent *event)
+{
+ GList *l = NULL;
+ const gchar *supersedes_token = tpl_text_event_get_supersedes_token (event);
+ TplTextEvent *dummy_event;
+
+ if (supersedes_token == NULL)
+ return _tpl_event_queue_insert_sorted_after (events, index,
+ TPL_EVENT (event));
+
+ l = g_hash_table_lookup (superseded_links, supersedes_token);
+ if (l != NULL)
+ {
+ event_queue_replace_and_supersede (events, l, superseded_links, event);
+ return index;
+ }
+
+ /* Search backwards from "now" and insert (but don't update "now") */
+ for (l = index; l != NULL; l = g_list_previous (l))
+ {
+ if (!tp_strdiff (tpl_text_event_get_message_token (l->data),
+ supersedes_token))
+ {
+ event_queue_replace_and_supersede (events, l, superseded_links,
+ event);
+ return index;
+ }
+ }
+
+ DEBUG ("Can't find event %s (superseded by %s). "
+ "Adding Dummy event.",
+ supersedes_token, tpl_text_event_get_message_token (event));
+
+ dummy_event = g_object_new (TPL_TYPE_TEXT_EVENT,
+ /* TplEvent */
+ "account", tpl_event_get_account (TPL_EVENT (event)),
+ /* MISSING: "channel-path", channel_path, */
+ "receiver", tpl_event_get_receiver (TPL_EVENT (event)),
+ "sender", tpl_event_get_sender (TPL_EVENT (event)),
+ "timestamp", tpl_event_get_timestamp (TPL_EVENT (event)),
+ /* TplTextEvent */
+ "message-type", tpl_text_event_get_message_type (event),
+ "message", "",
+ "message-token", supersedes_token,
+ NULL);
+
+ index = _tpl_event_queue_insert_sorted_after (events, index,
+ TPL_EVENT (dummy_event));
+ event_queue_replace_and_supersede (events, index, superseded_links, event);
+ return index;
+}
+
+
+/* returns a Glist of TplEvent instances.
+ *
+ * @account needs to have TP_ACCOUNT_FEATURE_CORE prepared (we use
+ * tp_account_get_nickname() and tp_account_get_normalized_name() which rely
+ * on CORE being prepared).
+ * */
+static void
+log_store_xml_get_events_for_file (TplLogStoreXml *self,
+ TpAccount *account,
+ const gchar *filename,
+ GType type,
+ GQueue *events)
+{
+ xmlParserCtxtPtr ctxt;
+ xmlDocPtr doc;
+ xmlNodePtr log_node;
+ xmlNodePtr node;
+ gboolean is_room;
+ gchar *dirname;
+ gchar *tmp;
+ gchar *target_id;
+ GHashTable *supersedes_links;
+ guint num_events = 0;
+ GList *index;
+
+ g_return_if_fail (TPL_IS_LOG_STORE_XML (self));
+ g_return_if_fail (TP_IS_ACCOUNT (account));
+ g_return_if_fail (!TPL_STR_EMPTY (filename));
+ g_return_if_fail (tp_proxy_is_prepared (account, TP_ACCOUNT_FEATURE_CORE));
+
+ DEBUG ("Attempting to parse filename:'%s'...", filename);
+
+ if (!g_file_test (filename, G_FILE_TEST_EXISTS))
+ {
+ DEBUG ("Filename:'%s' does not exist", filename);
+ return;
+ }
+
+ /* Create parser. */
+ ctxt = xmlNewParserCtxt ();
+
+ /* Parse and validate the file. */
+ doc = xmlCtxtReadFile (ctxt, filename, NULL, 0);
+ if (!doc)
+ {
+ g_warning ("Failed to parse file:'%s'", filename);
+ xmlFreeParserCtxt (ctxt);
+ return;
+ }
+
+ /* The root node, presets. */
+ log_node = xmlDocGetRootElement (doc);
+ if (!log_node)
+ {
+ xmlFreeDoc (doc);
+ xmlFreeParserCtxt (ctxt);
+ return;
+ }
+
+ /* Guess the target based on directory name */
+ dirname = g_path_get_dirname (filename);
+ target_id = g_path_get_basename (dirname);
+
+ /* Determine if it's a chatroom */
+ tmp = dirname;
+ dirname = g_path_get_dirname (tmp);
+ g_free (tmp);
+ tmp = g_path_get_basename (dirname);
+ is_room = (g_strcmp0 (LOG_DIR_CHATROOMS, tmp) == 0);
+ g_free (dirname);
+ g_free (tmp);
+
+ /* Temporary hash from (borrowed) supersedes-token to (borrowed) link in
+ * events, for any event that was once in events, but has since been
+ * superseded (and therefore won't be found by a linear search). */
+ supersedes_links = g_hash_table_new (g_str_hash, g_str_equal);
+
+ /* Now get the events. */
+ index = NULL;
+ for (node = log_node->children; node; node = node->next)
+ {
+ TplEvent *event = NULL;
+
+ if (type == TPL_TYPE_TEXT_EVENT
+ && strcmp ((const gchar *) node->name, "message") == 0)
+ {
+ event = parse_text_node (self, node, is_room, target_id, account);
+
+ if (event == NULL)
+ continue;
+
+ index = event_queue_add_text_event (events, index,
+ supersedes_links, TPL_TEXT_EVENT (event));
+ num_events++;
+ }
+ else if (type == TPL_TYPE_CALL_EVENT
+ && strcmp ((const char*) node->name, "call") == 0)
+ {
+ event = parse_call_node (self, node, is_room, target_id, account);
+
+ if (event == NULL)
+ continue;
+
+ index = _tpl_event_queue_insert_sorted_after (events, index, event);
+ num_events++;
+ }
+ }
+
+ DEBUG ("Parsed %u events", num_events);
+
+ g_free (target_id);
+ xmlFreeDoc (doc);
+ xmlFreeParserCtxt (ctxt);
+ g_hash_table_unref (supersedes_links);
+}
+
+
+/* If dir is NULL, basedir will be used instead.
+ * Used to make possible the full search vs. specific subtrees search */
+static GList *
+log_store_xml_get_all_files (TplLogStoreXml *self,
+ const gchar *dir,
+ gint type_mask)
+{
+ GDir *gdir;
+ GList *files = NULL;
+ const gchar *name;
+ const gchar *basedir;
+ GRegex *regex;
+
+ g_return_val_if_fail (TPL_IS_LOG_STORE_XML (self), NULL);
+ /* dir can be NULL, do not check */
+
+ basedir = (dir != NULL) ? dir : log_store_xml_get_basedir (self);
+
+ gdir = g_dir_open (basedir, 0, NULL);
+ if (!gdir)
+ return NULL;
+
+ regex = log_store_xml_create_filename_regex (type_mask);
+
+ if (regex == NULL)
+ goto out;
+
+ while ((name = g_dir_read_name (gdir)) != NULL)
+ {
+ gchar *filename;
+
+ filename = g_build_filename (basedir, name, NULL);
+
+ if (g_regex_match (regex, name, 0, NULL))
+ files = g_list_prepend (files, filename);
+ else if (g_file_test (filename, G_FILE_TEST_IS_DIR))
+ {
+ /* Recursively get all log files */
+ files = g_list_concat (files,
+ log_store_xml_get_all_files (self, filename, type_mask));
+ g_free (filename);
+ }
+ }
+
+out:
+ g_dir_close (gdir);
+
+ if (regex != NULL)
+ g_regex_unref (regex);
+
+ return files;
+}
+
+
+static GList *
+_log_store_xml_search_in_files (TplLogStoreXml *self,
+ const gchar *text,
+ GList *files,
+ gint type_mask)
+{
+ GList *l;
+ GList *hits = NULL;
+ gchar *markup_text;
+ gchar *escaped_text;
+ GString *pattern = NULL;
+ GRegex *regex = NULL;
+ GError *error = NULL;
+
+ g_return_val_if_fail (TPL_IS_LOG_STORE_XML (self), NULL);
+ g_return_val_if_fail (!TPL_STR_EMPTY (text), NULL);
+
+ markup_text = g_markup_escape_text (text, -1);
+ escaped_text = g_regex_escape_string (markup_text, -1);
+ g_free (markup_text);
+
+ pattern = g_string_new ("");
+
+ if (type_mask & TPL_EVENT_MASK_TEXT)
+ g_string_append_printf (pattern,
+ "<message [^>]*>[^<]*%s[^<]*</message>"
+ "|<message( [^>]* | )id='[^>]*%s[^>]*'"
+ "|<message( [^>]* | )name='[^>]*%s[^>]*'",
+ escaped_text, escaped_text, escaped_text);
+
+ if (type_mask & TPL_EVENT_MASK_CALL)
+ g_string_append_printf (pattern,
+ "%s<call( [^>]* | )id='[^>]*%s[^>]*'"
+ "|<call( [^>]* | )name='[^>]*%s[^>]*'"
+ "|<call( [^>]* | )actor='[^>]*%s[^>]*'"
+ "|<call( [^>]* | )actorname='[^>]*%s[^>]*'",
+ pattern->len == 0 ? "" : "|",
+ escaped_text, escaped_text, escaped_text, escaped_text);
+
+ if (TPL_STR_EMPTY (pattern->str))
+ goto out;
+
+ regex = g_regex_new (pattern->str,
+ G_REGEX_CASELESS | G_REGEX_OPTIMIZE,
+ 0,
+ &error);
+
+ if (!regex)
+ {
+ DEBUG ("Failed to compile regex: %s", error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ for (l = files; l; l = g_list_next (l))
+ {
+ gchar *filename = l->data;
+
+ if (log_store_xml_match_in_file (filename, regex))
+ {
+ TplLogSearchHit *hit;
+
+ hit = log_store_xml_search_hit_new (self, filename);
+ if (hit != NULL)
+ {
+ hits = g_list_prepend (hits, hit);
+ DEBUG ("Found text:'%s' in file:'%s' on date: %04u-%02u-%02u",
+ text, filename, g_date_get_year (hit->date),
+ g_date_get_month (hit->date), g_date_get_day (hit->date));
+ }
+ }
+ }
+
+out:
+ g_free (escaped_text);
+
+ if (pattern != NULL)
+ g_string_free (pattern, TRUE);
+
+ if (regex != NULL)
+ g_regex_unref (regex);
+
+ g_list_free (files);
+ return hits;
+}
+
+
+static GList *
+log_store_xml_search_new (TplLogStore *store,
+ const gchar *text,
+ gint type_mask)
+{
+ TplLogStoreXml *self = (TplLogStoreXml *) store;
+ GList *files;
+
+ g_return_val_if_fail (TPL_IS_LOG_STORE_XML (self), NULL);
+ g_return_val_if_fail (!TPL_STR_EMPTY (text), NULL);
+
+ files = log_store_xml_get_all_files (self, NULL, type_mask);
+ DEBUG ("Found %d log files in total", g_list_length (files));
+
+ return _log_store_xml_search_in_files (self, text, files, type_mask);
+}
+
+
+/* Returns: (GList *) of (TplLogSearchHit *) */
+static GList *
+log_store_xml_get_entities_for_dir (TplLogStoreXml *self,
+ const gchar *dir,
+ gboolean is_chatroom,
+ TpAccount *account)
+{
+ GDir *gdir;
+ GList *entities = NULL;
+ const gchar *name;
+ GError *error = NULL;
+
+ g_return_val_if_fail (TPL_IS_LOG_STORE_XML (self), NULL);
+ g_return_val_if_fail (!TPL_STR_EMPTY (dir), NULL);
+
+ gdir = g_dir_open (dir, 0, &error);
+ if (!gdir)
+ {
+ DEBUG ("Failed to open directory: %s, error: %s", dir, error->message);
+ g_error_free (error);
+ return NULL;
+ }
+
+ while ((name = g_dir_read_name (gdir)) != NULL)
+ {
+ TplEntity *entity;
+
+ if (!is_chatroom && strcmp (name, LOG_DIR_CHATROOMS) == 0)
+ {
+ gchar *filename = g_build_filename (dir, name, NULL);
+ entities = g_list_concat (entities,
+ log_store_xml_get_entities_for_dir (self, filename, TRUE, account));
+ g_free (filename);
+ continue;
+ }
+
+ if (is_chatroom)
+ entity = tpl_entity_new_from_room_id (name);
+ else
+ entity = tpl_entity_new (name, TPL_ENTITY_CONTACT, NULL, NULL);
+
+ entities = g_list_prepend (entities, entity);
+ }
+
+ g_dir_close (gdir);
+
+ return entities;
+}
+
+
+/* returns a Glist of TplEvent instances */
+static GList *
+log_store_xml_get_events_for_date (TplLogStore *store,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask,
+ const GDate *date)
+{
+ TplLogStoreXml *self = (TplLogStoreXml *) store;
+ gchar *filename;
+ GQueue events = G_QUEUE_INIT;
+
+ g_return_val_if_fail (TPL_IS_LOG_STORE_XML (self), NULL);
+ g_return_val_if_fail (TP_IS_ACCOUNT (account), NULL);
+ g_return_val_if_fail (TPL_IS_ENTITY (target), NULL);
+ g_return_val_if_fail (date != NULL, NULL);
+
+ if (type_mask & TPL_EVENT_MASK_TEXT)
+ {
+ filename = log_store_xml_get_filename_for_date (self, account, target,
+ date, TPL_TYPE_TEXT_EVENT);
+ log_store_xml_get_events_for_file (self, account, filename,
+ TPL_TYPE_TEXT_EVENT, &events);
+ g_free (filename);
+ }
+
+ if (type_mask & TPL_EVENT_MASK_CALL)
+ {
+ filename = log_store_xml_get_filename_for_date (self, account, target,
+ date, TPL_TYPE_CALL_EVENT);
+ log_store_xml_get_events_for_file (self, account, filename,
+ TPL_TYPE_CALL_EVENT, &events);
+ g_free (filename);
+ }
+
+ return events.head;
+}
+
+
+static GList *
+log_store_xml_get_entities (TplLogStore *store,
+ TpAccount *account)
+{
+ TplLogStoreXml *self = (TplLogStoreXml *) store;
+ gchar *dir;
+ GList *entities;
+
+ g_return_val_if_fail (TPL_IS_LOG_STORE_XML (self), NULL);
+ g_return_val_if_fail (TP_IS_ACCOUNT (account), NULL);
+
+ dir = log_store_xml_get_dir (self, account, NULL);
+ entities = log_store_xml_get_entities_for_dir (self, dir, FALSE, account);
+ g_free (dir);
+
+ return entities;
+}
+
+
+static const gchar *
+log_store_xml_get_name (TplLogStore *store)
+{
+ TplLogStoreXml *self = (TplLogStoreXml *) store;
+
+ g_return_val_if_fail (TPL_IS_LOG_STORE_XML (self), NULL);
+
+ return "TpLogger";
+}
+
+
+/* returns am absolute path for the base directory of LogStore */
+static const gchar *
+log_store_xml_get_basedir (TplLogStoreXml *self)
+{
+ g_return_val_if_fail (TPL_IS_LOG_STORE_XML (self), NULL);
+
+ /* set default based on name if NULL, see prop's comment about it in
+ * class_init method */
+ if (self->priv->basedir == NULL)
+ {
+ gchar *dir;
+ const char *user_data_dir;
+ const char *name;
+
+ if (self->priv->test_mode && g_getenv ("TPL_TEST_LOG_DIR") != NULL)
+ {
+ user_data_dir = g_getenv ("TPL_TEST_LOG_DIR");
+ }
+ else
+ {
+ user_data_dir = g_get_user_data_dir ();
+ }
+
+ name = _tpl_log_store_get_name ((TplLogStore *) self);
+ dir = g_build_path (G_DIR_SEPARATOR_S, user_data_dir, name, "logs",
+ NULL);
+ log_store_xml_set_basedir (self, dir);
+ g_free (dir);
+ }
+
+ return self->priv->basedir;
+}
+
+
+static void
+log_store_xml_set_basedir (TplLogStoreXml *self,
+ const gchar *data)
+{
+ g_return_if_fail (TPL_IS_LOG_STORE_XML (self));
+ g_return_if_fail (self->priv->basedir == NULL);
+ /* data may be NULL when the class is initialized and the default value is
+ * set */
+
+ self->priv->basedir = g_strdup (data);
+
+ /* at install_spec time, default value is set to NULL, ignore it */
+ if (self->priv->basedir != NULL)
+ DEBUG ("logstore set to dir: %s", data);
+}
+
+
+static GList *
+log_store_xml_get_filtered_events (TplLogStore *store,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask,
+ guint num_events,
+ TplLogEventFilter filter,
+ gpointer user_data)
+{
+ TplLogStoreXml *self = (TplLogStoreXml *) store;
+ GList *dates, *l, *events = NULL;
+ guint i = 0;
+
+ g_return_val_if_fail (TPL_IS_LOG_STORE_XML (self), NULL);
+ g_return_val_if_fail (TP_IS_ACCOUNT (account), NULL);
+ g_return_val_if_fail (TPL_IS_ENTITY (target), NULL);
+
+ dates = log_store_xml_get_dates (store, account, target, type_mask);
+
+ for (l = g_list_last (dates); l != NULL && i < num_events;
+ l = g_list_previous (l))
+ {
+ GList *new_events, *n;
+
+ /* FIXME: We should really restrict the event parsing to get only
+ * the newest num_events. */
+ new_events = log_store_xml_get_events_for_date (store, account,
+ target, type_mask, l->data);
+
+ n = g_list_last (new_events);
+ while (n != NULL && i < num_events)
+ {
+ if (filter == NULL || filter (n->data, user_data))
+ {
+ events = g_list_prepend (events, g_object_ref (n->data));
+ i++;
+ }
+ n = g_list_previous (n);
+ }
+ g_list_foreach (new_events, (GFunc) g_object_unref, NULL);
+ g_list_free (new_events);
+ }
+
+ g_list_foreach (dates, (GFunc) g_date_free, NULL);
+ g_list_free (dates);
+
+ return events;
+}
+
+
+static void
+log_store_xml_clear (TplLogStore *store)
+{
+ TplLogStoreXml *self = TPL_LOG_STORE_XML (store);
+ const gchar *basedir;
+
+ /* We need to use the getter otherwise the basedir might not be set yet */
+ basedir = log_store_xml_get_basedir (self);
+
+ DEBUG ("Clear all logs from XML store in: %s", basedir);
+
+ _tpl_rmdir_recursively (basedir);
+}
+
+
+static void
+log_store_xml_clear_account (TplLogStore *store,
+ TpAccount *account)
+{
+ TplLogStoreXml *self = TPL_LOG_STORE_XML (store);
+ gchar *account_dir;
+
+ account_dir = log_store_xml_get_dir (self, account, NULL);
+
+ if (account_dir)
+ {
+ DEBUG ("Clear account logs from XML store in: %s",
+ account_dir);
+ _tpl_rmdir_recursively (account_dir);
+ g_free (account_dir);
+ }
+ else
+ DEBUG ("Nothing to clear in account: %s",
+ tp_proxy_get_object_path (TP_PROXY (account)));
+}
+
+
+static void
+log_store_xml_clear_entity (TplLogStore *store,
+ TpAccount *account,
+ TplEntity *entity)
+{
+ TplLogStoreXml *self = TPL_LOG_STORE_XML (store);
+ gchar *entity_dir;
+
+ entity_dir = log_store_xml_get_dir (self, account, entity);
+
+ if (entity_dir)
+ {
+ DEBUG ("Clear entity logs from XML store in: %s",
+ entity_dir);
+
+ _tpl_rmdir_recursively (entity_dir);
+ g_free (entity_dir);
+ }
+ else
+ DEBUG ("Nothing to clear for account/entity: %s/%s",
+ tp_proxy_get_object_path (TP_PROXY (account)),
+ tpl_entity_get_identifier (entity));
+}
+
+
+static TplLogIter *
+log_store_xml_create_iter (TplLogStore *store,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask)
+{
+ g_return_val_if_fail (TPL_IS_LOG_STORE_XML (store), NULL);
+ g_return_val_if_fail (TP_IS_ACCOUNT (account), NULL);
+ g_return_val_if_fail (TPL_IS_ENTITY (target), NULL);
+
+ return tpl_log_iter_xml_new (store, account, target, type_mask);
+}
+
+
+static void
+log_store_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ TplLogStoreInterface *iface = (TplLogStoreInterface *) g_iface;
+
+ iface->get_name = log_store_xml_get_name;
+ iface->exists = log_store_xml_exists;
+ iface->add_event = log_store_xml_add_event;
+ iface->get_dates = log_store_xml_get_dates;
+ iface->get_events_for_date = log_store_xml_get_events_for_date;
+ iface->get_entities = log_store_xml_get_entities;
+ iface->search_new = log_store_xml_search_new;
+ iface->get_filtered_events = log_store_xml_get_filtered_events;
+ iface->clear = log_store_xml_clear;
+ iface->clear_account = log_store_xml_clear_account;
+ iface->clear_entity = log_store_xml_clear_entity;
+ iface->create_iter = log_store_xml_create_iter;
+}
diff --git a/telepathy-logger/log-store.c b/telepathy-logger/log-store.c
new file mode 100644
index 000000000..6e1d10f19
--- /dev/null
+++ b/telepathy-logger/log-store.c
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2008-2011 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Jonny Lamb <jonny.lamb@collabora.co.uk>,
+ * Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ */
+
+#include "config.h"
+
+#include <telepathy-logger/log-store-internal.h>
+
+#define DEBUG_FLAG TPL_DEBUG_LOG_STORE
+#include <telepathy-logger/debug-internal.h>
+
+/*
+ * SECTION:log-store
+ * @title: TplLogStore
+ * @short_description: LogStore interface can register into #TplLogManager as
+ * readable and/or writable log stores.
+ * @see_also: #text-event:#TplTextEvent and other subclasses when they'll exist
+ *
+ * The #TplLogStore defines all the public methods that a TPL Log Store has to
+ * implement in order to be used into a #TplLogManager.
+ */
+
+static void _tpl_log_store_init (gpointer g_iface);
+
+GType
+_tpl_log_store_get_type (void)
+{
+ static GType type = 0;
+ if (type == 0)
+ {
+ static const GTypeInfo info = {
+ sizeof (TplLogStoreInterface),
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ (GClassInitFunc) _tpl_log_store_init, /* class_init */
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ 0,
+ 0, /* n_preallocs */
+ NULL /* instance_init */
+ };
+ type = g_type_register_static (G_TYPE_INTERFACE, "TplLogStore",
+ &info, 0);
+ g_type_interface_add_prerequisite (type, G_TYPE_OBJECT);
+ }
+ return type;
+}
+
+static void
+_tpl_log_store_init (gpointer g_iface)
+{
+ /**
+ * TplLogStore:readable:
+ *
+ * Defines whether the object is readable for a #TplLogManager.
+ *
+ * If an TplLogStore implementation is readable, the #TplLogManager will
+ * use the query methods against the instance (e.g. _tpl_log_store_get_dates())
+ * every time a #TplLogManager instance is queried (e.g.
+ * _tpl_log_manager_get_dates()).
+ */
+ g_object_interface_install_property (g_iface,
+ g_param_spec_boolean ("readable",
+ "Readable",
+ "Whether this log store is readable",
+ TRUE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+}
+
+const gchar *
+_tpl_log_store_get_name (TplLogStore *self)
+{
+ g_return_val_if_fail (TPL_IS_LOG_STORE (self), NULL);
+ if (!TPL_LOG_STORE_GET_INTERFACE (self)->get_name)
+ return NULL;
+
+ return TPL_LOG_STORE_GET_INTERFACE (self)->get_name (self);
+}
+
+
+gboolean
+_tpl_log_store_exists (TplLogStore *self,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask)
+{
+ g_return_val_if_fail (TPL_IS_LOG_STORE (self), FALSE);
+ if (!TPL_LOG_STORE_GET_INTERFACE (self)->exists)
+ return FALSE;
+
+ return TPL_LOG_STORE_GET_INTERFACE (self)->exists (self, account, target,
+ type_mask);
+}
+
+
+/**
+ * _tpl_log_store_add_event:
+ * @self: a TplLogStore
+ * @event: an instance of a subclass of TplEvent (ie TplTextEvent)
+ * @error: memory location used if an error occurs
+ *
+ * Sends @event to the LogStore @self, in order to be stored.
+ *
+ * Returns: %TRUE if succeeds, %FALSE with @error set otherwise
+ */
+gboolean
+_tpl_log_store_add_event (TplLogStore *self,
+ TplEvent *event,
+ GError **error)
+{
+ g_return_val_if_fail (TPL_IS_LOG_STORE (self), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ if (TPL_LOG_STORE_GET_INTERFACE (self)->add_event == NULL)
+ {
+ g_set_error (error, TPL_LOG_STORE_ERROR,
+ TPL_LOG_STORE_ERROR_ADD_EVENT,
+ "%s: %s is not writable",
+ G_STRFUNC, G_OBJECT_CLASS_NAME (self));
+ return FALSE;
+ }
+
+ return TPL_LOG_STORE_GET_INTERFACE (self)->add_event (self, event,
+ error);
+}
+
+
+/**
+ * _tpl_log_store_get_dates:
+ * @self: a TplLogStore
+ * @account: a TpAccount
+ * @target: a #TplEntity
+ * @type_mask: event type mask see #TplEventTypeMask
+ *
+ * Retrieves a list of #GDate, corresponding to each day
+ * at least an event was sent to or received from @id.
+ *
+ * Returns: a GList of (GDate *), to be freed using something like
+ * g_list_foreach (lst, g_date_free, NULL);
+ * g_list_free (lst);
+ */
+GList *
+_tpl_log_store_get_dates (TplLogStore *self,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask)
+{
+ g_return_val_if_fail (TPL_IS_LOG_STORE (self), NULL);
+ if (TPL_LOG_STORE_GET_INTERFACE (self)->get_dates == NULL)
+ return NULL;
+
+ return TPL_LOG_STORE_GET_INTERFACE (self)->get_dates (self, account,
+ target, type_mask);
+}
+
+
+/**
+ * _tpl_log_store_get_events_for_date:
+ * @self: a TplLogStore
+ * @account: a TpAccount
+ * @target: a #TplEntity
+ * @type_mask: event type mask see #TplEventTypeMask
+ * @date: a #GDate
+ *
+ * Retrieves a list of events, with timestamp matching @date.
+ *
+ * Returns: a GList of TplTextEvent, to be freed using something like
+ * g_list_foreach (lst, g_object_unref, NULL);
+ * g_list_free (lst);
+ */
+GList *
+_tpl_log_store_get_events_for_date (TplLogStore *self,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask,
+ const GDate *date)
+{
+ g_return_val_if_fail (TPL_IS_LOG_STORE (self), NULL);
+ if (TPL_LOG_STORE_GET_INTERFACE (self)->get_events_for_date == NULL)
+ return NULL;
+
+ return TPL_LOG_STORE_GET_INTERFACE (self)->get_events_for_date (self,
+ account, target, type_mask, date);
+}
+
+
+GList *
+_tpl_log_store_get_recent_events (TplLogStore *self,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask)
+{
+ g_return_val_if_fail (TPL_IS_LOG_STORE (self), NULL);
+ if (TPL_LOG_STORE_GET_INTERFACE (self)->get_recent_events == NULL)
+ return NULL;
+
+ return TPL_LOG_STORE_GET_INTERFACE (self)->get_recent_events (self, account,
+ target, type_mask);
+}
+
+
+/**
+ * _tpl_log_store_get_entities:
+ * @self: a TplLogStore
+ * @account: a TpAccount
+ *
+ * Retrieves a list of #TplEntity, corresponding to each buddy/chatroom id
+ * the user exchanged at least a event with inside @account.
+ *
+ * Returns: a GList of #TplEntity, to be freed using something like
+ * g_list_foreach (lst, g_object_unref, NULL);
+ * g_list_free (lst);
+ */
+GList *
+_tpl_log_store_get_entities (TplLogStore *self,
+ TpAccount *account)
+{
+ g_return_val_if_fail (TPL_IS_LOG_STORE (self), NULL);
+ if (TPL_LOG_STORE_GET_INTERFACE (self)->get_entities == NULL)
+ return NULL;
+
+ return TPL_LOG_STORE_GET_INTERFACE (self)->get_entities (self, account);
+}
+
+
+/**
+ * _tpl_log_store_search_new:
+ * @self: a TplLogStore
+ * @text: a text to be searched among text messages
+ * @type_mask: event type mask see #TplEventTypeMask
+ *
+ * Searches all textual log entries matching @text.
+ *
+ * Returns: a GList of (TplLogSearchHit *), to be freed using something like
+ * g_list_foreach (lst, tpl_log_manager_search_free, NULL);
+ * g_list_free (lst);
+ */
+GList *
+_tpl_log_store_search_new (TplLogStore *self,
+ const gchar *text,
+ gint type_mask)
+{
+ g_return_val_if_fail (TPL_IS_LOG_STORE (self), NULL);
+ if (TPL_LOG_STORE_GET_INTERFACE (self)->search_new == NULL)
+ return NULL;
+
+ return TPL_LOG_STORE_GET_INTERFACE (self)->search_new (self, text,
+ type_mask);
+}
+
+
+/**
+ * _tpl_log_store_get_filtered_events:
+ * @self: a TplLogStore
+ * @account: a TpAccount
+ * @target: a #TplEntity
+ * @type_mask: event type mask see #TplEventTypeMask
+ * @num_events: max number of events to return
+ * @filter: filter function
+ * @user_data: data be passed to @filter, may be NULL
+ *
+ * Filters all events related to @id, using the boolean function
+ * @filter.
+ * It will return at most the last (ie most recent) @num_events events.
+ * Pass G_MAXUINT if all the events are needed.
+ *
+ * Returns: a GList of TplTextEvent, to be freed using something like
+ * g_list_foreach (lst, g_object_unref, NULL);
+ * g_list_free (lst);
+ */
+GList *
+_tpl_log_store_get_filtered_events (TplLogStore *self,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask,
+ guint num_events,
+ TplLogEventFilter filter,
+ gpointer user_data)
+{
+ g_return_val_if_fail (TPL_IS_LOG_STORE (self), NULL);
+ if (TPL_LOG_STORE_GET_INTERFACE (self)->get_filtered_events == NULL)
+ return NULL;
+
+ return TPL_LOG_STORE_GET_INTERFACE (self)->get_filtered_events (self,
+ account, target, type_mask, num_events, filter, user_data);
+}
+
+
+void
+_tpl_log_store_clear (TplLogStore *self)
+{
+ g_return_if_fail (TPL_IS_LOG_STORE (self));
+ if (TPL_LOG_STORE_GET_INTERFACE (self)->clear == NULL)
+ return;
+
+ TPL_LOG_STORE_GET_INTERFACE (self)->clear (self);
+}
+
+
+void
+_tpl_log_store_clear_account (TplLogStore *self, TpAccount *account)
+{
+ g_return_if_fail (TPL_IS_LOG_STORE (self));
+ if (TPL_LOG_STORE_GET_INTERFACE (self)->clear_account == NULL)
+ return;
+
+ TPL_LOG_STORE_GET_INTERFACE (self)->clear_account (self, account);
+}
+
+
+void
+_tpl_log_store_clear_entity (TplLogStore *self,
+ TpAccount *account,
+ TplEntity *entity)
+{
+ g_return_if_fail (TPL_IS_LOG_STORE (self));
+ if (TPL_LOG_STORE_GET_INTERFACE (self)->clear_entity == NULL)
+ return;
+
+ TPL_LOG_STORE_GET_INTERFACE (self)->clear_entity (self, account, entity);
+}
+
+
+TplLogIter *
+_tpl_log_store_create_iter (TplLogStore *self,
+ TpAccount *account,
+ TplEntity *target,
+ gint type_mask)
+{
+ g_return_val_if_fail (TPL_IS_LOG_STORE (self), NULL);
+ if (TPL_LOG_STORE_GET_INTERFACE (self)->create_iter == NULL)
+ return NULL;
+
+ return TPL_LOG_STORE_GET_INTERFACE (self)->create_iter (self,
+ account, target, type_mask);
+}
+
+
+gboolean
+_tpl_log_store_is_writable (TplLogStore *self)
+{
+ g_return_val_if_fail (TPL_IS_LOG_STORE (self), FALSE);
+
+ return (TPL_LOG_STORE_GET_INTERFACE (self)->add_event != NULL);
+}
+
+
+gboolean
+_tpl_log_store_is_readable (TplLogStore *self)
+{
+ gboolean readable;
+
+ g_return_val_if_fail (TPL_IS_LOG_STORE (self), FALSE);
+
+ g_object_get (self,
+ "readable", &readable,
+ NULL);
+
+ return readable;
+}
diff --git a/telepathy-logger/log-walker-internal.h b/telepathy-logger/log-walker-internal.h
new file mode 100644
index 000000000..742529076
--- /dev/null
+++ b/telepathy-logger/log-walker-internal.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Debarshi Ray <debarshir@freedesktop.org>
+ */
+
+#ifndef __TPL_LOG_WALKER_INTERNAL_H__
+#define __TPL_LOG_WALKER_INTERNAL_H__
+
+#include "log-iter-internal.h"
+#include "log-manager.h"
+#include "log-walker.h"
+
+G_BEGIN_DECLS
+
+TplLogWalker *tpl_log_walker_new (TplLogEventFilter filter,
+ gpointer filter_data);
+
+void tpl_log_walker_add_iter (TplLogWalker *walker,
+ TplLogIter *iter);
+
+G_END_DECLS
+
+#endif /* __TPL_LOG_WALKER_INTERNAL_H__ */
diff --git a/telepathy-logger/log-walker.c b/telepathy-logger/log-walker.c
new file mode 100644
index 000000000..f4763e1c5
--- /dev/null
+++ b/telepathy-logger/log-walker.c
@@ -0,0 +1,975 @@
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Debarshi Ray <debarshir@freedesktop.org>
+ */
+
+#include "config.h"
+
+#include "log-walker.h"
+#include "log-walker-internal.h"
+
+#include <telepathy-logger/event.h>
+#include <telepathy-logger/log-iter-internal.h>
+
+/**
+ * SECTION:log-walker
+ * @title: TplLogWalker
+ * @short_description: Iterate over the logs
+ *
+ * The #TplLogWalker object allows the user to sequentially iterate
+ * over the logs.
+ *
+ * <example>
+ * <title>Using a TplLogWalker to fetch text events from the logs.</title>
+ * <programlisting>
+ * #include <telepathy-glib/telepathy-glib.h>
+ * #include <telepathy-logger/telepathy-logger.h>
+ *
+ * static GMainLoop * loop = NULL;
+ *
+ * static void
+ * events_foreach (gpointer data, gpointer user_data)
+ * {
+ * TplEvent *event = TPL_EVENT (data);
+ * const gchar *message;
+ * gint64 timestamp;
+ *
+ * timestamp = tpl_event_get_timestamp (event);
+ * message = tpl_text_event_get_message (TPL_TEXT_EVENT (event));
+ * g_message ("%" G_GINT64_FORMAT " %s", timestamp, message);
+ * }
+ *
+ * static void
+ * log_walker_get_events_cb (GObject *source_object,
+ * GAsyncResult *res,
+ * gpointer user_data)
+ * {
+ * TplLogWalker *walker = TPL_LOG_WALKER (source_object);
+ * GList *events;
+ *
+ * if (!tpl_log_walker_get_events_finish (walker, res, &events, NULL))
+ * {
+ * g_main_loop_quit (loop);
+ * return;
+ * }
+ *
+ * g_list_foreach (events, events_foreach, NULL);
+ * g_list_free_full (events, g_object_unref);
+ * if (tpl_log_walker_is_end (walker))
+ * {
+ * g_main_loop_quit (loop);
+ * return;
+ * }
+ *
+ * g_message ("");
+ * tpl_log_walker_get_events_async (walker,
+ * 5,
+ * log_walker_get_events_cb,
+ * NULL);
+ * }
+ *
+ * static void
+ * accounts_foreach (gpointer data, gpointer user_data)
+ * {
+ * TpAccount **account_out = (TpAccount **) user_data;
+ * TpAccount *account = TP_ACCOUNT (data);
+ * const gchar *display_name;
+ *
+ * display_name = tp_account_get_display_name (account);
+ * if (0 != g_strcmp0 (display_name, "alice@bar.net"))
+ * return;
+ *
+ * g_object_ref (account);
+ * *account_out = account;
+ * }
+ *
+ * static void
+ * account_manager_prepare_cb (GObject * source_object,
+ * GAsyncResult * res,
+ * gpointer user_data)
+ * {
+ * TpAccountManager *account_manager = TP_ACCOUNT_MANAGER (source_object);
+ * GList *accounts;
+ * TpAccount *account = NULL;
+ * TplLogManager *log_manager;
+ * TplLogWalker *walker;
+ * TplEntity *target;
+ *
+ * if (!tp_proxy_prepare_finish (source_object, res, NULL))
+ * return;
+ *
+ * accounts = tp_account_manager_dup_usable_accounts (account_manager);
+ * g_list_foreach (accounts, accounts_foreach, &account);
+ * g_list_free_full (accounts, g_object_unref);
+ * if (account == NULL)
+ * {
+ * g_main_loop_quit (loop);
+ * return;
+ * }
+ *
+ * log_manager = tpl_log_manager_dup_singleton ();
+ *
+ * target = tpl_entity_new ("bob@foo.net", TPL_ENTITY_CONTACT, NULL, NULL);
+ *
+ * walker = tpl_log_manager_walk_filtered_events (log_manager,
+ * account,
+ * target,
+ * TPL_EVENT_MASK_TEXT,
+ * NULL,
+ * NULL);
+ *
+ * tpl_log_walker_get_events_async (walker,
+ * 5,
+ * log_walker_get_events_cb,
+ * NULL);
+ *
+ * g_object_unref (walker);
+ * g_object_unref (target);
+ * g_object_unref (log_manager);
+ * g_object_unref (account);
+ * }
+ *
+ * int
+ * main (int argc,
+ * char *argv[])
+ * {
+ * GQuark features[] = { TP_ACCOUNT_MANAGER_FEATURE_CORE, 0 };
+ * TpAccountManager * account_manager;
+ *
+ * g_type_init ();
+ * loop = g_main_loop_new (NULL, FALSE);
+ *
+ * account_manager = tp_account_manager_dup ();
+ * tp_proxy_prepare_async (account_manager,
+ * features,
+ * account_manager_prepare_cb,
+ * NULL);
+ *
+ * g_main_loop_run (loop);
+ *
+ * g_object_unref (account_manager);
+ * g_main_loop_unref (loop);
+ * return 0;
+ * }
+ * </programlisting>
+ * </example>
+ *
+ * Since: 0.8.0
+ */
+
+/**
+ * TplLogWalker:
+ *
+ * An object used to iterate over the logs
+ *
+ * Since: 0.8.0
+ */
+
+struct _TplLogWalkerPriv
+{
+ GList *caches;
+ GList *history;
+ GList *iters;
+ GQueue *queue;
+ TplLogEventFilter filter;
+ gboolean is_start;
+ gboolean is_end;
+ gpointer filter_data;
+};
+
+enum
+{
+ PROP_FILTER = 1,
+ PROP_FILTER_DATA
+};
+
+
+G_DEFINE_TYPE (TplLogWalker, tpl_log_walker, G_TYPE_OBJECT);
+
+
+static const gsize CACHE_SIZE = 5;
+
+typedef enum
+{
+ TPL_LOG_WALKER_OP_GET_EVENTS,
+ TPL_LOG_WALKER_OP_REWIND
+} TplLogWalkerOpType;
+
+typedef struct
+{
+ GAsyncReadyCallback cb;
+ GList *events;
+ GList *fill_cache;
+ GList *fill_iter;
+ GList *latest_cache;
+ GList *latest_event;
+ GList *latest_iter;
+ TplLogWalkerOpType op_type;
+ gint64 latest_timestamp;
+ guint num_events;
+} TplLogWalkerAsyncData;
+
+typedef struct
+{
+ TplLogIter *iter;
+ gboolean skip;
+ guint count;
+} TplLogWalkerHistoryData;
+
+static void tpl_log_walker_op_run (TplLogWalker *walker);
+
+
+static TplLogWalkerAsyncData *
+tpl_log_walker_async_data_new (void)
+{
+ return g_slice_new0 (TplLogWalkerAsyncData);
+}
+
+
+static void
+tpl_log_walker_async_data_free (TplLogWalkerAsyncData *data)
+{
+ g_list_free_full (data->events, g_object_unref);
+ g_slice_free (TplLogWalkerAsyncData, data);
+}
+
+
+static TplLogWalkerHistoryData *
+tpl_log_walker_history_data_new (void)
+{
+ return g_slice_new0 (TplLogWalkerHistoryData);
+}
+
+
+static void
+tpl_log_walker_history_data_free (TplLogWalkerHistoryData *data)
+{
+ g_object_unref (data->iter);
+ g_slice_free (TplLogWalkerHistoryData, data);
+}
+
+
+static void
+tpl_log_walker_async_operation_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TplLogWalker *walker;
+ TplLogWalkerPriv *priv;
+ GSimpleAsyncResult *simple;
+ TplLogWalkerAsyncData *async_data;
+
+ walker = TPL_LOG_WALKER (source_object);
+ priv = walker->priv;
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+ async_data = (TplLogWalkerAsyncData *)
+ g_simple_async_result_get_op_res_gpointer (simple);
+
+ if (async_data->cb)
+ async_data->cb (source_object, result, user_data);
+
+ g_object_unref (g_queue_pop_head (priv->queue));
+ tpl_log_walker_op_run (walker);
+}
+
+
+static void
+tpl_log_walker_caches_free_func (gpointer data)
+{
+ g_list_free_full ((GList *) data, g_object_unref);
+}
+
+
+static void
+tpl_log_walker_fill_cache_async_thread (GSimpleAsyncResult *simple,
+ GObject *object,
+ GCancellable *cancellable)
+{
+ GError *error = NULL;
+ TplLogWalkerAsyncData *async_data;
+
+ async_data = (TplLogWalkerAsyncData *)
+ g_simple_async_result_get_op_res_gpointer (simple);
+
+ async_data->fill_cache->data = tpl_log_iter_get_events (
+ TPL_LOG_ITER (async_data->fill_iter->data), CACHE_SIZE, &error);
+
+ if (error != NULL)
+ g_simple_async_result_take_error (simple, error);
+}
+
+
+static void
+tpl_log_walker_fill_cache_async (TplLogWalker *walker,
+ GList *cache,
+ GList *iter,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ TplLogWalkerAsyncData *async_data;
+
+ g_return_if_fail (TPL_IS_LOG_WALKER (walker));
+
+ async_data = tpl_log_walker_async_data_new ();
+ async_data->fill_cache = cache;
+ async_data->fill_iter = iter;
+
+ simple = g_simple_async_result_new (G_OBJECT (walker), callback, user_data,
+ tpl_log_walker_fill_cache_async);
+
+ g_simple_async_result_set_op_res_gpointer (simple, async_data,
+ (GDestroyNotify) tpl_log_walker_async_data_free);
+
+ g_simple_async_result_run_in_thread (simple,
+ tpl_log_walker_fill_cache_async_thread, G_PRIORITY_DEFAULT,
+ NULL);
+
+ g_object_unref (simple);
+}
+
+
+static gboolean
+tpl_log_walker_fill_cache_finish (TplLogWalker *walker,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (TPL_IS_LOG_WALKER (walker), FALSE);
+ g_return_val_if_fail (g_simple_async_result_is_valid (result,
+ G_OBJECT (walker), tpl_log_walker_fill_cache_async), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return FALSE;
+
+ return TRUE;
+}
+
+
+static void
+tpl_log_walker_get_events (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ TplLogWalker *walker;
+ TplLogWalkerPriv *priv;
+ TplLogWalkerAsyncData *async_data;
+ guint i;
+
+ walker = TPL_LOG_WALKER (source_object);
+ priv = walker->priv;
+
+ simple = G_SIMPLE_ASYNC_RESULT (user_data);
+ async_data = (TplLogWalkerAsyncData *)
+ g_simple_async_result_get_op_res_gpointer (simple);
+
+ /* If we are returning from a prior call to
+ * tpl_log_walker_fill_cache_async then finish it.
+ */
+ if (result != NULL)
+ tpl_log_walker_fill_cache_finish (walker, result, NULL);
+
+ if (priv->is_end == TRUE)
+ goto out;
+
+ i = g_list_length (async_data->events);
+
+ while (i < async_data->num_events && priv->is_end == FALSE)
+ {
+ GList *cache;
+ GList *iter;
+
+ /* Continue the loop from where we left, or start from the
+ * beginning as the case maybe.
+ */
+
+ cache = (async_data->fill_cache != NULL) ?
+ async_data->fill_cache : priv->caches;
+
+ iter = (async_data->fill_iter != NULL) ?
+ async_data->fill_iter : priv->iters;
+
+ for (; cache != NULL && iter != NULL;
+ cache = g_list_next (cache), iter = g_list_next (iter))
+ {
+ GList *event;
+ gint64 timestamp;
+
+ if (cache->data == NULL)
+ {
+ /* If the cache could not be filled, then the store
+ * must be empty.
+ */
+ if (cache == async_data->fill_cache)
+ continue;
+
+ /* Otherwise, try to fill it up. */
+ async_data->fill_cache = cache;
+ async_data->fill_iter = iter;
+ tpl_log_walker_fill_cache_async (walker, cache, iter,
+ tpl_log_walker_get_events, simple);
+ return;
+ }
+
+ event = g_list_last (cache->data);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (event->data));
+ if (timestamp > async_data->latest_timestamp)
+ {
+ async_data->latest_cache = cache;
+ async_data->latest_event = event;
+ async_data->latest_iter = iter;
+ async_data->latest_timestamp = timestamp;
+ }
+ }
+
+ /* These are used to maintain the continuity of the for loop
+ * which can get interrupted by the calls to
+ * tpl_log_walker_fill_cache_async(). Now that we are out of the
+ * loop we should reset them.
+ */
+ async_data->fill_cache = NULL;
+ async_data->fill_iter = NULL;
+ async_data->latest_timestamp = 0;
+
+ if (async_data->latest_event != NULL)
+ {
+ TplEvent *event;
+ TplLogWalkerHistoryData *data;
+ gboolean skip;
+
+ event = async_data->latest_event->data;
+ skip = TRUE;
+
+ if (priv->filter == NULL ||
+ (*priv->filter) (event, priv->filter_data))
+ {
+ async_data->events = g_list_prepend (async_data->events, event);
+ i++;
+ skip = FALSE;
+ }
+
+ async_data->latest_cache->data = g_list_delete_link (
+ async_data->latest_cache->data, async_data->latest_event);
+
+ data = (priv->history != NULL) ?
+ (TplLogWalkerHistoryData *) priv->history->data : NULL;
+
+ if (data == NULL ||
+ data->iter != async_data->latest_iter->data ||
+ data->skip != skip)
+ {
+ data = tpl_log_walker_history_data_new ();
+ data->iter = g_object_ref (async_data->latest_iter->data);
+ data->skip = skip;
+ priv->history = g_list_prepend (priv->history, data);
+ }
+
+ data->count++;
+
+ /* Now that the event has been inserted into the list we can
+ * forget about it.
+ */
+ async_data->latest_event = NULL;
+ }
+ else
+ priv->is_end = TRUE;
+ }
+
+ /* We are still at the beginning if all the log stores were empty. */
+ if (priv->history != NULL)
+ priv->is_start = FALSE;
+
+ out:
+ g_simple_async_result_complete_in_idle (simple);
+}
+
+
+static void
+tpl_log_walker_rewind (TplLogWalker *walker,
+ guint num_events,
+ GError **error)
+{
+ TplLogWalkerPriv *priv;
+ GList *k;
+ GList *l;
+ guint i;
+
+ g_return_if_fail (TPL_IS_LOG_WALKER (walker));
+
+ priv = walker->priv;
+ i = 0;
+
+ if (priv->is_start == TRUE || num_events == 0)
+ return;
+
+ priv->is_end = FALSE;
+
+ for (k = priv->caches, l = priv->iters;
+ k != NULL && l != NULL;
+ k = g_list_next (k), l = g_list_next (l))
+ {
+ GList **cache;
+ TplLogIter *iter;
+ guint length;
+
+ cache = (GList **) &k->data;
+ iter = TPL_LOG_ITER (l->data);
+
+ /* Flush the cache. */
+ length = g_list_length (*cache);
+ tpl_log_iter_rewind (iter, length, error);
+ g_list_free_full (*cache, g_object_unref);
+ *cache = NULL;
+ }
+
+ while (i < num_events && priv->is_start == FALSE)
+ {
+ TplLogWalkerHistoryData *data;
+
+ data = (TplLogWalkerHistoryData *) priv->history->data;
+ tpl_log_iter_rewind (data->iter, 1, error);
+ data->count--;
+ if (!data->skip)
+ i++;
+
+ if (data->count == 0)
+ {
+ tpl_log_walker_history_data_free (data);
+ priv->history = g_list_delete_link (priv->history, priv->history);
+ if (priv->history == NULL)
+ priv->is_start = TRUE;
+ }
+ }
+}
+
+
+static void
+tpl_log_walker_rewind_async_thread (GSimpleAsyncResult *simple,
+ GObject *object,
+ GCancellable *cancellable)
+{
+ GError *error = NULL;
+ TplLogWalkerAsyncData *async_data;
+
+ async_data = (TplLogWalkerAsyncData *)
+ g_simple_async_result_get_op_res_gpointer (simple);
+
+ tpl_log_walker_rewind (TPL_LOG_WALKER (object),
+ async_data->num_events, &error);
+
+ if (error != NULL)
+ g_simple_async_result_take_error (simple, error);
+}
+
+
+static void
+tpl_log_walker_op_run (TplLogWalker *walker)
+{
+ TplLogWalkerPriv *priv;
+ GSimpleAsyncResult *simple;
+ TplLogWalkerAsyncData *async_data;
+
+ priv = walker->priv;
+
+ if (g_queue_is_empty (priv->queue))
+ return;
+
+ simple = G_SIMPLE_ASYNC_RESULT (g_queue_peek_head (priv->queue));
+ async_data = (TplLogWalkerAsyncData *)
+ g_simple_async_result_get_op_res_gpointer (simple);
+
+ switch (async_data->op_type)
+ {
+ case TPL_LOG_WALKER_OP_GET_EVENTS:
+ tpl_log_walker_get_events (G_OBJECT (walker), NULL, simple);
+ break;
+
+ case TPL_LOG_WALKER_OP_REWIND:
+ g_simple_async_result_run_in_thread (simple,
+ tpl_log_walker_rewind_async_thread, G_PRIORITY_DEFAULT, NULL);
+ break;
+ }
+}
+
+
+static void
+tpl_log_walker_dispose (GObject *object)
+{
+ TplLogWalkerPriv *priv;
+
+ priv = TPL_LOG_WALKER (object)->priv;
+
+ g_list_free_full (priv->caches, tpl_log_walker_caches_free_func);
+ priv->caches = NULL;
+
+ g_list_free_full (priv->history,
+ (GDestroyNotify) tpl_log_walker_history_data_free);
+ priv->history = NULL;
+
+ g_list_free_full (priv->iters, g_object_unref);
+ priv->iters = NULL;
+
+ G_OBJECT_CLASS (tpl_log_walker_parent_class)->dispose (object);
+}
+
+
+static void
+tpl_log_walker_finalize (GObject *object)
+{
+ TplLogWalkerPriv *priv;
+
+ priv = TPL_LOG_WALKER (object)->priv;
+ g_queue_free_full (priv->queue, g_object_unref);
+
+ G_OBJECT_CLASS (tpl_log_walker_parent_class)->finalize (object);
+}
+
+
+static void
+tpl_log_walker_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TplLogWalkerPriv *priv;
+
+ priv = TPL_LOG_WALKER (object)->priv;
+
+ switch (param_id)
+ {
+ case PROP_FILTER:
+ g_value_set_pointer (value, priv->filter);
+ break;
+
+ case PROP_FILTER_DATA:
+ g_value_set_pointer (value, priv->filter_data);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+
+static void
+tpl_log_walker_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TplLogWalkerPriv *priv;
+
+ priv = TPL_LOG_WALKER (object)->priv;
+
+ switch (param_id)
+ {
+ case PROP_FILTER:
+ priv->filter = g_value_get_pointer (value);
+ break;
+
+ case PROP_FILTER_DATA:
+ priv->filter_data = g_value_get_pointer (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+
+static void
+tpl_log_walker_init (TplLogWalker *walker)
+{
+ TplLogWalkerPriv *priv;
+
+ walker->priv = G_TYPE_INSTANCE_GET_PRIVATE (walker, TPL_TYPE_LOG_WALKER,
+ TplLogWalkerPriv);
+ priv = walker->priv;
+
+ priv->queue = g_queue_new ();
+ priv->is_start = TRUE;
+ priv->is_end = FALSE;
+}
+
+
+static void
+tpl_log_walker_class_init (TplLogWalkerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GParamSpec *param_spec;
+
+ object_class->dispose = tpl_log_walker_dispose;
+ object_class->finalize = tpl_log_walker_finalize;
+ object_class->get_property = tpl_log_walker_get_property;
+ object_class->set_property = tpl_log_walker_set_property;
+
+ param_spec = g_param_spec_pointer ("filter",
+ "Filter",
+ "An optional filter function",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_FILTER, param_spec);
+
+ param_spec = g_param_spec_pointer ("filter-data",
+ "Filter Data",
+ "User data to pass to the filter function",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_FILTER_DATA, param_spec);
+
+ g_type_class_add_private (klass, sizeof (TplLogWalkerPriv));
+}
+
+
+TplLogWalker *
+tpl_log_walker_new (TplLogEventFilter filter, gpointer filter_data)
+{
+ return g_object_new (TPL_TYPE_LOG_WALKER,
+ "filter", filter,
+ "filter-data", filter_data,
+ NULL);
+}
+
+
+void
+tpl_log_walker_add_iter (TplLogWalker *walker, TplLogIter *iter)
+{
+ TplLogWalkerPriv *priv;
+
+ g_return_if_fail (TPL_IS_LOG_WALKER (walker));
+ g_return_if_fail (TPL_IS_LOG_ITER (iter));
+
+ priv = walker->priv;
+
+ priv->iters = g_list_prepend (priv->iters, g_object_ref (iter));
+ priv->caches = g_list_prepend (priv->caches, NULL);
+}
+
+
+/**
+ * tpl_log_walker_get_events_async:
+ * @walker: a #TplLogWalker
+ * @num_events: number of maximum events to fetch
+ * @callback: (scope async) (allow-none): a callback to call when
+ * the request is satisfied
+ * @user_data: data to pass to @callback
+ *
+ * Walk the logs to retrieve the next most recent @num_event events.
+ *
+ * Since: 0.8.0
+ */
+void
+tpl_log_walker_get_events_async (TplLogWalker *walker,
+ guint num_events,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TplLogWalkerPriv *priv;
+ GSimpleAsyncResult *simple;
+ TplLogWalkerAsyncData *async_data;
+
+ g_return_if_fail (TPL_IS_LOG_WALKER (walker));
+
+ priv = walker->priv;
+
+ async_data = tpl_log_walker_async_data_new ();
+ async_data->cb = callback;
+ async_data->num_events = num_events;
+ async_data->op_type = TPL_LOG_WALKER_OP_GET_EVENTS;
+
+ simple = g_simple_async_result_new (G_OBJECT (walker),
+ tpl_log_walker_async_operation_cb, user_data,
+ tpl_log_walker_get_events_async);
+
+ g_simple_async_result_set_op_res_gpointer (simple, async_data,
+ (GDestroyNotify) tpl_log_walker_async_data_free);
+
+ g_queue_push_tail (priv->queue, g_object_ref (simple));
+ if (g_queue_get_length (priv->queue) == 1)
+ tpl_log_walker_op_run (walker);
+
+ g_object_unref (simple);
+}
+
+
+/**
+ * tpl_log_walker_get_events_finish:
+ * @walker: a #TplLogWalker
+ * @result: a #GAsyncResult
+ * @events: (out) (transfer full) (element-type TelepathyLogger1.Event):
+ * a pointer to a #GList used to return the list #TplEvent
+ * @error: a #GError to fill
+ *
+ * Returns: #TRUE if the operation was successful, otherwise #FALSE.
+ *
+ * Since: 0.8.0
+ */
+gboolean
+tpl_log_walker_get_events_finish (TplLogWalker *walker,
+ GAsyncResult *result,
+ GList **events,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+ TplLogWalkerAsyncData *async_data;
+
+ g_return_val_if_fail (TPL_IS_LOG_WALKER (walker), FALSE);
+ g_return_val_if_fail (g_simple_async_result_is_valid (result,
+ G_OBJECT (walker), tpl_log_walker_get_events_async), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+ async_data = (TplLogWalkerAsyncData *)
+ g_simple_async_result_get_op_res_gpointer (simple);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return FALSE;
+
+ if (events != NULL)
+ {
+ *events = async_data->events;
+ async_data->events = NULL;
+ }
+
+ return TRUE;
+}
+
+
+/**
+ * tpl_log_walker_rewind_async:
+ * @walker: a #TplLogWalker
+ * @num_events: number of events to move back
+ * @callback: (scope async) (allow-none): a callback to call when
+ * the request is satisfied
+ * @user_data: data to pass to @callback
+ *
+ * Move the @walker back by the last @num_event events that were
+ * returned by tpl_log_walker_get_events_async().
+ *
+ * Since: 0.8.0
+ */
+void
+tpl_log_walker_rewind_async (TplLogWalker *walker,
+ guint num_events,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TplLogWalkerPriv *priv;
+ GSimpleAsyncResult *simple;
+ TplLogWalkerAsyncData *async_data;
+
+ g_return_if_fail (TPL_IS_LOG_WALKER (walker));
+
+ priv = walker->priv;
+
+ async_data = tpl_log_walker_async_data_new ();
+ async_data->cb = callback;
+ async_data->num_events = num_events;
+ async_data->op_type = TPL_LOG_WALKER_OP_REWIND;
+
+ simple = g_simple_async_result_new (G_OBJECT (walker),
+ tpl_log_walker_async_operation_cb, user_data,
+ tpl_log_walker_rewind_async);
+
+ g_simple_async_result_set_op_res_gpointer (simple, async_data,
+ (GDestroyNotify) tpl_log_walker_async_data_free);
+
+ g_queue_push_tail (priv->queue, g_object_ref (simple));
+ if (g_queue_get_length (priv->queue) == 1)
+ tpl_log_walker_op_run (walker);
+
+ g_object_unref (simple);
+}
+
+
+/**
+ * tpl_log_walker_rewind_finish:
+ * @walker: a #TplLogWalker
+ * @result: a #GAsyncResult
+ * @error: a #GError to fill
+ *
+ * Returns: #TRUE if the operation was successful, otherwise #FALSE.
+ *
+ * Since: 0.8.0
+ */
+gboolean
+tpl_log_walker_rewind_finish (TplLogWalker *walker,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (TPL_IS_LOG_WALKER (walker), FALSE);
+ g_return_val_if_fail (g_simple_async_result_is_valid (result,
+ G_OBJECT (walker), tpl_log_walker_rewind_async), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return FALSE;
+
+ return TRUE;
+}
+
+
+/**
+ * tpl_log_walker_is_start:
+ * @walker: a #TplLogWalker
+ *
+ * Determines whether @walker is pointing at the most recent event in
+ * the logs. This is the case when @walker has not yet returned any
+ * events or has been rewound completely.
+ *
+ * Returns: #TRUE if @walker is pointing at the most recent event,
+ * otherwise #FALSE.
+ *
+ * Since: 0.8.0
+ */
+gboolean
+tpl_log_walker_is_start (TplLogWalker *walker)
+{
+ TplLogWalkerPriv *priv;
+
+ priv = walker->priv;
+ return priv->is_start;
+}
+
+
+/**
+ * tpl_log_walker_is_end:
+ * @walker: a #TplLogWalker
+ *
+ * Determines whether @walker has run out of events. This is the case
+ * when @walker has returned all the events from the logs.
+ *
+ * Returns: #TRUE if @walker has run out of events, otherwise #FALSE.
+ *
+ * Since: 0.8.0
+ */
+gboolean
+tpl_log_walker_is_end (TplLogWalker *walker)
+{
+ TplLogWalkerPriv *priv;
+
+ priv = walker->priv;
+ return priv->is_end;
+}
diff --git a/telepathy-logger/log-walker.h b/telepathy-logger/log-walker.h
new file mode 100644
index 000000000..b341dc624
--- /dev/null
+++ b/telepathy-logger/log-walker.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Debarshi Ray <debarshir@freedesktop.org>
+ */
+
+#ifndef __TPL_LOG_WALKER_H__
+#define __TPL_LOG_WALKER_H__
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define TPL_TYPE_LOG_WALKER (tpl_log_walker_get_type ())
+
+#define TPL_LOG_WALKER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ TPL_TYPE_LOG_WALKER, TplLogWalker))
+
+#define TPL_LOG_WALKER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ TPL_TYPE_LOG_WALKER, TplLogWalkerClass))
+
+#define TPL_IS_LOG_WALKER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ TPL_TYPE_LOG_WALKER))
+
+#define TPL_IS_LOG_WALKER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ TPL_TYPE_LOG_WALKER))
+
+#define TPL_LOG_WALKER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ TPL_TYPE_LOG_WALKER, TplLogWalkerClass))
+
+typedef struct _TplLogWalker TplLogWalker;
+typedef struct _TplLogWalkerClass TplLogWalkerClass;
+typedef struct _TplLogWalkerPriv TplLogWalkerPriv;
+
+struct _TplLogWalker
+{
+ GObject parent_instance;
+ TplLogWalkerPriv *priv;
+};
+
+struct _TplLogWalkerClass
+{
+ /*< private >*/
+ GObjectClass parent_class;
+};
+
+GType tpl_log_walker_get_type (void) G_GNUC_CONST;
+
+void tpl_log_walker_get_events_async (TplLogWalker *walker,
+ guint num_events,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean tpl_log_walker_get_events_finish (TplLogWalker *walker,
+ GAsyncResult *result,
+ GList **events,
+ GError **error);
+
+void tpl_log_walker_rewind_async (TplLogWalker *walker,
+ guint num_events,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean tpl_log_walker_rewind_finish (TplLogWalker *walker,
+ GAsyncResult *result,
+ GError **error);
+
+gboolean tpl_log_walker_is_start (TplLogWalker *walker);
+
+gboolean tpl_log_walker_is_end (TplLogWalker *walker);
+
+G_END_DECLS
+
+#endif /* __TPL_LOG_WALKER_H__ */
diff --git a/telepathy-logger/observer-internal.h b/telepathy-logger/observer-internal.h
new file mode 100644
index 000000000..a0f1acf90
--- /dev/null
+++ b/telepathy-logger/observer-internal.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2009 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ */
+
+#ifndef __TPL_OBSERVER_H__
+#define __TPL_OBSERVER_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#define TPL_OBSERVER_WELL_KNOWN_BUS_NAME \
+ "im.telepathy1.Client.Logger"
+#define TPL_OBSERVER_OBJECT_PATH \
+ "/im.telepathy1/Client/Logger"
+
+
+G_BEGIN_DECLS
+#define TPL_TYPE_OBSERVER (_tpl_observer_get_type ())
+#define TPL_OBSERVER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TPL_TYPE_OBSERVER, TplObserver))
+#define TPL_OBSERVER_CLASS(obj) (G_TYPE_CHECK_CLASS_CAST ((obj), TPL_TYPE_OBSERVER, TplObserverClass))
+#define TPL_IS_OBSERVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TPL_TYPE_OBSERVER))
+#define TPL_IS_OBSERVER_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE ((obj), TPL_TYPE_OBSERVER))
+#define TPL_OBSERVER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TPL_TYPE_OBSERVER, TplObserverClass))
+
+typedef struct _TplObserverPriv TplObserverPriv;
+
+typedef struct
+{
+ TpBaseClient parent;
+
+ /* private */
+ TplObserverPriv *priv;
+} TplObserver;
+
+typedef struct
+{
+ TpBaseClientClass parent_class;
+} TplObserverClass;
+
+GType _tpl_observer_get_type (void);
+
+TplObserver * _tpl_observer_dup (GError **error);
+
+gboolean _tpl_observer_unregister_channel (TplObserver *self,
+ TpChannel *channel);
+
+
+G_END_DECLS
+#endif // __TPL_OBSERVER_H__
diff --git a/telepathy-logger/observer.c b/telepathy-logger/observer.c
new file mode 100644
index 000000000..7307ffbf6
--- /dev/null
+++ b/telepathy-logger/observer.c
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2009 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ */
+
+#include "config.h"
+#include "observer-internal.h"
+
+#include <glib.h>
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/telepathy-glib-dbus.h>
+
+#include <telepathy-logger/log-manager.h>
+
+#define DEBUG_FLAG TPL_DEBUG_OBSERVER
+#include <telepathy-logger/action-chain-internal.h>
+#include <telepathy-logger/client-factory-internal.h>
+#include <telepathy-logger/debug-internal.h>
+#include <telepathy-logger/util-internal.h>
+
+/*
+ * SECTION:observer
+ * @title: TplObserver
+ * @short_description: TPL Observer main class, used to handle received
+ * signals
+ * @see_also: #TpSvcClientObserver
+ *
+ * The Telepathy Logger's Observer implements
+ * im.telepathy1.Client.Observer DBus interface and is called by
+ * the Channel Dispatcher when a new channel is created, in order to log
+ * received signals.
+ *
+ * Since: 0.1
+ */
+
+/**
+ * TplObserver:
+ *
+ * The Telepathy Logger's Observer implements
+ * im.telepathy1.Client.Observer DBus interface and is called by
+ * the Channel Dispatcher when a new channel is created, in order to log
+ * received signals using its #LogManager.
+ *
+ * This object is a signleton, any call to tpl_observer_new will return the
+ * same object with an incremented reference counter. One has to
+ * unreference the object properly when the used reference is not used
+ * anymore.
+ *
+ * This object will register to it's DBus interface when
+ * tp_base_client_register is called, ensuring that the registration will
+ * happen only once per singleton instance.
+ *
+ * Since: 0.1
+ */
+
+/**
+ * TplObserverClass:
+ *
+ * The class of a #TplObserver.
+ */
+
+static void tpl_observer_dispose (GObject * obj);
+static gboolean _tpl_observer_register_channel (TplObserver *self,
+ TpChannel *channel);
+
+struct _TplObserverPriv
+{
+ /* Registered channels
+ * channel path borrowed from the TplChannel => reffed TplChannel */
+ GHashTable *channels;
+ TplLogManager *logmanager;
+ gboolean dbus_registered;
+};
+
+typedef struct
+{
+ TplObserver *self;
+ guint chan_n;
+ TpObserveChannelsContext *ctx;
+} ObservingContext;
+
+static TplObserver *observer_singleton = NULL;
+
+enum
+{
+ PROP_0,
+ PROP_REGISTERED_CHANNELS
+};
+
+G_DEFINE_TYPE (TplObserver, _tpl_observer, TP_TYPE_BASE_CLIENT)
+
+static void
+tpl_observer_observe_channels (TpBaseClient *client,
+ TpAccount *account,
+ TpConnection *connection,
+ GList *channels,
+ TpChannelDispatchOperation *dispatch_operation,
+ GList *requests,
+ TpObserveChannelsContext *context)
+{
+ TplObserver *self = TPL_OBSERVER (client);
+ GList *l;
+
+ for (l = channels; l != NULL; l = g_list_next (l))
+ _tpl_observer_register_channel (self, l->data);
+
+ tp_observe_channels_context_accept (context);
+}
+
+static gboolean
+_tpl_observer_register_channel (TplObserver *self,
+ TpChannel *channel)
+{
+ gchar *key;
+
+ g_return_val_if_fail (TPL_IS_OBSERVER (self), FALSE);
+ g_return_val_if_fail (TP_IS_CHANNEL (channel), FALSE);
+
+ key = (char *) tp_proxy_get_object_path (G_OBJECT (channel));
+
+ DEBUG ("Registering channel %s", key);
+
+ g_hash_table_insert (self->priv->channels, key, g_object_ref (channel));
+ g_object_notify (G_OBJECT (self), "registered-channels");
+
+ return TRUE;
+}
+
+
+static void
+tpl_observer_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TplObserver *self = TPL_OBSERVER (object);
+
+ switch (property_id)
+ {
+ case PROP_REGISTERED_CHANNELS:
+ {
+ GPtrArray *array = g_ptr_array_new ();
+ GList *keys, *ptr;
+
+ keys = g_hash_table_get_keys (self->priv->channels);
+
+ for (ptr = keys; ptr != NULL; ptr = ptr->next)
+ {
+ g_ptr_array_add (array, ptr->data);
+ }
+
+ g_value_set_boxed (value, array);
+
+ g_ptr_array_unref (array);
+ g_list_free (keys);
+
+ break;
+ }
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec);
+ break;
+ }
+}
+
+static void
+_tpl_observer_class_init (TplObserverClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ TpBaseClientClass *base_clt_cls = TP_BASE_CLIENT_CLASS (klass);
+
+ object_class->dispose = tpl_observer_dispose;
+ object_class->get_property = tpl_observer_get_property;
+
+ /**
+ * TplObserver:registered-channels:
+ *
+ * A list of channel's paths currently registered to this object.
+ *
+ * One can receive change notifications on this property by connecting
+ * to the #GObject::notify signal and using this property as the signal
+ * detail.
+ */
+ g_object_class_install_property (object_class, PROP_REGISTERED_CHANNELS,
+ g_param_spec_boxed ("registered-channels",
+ "Registered Channels",
+ "open TpChannels which the TplObserver is logging",
+ TP_ARRAY_TYPE_OBJECT_PATH_LIST,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_type_class_add_private (object_class, sizeof (TplObserverPriv));
+
+ tp_base_client_implement_observe_channels (base_clt_cls,
+ tpl_observer_observe_channels);
+}
+
+static void
+_tpl_observer_init (TplObserver *self)
+{
+ TplObserverPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TPL_TYPE_OBSERVER, TplObserverPriv);
+ self->priv = priv;
+
+ priv->channels = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, g_object_unref);
+
+ priv->logmanager = tpl_log_manager_dup_singleton ();
+
+ /* Observe contact text channels */
+ tp_base_client_take_observer_filter (TP_BASE_CLIENT (self),
+ tp_asv_new (
+ TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
+ TP_IFACE_CHANNEL_TYPE_TEXT,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
+ TP_HANDLE_TYPE_CONTACT,
+ NULL));
+
+ /* Observe room text channels */
+ tp_base_client_take_observer_filter (TP_BASE_CLIENT (self),
+ tp_asv_new (
+ TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
+ TP_IFACE_CHANNEL_TYPE_TEXT,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
+ TP_HANDLE_TYPE_ROOM,
+ NULL));
+
+ /* Observe contact call channels */
+ tp_base_client_take_observer_filter (TP_BASE_CLIENT (self),
+ tp_asv_new (
+ TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
+ "im.telepathy1.Channel.Type.Call1",
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
+ TP_HANDLE_TYPE_CONTACT,
+ NULL));
+
+ /* Observe room call channels */
+ tp_base_client_take_observer_filter (TP_BASE_CLIENT (self),
+ tp_asv_new (
+ TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
+ "im.telepathy1.Channel.Type.Call1",
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
+ TP_HANDLE_TYPE_ROOM,
+ NULL));
+
+ tp_base_client_set_observer_recover (TP_BASE_CLIENT (self), TRUE);
+}
+
+
+static void
+tpl_observer_dispose (GObject *obj)
+{
+ TplObserverPriv *priv = TPL_OBSERVER (obj)->priv;
+
+ tp_clear_pointer (&priv->channels, g_hash_table_unref);
+ g_clear_object (&priv->logmanager);
+
+ G_OBJECT_CLASS (_tpl_observer_parent_class)->dispose (obj);
+}
+
+
+TplObserver *
+_tpl_observer_dup (GError **error)
+{
+ /* WARNING Not thread safe */
+ if (G_UNLIKELY (observer_singleton == NULL))
+ {
+ GError *dbus_error = NULL;
+ TpDBusDaemon *dbus = tp_dbus_daemon_dup (&dbus_error);
+ TpClientFactory *factory;
+
+ if (dbus == NULL)
+ {
+ g_propagate_error (error, dbus_error);
+ return NULL;
+ }
+
+ factory = _tpl_client_factory_dup (dbus);
+
+ /* Pre-select feature to be initialized. */
+ tp_client_factory_add_contact_features_varargs (factory,
+ TP_CONTACT_FEATURE_ALIAS,
+ TP_CONTACT_FEATURE_PRESENCE,
+ TP_CONTACT_FEATURE_AVATAR_TOKEN,
+ 0);
+
+ observer_singleton = g_object_new (TPL_TYPE_OBSERVER,
+ "factory", factory,
+ "name", "Logger",
+ "uniquify-name", FALSE,
+ NULL);
+
+ g_object_add_weak_pointer (G_OBJECT (observer_singleton),
+ (gpointer *) &observer_singleton);
+
+ g_object_unref (dbus);
+ g_object_unref (factory);
+ }
+ else
+ {
+ g_object_ref (observer_singleton);
+ }
+
+ return observer_singleton;
+}
+
+/**
+ * _tpl_observer_unregister_channel:
+ * @self: #TplObserver instance, cannot be %NULL.
+ * @channel: a #TplChannel cast of a TplChannel subclass instance
+ *
+ * Un-registers a TplChannel subclass instance, i.e. TplTextChannel instance,
+ * as TplChannel instance.
+ * It is supposed to be called when the Closed signal for a channel is
+ * emitted or when an un-recoverable error during the life or a TplChannel
+ * happens.
+ *
+ * Every time that a channel is registered or unregistered, a notification is
+ * sent for the 'registered-channels' property.
+ *
+ * Returns: %TRUE if @channel is registered and can thus be un-registered or
+ * %FALSE if the @channel is not currently among registered channels and thus
+ * cannot be un-registered.
+ */
+gboolean
+_tpl_observer_unregister_channel (TplObserver *self,
+ TpChannel *channel)
+{
+ gboolean retval;
+ gchar *key;
+
+ g_return_val_if_fail (TPL_IS_OBSERVER (self), FALSE);
+ g_return_val_if_fail (TP_IS_CHANNEL (channel), FALSE);
+
+ key = (char *) tp_proxy_get_object_path (TP_PROXY (channel));
+
+ DEBUG ("Unregistering channel path %s", key);
+
+ /* this will destroy the associated value object: at this point
+ the hash table reference should be the only one for the
+ value's object
+ */
+ retval = g_hash_table_remove (self->priv->channels, key);
+
+ if (retval)
+ g_object_notify (G_OBJECT (self), "registered-channels");
+
+ return retval;
+}
diff --git a/telepathy-logger/telepathy-logger-1-uninstalled.pc.in b/telepathy-logger/telepathy-logger-1-uninstalled.pc.in
new file mode 100644
index 000000000..2f993fde2
--- /dev/null
+++ b/telepathy-logger/telepathy-logger-1-uninstalled.pc.in
@@ -0,0 +1,11 @@
+prefix=
+exec_prefix=
+abs_top_srcdir=@abs_top_srcdir@
+abs_top_builddir=@abs_top_builddir@
+
+Name: Telepathy Logger library (uninstalled copy)
+Description: Access to Telepathy logs
+Requires: telepathy-glib-1 telepathy-glib-1-dbus libxml-2.0
+Version: @VERSION@
+Libs: ${abs_top_builddir}/telepathy-logger/libtelepathy-logger-1.la
+Cflags: -I${abs_top_srcdir} -I${abs_top_builddir}
diff --git a/telepathy-logger/telepathy-logger-1.pc.in b/telepathy-logger/telepathy-logger-1.pc.in
new file mode 100644
index 000000000..3da2ce917
--- /dev/null
+++ b/telepathy-logger/telepathy-logger-1.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@/telepathy-logger-1
+
+Name: Telepathy Logger library
+Description: Access to Telepathy logs
+Requires: telepathy-glib-1 telepathy-glib-1-dbus libxml-2.0
+Version: @VERSION@
+Libs: -L${libdir} -ltelepathy-logger-1
+Cflags: -I${includedir}
diff --git a/telepathy-logger/telepathy-logger.c b/telepathy-logger/telepathy-logger.c
new file mode 100644
index 000000000..9f63bdf90
--- /dev/null
+++ b/telepathy-logger/telepathy-logger.c
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2009 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ */
+
+#include <config.h>
+
+#include <glib.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include <telepathy-logger/observer-internal.h>
+#include <telepathy-logger/dbus-service-internal.h>
+
+#define DEBUG_FLAG TPL_DEBUG_MAIN
+#include <telepathy-logger/debug-internal.h>
+
+static GMainLoop *loop = NULL;
+
+#ifdef ENABLE_DEBUG
+static TpDebugSender *debug_sender = NULL;
+static gboolean stamp_logs = FALSE;
+
+
+static void
+log_to_debug_sender (const gchar *log_domain,
+ GLogLevelFlags log_level,
+ const gchar *string)
+{
+ GTimeVal now;
+
+ g_return_if_fail (TP_IS_DEBUG_SENDER (debug_sender));
+
+ g_get_current_time (&now);
+
+ tp_debug_sender_add_message (debug_sender, &now, log_domain, log_level,
+ string);
+}
+
+
+static void
+log_handler (const gchar *log_domain,
+ GLogLevelFlags log_level,
+ const gchar *message,
+ gpointer user_data)
+{
+ if (stamp_logs)
+ {
+ GTimeVal now;
+ gchar now_str[32];
+ gchar *tmp;
+ struct tm tm;
+
+ g_get_current_time (&now);
+ localtime_r (&(now.tv_sec), &tm);
+ strftime (now_str, 32, "%Y-%m-%d %H:%M:%S", &tm);
+ tmp = g_strdup_printf ("%s.%06ld: %s",
+ now_str, now.tv_usec, message);
+
+ g_log_default_handler (log_domain, log_level, tmp, NULL);
+
+ g_free (tmp);
+ }
+ else
+ {
+ g_log_default_handler (log_domain, log_level, message, NULL);
+ }
+
+ log_to_debug_sender (log_domain, log_level, message);
+}
+#endif /* ENABLE_DEBUG */
+
+
+static TplDBusService *
+telepathy_logger_dbus_init (void)
+{
+ TplDBusService *dbus_srv = NULL;
+ TpDBusDaemon *tp_bus = NULL;
+ GError *error = NULL;
+
+
+ DEBUG ("Initializing TPL DBus service");
+ tp_bus = tp_dbus_daemon_dup (&error);
+ if (tp_bus == NULL)
+ {
+ g_critical ("Failed to acquire bus daemon: %s", error->message);
+ goto out;
+ }
+
+ if (!tp_dbus_daemon_request_name (tp_bus, TPL_DBUS_SRV_WELL_KNOWN_BUS_NAME,
+ FALSE, &error))
+ {
+ g_critical ("Failed to acquire bus name %s: %s",
+ TPL_DBUS_SRV_WELL_KNOWN_BUS_NAME, error->message);
+ goto out;
+ }
+
+ dbus_srv = _tpl_dbus_service_new ();
+ tp_dbus_daemon_register_object (tp_bus, TPL_DBUS_SRV_OBJECT_PATH,
+ G_OBJECT (dbus_srv));
+
+ DEBUG ("TPL DBus service registered to: %s",
+ TPL_DBUS_SRV_WELL_KNOWN_BUS_NAME);
+
+out:
+ if (error != NULL)
+ g_clear_error (&error);
+ if (tp_bus != NULL)
+ g_object_unref (tp_bus);
+
+ return dbus_srv;
+}
+
+
+int
+main (int argc,
+ char *argv[])
+{
+ TplDBusService *dbus_srv = NULL;
+ TplObserver *observer = NULL;
+ GError *error = NULL;
+
+ g_type_init ();
+
+ g_set_prgname (PACKAGE_NAME);
+
+ tp_debug_divert_messages (g_getenv ("TPL_LOGFILE"));
+
+#ifdef ENABLE_DEBUG
+ _tpl_debug_set_flags_from_env ();
+
+ stamp_logs = (g_getenv ("TPL_TIMING") != NULL);
+ debug_sender = tp_debug_sender_dup ();
+
+ g_log_set_default_handler (log_handler, NULL);
+#endif /* ENABLE_DEBUG */
+
+ observer = _tpl_observer_dup (&error);
+
+ if (observer == NULL) {
+ g_critical ("Failed to create observer: %s", error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ if (!tp_base_client_register (TP_BASE_CLIENT (observer), &error))
+ {
+ g_critical ("Error during D-Bus registration: %s", error->message);
+ goto out;
+ }
+ DEBUG ("TPL Observer registered to: %s", TPL_OBSERVER_WELL_KNOWN_BUS_NAME);
+
+ dbus_srv = telepathy_logger_dbus_init ();
+
+ loop = g_main_loop_new (NULL, FALSE);
+ g_main_loop_run (loop);
+
+out:
+ if (observer != NULL)
+ g_object_unref (observer);
+ if (dbus_srv != NULL)
+ g_object_unref (dbus_srv);
+
+#ifdef ENABLE_DEBUG
+ g_log_set_default_handler (g_log_default_handler, NULL);
+ g_object_unref (debug_sender);
+#endif /* ENABLE_DEBUG */
+
+ return 0;
+}
diff --git a/telepathy-logger/telepathy-logger.h b/telepathy-logger/telepathy-logger.h
new file mode 100644
index 000000000..54d42d848
--- /dev/null
+++ b/telepathy-logger/telepathy-logger.h
@@ -0,0 +1,31 @@
+/*
+ * telepathy-logger.h - Headers for telepathy-logger
+ *
+ * Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __TELEPATHY_LOGGER_H__
+#define __TELEPATHY_LOGGER_H__
+
+#include <telepathy-logger/entity.h>
+#include <telepathy-logger/text-event.h>
+#include <telepathy-logger/call-event.h>
+#include <telepathy-logger/event.h>
+#include <telepathy-logger/log-manager.h>
+#include <telepathy-logger/log-walker.h>
+
+#endif
diff --git a/telepathy-logger/test-api.c b/telepathy-logger/test-api.c
new file mode 100644
index 000000000..564e25888
--- /dev/null
+++ b/telepathy-logger/test-api.c
@@ -0,0 +1,76 @@
+/* TODO: fdo#69815 use GDBus */
+/*
+ * Copyright (C) 2009 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ */
+
+
+#include "config.h"
+
+#include <glib.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/proxy-subclass.h>
+
+#include <telepathy-logger/dbus-service-internal.h>
+#include "telepathy-logger/extensions/extensions.h"
+
+static GMainLoop *mainloop = NULL;
+
+int
+main (int argc, char *argv[])
+{
+ TpDBusDaemon *bus;
+ TpProxy *proxy;
+ GError *error = NULL;
+ char *account;
+
+ g_type_init ();
+ mainloop = g_main_loop_new (NULL, FALSE);
+
+ if (argc != 3)
+ {
+ g_printerr ("Usage: ./test-api <account> <identifier>\n");
+ return -1;
+ }
+
+ account = g_strdup_printf ("%s%s", TP_ACCOUNT_OBJECT_PATH_BASE, argv[1]);
+
+ bus = tp_dbus_daemon_dup (&error);
+ g_assert_no_error (error);
+
+ proxy = g_object_new (TP_TYPE_PROXY,
+ "bus-name", TPL_DBUS_SRV_WELL_KNOWN_BUS_NAME,
+ "object-path", TPL_DBUS_SRV_OBJECT_PATH,
+ "dbus-daemon", bus,
+ NULL);
+
+ g_object_unref (bus);
+
+ tp_proxy_add_interface_by_id (proxy, TPL_IFACE_QUARK_LOGGER);
+
+ // FIXME Test favorites
+
+ g_free (account);
+
+ g_main_loop_run (mainloop);
+
+ g_object_unref (proxy);
+
+ return 0;
+}
diff --git a/telepathy-logger/text-channel-internal.h b/telepathy-logger/text-channel-internal.h
new file mode 100644
index 000000000..f4f7db5b9
--- /dev/null
+++ b/telepathy-logger/text-channel-internal.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2009 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ */
+
+#ifndef __TPL_TEXT_CHANNEL_H__
+#define __TPL_TEXT_CHANNEL_H__
+
+/*
+ * http://telepathy.freedesktop.org/doc/telepathy-glib/telepathy-glib-channel-text.html#tp-cli-channel-type-text-connect-to-received
+ */
+
+#include <glib-object.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+G_BEGIN_DECLS
+
+#define TPL_TYPE_TEXT_CHANNEL (_tpl_text_channel_get_type ())
+#define TPL_TEXT_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TPL_TYPE_TEXT_CHANNEL, TplTextChannel))
+#define TPL_TEXT_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TPL_TYPE_TEXT_CHANNEL, TplTextChannelClass))
+#define TPL_IS_TEXT_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TPL_TYPE_TEXT_CHANNEL))
+#define TPL_IS_TEXT_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TPL_TYPE_TEXT_CHANNEL))
+#define TPL_TEXT_CHANNEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TPL_TYPE_TEXT_CHANNEL, TplTextChannelClass))
+
+
+#define TPL_TEXT_CHANNEL_ERROR \
+ g_quark_from_static_string ("tpl-text-channel-error-quark")
+
+typedef enum
+{
+ /* generic error */
+ TPL_TEXT_CHANNEL_ERROR_FAILED,
+ TPL_TEXT_CHANNEL_ERROR_NEED_MESSAGE_INTERFACE,
+} TplTextChannelError;
+
+#define TPL_TEXT_CHANNEL_FEATURE_CORE \
+ _tpl_text_channel_get_feature_quark_core ()
+GQuark _tpl_text_channel_get_feature_quark_core (void) G_GNUC_CONST;
+
+typedef struct _TplTextChannelPriv TplTextChannelPriv;
+typedef struct
+{
+ TpTextChannel parent;
+
+ /* private */
+ TplTextChannelPriv *priv;
+} TplTextChannel;
+
+typedef struct
+{
+ TpTextChannelClass parent_class;
+} TplTextChannelClass;
+
+GType _tpl_text_channel_get_type (void);
+
+TplTextChannel * _tpl_text_channel_new (TpConnection *conn,
+ const gchar *object_path,
+ GHashTable *tp_chan_props,
+ GError **error);
+
+TplTextChannel * _tpl_text_channel_new_with_factory (
+ TpClientFactory *factory,
+ TpConnection *conn,
+ const gchar *object_path,
+ const GHashTable *tp_chan_props,
+ GError **error);
+
+G_END_DECLS
+#endif /* __TPL_TEXT_CHANNEL_H__ */
diff --git a/telepathy-logger/text-channel.c b/telepathy-logger/text-channel.c
new file mode 100644
index 000000000..1444df93b
--- /dev/null
+++ b/telepathy-logger/text-channel.c
@@ -0,0 +1,722 @@
+/*
+ * Copyright (C) 2009-2011 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ * Nicolas Dufresne <nicolas.dufresne@collabora.co.uk>
+ */
+
+#include "config.h"
+#include "text-channel-internal.h"
+
+#include <glib.h>
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/telepathy-glib-dbus.h>
+
+#include "action-chain-internal.h"
+#include "entity-internal.h"
+#include "event-internal.h"
+#include "log-manager-internal.h"
+#include "log-store-sqlite-internal.h"
+#include "observer-internal.h"
+#include "text-event.h"
+#include "text-event-internal.h"
+#include "util-internal.h"
+
+#define DEBUG_FLAG TPL_DEBUG_CHANNEL
+#include "debug-internal.h"
+
+struct _TplTextChannelPriv
+{
+ TpAccount *account;
+ TplEntity *self;
+ gboolean is_chatroom;
+ TplEntity *remote;
+};
+
+G_DEFINE_TYPE (TplTextChannel, _tpl_text_channel, TP_TYPE_TEXT_CHANNEL)
+
+
+static void
+get_my_contact (TplTextChannel *self)
+{
+ TpChannel *chan = TP_CHANNEL (self);
+ TpConnection *tp_conn = tp_channel_get_connection (chan);
+ TpContact *my_contact;
+
+ my_contact = tp_channel_group_get_self_contact (chan);
+ if (my_contact == 0)
+ my_contact = tp_connection_get_self_contact (tp_conn);
+
+ self->priv->self = tpl_entity_new_from_tp_contact (my_contact,
+ TPL_ENTITY_SELF);
+}
+
+
+static void
+get_remote_contact (TplTextChannel *self)
+{
+ TpChannel *chan = TP_CHANNEL (self);
+ TpContact *contact;
+
+ contact = tp_channel_get_target_contact (chan);
+
+ if (contact == NULL)
+ {
+ self->priv->is_chatroom = TRUE;
+ self->priv->remote =
+ tpl_entity_new_from_room_id (tp_channel_get_identifier (chan));
+
+ PATH_DEBUG (self, "Chatroom id: %s",
+ tpl_entity_get_identifier (self->priv->remote));
+ }
+ else
+ {
+ self->priv->remote =
+ tpl_entity_new_from_tp_contact (contact, TPL_ENTITY_CONTACT);
+ }
+}
+
+
+static void
+on_channel_invalidated_cb (TpProxy *proxy,
+ guint domain,
+ gint code,
+ gchar *message,
+ gpointer user_data)
+{
+ TpChannel *chan = TP_CHANNEL (user_data);
+ TplObserver *observer = _tpl_observer_dup (NULL);
+
+ g_return_if_fail (observer);
+
+ PATH_DEBUG (chan, "%s #%d %s",
+ g_quark_to_string (domain), code, message);
+
+ if (!_tpl_observer_unregister_channel (observer, chan))
+ PATH_DEBUG (chan, "Channel couldn't be unregistered correctly (BUG?)");
+
+ g_object_unref (observer);
+}
+
+
+static guint
+get_message_pending_id (TpMessage *m)
+{
+ return tp_asv_get_uint32 (tp_message_peek (TP_MESSAGE (m), 0),
+ "pending-message-id", NULL);
+}
+
+
+static gint64
+get_original_message_timestamp (TpMessage *message)
+{
+ gint64 timestamp;
+
+ timestamp = tp_asv_get_int64 (tp_message_peek (message, 0),
+ "original-message-sent", NULL);
+
+ if (timestamp == 0)
+ timestamp = tp_asv_get_int64 (tp_message_peek (message, 0),
+ "original-message-received", NULL);
+
+ return timestamp;
+}
+
+
+static gint64
+get_network_timestamp (TpMessage *message)
+{
+ GDateTime *datetime = g_date_time_new_now_utc ();
+ gint64 now = g_date_time_to_unix (datetime);
+ gint64 timestamp;
+
+ timestamp = tp_message_get_sent_timestamp (message);
+
+ if (timestamp == 0)
+ timestamp = tp_message_get_received_timestamp (message);
+
+ if (timestamp == 0)
+ {
+ DEBUG ("TpMessage is not timestamped. Using current time instead.");
+ timestamp = now;
+ }
+
+ if (timestamp - now > 60 * 60)
+ DEBUG ("timestamp is more than an hour in the future.");
+ else if (now - timestamp > 60 * 60)
+ DEBUG ("timestamp is more than an hour in the past.");
+
+ g_date_time_unref (datetime);
+
+ return timestamp;
+}
+
+
+static gint64
+get_message_edit_timestamp (TpMessage *message)
+{
+ if (tp_message_get_supersedes (message) != NULL)
+ return get_network_timestamp (message);
+ else
+ return 0;
+}
+
+
+static gint64
+get_message_timestamp (TpMessage *message)
+{
+ gint64 timestamp;
+
+ timestamp = get_original_message_timestamp (message);
+
+ if (timestamp == 0)
+ timestamp = get_network_timestamp (message);
+
+ return timestamp;
+}
+
+
+static void
+tpl_text_channel_store_message (TplTextChannel *self,
+ TpMessage *message,
+ TplEntity *sender,
+ TplEntity *receiver)
+{
+ TplTextChannelPriv *priv = self->priv;
+ const gchar *direction;
+ TpChannelTextMessageType type;
+ gint64 timestamp;
+ gchar *text;
+ TplTextEvent *event;
+ TplLogManager *logmanager;
+ GError *error = NULL;
+
+ if (tpl_entity_get_entity_type (sender) == TPL_ENTITY_SELF)
+ direction = "sent";
+ else
+ direction = "received";
+
+ if (tp_message_is_scrollback (message))
+ {
+ DEBUG ("Ignoring %s scrollback message.", direction);
+ return;
+ }
+
+ if (tp_message_is_rescued (message))
+ {
+ DEBUG ("Ignoring %s rescued message.", direction);
+ return;
+ }
+
+ type = tp_message_get_message_type (message);
+
+ if (type == TP_CHANNEL_TEXT_MESSAGE_TYPE_DELIVERY_REPORT)
+ {
+ DEBUG ("Ignoring %s delivery report message.", direction);
+ return;
+ }
+
+ /* Ensure timestamp */
+ timestamp = get_message_timestamp (message);
+
+ text = tp_message_to_text (message);
+
+ if (text == NULL)
+ {
+ DEBUG ("Ignoring %s message with no supported content", direction);
+ return;
+ }
+
+ if (tpl_entity_get_entity_type (sender) == TPL_ENTITY_SELF)
+ DEBUG ("Logging message sent to %s (%s)",
+ tpl_entity_get_alias (receiver),
+ tpl_entity_get_identifier (receiver));
+ else
+ DEBUG ("Logging message received from %s (%s)",
+ tpl_entity_get_alias (sender),
+ tpl_entity_get_identifier (sender));
+
+
+ /* Initialise TplTextEvent */
+ event = g_object_new (TPL_TYPE_TEXT_EVENT,
+ /* TplEvent */
+ "account", priv->account,
+ "channel-path", tp_proxy_get_object_path (TP_PROXY (self)),
+ "receiver", receiver,
+ "sender", sender,
+ "timestamp", timestamp,
+ "message-token", tp_message_get_token (message),
+ "supersedes-token", tp_message_get_supersedes (message),
+ "edit-timestamp", get_message_edit_timestamp (message),
+ /* TplTextEvent */
+ "message-type", type,
+ "message", text,
+ NULL);
+
+ /* Store sent event */
+ logmanager = tpl_log_manager_dup_singleton ();
+ _tpl_log_manager_add_event (logmanager, TPL_EVENT (event), &error);
+
+ if (error != NULL)
+ {
+ PATH_DEBUG (self, "LogStore: %s", error->message);
+ g_error_free (error);
+ }
+ else if (tpl_entity_get_entity_type (sender) != TPL_ENTITY_SELF)
+ {
+ TplLogStore *cache = _tpl_log_store_sqlite_dup ();
+ _tpl_log_store_sqlite_add_pending_message (cache,
+ TP_CHANNEL (self),
+ get_message_pending_id (message),
+ timestamp,
+ &error);
+
+ if (error != NULL)
+ {
+ PATH_DEBUG (self, "Failed to cache pending message: %s",
+ error->message);
+ g_error_free (error);
+ }
+ }
+
+ g_object_unref (logmanager);
+ g_object_unref (event);
+ g_free (text);
+}
+
+
+static void
+on_message_received_cb (TpTextChannel *text_chan,
+ TpSignalledMessage *message,
+ gpointer user_data)
+{
+ TplTextChannel *self = TPL_TEXT_CHANNEL (text_chan);
+ TplTextChannelPriv *priv = self->priv;
+ TplEntity *receiver;
+ TplEntity *sender;
+
+ if (priv->is_chatroom)
+ receiver = priv->remote;
+ else
+ receiver = priv->self;
+
+ sender = tpl_entity_new_from_tp_contact (
+ tp_signalled_message_get_sender (TP_MESSAGE (message)),
+ TPL_ENTITY_CONTACT);
+
+ tpl_text_channel_store_message (self, TP_MESSAGE (message),
+ sender, receiver);
+
+ g_object_unref (sender);
+}
+
+
+static void
+on_message_sent_cb (TpChannel *proxy,
+ TpSignalledMessage *message,
+ guint flags,
+ const gchar *token,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TplTextChannel *self = TPL_TEXT_CHANNEL (proxy);
+ TplTextChannelPriv *priv = self->priv;
+ TplEntity *sender;
+ TplEntity *receiver = priv->remote;
+
+ if (tp_signalled_message_get_sender (TP_MESSAGE (message)) != NULL)
+ sender = tpl_entity_new_from_tp_contact (
+ tp_signalled_message_get_sender (TP_MESSAGE (message)),
+ TPL_ENTITY_SELF);
+ else
+ sender = g_object_ref (priv->self);
+
+ tpl_text_channel_store_message (self, TP_MESSAGE (message),
+ sender, receiver);
+
+ g_object_unref (sender);
+}
+
+
+static void
+on_pending_message_removed_cb (TpTextChannel *self,
+ TpSignalledMessage *message,
+ gpointer user_data)
+{
+ TplLogStore *cache;
+ GList *ids = NULL;
+ GError *error = NULL;
+
+ ids = g_list_prepend (ids,
+ GUINT_TO_POINTER (get_message_pending_id (TP_MESSAGE (message))));
+
+ cache = _tpl_log_store_sqlite_dup ();
+ _tpl_log_store_sqlite_remove_pending_messages (cache, TP_CHANNEL (self),
+ ids, &error);
+
+ if (error != NULL)
+ {
+ PATH_DEBUG (self, "Failed to remove pending message from cache: %s",
+ error->message);
+ g_error_free (error);
+ }
+
+ g_object_unref (cache);
+}
+
+
+static gint
+pending_message_compare_id (TpSignalledMessage *m1,
+ TpSignalledMessage *m2)
+{
+ guint id1, id2;
+
+ id1 = get_message_pending_id (TP_MESSAGE (m1));
+ id2 = get_message_pending_id (TP_MESSAGE (m2));
+
+ if (id1 > id2)
+ return 1;
+ else if (id1 < id2)
+ return -1;
+ else
+ return 0;
+}
+
+
+static gint
+pending_message_compare_timestamp (TpSignalledMessage *m1,
+ TpSignalledMessage *m2)
+{
+ gint64 ts1, ts2;
+
+ ts1 = get_message_timestamp (TP_MESSAGE (m1));
+ ts2 = get_message_timestamp (TP_MESSAGE (m2));
+
+ if (ts1 > ts2)
+ return 1;
+ else if (ts1 < ts2)
+ return -1;
+ else
+ return 0;
+}
+
+
+static void
+store_pending_messages (TplTextChannel *self)
+{
+ TplLogStore *cache;
+ GError *error = NULL;
+ GList *cached_messages;
+ GList *pending_messages;
+ GList *cached_it, *pending_it;
+ GList *to_remove = NULL;
+ GList *to_log = NULL;
+
+ cache = _tpl_log_store_sqlite_dup ();
+ cached_messages = _tpl_log_store_sqlite_get_pending_messages (cache,
+ TP_CHANNEL (self), &error);
+
+ if (error != NULL)
+ {
+ DEBUG ("Failed to read pending_message cache: %s.", error->message);
+ g_error_free (error);
+ /* We simply ignore this error, as if the list was empty */
+ }
+
+ pending_messages =
+ tp_text_channel_dup_pending_messages (TP_TEXT_CHANNEL (self));
+
+ pending_messages = g_list_sort (pending_messages,
+ (GCompareFunc) pending_message_compare_id);
+
+ cached_it = cached_messages;
+ pending_it = pending_messages;
+
+ while (cached_it != NULL || pending_it != NULL)
+ {
+ TplPendingMessage *cached;
+ TpSignalledMessage *pending;
+ guint pending_id;
+ gint64 pending_ts;
+
+ if (cached_it == NULL)
+ {
+ /* No more cached pending, just log the pending messages */
+ to_log = g_list_prepend (to_log, pending_it->data);
+ pending_it = g_list_next (pending_it);
+ continue;
+ }
+
+ cached = cached_it->data;
+
+ if (pending_it == NULL)
+ {
+ /* No more pending, just remove the cached messages */
+ to_remove = g_list_prepend (to_remove, GUINT_TO_POINTER (cached->id));
+ cached_it = g_list_next (cached_it);
+ continue;
+ }
+
+ pending = pending_it->data;
+ pending_id = get_message_pending_id (TP_MESSAGE (pending));
+ pending_ts = get_message_timestamp (TP_MESSAGE (pending));
+
+ if (cached->id == pending_id)
+ {
+ if (cached->timestamp != pending_ts)
+ {
+ /* The cache messaged is invalid, remove it */
+ to_remove = g_list_prepend (to_remove,
+ GUINT_TO_POINTER (cached->id));
+ cached_it = g_list_next (cached_it);
+ }
+ else
+ {
+ /* The message is already logged */
+ cached_it = g_list_next (cached_it);
+ pending_it = g_list_next (pending_it);
+ }
+ }
+ else if (cached->id < pending_id)
+ {
+ /* The cached ID is not valid anymore, remove it */
+ to_remove = g_list_prepend (to_remove, GUINT_TO_POINTER (cached->id));
+ cached_it = g_list_next (cached_it);
+ }
+ else
+ {
+ /* The pending message has not been logged */
+ to_log = g_list_prepend (to_log, pending);
+ pending_it = g_list_next (pending_it);
+ }
+ }
+
+ g_list_foreach (cached_messages, (GFunc) g_free, NULL);
+ g_list_free (cached_messages);
+ g_list_free_full (pending_messages, g_object_unref);
+
+
+ /* We need to remove before we log to avoid collisions */
+ if (to_remove != NULL)
+ {
+ if (!_tpl_log_store_sqlite_remove_pending_messages (cache,
+ TP_CHANNEL (self), to_remove, &error))
+ {
+ DEBUG ("Failed remove old pending messages from cache: %s", error->message);
+ g_error_free (error);
+ }
+ g_list_free (to_remove);
+ }
+
+ if (to_log != NULL)
+ {
+ GList *it;
+
+ to_log = g_list_sort (to_log,
+ (GCompareFunc) pending_message_compare_timestamp);
+
+ for (it = to_log; it != NULL; it = g_list_next (it))
+ on_message_received_cb (TP_TEXT_CHANNEL (self),
+ TP_SIGNALLED_MESSAGE (it->data), self);
+
+ g_list_free (to_log);
+ }
+
+ g_object_unref (cache);
+}
+
+
+static void
+connect_message_signals (TplTextChannel *self)
+{
+ tp_g_signal_connect_object (self, "invalidated",
+ G_CALLBACK (on_channel_invalidated_cb), self, 0);
+
+ tp_g_signal_connect_object (self, "message-received",
+ G_CALLBACK (on_message_received_cb), self, 0);
+
+ tp_g_signal_connect_object (self, "message-sent",
+ G_CALLBACK (on_message_sent_cb), self, 0);
+
+ tp_g_signal_connect_object (self, "pending-message-removed",
+ G_CALLBACK (on_pending_message_removed_cb), self, 0);
+}
+
+
+static void
+_tpl_text_channel_prepare_core_async (TpProxy *proxy,
+ const TpProxyFeature *feature,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TplTextChannel *self = (TplTextChannel *) proxy;
+
+ get_my_contact (self);
+ get_remote_contact (self);
+ store_pending_messages (self);
+ connect_message_signals (self);
+
+ tp_simple_async_report_success_in_idle ((GObject *) self, callback, user_data,
+ _tpl_text_channel_prepare_core_async);
+}
+
+
+GQuark
+_tpl_text_channel_get_feature_quark_core (void)
+{
+ return g_quark_from_static_string ("tpl-text-channel-feature-core");
+}
+
+enum {
+ FEAT_CORE,
+ N_FEAT
+};
+
+static const TpProxyFeature *
+tpl_text_channel_list_features (TpProxyClass *cls G_GNUC_UNUSED)
+{
+ static TpProxyFeature features[N_FEAT + 1] = { { 0 } };
+ static GQuark depends_on[3] = { 0, 0, 0 };
+
+ if (G_LIKELY (features[0].name != 0))
+ return features;
+
+ features[FEAT_CORE].name = TPL_TEXT_CHANNEL_FEATURE_CORE;
+ features[FEAT_CORE].prepare_async = _tpl_text_channel_prepare_core_async;
+ depends_on[0] = TP_TEXT_CHANNEL_FEATURE_INCOMING_MESSAGES;
+ depends_on[1] = TP_CHANNEL_FEATURE_GROUP;
+ features[FEAT_CORE].depends_on = depends_on;
+
+ /* assert that the terminator at the end is there */
+ g_assert (features[N_FEAT].name == 0);
+
+ return features;
+}
+
+static void
+tpl_text_channel_dispose (GObject *obj)
+{
+ TplTextChannelPriv *priv = TPL_TEXT_CHANNEL (obj)->priv;
+
+ tp_clear_object (&priv->remote);
+ tp_clear_object (&priv->self);
+
+ G_OBJECT_CLASS (_tpl_text_channel_parent_class)->dispose (obj);
+}
+
+
+static void
+tpl_text_channel_finalize (GObject *obj)
+{
+ PATH_DEBUG (obj, "finalizing channel %p", obj);
+
+ G_OBJECT_CLASS (_tpl_text_channel_parent_class)->finalize (obj);
+}
+
+
+static void
+_tpl_text_channel_class_init (TplTextChannelClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ TpProxyClass *proxy_class = (TpProxyClass *) klass;
+
+ object_class->dispose = tpl_text_channel_dispose;
+ object_class->finalize = tpl_text_channel_finalize;
+
+ proxy_class->list_features = tpl_text_channel_list_features;
+
+ g_type_class_add_private (object_class, sizeof (TplTextChannelPriv));
+}
+
+
+static void
+_tpl_text_channel_init (TplTextChannel *self)
+{
+ TplTextChannelPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TPL_TYPE_TEXT_CHANNEL, TplTextChannelPriv);
+
+ self->priv = priv;
+}
+
+
+/**
+ * _tpl_text_channel_new:
+ * @conn: TpConnection instance owning the channel
+ * @object_path: the channel's DBus path
+ * @tp_chan_props: channel's immutable properties, obtained for example by
+ * %tp_channel_borrow_immutable_properties()
+ * @error: location of the GError, used in case a problem is raised while
+ * creating the channel
+ *
+ * Convenience function to create a new TPL Channel Text proxy.
+ * The returned #TplTextChannel is not guaranteed to be ready at the point of
+ * return.
+ *
+ * TplTextChannel is actually a subclass of the abstract TplChannel which is a
+ * subclass of TpChannel.
+ * Use #TpChannel methods, casting the #TplTextChannel instance to a
+ * TpChannel, to access TpChannel data/methods from it.
+ *
+ * TplTextChannel is usually created using #tpl_channel_factory_build, from
+ * within a #TplObserver singleton, when its Observer_Channel method is called
+ * by the Channel Dispatcher.
+ *
+ * Returns: the TplTextChannel instance or %NULL if @object_path is not valid
+ */
+TplTextChannel *
+_tpl_text_channel_new (TpConnection *conn,
+ const gchar *object_path,
+ GHashTable *tp_chan_props,
+ GError **error)
+{
+ return _tpl_text_channel_new_with_factory (NULL, conn, object_path,
+ tp_chan_props, error);
+}
+
+TplTextChannel *
+_tpl_text_channel_new_with_factory (TpClientFactory *factory,
+ TpConnection *conn,
+ const gchar *object_path,
+ const GHashTable *tp_chan_props,
+ GError **error)
+{
+ TplTextChannel *self;
+
+ /* Do what tpl_channel_new does + set TplTextChannel specific */
+
+ g_return_val_if_fail (TP_IS_CONNECTION (conn), NULL);
+ g_return_val_if_fail (!TPL_STR_EMPTY (object_path), NULL);
+ g_return_val_if_fail (tp_chan_props != NULL, NULL);
+
+ if (!tp_dbus_check_valid_object_path (object_path, error))
+ return NULL;
+
+ self = g_object_new (TPL_TYPE_TEXT_CHANNEL,
+ /* TpChannel properties */
+ "factory", factory,
+ "connection", conn,
+ "dbus-daemon", tp_proxy_get_dbus_daemon (conn),
+ "bus-name", tp_proxy_get_bus_name (conn),
+ "object-path", object_path,
+ "handle-type", (guint) TP_UNKNOWN_HANDLE_TYPE,
+ "channel-properties", tp_chan_props,
+ NULL);
+
+ self->priv->account = g_object_ref (tp_connection_get_account (conn));
+
+ return self;
+}
diff --git a/telepathy-logger/text-event-internal.h b/telepathy-logger/text-event-internal.h
new file mode 100644
index 000000000..9725fd11f
--- /dev/null
+++ b/telepathy-logger/text-event-internal.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2009 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ */
+
+#ifndef __TPL_TEXT_EVENT_INTERNAL_H__
+#define __TPL_TEXT_EVENT_INTERNAL_H__
+
+#include <telepathy-logger/text-event.h>
+#include <telepathy-logger/event-internal.h>
+#include <telepathy-logger/text-channel-internal.h>
+
+#define TPL_TEXT_EVENT_MSG_ID_IS_VALID(msg) (msg >= 0)
+
+#define TPL_TEXT_EVENT_MSG_ID_UNKNOWN -2
+#define TPL_TEXT_EVENT_MSG_ID_ACKNOWLEDGED -1
+
+G_BEGIN_DECLS
+
+struct _TplTextEvent
+{
+ TplEvent parent;
+
+ /* Private */
+ TplTextEventPriv *priv;
+};
+
+struct _TplTextEventClass
+{
+ TplEventClass parent_class;
+};
+
+TpChannelTextMessageType _tpl_text_event_message_type_from_str (
+ const gchar *type_str);
+
+const gchar * _tpl_text_event_message_type_to_str (
+ TpChannelTextMessageType msg_type);
+
+gint _tpl_text_event_get_pending_msg_id (TplTextEvent *self);
+
+gboolean _tpl_text_event_is_pending (TplTextEvent *self);
+
+void _tpl_text_event_add_supersedes (TplTextEvent *self,
+ TplTextEvent *old_event);
+
+G_END_DECLS
+#endif // __TPL_TEXT_EVENT_INTERNAL_H__
diff --git a/telepathy-logger/text-event.c b/telepathy-logger/text-event.c
new file mode 100644
index 000000000..4ef6923a2
--- /dev/null
+++ b/telepathy-logger/text-event.c
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2009 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ */
+
+#include "config.h"
+#include "text-event.h"
+#include "text-event-internal.h"
+
+#include <glib-object.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+#include <telepathy-logger/event.h>
+#include <telepathy-logger/event-internal.h>
+
+#define DEBUG_FLAG TPL_DEBUG_LOG_STORE
+#include <telepathy-logger/debug-internal.h>
+#include <telepathy-logger/util-internal.h>
+
+/**
+ * SECTION:text-event
+ * @title: TplTextEvent
+ * @short_description: Representation of a text log event
+ *
+ * A subclass of #TplEvent representing a text log event.
+ */
+
+/**
+ * TplTextEvent:
+ *
+ * An object representing a text log event.
+ */
+
+
+G_DEFINE_TYPE (TplTextEvent, tpl_text_event, TPL_TYPE_EVENT)
+
+struct _TplTextEventPriv
+{
+ TpChannelTextMessageType message_type;
+ gint64 edit_timestamp;
+ gchar *message;
+ gchar *token;
+ gchar *supersedes_token;
+ /* A list of TplTextEvent that we supersede.
+ * This is only populated when reading logs (not storing them). */
+ GQueue supersedes;
+};
+
+enum
+{
+ PROP_MESSAGE_TYPE = 1,
+ PROP_EDIT_TIMESTAMP,
+ PROP_MESSAGE,
+ PROP_TOKEN,
+ PROP_SUPERSEDES
+};
+
+static gchar *message_types[] = {
+ "normal",
+ "action",
+ "notice",
+ "auto-reply",
+ "delivery-report",
+ NULL
+};
+
+
+static void
+tpl_text_event_dispose (GObject *obj)
+{
+ TplTextEventPriv *priv = TPL_TEXT_EVENT (obj)->priv;
+
+ g_list_foreach (priv->supersedes.head, (GFunc) g_object_unref, NULL);
+ g_list_free (priv->supersedes.head);
+ g_queue_init (&priv->supersedes);
+
+ G_OBJECT_CLASS (tpl_text_event_parent_class)->dispose (obj);
+}
+
+
+static void
+tpl_text_event_finalize (GObject *obj)
+{
+ TplTextEventPriv *priv = TPL_TEXT_EVENT (obj)->priv;
+
+ g_free (priv->message);
+ priv->message = NULL;
+
+ g_free (priv->token);
+ priv->token = NULL;
+
+ g_free (priv->supersedes_token);
+ priv->supersedes_token = NULL;
+
+ G_OBJECT_CLASS (tpl_text_event_parent_class)->finalize (obj);
+}
+
+
+static void
+tpl_text_event_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TplTextEventPriv *priv = TPL_TEXT_EVENT (object)->priv;
+
+ switch (param_id)
+ {
+ case PROP_MESSAGE_TYPE:
+ g_value_set_uint (value, priv->message_type);
+ break;
+ case PROP_EDIT_TIMESTAMP:
+ g_value_set_int64 (value, priv->edit_timestamp);
+ break;
+ case PROP_MESSAGE:
+ g_value_set_string (value, priv->message);
+ break;
+ case PROP_TOKEN:
+ g_value_set_string (value, priv->token);
+ break;
+ case PROP_SUPERSEDES:
+ g_value_set_string (value, priv->supersedes_token);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+
+static void
+tpl_text_event_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TplTextEventPriv *priv = TPL_TEXT_EVENT (object)->priv;
+
+ switch (param_id) {
+ case PROP_MESSAGE_TYPE:
+ priv->message_type = g_value_get_uint (value);
+ break;
+ case PROP_EDIT_TIMESTAMP:
+ priv->edit_timestamp = g_value_get_int64 (value);
+ break;
+ case PROP_MESSAGE:
+ g_assert (priv->message == NULL);
+ priv->message = g_value_dup_string (value);
+ break;
+ case PROP_TOKEN:
+ g_assert (priv->token == NULL);
+ priv->token = g_value_dup_string (value);
+ break;
+ case PROP_SUPERSEDES:
+ g_assert (priv->supersedes_token == NULL);
+ priv->supersedes_token = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+
+static gboolean tpl_text_event_equal (TplEvent *event1,
+ TplEvent *event2)
+{
+ TplTextEvent *text_event1 = TPL_TEXT_EVENT (event1);
+ TplTextEvent *text_event2 = TPL_TEXT_EVENT (event2);
+
+ return TPL_EVENT_CLASS (tpl_text_event_parent_class)->equal (event1, event2)
+ && text_event1->priv->message_type == text_event2->priv->message_type
+ && !tp_strdiff (text_event1->priv->message, text_event2->priv->message);
+}
+
+
+static void tpl_text_event_class_init (TplTextEventClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ TplEventClass *event_class = TPL_EVENT_CLASS (klass);
+ GParamSpec *param_spec;
+
+ object_class->dispose = tpl_text_event_dispose;
+ object_class->finalize = tpl_text_event_finalize;
+ object_class->get_property = tpl_text_event_get_property;
+ object_class->set_property = tpl_text_event_set_property;
+
+ event_class->equal = tpl_text_event_equal;
+
+ param_spec = g_param_spec_uint ("message-type",
+ "MessageType",
+ "The message type for a Text log event",
+ 0, G_MAXUINT32, TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_MESSAGE_TYPE, param_spec);
+
+ param_spec = g_param_spec_int64 ("edit-timestamp",
+ "Timestamp of edit message",
+ "message-{sent,received} if this is an edit, or 0 otherwise.",
+ G_MININT64, G_MAXINT64, 0,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_EDIT_TIMESTAMP,
+ param_spec);
+
+ param_spec = g_param_spec_string ("message",
+ "Message",
+ "The text message of the log event",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_MESSAGE, param_spec);
+
+ param_spec = g_param_spec_string ("message-token",
+ "Message Token",
+ "The message-token field of this message.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TOKEN, param_spec);
+
+ param_spec = g_param_spec_string ("supersedes-token",
+ "Message Token",
+ "The message-token field of the message that this one supersedes.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SUPERSEDES, param_spec);
+
+ g_type_class_add_private (object_class, sizeof (TplTextEventPriv));
+}
+
+
+static void
+tpl_text_event_init (TplTextEvent *self)
+{
+ TplTextEventPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TPL_TYPE_TEXT_EVENT, TplTextEventPriv);
+ self->priv = priv;
+}
+
+
+/**
+ * _tpl_text_event_message_type_from_str:
+ * @type_str: string to transform into a #TpChannelTextMessageType
+ *
+ * Maps strings into enum #TpChannelTextMessageType values.
+ *
+ * Returns: the relative value from enum #TpChannelTextMessageType if a
+ * mapping is found, or defaults to %TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL if not.
+ */
+TpChannelTextMessageType
+_tpl_text_event_message_type_from_str (const gchar *type_str)
+{
+ guint i;
+ for (i = 0; i < G_N_ELEMENTS (message_types); ++i)
+ if (!tp_strdiff (type_str, message_types[i]))
+ return (TpChannelTextMessageType) i;
+
+ /* default case */
+ return TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL;
+}
+
+
+/**
+ * _tpl_text_event_message_type_to_str:
+ * @msg_type: message type to transform into a string
+ *
+ * Maps enum #TpChannelTextMessageType values into strings
+ *
+ * Returns: a string representation for @msg_type or NULL if @msg_type is not
+ * a legal value for %TpChannelTextMessageType.
+ */
+const gchar *
+_tpl_text_event_message_type_to_str (TpChannelTextMessageType msg_type)
+{
+ g_return_val_if_fail (G_N_ELEMENTS (message_types) >= msg_type, NULL);
+
+ return message_types[msg_type];
+}
+
+
+/**
+ * tpl_text_event_get_message:
+ * @self: a #TplTextEvent
+ *
+ * Returns: the same message as the #TplTextEvent:message property
+ */
+const gchar *
+tpl_text_event_get_message (TplTextEvent *self)
+{
+ g_return_val_if_fail (TPL_IS_TEXT_EVENT (self), NULL);
+
+ return self->priv->message;
+}
+
+
+/**
+ * tpl_text_event_get_message_token:
+ * @self: a #TplTextEvent
+ *
+ * Returns: the same message as the #TplTextEvent:message-token property
+ */
+const gchar *
+tpl_text_event_get_message_token (TplTextEvent *self)
+{
+ g_return_val_if_fail (TPL_IS_TEXT_EVENT (self), NULL);
+
+ return self->priv->token;
+}
+
+
+/**
+ * tpl_text_event_get_supersedes_token:
+ * @self: a #TplTextEvent
+ *
+ * Returns: the same message as the #TplTextEvent:supersedes-token property
+ */
+const gchar *
+tpl_text_event_get_supersedes_token (TplTextEvent *self)
+{
+ g_return_val_if_fail (TPL_IS_TEXT_EVENT (self), NULL);
+
+ return self->priv->supersedes_token;
+}
+
+
+/**
+ * _tpl_text_event_add_supersedes:
+ * @self: a #TplTextEvent
+ * @old_event: (transfer none): an #TplTextEvent which this one supersedes
+ *
+ * If there are other known entries in the message edit/succession chain,
+ * they should be added to old_event before linking these two events,
+ * as they will be copied onto this event for convenience.
+ */
+void
+_tpl_text_event_add_supersedes (TplTextEvent *self,
+ TplTextEvent *old_event)
+{
+ GList *l;
+
+ g_object_ref (old_event);
+ g_queue_push_tail (&self->priv->supersedes, old_event);
+
+ for (l = old_event->priv->supersedes.head; l != NULL; l = g_list_next (l))
+ g_queue_push_tail (&self->priv->supersedes, g_object_ref (l->data));
+
+ if (self->priv->supersedes_token == NULL)
+ self->priv->supersedes_token = g_strdup (old_event->priv->token);
+}
+
+
+/**
+ * tpl_text_event_get_supersedes:
+ * @self: a #TplTextEvent
+ *
+ * Returns: (transfer none) (element-type TelepathyLogger1.TextEvent): A #GList
+ * of #TplTextEvent that this event
+ * supersedes.
+ */
+GList *
+tpl_text_event_get_supersedes (TplTextEvent *self)
+{
+ return self->priv->supersedes.head;
+}
+
+
+/**
+ * tpl_text_event_get_message_type:
+ * @self: a #TplTextEvent
+ *
+ * Returns: the same message as the #TplTextEvent:message-type property
+ */
+TpChannelTextMessageType
+tpl_text_event_get_message_type (TplTextEvent *self)
+{
+ g_return_val_if_fail (TPL_IS_TEXT_EVENT (self),
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL);
+
+ return self->priv->message_type;
+}
+
+
+/**
+ * tpl_text_event_get_edit_timestamp:
+ * @self: a #TplTextEvent
+ *
+ * Returns: the same value as the #TplTextEvent:edit-timestamp property
+ */
+gint64
+tpl_text_event_get_edit_timestamp (TplTextEvent *self)
+{
+ g_return_val_if_fail (TPL_IS_TEXT_EVENT (self), 0);
+
+ return self->priv->edit_timestamp;
+}
+
+
diff --git a/telepathy-logger/text-event.h b/telepathy-logger/text-event.h
new file mode 100644
index 000000000..f765507d7
--- /dev/null
+++ b/telepathy-logger/text-event.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2009 Collabora Ltd.
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ */
+
+#ifndef __TPL_TEXT_EVENT_H__
+#define __TPL_TEXT_EVENT_H__
+
+#include <glib-object.h>
+
+#include <telepathy-logger/event.h>
+
+G_BEGIN_DECLS
+#define TPL_TYPE_TEXT_EVENT (tpl_text_event_get_type ())
+#define TPL_TEXT_EVENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TPL_TYPE_TEXT_EVENT, TplTextEvent))
+#define TPL_TEXT_EVENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TPL_TYPE_TEXT_EVENT, TplTextEventClass))
+#define TPL_IS_TEXT_EVENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TPL_TYPE_TEXT_EVENT))
+#define TPL_IS_TEXT_EVENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TPL_TYPE_TEXT_EVENT))
+#define TPL_TEXT_EVENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TPL_TYPE_TEXT_EVENT, TplTextEventClass))
+
+typedef struct _TplTextEvent TplTextEvent;
+typedef struct _TplTextEventClass TplTextEventClass;
+typedef struct _TplTextEventPriv TplTextEventPriv;
+
+GType tpl_text_event_get_type (void);
+
+TpChannelTextMessageType tpl_text_event_get_message_type (TplTextEvent *self);
+gint64 tpl_text_event_get_edit_timestamp (TplTextEvent *self);
+
+const gchar *tpl_text_event_get_message (TplTextEvent *self);
+const gchar *tpl_text_event_get_message_token (TplTextEvent *self);
+const gchar *tpl_text_event_get_supersedes_token (TplTextEvent *self);
+
+GList *tpl_text_event_get_supersedes (TplTextEvent *self);
+
+G_END_DECLS
+#endif // __TPL_TEXT_EVENT_H__
diff --git a/telepathy-logger/tpl-marshal.list b/telepathy-logger/tpl-marshal.list
new file mode 100644
index 000000000..2c852dd1b
--- /dev/null
+++ b/telepathy-logger/tpl-marshal.list
@@ -0,0 +1,3 @@
+# add marshallers here
+VOID:UINT,UINT,BOXED,BOXED
+VOID:BOXED,BOXED
diff --git a/telepathy-logger/util-internal.h b/telepathy-logger/util-internal.h
new file mode 100644
index 000000000..eabcaa11a
--- /dev/null
+++ b/telepathy-logger/util-internal.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2009-2011 Collabora Ltd.
+ * Copyright (C) 2003-2007 Imendio AB
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ * Richard Hult <richard@imendio.com>
+ */
+
+#ifndef __TPL_UTIL_H__
+#define __TPL_UTIL_H__
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include "event.h"
+
+#define TPL_STR_EMPTY(x) ((x) == NULL || (x)[0] == '\0')
+
+void _tpl_rmdir_recursively (const gchar *dir_name);
+
+gint64 _tpl_time_parse (const gchar * str);
+
+GList *_tpl_event_queue_insert_sorted_after (GQueue *events,
+ GList *index,
+ TplEvent *event);
+
+#endif // __TPL_UTIL_H__
diff --git a/telepathy-logger/util.c b/telepathy-logger/util.c
new file mode 100644
index 000000000..fd871bf9b
--- /dev/null
+++ b/telepathy-logger/util.c
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2009-2011 Collabora Ltd.
+ * Copyright (C) 2003-2007 Imendio AB
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+ * Richard Hult <richard@imendio.com>
+ */
+
+#include "config.h"
+
+#include "util-internal.h"
+
+#include <errno.h>
+#include <glib.h>
+#include <glib/gstdio.h>
+
+
+void
+_tpl_rmdir_recursively (const gchar *dir_name)
+{
+ GDir *dir;
+ const gchar *name;
+
+ dir = g_dir_open (dir_name, 0, NULL);
+
+ /* Directory does not exist, nothing to do */
+ if (dir == NULL)
+ return;
+
+ while ((name = g_dir_read_name (dir)) != NULL)
+ {
+ gchar *filename = g_build_path (G_DIR_SEPARATOR_S,
+ dir_name, name, NULL);
+
+ if (g_file_test (filename, G_FILE_TEST_IS_DIR))
+ _tpl_rmdir_recursively (filename);
+ else if (g_unlink (filename) < 0)
+ g_warning ("Could not unlink '%s': %s", filename, g_strerror (errno));
+
+ g_free (filename);
+ }
+
+ g_dir_close (dir);
+
+ if (g_rmdir (dir_name) < 0)
+ g_warning ("Could not remove directory '%s': %s",
+ dir_name, g_strerror (errno));
+}
+
+/* We leak a single tz struct as freeing them is not thread-safe,
+ * see https://bugzilla.gnome.org/show_bug.cgi?id=646435 */
+static GTimeZone *tz;
+
+/* The format is: "20021209T23:51:30" and is in UTC. 0 is returned on
+ * failure. The alternative format "20021209" is also accepted.
+ */
+gint64
+_tpl_time_parse (const gchar *str)
+{
+ gint year = 0;
+ gint month = 0;
+ gint day = 0;
+ gint hour = 0;
+ gint min = 0;
+ gint sec = 0;
+ gint n_parsed;
+ GDateTime *dt;
+ gint64 ts;
+
+ n_parsed = sscanf (str, "%4d%2d%2dT%2d:%2d:%2d",
+ &year, &month, &day, &hour,
+ &min, &sec);
+
+ if (n_parsed != 3 && n_parsed != 6)
+ return 0;
+
+ if (G_UNLIKELY (tz == NULL))
+ tz = g_time_zone_new_utc ();
+
+ dt = g_date_time_new (tz, year, month, day, hour, min, sec);
+ ts = g_date_time_to_unix (dt);
+
+ g_date_time_unref (dt);
+
+ return ts;
+}
+
+
+GList *
+_tpl_event_queue_insert_sorted_after (GQueue *events,
+ GList *index,
+ TplEvent *event)
+{
+ if (g_queue_is_empty (events))
+ {
+ g_queue_push_tail (events, event);
+ return events->tail;
+ }
+
+ /* The initial index might go before the first one */
+ if (index == NULL)
+ {
+ index = events->head;
+
+ if (tpl_event_get_timestamp (event) <
+ tpl_event_get_timestamp (TPL_EVENT (index->data)))
+ {
+ g_queue_insert_before (events, index, event);
+ return events->head;
+ }
+ }
+
+ /* Find the last event that this event can go after */
+ while (g_list_next (index) != NULL &&
+ tpl_event_get_timestamp (event) >=
+ tpl_event_get_timestamp (TPL_EVENT (g_list_next (index)->data)))
+ index = g_list_next (index);
+
+ g_queue_insert_after (events, index, event);
+ return g_list_next (index);
+}
diff --git a/tests/Makefile.am b/tests/Makefile.am
index f6ede2a57..41987cc33 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -2,8 +2,13 @@ SUBDIRS = \
lib \
. \
dbus \
+ suppressions \
tools
+if ENABLE_LOGGER
+SUBDIRS += logger
+endif
+
programs_list = \
test-asv \
test-capabilities \
diff --git a/tests/lib/Makefile.am b/tests/lib/Makefile.am
index f8541f8f5..6a10f0f7c 100644
--- a/tests/lib/Makefile.am
+++ b/tests/lib/Makefile.am
@@ -104,3 +104,17 @@ libtp_glib_tests_la_LIBADD = \
$(top_builddir)/telepathy-glib/libtelepathy-glib-1-dbus.la \
$(top_builddir)/telepathy-glib/libtelepathy-glib-1-core.la \
$(NULL)
+
+if ENABLE_LOGGER
+
+noinst_LTLIBRARIES += libtp-logger-tests.la
+
+libtp_logger_tests_la_SOURCES = \
+ logger-test-helper.c \
+ logger-test-helper.h
+
+libtp_logger_tests_la_LIBADD = \
+ libtp-glib-tests.la \
+ $(NULL)
+
+endif
diff --git a/tests/lib/logger-test-helper.c b/tests/lib/logger-test-helper.c
new file mode 100644
index 000000000..fd20bafbd
--- /dev/null
+++ b/tests/lib/logger-test-helper.c
@@ -0,0 +1,86 @@
+/*
+ * logger-test-helper.c
+ *
+ * Copyright (C) 2013 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+
+#include "logger-test-helper.h"
+
+#include <stdlib.h>
+
+#include "util.h"
+
+void
+tpl_test_create_and_prepare_account (TpDBusDaemon *dbus,
+ TpClientFactory *factory,
+ const gchar *path,
+ TpAccount **account,
+ TpTestsSimpleAccount **account_service)
+{
+ GError *error = NULL;
+ GArray *features;
+ GQuark zero = 0;
+
+ *account_service = g_object_new (TP_TESTS_TYPE_SIMPLE_ACCOUNT,
+ NULL);
+ g_assert (*account_service != NULL);
+
+ tp_dbus_daemon_register_object (dbus, path, *account_service);
+
+ *account = tp_client_factory_ensure_account (factory, path, NULL,
+ &error);
+ g_assert_no_error (error);
+ g_assert (*account != NULL);
+
+ features = tp_client_factory_dup_account_features (factory, *account);
+ g_array_append_val (features, zero);
+
+ tp_tests_proxy_run_until_prepared (*account, (GQuark *) features->data);
+ g_array_free (features, FALSE);
+}
+
+void
+tpl_test_release_account (TpDBusDaemon *dbus,
+ TpAccount *account,
+ TpTestsSimpleAccount *account_service)
+{
+ tp_dbus_daemon_unregister_object (dbus, account_service);
+ g_object_unref (account_service);
+ g_object_unref (account);
+}
+
+void
+tp_tests_copy_dir (const gchar *from_dir, const gchar *to_dir)
+{
+ gchar *command;
+
+ // If destination directory exist erase it
+ command = g_strdup_printf ("rm -rf %s", to_dir);
+ g_assert (system (command) == 0);
+ g_free (command);
+
+ command = g_strdup_printf ("cp -r %s %s", from_dir, to_dir);
+ g_assert (system (command) == 0);
+ g_free (command);
+
+ // In distcheck mode the files and directory are read-only, fix that
+ command = g_strdup_printf ("chmod -R +w %s", to_dir);
+ g_assert (system (command) == 0);
+ g_free (command);
+}
diff --git a/tests/lib/logger-test-helper.h b/tests/lib/logger-test-helper.h
new file mode 100644
index 000000000..cb836f257
--- /dev/null
+++ b/tests/lib/logger-test-helper.h
@@ -0,0 +1,40 @@
+/*
+ * logger-test-helper.h
+ *
+ * Copyright (C) 2013 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * This library 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 library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __LOGGER_TEST_HELPER_H__
+#define __LOGGER_TEST_HELPER_H__
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "simple-account.h"
+
+void tpl_test_create_and_prepare_account (TpDBusDaemon *dbus,
+ TpClientFactory *factory,
+ const gchar *path,
+ TpAccount **account,
+ TpTestsSimpleAccount **account_service);
+
+void tpl_test_release_account (TpDBusDaemon *dbus,
+ TpAccount *account,
+ TpTestsSimpleAccount *account_service);
+
+void tp_tests_copy_dir (const gchar *from_dir, const gchar *to_dir);
+
+#endif
diff --git a/tests/logger/Makefile.am b/tests/logger/Makefile.am
new file mode 100644
index 000000000..7f8e0e9b9
--- /dev/null
+++ b/tests/logger/Makefile.am
@@ -0,0 +1,54 @@
+# These tests aren't ready for parallel invocation yet: they all use
+# the same log directory.
+.NOTPARALLEL:
+
+SUBDIRS = $(CHECKTWISTED) dbus
+
+EXTRA_DIST = logs
+
+noinst_PROGRAMS = \
+ test-tpl-conf \
+ $(NULL)
+
+TESTS = $(noinst_PROGRAMS)
+
+LDADD = \
+ $(top_builddir)/telepathy-logger/libtelepathy-logger-1.la \
+ $(top_builddir)/telepathy-glib/libtelepathy-glib-1.la \
+ $(top_builddir)/telepathy-glib/libtelepathy-glib-1-dbus.la \
+ $(top_builddir)/telepathy-glib/libtelepathy-glib-1-core.la \
+ $(TPL_LIBS)
+
+AM_CFLAGS = \
+ $(ERROR_CFLAGS) \
+ $(TPL_CFLAGS) \
+ -I$(top_srcdir) \
+ -I$(top_builddir) \
+ $(NULL)
+
+AM_TESTS_ENVIRONMENT = \
+ G_DEBUG=fatal-warnings,fatal-criticals \
+ TPL_TEST_MODE=true \
+ TEST_LOG_DIR=@abs_top_srcdir@/tests/logger/logs \
+ $(NULL)
+
+check-valgrind: $(TESTS)
+ G_SLICE=always-malloc \
+ G_DEBUG=gc-friendly \
+ $(MAKE) \
+ LOG_COMPILER="libtool --mode=execute valgrind \
+ --leak-check=full \
+ --show-reachable=no \
+ --gen-suppressions=all \
+ --num-callers=20 \
+ --suppressions=@abs_top_srcdir@/tests/suppressions/tpl.supp \
+ --error-exitcode=1" \
+ check-TESTS
+
+check_c_sources = \
+ $(dbus_test_sources) \
+ test-tpl-conf.c \
+ $(NULL)
+
+include $(top_srcdir)/tools/check-coding-style.mk
+check-local: check-coding-style
diff --git a/tests/logger/README b/tests/logger/README
new file mode 100644
index 000000000..b402ba511
--- /dev/null
+++ b/tests/logger/README
@@ -0,0 +1,48 @@
+To run all tests:
+
+ make check
+
+or with coverage info:
+
+ ./configure --enable-compiler-coverage
+ make lcov-check
+
+== C tests ==
+
+To run all C tests (assuming the current directory is $top_srcdir):
+
+ make -C tests check-TESTS
+
+To run an individual test:
+
+ make -C tests check-TESTS TESTS=test-handles
+
+To run tests under Valgrind:
+
+ make -C tests check-valgrind
+
+To run an individual test under Valgrind:
+
+ make -C tests check-valgrind TESTS=test-handles
+
+To debug an individual test you can set one of the following env variable:
+
+ * TPL_TEST_VALGRIND : to run Gabble inside valgrind. The report is
+ added to tools/tpl-testing.log.
+ export TPL_TEST_VALGRIND=1
+
+ * TPL_TEST_REFDBG : to run Gabble inside refdbg. The report is written
+ to tools/refdbg.log. You can change TPL_WRAPPER to use an alternative
+ refdbg and change REFDBG_OPTIONS to set your own parameters. Example:
+ export TPL_TEST_REFDBG=1
+ export TPL_WRAPPER="/path/to/refdbg"
+ export REFDBG_OPTIONS="btnum=16"
+
+ * TPL_WRAPPER="nemiver" : to run Gabble inside the graphical debugger
+ nemiver. You'll be able to set up breakpoints; then hit the "continue"
+ button to launch Gabble.
+
+ * TPL_TEST_STRACE : to run Gabble inside strace. The report is written
+ to tools/strace.log.
+ export TPL_TEST_STRACE=1
+
diff --git a/tests/logger/constants.h b/tests/logger/constants.h
new file mode 100644
index 000000000..7eb4b9eb0
--- /dev/null
+++ b/tests/logger/constants.h
@@ -0,0 +1,2 @@
+#define ACCOUNT_PATH "/im.telepathy1/Account/gabble/jabber/cosimo_2ealfarano_40collabora_2eco_2euk0"
+#define ID "echo@test.collabora.co.uk"
diff --git a/tests/logger/dbus/Makefile.am b/tests/logger/dbus/Makefile.am
new file mode 100644
index 000000000..cb84655a0
--- /dev/null
+++ b/tests/logger/dbus/Makefile.am
@@ -0,0 +1,89 @@
+# These tests aren't ready for parallel invocation yet: they all use
+# the same log directory.
+.NOTPARALLEL:
+
+noinst_PROGRAMS = \
+ test-entity \
+ test-log-manager \
+ test-tpl-log-store-pidgin \
+ test-tpl-log-iter-pidgin \
+ test-tpl-log-store-sqlite \
+ test-tpl-log-store-xml \
+ test-tpl-log-iter-xml \
+ test-tpl-log-walker \
+ test-tpl-observer \
+ $(NULL)
+
+TESTS = $(noinst_PROGRAMS)
+
+LDADD = \
+ $(top_builddir)/tests/lib/libtp-logger-tests.la \
+ $(top_builddir)/telepathy-logger/libtelepathy-logger-1.la \
+ $(TPL_LIBS) \
+ $(NULL)
+
+test_entity_LDADD = \
+ $(top_builddir)/tests/lib/libtp-logger-tests.la \
+ $(LDADD) \
+ $(NULL)
+
+check_c_sources = *.c
+include $(top_srcdir)/tools/check-coding-style.mk
+check-local: check-coding-style
+
+AM_CFLAGS = \
+ $(ERROR_CFLAGS) \
+ $(TPL_CFLAGS) \
+ -I$(top_srcdir) \
+ -I$(top_builddir) \
+ -I$(top_srcdir)/tests
+ $(NULL)
+
+AM_TESTS_ENVIRONMENT = \
+ abs_top_builddir=@abs_top_builddir@ \
+ TPL_TEST_MODE=true \
+ TPL_TEST_LOG_DIR=@abs_top_srcdir@/tests/logger/logs \
+ GSETTINGS_SCHEMA_DIR=@abs_srcdir@/data \
+ XDG_DATA_HOME=@abs_top_builddir@/tests/logger/logs \
+ XDG_DATA_DIRS=@abs_srcdir@ \
+ G_SLICE=debug-blocks \
+ TPL_DEBUG=all \
+ G_DEBUG=fatal_warnings,fatal_criticals$(maybe_gc_friendly) \
+ $(NULL)
+
+LOG_COMPILER = \
+ sh $(top_srcdir)/tools/with-session-bus.sh \
+ --config-file=dbus-1/session.conf -- \
+ $(NULL)
+
+check-valgrind:
+ $(MAKE) check-TESTS \
+ maybe_gc_friendly=,gc-friendly \
+ LOG_COMPILER="$(VALGRIND_LOG_COMPILER)"
+
+include $(top_srcdir)/tools/valgrind.mk
+
+VALGRIND_LOG_COMPILER = \
+ $(LOG_COMPILER) \
+ env G_SLICE=always-malloc CHECK_VERBOSE=1 \
+ $(top_builddir)/libtool --mode=execute \
+ $(VALGRIND) --suppressions=$(top_srcdir)/tests/suppressions/tpl.supp $(VALGRIND_FLAGS) \
+ $(NULL)
+
+BUILT_SOURCES = \
+ dbus-1/session.conf \
+ $(NULL)
+
+CLEANFILES = $(BUILT_SOURCES)
+
+distclean-local:
+ rm -f capture-*.log
+ rm -rf _gen
+
+EXTRA_DIST = \
+ dbus-1/session.conf.in \
+ $(NULL)
+
+dbus-1/%.conf: $(srcdir)/dbus-1/%.conf.in
+ $(AM_V_at)$(MKDIR_P) dbus-1
+ $(AM_V_GEN)sed -e "s|[@]abs_top_builddir[@]|@abs_top_builddir@|g" $< > $@
diff --git a/tests/logger/dbus/dbus-1/session.conf.in b/tests/logger/dbus/dbus-1/session.conf.in
new file mode 100644
index 000000000..b934b1d3c
--- /dev/null
+++ b/tests/logger/dbus/dbus-1/session.conf.in
@@ -0,0 +1,30 @@
+<!-- Copied from telepathy-gabble (which doubtless copied it from somewhere
+ else) and modified.
+ This configuration file controls the per-user-login-session message bus.
+ Add a session-local.conf and edit that rather than changing this
+ file directly. -->
+
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+ <!-- Our well-known bus type, don't change this -->
+ <type>session</type>
+
+ <listen>unix:tmpdir=/tmp</listen>
+
+ <servicedir>@abs_top_builddir@/tests/dbus/dbus-1/services/</servicedir>
+
+ <policy context="default">
+ <!-- Allow everything to be sent -->
+ <allow send_destination="*" eavesdrop="true"/>
+ <!-- Allow everything to be received -->
+ <allow eavesdrop="true"/>
+ <!-- Allow anyone to own anything -->
+ <allow own="*"/>
+ </policy>
+
+ <!-- This is included last so local configuration can override what's
+ in this standard file -->
+
+
+</busconfig>
diff --git a/tests/logger/dbus/test-entity.c b/tests/logger/dbus/test-entity.c
new file mode 100644
index 000000000..b345b5e0a
--- /dev/null
+++ b/tests/logger/dbus/test-entity.c
@@ -0,0 +1,171 @@
+#include "config.h"
+
+#include <glib.h>
+#include <glib/gprintf.h>
+#include <telepathy-logger/entity.h>
+#include <telepathy-logger/entity-internal.h>
+
+#include "lib/util.h"
+#include "lib/contacts-conn.h"
+
+static void
+test_entity_instantiation (void)
+{
+ TplEntity *entity;
+
+ entity = tpl_entity_new ("my-identifier", TPL_ENTITY_CONTACT,
+ "my-alias", "my-token");
+
+ g_assert_cmpstr (tpl_entity_get_identifier (entity), ==, "my-identifier");
+ g_assert (tpl_entity_get_entity_type (entity) == TPL_ENTITY_CONTACT);
+ g_assert_cmpstr (tpl_entity_get_alias (entity), ==, "my-alias");
+ g_assert_cmpstr (tpl_entity_get_avatar_token (entity), ==, "my-token");
+
+ g_object_unref (entity);
+
+ /* Check that identifier is copied in absence of ID */
+ entity = tpl_entity_new ("my-identifier", TPL_ENTITY_CONTACT,
+ NULL, NULL);
+
+ g_assert_cmpstr (tpl_entity_get_alias (entity), ==, "my-identifier");
+ g_assert_cmpstr (tpl_entity_get_avatar_token (entity), ==, "");
+
+ g_object_unref (entity);
+}
+
+static void
+test_entity_instantiation_from_room_id (void)
+{
+ TplEntity *entity;
+
+ entity = tpl_entity_new_from_room_id ("my-room-id");
+
+ g_assert_cmpstr (tpl_entity_get_identifier (entity), ==, "my-room-id");
+ g_assert (tpl_entity_get_entity_type (entity) == TPL_ENTITY_ROOM);
+ g_assert_cmpstr (tpl_entity_get_alias (entity), ==, "my-room-id");
+ g_assert_cmpstr (tpl_entity_get_avatar_token (entity), ==, "");
+
+ g_object_unref (entity);
+}
+
+typedef struct {
+ TpContact *contact;
+ GMainLoop *loop;
+} Result;
+
+static void
+ensure_contact_cb (GObject *source,
+ GAsyncResult *op_result,
+ gpointer user_data)
+{
+ Result *result = user_data;
+ GError *error = NULL;
+
+ result->contact = tp_client_factory_ensure_contact_by_id_finish (
+ TP_CLIENT_FACTORY (source), op_result, &error);
+
+ g_assert_no_error (error);
+ g_assert (TP_IS_CONTACT (result->contact));
+
+ g_main_loop_quit (result->loop);
+}
+
+static void
+test_entity_instantiation_from_tp_contact (void)
+{
+ TpBaseConnection *base_connection;
+ TpConnection *client_connection;
+ TpTestsContactsConnection *connection;
+ TpHandleRepoIface *repo;
+ TpHandle handles[2];
+ const char *alias[] = {"Alice in Wonderland", "Bob the builder"};
+ const char *avatar_tokens[] = {"alice-token", NULL};
+ Result result;
+ TplEntity *entity;
+ TpContact *alice, *bob;
+ TpClientFactory *factory;
+
+ tp_tests_create_and_connect_conn (TP_TESTS_TYPE_CONTACTS_CONNECTION,
+ "me@test.com", &base_connection, &client_connection);
+
+ connection = TP_TESTS_CONTACTS_CONNECTION (base_connection);
+
+ repo = tp_base_connection_get_handles (base_connection,
+ TP_HANDLE_TYPE_CONTACT);
+
+ handles[0] = tp_handle_ensure (repo, "alice", NULL, NULL);
+ g_assert (handles[0] != 0);
+
+ handles[1] = tp_handle_ensure (repo, "bob", NULL, NULL);
+ g_assert (handles[1] != 0);
+
+ tp_tests_contacts_connection_change_aliases (connection, 2, handles,
+ alias);
+ tp_tests_contacts_connection_change_avatar_tokens (connection, 2, handles,
+ avatar_tokens);
+
+ factory = tp_proxy_get_factory (client_connection);
+ tp_client_factory_add_contact_features_varargs (factory,
+ TP_CONTACT_FEATURE_ALIAS,
+ TP_CONTACT_FEATURE_AVATAR_TOKEN,
+ 0);
+
+ result.loop = g_main_loop_new (NULL, FALSE);
+
+ tp_client_factory_ensure_contact_by_id_async (factory,
+ client_connection, "alice", ensure_contact_cb, &result);
+ g_main_loop_run (result.loop);
+ alice = result.contact;
+
+ tp_client_factory_ensure_contact_by_id_async (factory,
+ client_connection, "bob", ensure_contact_cb, &result);
+ g_main_loop_run (result.loop);
+ bob = result.contact;
+
+ entity = tpl_entity_new_from_tp_contact (alice, TPL_ENTITY_SELF);
+
+ g_assert_cmpstr (tpl_entity_get_identifier (entity), ==, "alice");
+ g_assert (tpl_entity_get_entity_type (entity) == TPL_ENTITY_SELF);
+ g_assert_cmpstr (tpl_entity_get_alias (entity), ==, alias[0]);
+ g_assert_cmpstr (tpl_entity_get_avatar_token (entity), ==, avatar_tokens[0]);
+ g_object_unref (entity);
+
+ entity = tpl_entity_new_from_tp_contact (bob, TPL_ENTITY_CONTACT);
+
+ g_assert_cmpstr (tpl_entity_get_identifier (entity), ==, "bob");
+ g_assert (tpl_entity_get_entity_type (entity) == TPL_ENTITY_CONTACT);
+ g_assert_cmpstr (tpl_entity_get_alias (entity), ==, alias[1]);
+ g_assert_cmpstr (tpl_entity_get_avatar_token (entity), ==, "");
+ g_object_unref (entity);
+
+ g_object_unref (alice);
+ g_object_unref (bob);
+ g_main_loop_unref (result.loop);
+
+ tp_base_connection_change_status (base_connection,
+ TP_CONNECTION_STATUS_DISCONNECTED,
+ TP_CONNECTION_STATUS_REASON_REQUESTED);
+ tp_base_connection_finish_shutdown (base_connection);
+
+ g_object_unref (base_connection);
+ g_object_unref (client_connection);
+}
+
+int main (int argc,
+ char **argv)
+{
+ g_test_init (&argc, &argv, NULL);
+
+ g_type_init ();
+
+ g_test_add_func ("/entity/instantiation",
+ test_entity_instantiation);
+
+ g_test_add_func ("/entity/instantiation-from-room-id",
+ test_entity_instantiation_from_room_id);
+
+ g_test_add_func ("/entity/instantiation-from-tp-contact",
+ test_entity_instantiation_from_tp_contact);
+
+ return g_test_run ();
+}
diff --git a/tests/logger/dbus/test-log-manager.c b/tests/logger/dbus/test-log-manager.c
new file mode 100644
index 000000000..6bd7b66f9
--- /dev/null
+++ b/tests/logger/dbus/test-log-manager.c
@@ -0,0 +1,807 @@
+#include "config.h"
+
+#include "telepathy-logger/log-manager.c"
+
+#include "lib/util.h"
+#include "lib/simple-account.h"
+#include "lib/simple-account-manager.h"
+#include "lib/logger-test-helper.h"
+
+#include "telepathy-logger/debug-internal.h"
+#include "telepathy-logger/log-manager-internal.h"
+#include "telepathy-logger/log-store-internal.h"
+#include <telepathy-logger/text-event.h>
+#include <telepathy-logger/client-factory-internal.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+/* it was defined in telepathy-logger/log-manager.c */
+#undef DEBUG_FLAG
+#define DEBUG_FLAG TPL_DEBUG_TESTSUITE
+
+#define ACCOUNT_PATH_JABBER TP_ACCOUNT_OBJECT_PATH_BASE "gabble/jabber/user_40collabora_2eco_2euk"
+#define MY_ID "user@collabora.co.uk"
+#define ID "user2@collabora.co.uk"
+
+typedef struct
+{
+ GMainLoop *main_loop;
+
+ TpDBusDaemon *dbus;
+ TpAccount *account;
+ TpTestsSimpleAccount *account_service;
+ TpClientFactory *factory;
+
+ GList *ret;
+
+ gchar *tmp_basedir;
+
+ TplLogManager *manager;
+} TestCaseFixture;
+
+
+
+#ifdef ENABLE_DEBUG
+static TpDebugSender *debug_sender = NULL;
+static gboolean stamp_logs = FALSE;
+
+
+static void
+log_to_debug_sender (const gchar *log_domain,
+ GLogLevelFlags log_level,
+ const gchar *string)
+{
+ GTimeVal now;
+
+ g_return_if_fail (TP_IS_DEBUG_SENDER (debug_sender));
+
+ g_get_current_time (&now);
+
+ tp_debug_sender_add_message (debug_sender, &now, log_domain, log_level,
+ string);
+}
+
+
+static void
+log_handler (const gchar *log_domain,
+ GLogLevelFlags log_level,
+ const gchar *message,
+ gpointer user_data)
+{
+ if (stamp_logs)
+ {
+ GTimeVal now;
+ gchar now_str[32];
+ gchar *tmp;
+ struct tm tm;
+
+ g_get_current_time (&now);
+ localtime_r (&(now.tv_sec), &tm);
+ strftime (now_str, 32, "%Y-%m-%d %H:%M:%S", &tm);
+ tmp = g_strdup_printf ("%s.%06ld: %s",
+ now_str, now.tv_usec, message);
+
+ g_log_default_handler (log_domain, log_level, tmp, NULL);
+
+ g_free (tmp);
+ }
+ else
+ {
+ g_log_default_handler (log_domain, log_level, message, NULL);
+ }
+
+ log_to_debug_sender (log_domain, log_level, message);
+}
+#endif /* ENABLE_DEBUG */
+
+
+static void
+teardown_service (TestCaseFixture* fixture,
+ gconstpointer user_data)
+{
+ GError *error = NULL;
+
+ g_assert (user_data != NULL);
+
+ if (fixture->account != NULL)
+ {
+ /* FIXME is it useful in this suite */
+ tp_tests_proxy_run_until_dbus_queue_processed (fixture->account);
+
+ g_object_unref (fixture->account);
+ fixture->account = NULL;
+ }
+
+ tp_dbus_daemon_unregister_object (fixture->dbus, fixture->account_service);
+ g_object_unref (fixture->account_service);
+ fixture->account_service = NULL;
+
+ tp_dbus_daemon_release_name (fixture->dbus, TP_ACCOUNT_MANAGER_BUS_NAME,
+ &error);
+ g_assert_no_error (error);
+
+ g_object_unref (fixture->dbus);
+ fixture->dbus = NULL;
+
+ g_clear_object (&fixture->factory);
+}
+
+static void
+teardown (TestCaseFixture* fixture,
+ gconstpointer user_data)
+{
+ if (fixture->tmp_basedir != NULL)
+ {
+ gchar *command = g_strdup_printf ("rm -rf %s", fixture->tmp_basedir);
+
+ if (system (command) == -1)
+ g_warning ("Failed to cleanup tempory test log dir: %s",
+ fixture->tmp_basedir);
+
+ g_free (fixture->tmp_basedir);
+ }
+
+ g_object_unref (fixture->manager);
+ fixture->manager = NULL;
+
+ if (user_data != NULL)
+ teardown_service (fixture, user_data);
+
+ g_main_loop_unref (fixture->main_loop);
+ fixture->main_loop = NULL;
+}
+
+
+static void
+account_prepare_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TestCaseFixture *fixture = user_data;
+ GError *error = NULL;
+
+ tp_proxy_prepare_finish (source, result, &error);
+ g_assert_no_error (error);
+
+ g_main_loop_quit (fixture->main_loop);
+}
+
+
+static void
+setup_service (TestCaseFixture* fixture,
+ gconstpointer user_data)
+{
+ GQuark account_features[] = { TP_ACCOUNT_FEATURE_CORE, 0 };
+ const gchar *account_path;
+ GValue *boxed_params;
+ GHashTable *params = (GHashTable *) user_data;
+ GError *error = NULL;
+
+ g_assert (params != NULL);
+
+ fixture->dbus = tp_tests_dbus_daemon_dup_or_die ();
+ g_assert (fixture->dbus != NULL);
+
+ tp_dbus_daemon_request_name (fixture->dbus,
+ TP_ACCOUNT_MANAGER_BUS_NAME, FALSE, &error);
+ g_assert_no_error (error);
+
+ /* Create service-side Account object with the passed parameters */
+ fixture->account_service = g_object_new (TP_TESTS_TYPE_SIMPLE_ACCOUNT,
+ NULL);
+ g_assert (fixture->account_service != NULL);
+
+ /* account-path will be set-up as parameter as well, this is not an issue */
+ account_path = g_value_get_string (
+ (const GValue *) g_hash_table_lookup (params, "account-path"));
+ g_assert (account_path != NULL);
+
+ boxed_params = tp_g_value_slice_new_boxed (TP_HASH_TYPE_STRING_VARIANT_MAP,
+ params);
+ g_object_set_property (G_OBJECT (fixture->account_service),
+ "parameters", boxed_params);
+
+ tp_dbus_daemon_register_object (fixture->dbus, account_path,
+ fixture->account_service);
+
+ fixture->factory = _tpl_client_factory_dup (fixture->dbus);
+
+ fixture->account = tp_client_factory_ensure_account (fixture->factory,
+ account_path, NULL, NULL);
+ g_assert (fixture->account != NULL);
+
+ tp_proxy_prepare_async (fixture->account, account_features,
+ account_prepare_cb, fixture);
+ g_main_loop_run (fixture->main_loop);
+
+ g_assert (tp_proxy_is_prepared (fixture->account, TP_ACCOUNT_FEATURE_CORE));
+
+ tp_g_value_slice_free (boxed_params);
+}
+
+
+static void
+setup (TestCaseFixture* fixture,
+ gconstpointer user_data)
+{
+ DEBUG ("setting up");
+
+ fixture->main_loop = g_main_loop_new (NULL, FALSE);
+ g_assert (fixture->main_loop != NULL);
+
+ fixture->manager = tpl_log_manager_dup_singleton ();
+
+ if (user_data != NULL)
+ setup_service (fixture, user_data);
+
+ DEBUG ("set up finished");
+}
+
+static void
+setup_for_writing (TestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ gchar *readonly_dir;
+ gchar *writable_dir;
+
+ readonly_dir = g_build_path (G_DIR_SEPARATOR_S,
+ g_getenv ("TPL_TEST_LOG_DIR"), "TpLogger", "logs", NULL);
+
+ writable_dir = g_build_path (G_DIR_SEPARATOR_S,
+ g_get_tmp_dir (), "logger-test-logs", NULL);
+
+ tp_tests_copy_dir (readonly_dir, writable_dir);
+ fixture->tmp_basedir = writable_dir;
+ g_setenv ("TPL_TEST_LOG_DIR", writable_dir, TRUE);
+ g_free (readonly_dir);
+
+ setup (fixture, user_data);
+}
+
+static void
+setup_debug (void)
+{
+ tp_debug_divert_messages (g_getenv ("TPL_LOGFILE"));
+
+#ifdef ENABLE_DEBUG
+ _tpl_debug_set_flags_from_env ();
+
+ stamp_logs = (g_getenv ("TPL_TIMING") != NULL);
+ debug_sender = tp_debug_sender_dup ();
+
+ g_log_set_default_handler (log_handler, NULL);
+#endif /* ENABLE_DEBUG */
+}
+
+
+static void
+test_exists (TestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ TplEntity *entity;
+ TplEntity *no_entity;
+
+ entity = tpl_entity_new (ID, TPL_ENTITY_CONTACT, NULL, NULL);
+ no_entity = tpl_entity_new ("unknown", TPL_ENTITY_CONTACT, NULL, NULL);
+
+ g_assert (tpl_log_manager_exists (fixture->manager, fixture->account,
+ entity, TPL_EVENT_MASK_ANY));
+
+ g_assert (!tpl_log_manager_exists (fixture->manager, fixture->account,
+ no_entity, TPL_EVENT_MASK_ANY));
+
+ g_object_unref (entity);
+ g_object_unref (no_entity);
+}
+
+
+static void
+get_dates_async_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TestCaseFixture *fixture = user_data;
+ GError *error = NULL;
+
+ tpl_log_manager_get_dates_finish (TPL_LOG_MANAGER (object),
+ result, &fixture->ret, &error);
+
+ g_assert_no_error (error);
+ g_main_loop_quit (fixture->main_loop);
+}
+
+static void
+test_get_dates (TestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ GList *loc;
+ TplEntity *entity;
+
+ entity = tpl_entity_new (ID, TPL_ENTITY_CONTACT, NULL, NULL);
+
+ tpl_log_manager_get_dates_async (fixture->manager,
+ fixture->account, entity, TPL_EVENT_MASK_ANY,
+ get_dates_async_cb, fixture);
+ g_main_loop_run (fixture->main_loop);
+
+ g_object_unref (entity);
+
+ /* it includes 1 date from libpurple logs, 5 from TpLogger. Empathy
+ * log-store date are the same of the TpLogger store, and wont' be present,
+ * being duplicates */
+ g_assert_cmpint (g_list_length (fixture->ret), ==, 6);
+
+ /* we do not want duplicates, dates are suppose to be ordered */
+ fixture->ret = g_list_sort (fixture->ret, (GCompareFunc) g_date_compare);
+ for (loc = fixture->ret; loc != NULL; loc = g_list_next (loc))
+ if (loc->next)
+ g_assert (g_date_compare (loc->data, loc->next->data) != 0);
+
+ g_list_foreach (fixture->ret, (GFunc) g_date_free, NULL);
+ g_list_free (fixture->ret);
+}
+
+
+static void
+get_events_for_date_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TestCaseFixture *fixture = user_data;
+ GError *error = NULL;
+
+ tpl_log_manager_get_events_for_date_finish (TPL_LOG_MANAGER (object),
+ result, &fixture->ret, &error);
+
+ g_assert_no_error (error);
+ g_main_loop_quit (fixture->main_loop);
+}
+
+
+static void
+test_get_events_for_date (TestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ TplEntity *entity;
+ GDate *date;
+
+ entity = tpl_entity_new (ID, TPL_ENTITY_CONTACT, NULL, NULL);
+ date = g_date_new_dmy (13, 1, 2010);
+
+ tpl_log_manager_get_events_for_date_async (fixture->manager,
+ fixture->account,
+ entity,
+ TPL_EVENT_MASK_TEXT,
+ date,
+ get_events_for_date_cb,
+ fixture);
+ g_main_loop_run (fixture->main_loop);
+
+ g_object_unref (entity);
+ g_date_free (date);
+
+ /* We got 6 events in old Empathy and 6 in new TpLogger storage */
+ g_assert_cmpint (g_list_length (fixture->ret), ==, 12);
+
+ g_list_foreach (fixture->ret, (GFunc) g_object_unref, NULL);
+ g_list_free (fixture->ret);
+}
+
+static void
+test_get_events_for_date_account_unprepared (TestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ GHashTable *params = (GHashTable *) user_data;
+ TplEntity *entity;
+ GDate *date;
+ TpAccount *account;
+ const gchar *account_path;
+
+ g_clear_object (&fixture->account);
+
+ account_path = g_value_get_string (
+ (const GValue *) g_hash_table_lookup (params, "account-path"));
+
+ account = tp_client_factory_ensure_account (fixture->factory,
+ account_path, NULL, NULL);
+ g_assert (!tp_proxy_is_prepared (account, TP_ACCOUNT_FEATURE_CORE));
+
+ entity = tpl_entity_new (ID, TPL_ENTITY_CONTACT, NULL, NULL);
+ date = g_date_new_dmy (13, 1, 2010);
+
+ tpl_log_manager_get_events_for_date_async (fixture->manager,
+ account,
+ entity,
+ TPL_EVENT_MASK_TEXT,
+ date,
+ get_events_for_date_cb,
+ fixture);
+ g_main_loop_run (fixture->main_loop);
+
+ g_object_unref (entity);
+ g_date_free (date);
+
+ /* We got 6 events in old Empathy and 6 in new TpLogger storage */
+ g_assert_cmpint (g_list_length (fixture->ret), ==, 12);
+
+ g_list_foreach (fixture->ret, (GFunc) g_object_unref, NULL);
+ g_list_free (fixture->ret);
+ g_object_unref (account);
+}
+
+static void
+get_filtered_events_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TestCaseFixture *fixture = user_data;
+ GError *error = NULL;
+
+ tpl_log_manager_get_filtered_events_finish (TPL_LOG_MANAGER (object),
+ result, &fixture->ret, &error);
+
+ g_assert_no_error (error);
+ g_main_loop_quit (fixture->main_loop);
+}
+
+
+static gboolean
+events_filter (TplEvent *event,
+ gpointer user_data)
+{
+ gboolean keep;
+ GDateTime *timestamp;
+ GDate *date = user_data;
+
+ timestamp = g_date_time_new_from_unix_utc (tpl_event_get_timestamp (event));
+
+ keep = g_date_time_get_year (timestamp) == g_date_get_year (date)
+ && g_date_time_get_month (timestamp) == (gint) g_date_get_month (date)
+ && g_date_time_get_day_of_month (timestamp) == g_date_get_day (date);
+
+ g_date_time_unref (timestamp);
+
+ return keep;
+}
+
+
+static void
+test_get_filtered_events (TestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ TplEntity *entity;
+ GDate *date;
+
+ entity = tpl_entity_new (ID, TPL_ENTITY_CONTACT, NULL, NULL);
+ date = g_date_new_dmy (13, 1, 2010);
+
+ tpl_log_manager_get_filtered_events_async (fixture->manager,
+ fixture->account,
+ entity,
+ TPL_EVENT_MASK_TEXT,
+ 11,
+ events_filter,
+ date,
+ get_filtered_events_cb,
+ fixture);
+ g_main_loop_run (fixture->main_loop);
+
+ g_object_unref (entity);
+ g_date_free (date);
+
+ /* We got 6 events in old Empathy and 6 in new TpLogger storage,
+ * but we limited to 11 */
+ g_assert_cmpint (g_list_length (fixture->ret), ==, 11);
+
+ g_list_foreach (fixture->ret, (GFunc) g_object_unref, NULL);
+ g_list_free (fixture->ret);
+}
+
+
+static void
+get_entities_37288_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TestCaseFixture *fixture = user_data;
+ GError *error = NULL;
+
+ tpl_log_manager_get_entities_finish (TPL_LOG_MANAGER (object), result,
+ NULL, &error);
+
+ g_assert_no_error (error);
+ g_main_loop_quit (fixture->main_loop);
+}
+
+
+static void
+get_entities_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TestCaseFixture *fixture = user_data;
+ GError *error = NULL;
+
+ tpl_log_manager_get_entities_finish (TPL_LOG_MANAGER (object), result,
+ &fixture->ret, &error);
+
+ g_assert_no_error (error);
+ g_main_loop_quit (fixture->main_loop);
+}
+
+
+static void
+test_get_entities (TestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ GList *loc;
+
+ tpl_log_manager_get_entities_async (fixture->manager, fixture->account,
+ get_entities_cb, fixture);
+ g_main_loop_run (fixture->main_loop);
+
+ g_assert_cmpint (g_list_length (fixture->ret), ==, 5);
+
+ /* we do not want duplicates */
+ fixture->ret = g_list_sort (fixture->ret, (GCompareFunc) _tpl_entity_compare);
+ for (loc = fixture->ret; loc != NULL; loc = g_list_next (loc))
+ if (loc->next)
+ g_assert (_tpl_entity_compare (loc->data, loc->next->data) != 0);
+
+ g_list_foreach (fixture->ret, (GFunc) g_object_unref, NULL);
+ g_list_free (fixture->ret);
+
+ /* Check that the GSimpleAsyncResult res_gpointer's GDestroyNotify func
+ * is the appropriate one.
+ * Reproduces: https://bugs.freedesktop.org/show_bug.cgi?id=37288 */
+ tpl_log_manager_get_entities_async (fixture->manager, fixture->account,
+ get_entities_37288_cb, fixture);
+ g_main_loop_run (fixture->main_loop);
+}
+
+
+static void
+search_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TestCaseFixture *fixture = user_data;
+ GError *error = NULL;
+
+ tpl_log_manager_search_finish (TPL_LOG_MANAGER (object),
+ result, &fixture->ret, &error);
+
+ g_assert_no_error (error);
+ g_main_loop_quit (fixture->main_loop);
+}
+
+
+static void
+test_search (TestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ tpl_log_manager_search_async (fixture->manager,
+ "user2@collabora.co.uk",
+ TPL_EVENT_MASK_TEXT,
+ search_cb,
+ fixture);
+ g_main_loop_run (fixture->main_loop);
+
+ /* We got 4 events in old Empathy, 4 in new TpLogger and
+ * 2 in Pidgin storage */
+ g_assert_cmpint (g_list_length (fixture->ret), ==, 10);
+
+ tpl_log_manager_search_free (fixture->ret);
+ fixture->ret = NULL;
+}
+
+static gboolean
+check_ignored_messages (TestCaseFixture *fixture,
+ TplTextEvent *event,
+ gboolean should_exist)
+{
+ TplEntity *entity;
+ GList *iter;
+ GDate *date;
+
+ g_object_get (event, "sender", &entity, NULL);
+ date = g_date_new_dmy (1, 1, 1970);
+ tpl_log_manager_get_events_for_date_async (
+ fixture->manager,
+ fixture->account,
+ entity,
+ TPL_EVENT_MASK_ANY,
+ date,
+ get_events_for_date_cb,
+ fixture);
+ g_main_loop_run (fixture->main_loop);
+
+ for (iter = fixture->ret; iter; iter = g_list_next (iter)) {
+ TplEvent *found_event = iter->data;
+ gchar *result_token, *ref_token;
+ gboolean exists;
+
+ g_object_get (G_OBJECT (found_event), "message-token", &result_token, NULL);
+ g_object_get (G_OBJECT (event), "message-token", &ref_token, NULL);
+ exists = (g_strcmp0 (result_token, ref_token) == 0);
+
+ if (should_exist != exists) {
+ g_list_free_full (fixture->ret, g_object_unref);
+ return FALSE;
+ }
+ }
+
+ g_list_free_full (fixture->ret, g_object_unref);
+
+ return TRUE;
+}
+
+static void
+test_ignorelist (TestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ TplTextEvent *event1, *event2;
+ TplEntity *receiver, *sender;
+ TplConf *conf;
+ gboolean passed;
+
+ receiver = tpl_entity_new ("ignoreduser1@collabora.co.uk", TPL_ENTITY_CONTACT, "Me", "no-avatar");
+ sender = tpl_entity_new ("ignoreduser2@collabora.co.uk", TPL_ENTITY_CONTACT, "Someone Else", "no-avatar");
+
+ event1 = g_object_new (TPL_TYPE_TEXT_EVENT,
+ "account", fixture->account,
+ "channel-path", "im.telepathy1.channel.path",
+ "receiver", receiver,
+ "sender", sender,
+ "timestamp", (gint64) 1,
+ "message-token", "1234",
+ "supersedes-token", "5678",
+ "edit-timestamp", 0,
+ "message-type", TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+ "message", "Test 1",
+ NULL);
+
+ event2 = g_object_new (TPL_TYPE_TEXT_EVENT,
+ "account", fixture->account,
+ "channel-path", "im.telepathy1.channel.path",
+ "receiver", sender,
+ "sender", receiver,
+ "timestamp", (gint64) 2,
+ "message-token", "5678",
+ "supersedes-token", "9012",
+ "edit-timestamp", 0,
+ "message-type", TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+ "message", "Test 2",
+ NULL);
+
+ conf = _tpl_conf_dup ();
+
+ /* Ignore messages from both */
+ tpl_log_manager_disable_for_entity (fixture->manager, fixture->account, receiver);
+ tpl_log_manager_disable_for_entity (fixture->manager, fixture->account, sender);
+ g_assert (tpl_log_manager_is_disabled_for_entity (fixture->manager, fixture->account, receiver));
+ g_assert (tpl_log_manager_is_disabled_for_entity (fixture->manager, fixture->account, sender));
+
+ _tpl_log_manager_add_event (fixture->manager, TPL_EVENT (event1), NULL);
+ _tpl_log_manager_add_event (fixture->manager, TPL_EVENT (event2), NULL);
+
+ passed = check_ignored_messages (fixture, event1, FALSE);
+ if (!passed) {
+ _tpl_log_manager_clear_entity (fixture->manager, fixture->account, sender);
+ _tpl_log_manager_clear_entity (fixture->manager, fixture->account, receiver);
+ g_assert (passed);
+ }
+
+ passed = check_ignored_messages (fixture, event2, FALSE);
+ _tpl_log_manager_clear_entity (fixture->manager, fixture->account, sender);
+ _tpl_log_manager_clear_entity (fixture->manager, fixture->account, receiver);
+ if (!passed) {
+ g_assert (passed);
+ }
+
+ /* Ignore message only from ignoreduser1 */
+ tpl_log_manager_enable_for_entity (fixture->manager, fixture->account, sender);
+ g_assert (!tpl_log_manager_is_disabled_for_entity (fixture->manager, fixture->account, sender));
+ g_assert (tpl_log_manager_is_disabled_for_entity (fixture->manager, fixture->account, receiver));
+ _tpl_log_manager_add_event (fixture->manager, TPL_EVENT (event1), NULL);
+ _tpl_log_manager_add_event (fixture->manager, TPL_EVENT (event2), NULL);
+
+ passed = check_ignored_messages (fixture, event1, FALSE);
+ if (!passed) {
+ _tpl_log_manager_clear_entity (fixture->manager, fixture->account, sender);
+ _tpl_log_manager_clear_entity (fixture->manager, fixture->account, receiver);
+ g_assert (passed);
+ }
+
+ passed = check_ignored_messages (fixture, event2, TRUE);
+ _tpl_log_manager_clear_entity (fixture->manager, fixture->account, sender);
+ _tpl_log_manager_clear_entity (fixture->manager, fixture->account, receiver);
+ if (!passed) {
+ g_assert (passed);
+ }
+
+ /* Don't ignore any message */
+ tpl_log_manager_enable_for_entity (fixture->manager, fixture->account, receiver);
+ g_assert (!tpl_log_manager_is_disabled_for_entity (fixture->manager, fixture->account, sender));
+ g_assert (!tpl_log_manager_is_disabled_for_entity (fixture->manager, fixture->account, receiver));
+ _tpl_log_manager_add_event (fixture->manager, TPL_EVENT (event1), NULL);
+
+ passed = check_ignored_messages (fixture, event1, TRUE);
+ _tpl_log_manager_clear_entity (fixture->manager, fixture->account, sender);
+ _tpl_log_manager_clear_entity (fixture->manager, fixture->account, receiver);
+ if (!passed) {
+ g_assert (passed);
+ }
+
+ g_object_unref (conf);
+ g_object_unref (event1);
+ g_object_unref (event2);
+ g_object_unref (sender);
+ g_object_unref (receiver);
+}
+
+int
+main (int argc, char **argv)
+{
+ GHashTable *params = NULL;
+ GList *l = NULL;
+ int retval;
+
+ g_type_init ();
+
+ setup_debug ();
+
+ /* no account tests */
+ g_test_init (&argc, &argv, NULL);
+ g_test_bug_base ("http://bugs.freedesktop.org/show_bug.cgi?id=");
+
+ /* account related tests */
+ params = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
+ (GDestroyNotify) tp_g_value_slice_free);
+ g_assert (params != NULL);
+
+ l = g_list_prepend (l, params);
+
+ g_hash_table_insert (params, "account",
+ tp_g_value_slice_new_static_string (MY_ID));
+ g_hash_table_insert (params, "account-path",
+ tp_g_value_slice_new_static_string (ACCOUNT_PATH_JABBER));
+
+ g_test_add ("/log-manager/exists",
+ TestCaseFixture, params,
+ setup, test_exists, teardown);
+
+ g_test_add ("/log-manager/get-dates",
+ TestCaseFixture, params,
+ setup, test_get_dates, teardown);
+
+ g_test_add ("/log-manager/get-events-for-date",
+ TestCaseFixture, params,
+ setup, test_get_events_for_date, teardown);
+
+ g_test_add ("/log-manager/get-events-for-date-account-unprepared",
+ TestCaseFixture, params,
+ setup, test_get_events_for_date_account_unprepared, teardown);
+
+ g_test_add ("/log-manager/get-filtered-events",
+ TestCaseFixture, params,
+ setup, test_get_filtered_events, teardown);
+
+ g_test_add ("/log-manager/get-entities",
+ TestCaseFixture, params,
+ setup, test_get_entities, teardown);
+
+ g_test_add ("/log-manager/search",
+ TestCaseFixture, params,
+ setup, test_search, teardown);
+
+ g_test_add ("/log-manager/ignorelist",
+ TestCaseFixture, params,
+ setup_for_writing, test_ignorelist, teardown);
+
+ retval = g_test_run ();
+
+ g_list_foreach (l, (GFunc) g_hash_table_unref, NULL);
+
+ return retval;
+}
diff --git a/tests/logger/dbus/test-tpl-log-iter-pidgin.c b/tests/logger/dbus/test-tpl-log-iter-pidgin.c
new file mode 100644
index 000000000..11e1b5e22
--- /dev/null
+++ b/tests/logger/dbus/test-tpl-log-iter-pidgin.c
@@ -0,0 +1,849 @@
+#include "config.h"
+
+#include "lib/simple-account.h"
+#include "lib/util.h"
+
+#include "telepathy-logger/debug-internal.h"
+#include "telepathy-logger/log-iter-internal.h"
+#include "telepathy-logger/log-iter-pidgin-internal.h"
+#include "telepathy-logger/log-store-pidgin-internal.h"
+#include "telepathy-logger/text-event.h"
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/telepathy-glib-dbus.h>
+#include <glib.h>
+
+#define DEBUG_FLAG TPL_DEBUG_TESTSUITE
+
+
+typedef struct
+{
+ GMainLoop *main_loop;
+ TplLogStore *store;
+ TpAccount *account;
+ TpDBusDaemon *bus;
+ TpClientFactory *factory;
+ TpTestsSimpleAccount *account_service;
+} PidginTestCaseFixture;
+
+
+static void
+account_prepare_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ PidginTestCaseFixture *fixture = user_data;
+ GError *error = NULL;
+
+ tp_proxy_prepare_finish (source, result, &error);
+ g_assert_no_error (error);
+
+ g_main_loop_quit (fixture->main_loop);
+}
+
+
+static void
+setup (PidginTestCaseFixture* fixture,
+ gconstpointer user_data)
+{
+ GArray *features;
+ GError *error = NULL;
+ GHashTable *params = (GHashTable *) user_data;
+ GValue *boxed_params;
+ const gchar *account_path;
+
+ fixture->main_loop = g_main_loop_new (NULL, FALSE);
+ g_assert (fixture->main_loop != NULL);
+
+ fixture->store = g_object_new (TPL_TYPE_LOG_STORE_PIDGIN,
+ "testmode", TRUE,
+ NULL);
+
+ fixture->bus = tp_tests_dbus_daemon_dup_or_die ();
+ g_assert (fixture->bus != NULL);
+
+ tp_dbus_daemon_request_name (fixture->bus,
+ TP_ACCOUNT_MANAGER_BUS_NAME,
+ FALSE,
+ &error);
+ g_assert_no_error (error);
+
+ /* Create service-side Account object with the passed parameters */
+ fixture->account_service = g_object_new (TP_TESTS_TYPE_SIMPLE_ACCOUNT,
+ NULL);
+ g_assert (fixture->account_service != NULL);
+
+ /* account-path will be set-up as parameter as well, this is not an issue */
+ account_path = tp_asv_get_string (params, "account-path");
+ g_assert (account_path != NULL);
+
+ boxed_params = tp_g_value_slice_new_boxed (TP_HASH_TYPE_STRING_VARIANT_MAP,
+ params);
+ g_object_set_property (G_OBJECT (fixture->account_service),
+ "parameters",
+ boxed_params);
+ tp_g_value_slice_free (boxed_params);
+
+ tp_dbus_daemon_register_object (fixture->bus,
+ account_path,
+ fixture->account_service);
+
+ fixture->factory = tp_client_factory_new (fixture->bus);
+ g_assert (fixture->factory != NULL);
+
+ fixture->account = tp_client_factory_ensure_account (fixture->factory,
+ tp_asv_get_string (params, "account-path"),
+ params,
+ &error);
+ g_assert_no_error (error);
+ g_assert (fixture->account != NULL);
+
+ features = tp_client_factory_dup_account_features (fixture->factory,
+ fixture->account);
+
+ tp_proxy_prepare_async (fixture->account,
+ (GQuark *) features->data,
+ account_prepare_cb,
+ fixture);
+ g_free (features->data);
+ g_array_free (features, FALSE);
+
+ g_main_loop_run (fixture->main_loop);
+
+ tp_debug_divert_messages (g_getenv ("TPL_LOGFILE"));
+
+#ifdef ENABLE_DEBUG
+ _tpl_debug_set_flags_from_env ();
+#endif /* ENABLE_DEBUG */
+}
+
+
+static void
+teardown (PidginTestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ GError *error = NULL;
+
+ tp_dbus_daemon_release_name (fixture->bus,
+ TP_ACCOUNT_MANAGER_BUS_NAME,
+ &error);
+ g_assert_no_error (error);
+
+ g_clear_object (&fixture->account);
+ g_clear_object (&fixture->factory);
+
+ tp_dbus_daemon_unregister_object (fixture->bus, fixture->account_service);
+ g_clear_object (&fixture->account_service);
+
+ g_clear_object (&fixture->bus);
+ g_clear_object (&fixture->store);
+ g_main_loop_unref (fixture->main_loop);
+}
+
+
+static void
+test_get_events (PidginTestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ TplEntity *room;
+ TplLogIter *iter;
+ GList *events;
+ GError *error = NULL;
+ const gchar *message;
+ gint64 timestamp;
+
+ room = tpl_entity_new_from_room_id ("#telepathy");
+
+ iter = tpl_log_iter_pidgin_new (fixture->store, fixture->account, room,
+ TPL_EVENT_MASK_ANY);
+
+ events = tpl_log_iter_get_events (iter, 5, &error);
+ events = events;
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 5);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291133254);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "tbh it&apos;s not necessarily too niche to have in telepathy-spec");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 3, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 3);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291133097);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "I think that&apos;s better than modifying the client libraries");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 2, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 2);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291133035);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "oh right I thought by &quot;alongside&quot; you meant in o.fd.T.AM");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 7, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 7);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291132904);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "you&apos;re just moving the incompatibility into the client libraries");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 1, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 1);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291132892);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "if the libraries hide those accounts by default, that&apos;s no more "
+ "compatible than changing the D-Bus API");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 2, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 2);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291132838);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "alternative possibly less-beating-worthy proposals include just "
+ "adding the flag to the account and then modifying tp-{glib,qt4,...} "
+ "to hide &apos;em by default");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 10, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 10);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291131885);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "wjt: hrm, can you disco remote servers for their jud and does gabble "
+ "do that if needed or does it rely on the given server being the jud?");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 4, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 4);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291131667);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "one of whose possible values is the dreaded NetworkError");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 5, &error);
+ events = events;
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 5);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291131614);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "nod");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 3, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 3);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291131587);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "ejabberd isn&apos;t even telling me why it&apos;s disconnecting some "
+ "test accounts");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 2, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 2);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291131566);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "Heh");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 7, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 7);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291131502);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "if the server provides &lt;text/&gt;, use that; otherwise, use a "
+ "locally-supplied debug string");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 1, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 1);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291131493);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "MattJ: what language is the &lt;text&gt; in btw?");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 2, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 2);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291131480);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "hey");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 10, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 10);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291131383);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "Good :)");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 5, &error);
+ events = events;
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 5);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291131350);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "that&apos;s mostly fixed though");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 3, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 3);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291131335);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "\\o\\ /o/");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 2, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 2);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291131288);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "Good that a proper register interface is getting higher on the todo "
+ "list");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 7, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 7);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291130982);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "no biscuit.");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 1, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 1);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291130967);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "no gitorious merge request.");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 2, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 2);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291130885);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "pessi: Hi, I fixed some bugs in ring: "
+ "http://git.collabora.co.uk/?p=user/jonny/telepathy-ring.git;a="
+ "shortlog;h=refs/heads/trivia");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 10, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 10);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291130110);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "i guess the collabora xmpp server does privacy list-based "
+ "invisibility, so it&apos;s only doing what i asked");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 4, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 4);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291130015);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "MattJ: so about that xep-0186 support? ;-)");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 5, &error);
+ events = events;
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 5);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291129872);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "Oh, i noticed that our iq request queue somethings fill up and then "
+ "doesn&apos;t seem to get unstuck");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 3, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 3);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291129805);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "huh");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 2, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 2);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291128926);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "kkszysiu, heya; i seem to remember you were hacking on a "
+ "im-via-web-using-telepathy stuff? how&apos;s that going? i&apos;d be "
+ "interested in doing something along the same lines");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 7, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 7);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291126346);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "invisible&apos;s a good idea. we do implement xmpp ping");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 1, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 1);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291126340);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "oh yeah, dwd implemented google:queue in M-Link");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 2, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 2);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291126290);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "not sure if we implement this one");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 8, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 8);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291123078);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "those who like contact lists: "
+ "https://bugs.freedesktop.org/show_bug.cgi?id=31997");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 3, &error);
+ g_assert_no_error (error);
+ g_assert (events == NULL);
+
+ g_object_unref (iter);
+ g_object_unref (room);
+}
+
+
+static void
+test_rewind (PidginTestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ TplEntity *room;
+ TplLogIter *iter;
+ GList *events;
+ GError *error = NULL;
+ const gchar *message;
+ gint64 timestamp;
+
+ room = tpl_entity_new_from_room_id ("#telepathy");
+
+ iter = tpl_log_iter_pidgin_new (fixture->store, fixture->account, room,
+ TPL_EVENT_MASK_ANY);
+
+ tpl_log_iter_rewind (iter, 8, &error);
+ g_assert_no_error (error);
+
+ events = tpl_log_iter_get_events (iter, 0, &error);
+ g_assert_no_error (error);
+ g_assert (events == NULL);
+
+ tpl_log_iter_rewind (iter, 8, &error);
+ g_assert_no_error (error);
+
+ events = tpl_log_iter_get_events (iter, 5, &error);
+ events = events;
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 5);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291133254);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "tbh it&apos;s not necessarily too niche to have in telepathy-spec");
+ g_list_free_full (events, g_object_unref);
+
+ tpl_log_iter_rewind (iter, 8, &error);
+ g_assert_no_error (error);
+
+ events = tpl_log_iter_get_events (iter, 5, &error);
+ events = events;
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 5);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291133254);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "tbh it&apos;s not necessarily too niche to have in telepathy-spec");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 20, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 20);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291132137);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "wjt: we should probably cope with both cases.. i wonder if jud server "
+ "correctly indicate in a disco response that they&apos;re the jud "
+ "server");
+ g_list_free_full (events, g_object_unref);
+
+ tpl_log_iter_rewind (iter, 7, &error);
+ g_assert_no_error (error);
+
+ events = tpl_log_iter_get_events (iter, 17, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 17);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291131655);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "the primary thing to present is a D-Bus error code which UIs are "
+ "expected to localize");
+ g_list_free_full (events, g_object_unref);
+
+ tpl_log_iter_rewind (iter, 7, &error);
+ g_assert_no_error (error);
+
+ events = tpl_log_iter_get_events (iter, 13, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 13);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291131595);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "There are vague errors like &quot;bad-request&quot; or "
+ "&quot;not-authorized&quot; where Prosody usually gives more specific "
+ "information about why the error occured");
+ g_list_free_full (events, g_object_unref);
+
+ tpl_log_iter_rewind (iter, 17, &error);
+ g_assert_no_error (error);
+
+ events = tpl_log_iter_get_events (iter, 33, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 33);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291131445);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "dear ejabberd, why are you not showing your xep 55 in your disco "
+ "response");
+ g_list_free_full (events, g_object_unref);
+
+ tpl_log_iter_rewind (iter, 5, &error);
+ g_assert_no_error (error);
+
+ events = tpl_log_iter_get_events (iter, 10, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 10);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291131401);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "the UI doesn&apos;t show it though");
+ g_list_free_full (events, g_object_unref);
+
+ tpl_log_iter_rewind (iter, 25, &error);
+ g_assert_no_error (error);
+
+ events = tpl_log_iter_get_events (iter, 10, &error);
+ events = events;
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 10);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291131537);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "well, s/you/this channel/");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 25, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 25);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291131335);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "\\o\\ /o/");
+ g_list_free_full (events, g_object_unref);
+
+ tpl_log_iter_rewind (iter, 3, &error);
+ g_assert_no_error (error);
+
+ events = tpl_log_iter_get_events (iter, 15, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 15);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291130885);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "pessi: Hi, I fixed some bugs in ring: "
+ "http://git.collabora.co.uk/?p=user/jonny/telepathy-ring.git;a="
+ "shortlog;h=refs/heads/trivia");
+ g_list_free_full (events, g_object_unref);
+
+ tpl_log_iter_rewind (iter, 1, &error);
+ g_assert_no_error (error);
+
+ events = tpl_log_iter_get_events (iter, 10, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 10);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291130210);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "wjt, how can you test if you are actually invisible? The account "
+ "presence is always sync with your real status?");
+ g_list_free_full (events, g_object_unref);
+
+ tpl_log_iter_rewind (iter, 7, &error);
+ g_assert_no_error (error);
+
+ events = tpl_log_iter_get_events (iter, 20, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 20);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291129805);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "huh");
+ g_list_free_full (events, g_object_unref);
+
+ tpl_log_iter_rewind (iter, 23, &error);
+ g_assert_no_error (error);
+
+ events = tpl_log_iter_get_events (iter, 20, &error);
+ events = events;
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 20);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291129872);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "Oh, i noticed that our iq request queue somethings fill up and then "
+ "doesn&apos;t seem to get unstuck");
+ g_list_free_full (events, g_object_unref);
+
+ tpl_log_iter_rewind (iter, 3, &error);
+ g_assert_no_error (error);
+
+ events = tpl_log_iter_get_events (iter, 20, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 20);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291126206);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "invisible is a good one");
+ g_list_free_full (events, g_object_unref);
+
+ tpl_log_iter_rewind (iter, 3, &error);
+ g_assert_no_error (error);
+
+ events = tpl_log_iter_get_events (iter, 9, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 9);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1291123078);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message,
+ ==,
+ "those who like contact lists: "
+ "https://bugs.freedesktop.org/show_bug.cgi?id=31997");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 3, &error);
+ g_assert_no_error (error);
+ g_assert (events == NULL);
+
+ g_object_unref (iter);
+ g_object_unref (room);
+}
+
+
+gint
+main (gint argc, gchar **argv)
+{
+ GHashTable *params;
+ gint retval;
+
+ g_type_init ();
+
+ g_test_init (&argc, &argv, NULL);
+ g_test_bug_base ("http://bugs.freedesktop.org/show_bug.cgi?id=");
+
+ params = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
+ (GDestroyNotify) tp_g_value_slice_free);
+ g_assert (params != NULL);
+
+ g_hash_table_insert (params, "account",
+ tp_g_value_slice_new_static_string ("user"));
+ g_hash_table_insert (params, "server",
+ tp_g_value_slice_new_static_string ("irc.freenode.net"));
+ g_hash_table_insert (params, "account-path",
+ tp_g_value_slice_new_static_string (
+ TP_ACCOUNT_OBJECT_PATH_BASE "foo/irc/baz"));
+
+ g_test_add ("/log-iter-xml/get-events",
+ PidginTestCaseFixture, params,
+ setup, test_get_events, teardown);
+
+ g_test_add ("/log-iter-xml/rewind",
+ PidginTestCaseFixture, params,
+ setup, test_rewind, teardown);
+
+ retval = g_test_run ();
+
+ g_hash_table_unref (params);
+
+ return retval;
+}
diff --git a/tests/logger/dbus/test-tpl-log-iter-xml.c b/tests/logger/dbus/test-tpl-log-iter-xml.c
new file mode 100644
index 000000000..9053f7d89
--- /dev/null
+++ b/tests/logger/dbus/test-tpl-log-iter-xml.c
@@ -0,0 +1,446 @@
+#include "config.h"
+
+#include "lib/logger-test-helper.h"
+#include "lib/util.h"
+
+#include "telepathy-logger/call-event.h"
+#include "telepathy-logger/debug-internal.h"
+#include "telepathy-logger/log-iter-internal.h"
+#include "telepathy-logger/log-iter-xml-internal.h"
+#include "telepathy-logger/log-store-xml-internal.h"
+#include "telepathy-logger/text-event.h"
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/telepathy-glib-dbus.h>
+#include <glib.h>
+
+#define DEBUG_FLAG TPL_DEBUG_TESTSUITE
+
+
+typedef struct
+{
+ GMainLoop *main_loop;
+ TplLogStore *store;
+ TpAccount *account;
+ TpDBusDaemon *bus;
+ TpClientFactory *factory;
+ TpTestsSimpleAccount *account_service;
+} XmlTestCaseFixture;
+
+
+static void
+setup (XmlTestCaseFixture* fixture,
+ gconstpointer user_data)
+{
+ GError *error = NULL;
+
+ fixture->main_loop = g_main_loop_new (NULL, FALSE);
+
+ fixture->store = g_object_new (TPL_TYPE_LOG_STORE_XML,
+ "testmode", TRUE,
+ NULL);
+
+ fixture->bus = tp_tests_dbus_daemon_dup_or_die ();
+ g_assert (fixture->bus != NULL);
+
+ tp_dbus_daemon_request_name (fixture->bus,
+ TP_ACCOUNT_MANAGER_BUS_NAME,
+ FALSE,
+ &error);
+ g_assert_no_error (error);
+
+ fixture->factory = tp_client_factory_new (fixture->bus);
+ g_assert (fixture->factory != NULL);
+
+ tpl_test_create_and_prepare_account (fixture->bus, fixture->factory,
+ TP_ACCOUNT_OBJECT_PATH_BASE "gabble/jabber/user_40collabora_2eco_2euk",
+ &fixture->account, &fixture->account_service);
+
+ tp_debug_divert_messages (g_getenv ("TPL_LOGFILE"));
+
+#ifdef ENABLE_DEBUG
+ _tpl_debug_set_flags_from_env ();
+#endif /* ENABLE_DEBUG */
+}
+
+
+static void
+teardown (XmlTestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ GError *error = NULL;
+
+ tp_dbus_daemon_release_name (fixture->bus, TP_ACCOUNT_MANAGER_BUS_NAME,
+ &error);
+ g_assert_no_error (error);
+
+ tpl_test_release_account (fixture->bus, fixture->account,
+ fixture->account_service);
+
+ g_clear_object (&fixture->factory);
+ g_clear_object (&fixture->bus);
+ g_clear_object (&fixture->store);
+}
+
+
+static void
+test_get_events (XmlTestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ TplEntity *user2, *user4;
+ TplLogIter *iter;
+ GList *events;
+ GError *error = NULL;
+ GTimeSpan duration;
+ const gchar *message;
+ gint64 timestamp;
+
+ user2 = tpl_entity_new ("user2@collabora.co.uk", TPL_ENTITY_CONTACT,
+ "User2", "");
+
+ user4 = tpl_entity_new ("user4@collabora.co.uk", TPL_ENTITY_CONTACT,
+ "User4", "");
+
+ /* Text events spanning multiple days */
+ iter = tpl_log_iter_xml_new (fixture->store, fixture->account, user2,
+ TPL_EVENT_MASK_ANY);
+
+ events = tpl_log_iter_get_events (iter, 5, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 5);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1266425566);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message, ==, "4");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 3, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 3);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1266425572);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message, ==, "3");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 2, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 2);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1266425566);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message, ==, "5");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 7, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 7);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1266414451);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message, ==, "1");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 1, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 1);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1266335850);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message, ==, "bar");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 2, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 2);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1266335556);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message, ==, "1");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 10, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 10);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1263405178);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message, ==, "5");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 4, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 4);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1263404877);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message, ==, "1");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 3, &error);
+ g_assert_no_error (error);
+ g_assert (events == NULL);
+
+ g_object_unref (iter);
+
+ /* A mix of call and text events */
+ iter = tpl_log_iter_xml_new (fixture->store, fixture->account, user4,
+ TPL_EVENT_MASK_ANY);
+
+ events = tpl_log_iter_get_events (iter, 4, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 4);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1263404881);
+ duration = tpl_call_event_get_duration (TPL_CALL_EVENT (events->data));
+ g_assert_cmpint (duration, ==, 1);
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 1, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 1);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1263404881);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message, ==, "8");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 1, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 1);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1263404877);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message, ==, "7");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 1, &error);
+ g_assert_no_error (error);
+ g_assert (events == NULL);
+
+ g_object_unref (iter);
+
+ g_object_unref (user2);
+ g_object_unref (user4);
+}
+
+
+static void
+test_rewind (XmlTestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ TplEntity *user2, *user4;
+ TplLogIter *iter;
+ GList *events;
+ GError *error = NULL;
+ GTimeSpan duration;
+ const gchar *message;
+ gint64 timestamp;
+
+ user2 = tpl_entity_new ("user2@collabora.co.uk", TPL_ENTITY_CONTACT,
+ "User2", "");
+
+ user4 = tpl_entity_new ("user4@collabora.co.uk", TPL_ENTITY_CONTACT,
+ "User4", "");
+
+ /* Text events spanning multiple days */
+ iter = tpl_log_iter_xml_new (fixture->store, fixture->account, user2,
+ TPL_EVENT_MASK_ANY);
+
+ tpl_log_iter_rewind (iter, 8, &error);
+ g_assert_no_error (error);
+
+ events = tpl_log_iter_get_events (iter, 0, &error);
+ g_assert_no_error (error);
+ g_assert (events == NULL);
+
+ tpl_log_iter_rewind (iter, 8, &error);
+ g_assert_no_error (error);
+
+ events = tpl_log_iter_get_events (iter, 5, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 5);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1266425566);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message, ==, "4");
+ g_list_free_full (events, g_object_unref);
+
+ tpl_log_iter_rewind (iter, 8, &error);
+ g_assert_no_error (error);
+
+ events = tpl_log_iter_get_events (iter, 10, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 10);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1266425566);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message, ==, "5");
+ g_list_free_full (events, g_object_unref);
+
+ tpl_log_iter_rewind (iter, 3, &error);
+ g_assert_no_error (error);
+
+ events = tpl_log_iter_get_events (iter, 5, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 5);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1266425566);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message, ==, "3");
+ g_list_free_full (events, g_object_unref);
+
+ tpl_log_iter_rewind (iter, 1, &error);
+ g_assert_no_error (error);
+
+ tpl_log_iter_rewind (iter, 9, &error);
+ g_assert_no_error (error);
+
+ events = tpl_log_iter_get_events (iter, 10, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 10);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1266425566);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message, ==, "3");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 10, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 10);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1266329628);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message, ==, "123");
+ g_list_free_full (events, g_object_unref);
+
+ tpl_log_iter_rewind (iter, 13, &error);
+ g_assert_no_error (error);
+
+ events = tpl_log_iter_get_events (iter, 10, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 10);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1266335803);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message, ==, "a");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 10, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 10);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1263405203);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message, ==, "6");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 5, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 5);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1263404877);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message, ==, "1");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 3, &error);
+ g_assert_no_error (error);
+ g_assert (events == NULL);
+
+ g_object_unref (iter);
+
+ /* A mix of call and text events */
+ iter = tpl_log_iter_xml_new (fixture->store, fixture->account, user4,
+ TPL_EVENT_MASK_ANY);
+
+ tpl_log_iter_rewind (iter, 8, &error);
+ g_assert_no_error (error);
+
+ events = tpl_log_iter_get_events (iter, 0, &error);
+ g_assert_no_error (error);
+ g_assert (events == NULL);
+
+ tpl_log_iter_rewind (iter, 8, &error);
+ g_assert_no_error (error);
+
+ events = tpl_log_iter_get_events (iter, 4, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 4);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1263404881);
+ duration = tpl_call_event_get_duration (TPL_CALL_EVENT (events->data));
+ g_assert_cmpint (duration, ==, 1);
+ g_list_free_full (events, g_object_unref);
+
+ tpl_log_iter_rewind (iter, 8, &error);
+ g_assert_no_error (error);
+
+ events = tpl_log_iter_get_events (iter, 4, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 4);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1263404881);
+ duration = tpl_call_event_get_duration (TPL_CALL_EVENT (events->data));
+ g_assert_cmpint (duration, ==, 1);
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 2, &error);
+ g_assert_no_error (error);
+ g_assert (events != NULL);
+ g_assert_cmpint (g_list_length (events), ==, 2);
+ timestamp = tpl_event_get_timestamp (TPL_EVENT (events->data));
+ g_assert_cmpint (timestamp, ==, 1263404877);
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (events->data));
+ g_assert_cmpstr (message, ==, "7");
+ g_list_free_full (events, g_object_unref);
+
+ events = tpl_log_iter_get_events (iter, 1, &error);
+ g_assert_no_error (error);
+ g_assert (events == NULL);
+
+ g_object_unref (iter);
+
+ g_object_unref (user2);
+ g_object_unref (user4);
+}
+
+
+gint main (gint argc, gchar **argv)
+{
+ g_type_init ();
+
+ g_test_init (&argc, &argv, NULL);
+ g_test_bug_base ("http://bugs.freedesktop.org/show_bug.cgi?id=");
+
+ g_test_add ("/log-iter-xml/get-events",
+ XmlTestCaseFixture, NULL,
+ setup, test_get_events, teardown);
+
+ g_test_add ("/log-iter-xml/rewind",
+ XmlTestCaseFixture, NULL,
+ setup, test_rewind, teardown);
+
+ return g_test_run ();
+}
diff --git a/tests/logger/dbus/test-tpl-log-store-pidgin.c b/tests/logger/dbus/test-tpl-log-store-pidgin.c
new file mode 100644
index 000000000..edd3ddb11
--- /dev/null
+++ b/tests/logger/dbus/test-tpl-log-store-pidgin.c
@@ -0,0 +1,622 @@
+#include "config.h"
+
+/* FIXME: hugly kludge: we need to include all the declarations which are used
+ * by the GInterface and thus not in the -internal.h */
+#include "telepathy-logger/log-store-pidgin.c"
+
+
+#include "lib/util.h"
+#include "lib/simple-account.h"
+#include "lib/simple-account-manager.h"
+
+#include <telepathy-logger/log-store-pidgin-internal.h>
+#include <telepathy-logger/text-event-internal.h>
+#include <telepathy-logger/client-factory-internal.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+/* it was defined in telepathy-logger/log-store-pidgin.c */
+#undef DEBUG_FLAG
+#define DEBUG_FLAG TPL_DEBUG_TESTSUITE
+#include <telepathy-logger/debug-internal.h>
+
+#include <glib.h>
+
+#define ACCOUNT_PATH_JABBER TP_ACCOUNT_OBJECT_PATH_BASE "foo/jabber/baz"
+#define ACCOUNT_PATH_IRC TP_ACCOUNT_OBJECT_PATH_BASE "foo/irc/baz"
+#define ACCOUNT_PATH_ICQ TP_ACCOUNT_OBJECT_PATH_BASE "foo/icq/baz"
+
+typedef struct
+{
+ gchar *basedir;
+
+ GMainLoop *main_loop;
+
+ TpDBusDaemon *dbus;
+ TpAccount *account;
+ TpTestsSimpleAccount *account_service;
+ TpClientFactory *factory;
+
+ TplLogStorePidgin *store;
+ TplEntity *room;
+ TplEntity *irc_room;
+ TplEntity *contact;
+} PidginTestCaseFixture;
+
+#ifdef ENABLE_DEBUG
+static TpDebugSender *debug_sender = NULL;
+static gboolean stamp_logs = FALSE;
+
+
+static void
+log_to_debug_sender (const gchar *log_domain,
+ GLogLevelFlags log_level,
+ const gchar *string)
+{
+ GTimeVal now;
+
+ g_return_if_fail (TP_IS_DEBUG_SENDER (debug_sender));
+
+ g_get_current_time (&now);
+
+ tp_debug_sender_add_message (debug_sender, &now, log_domain, log_level,
+ string);
+}
+
+
+static void
+log_handler (const gchar *log_domain,
+ GLogLevelFlags log_level,
+ const gchar *message,
+ gpointer user_data)
+{
+ if (stamp_logs)
+ {
+ GTimeVal now;
+ gchar now_str[32];
+ gchar *tmp;
+ struct tm tm;
+
+ g_get_current_time (&now);
+ localtime_r (&(now.tv_sec), &tm);
+ strftime (now_str, 32, "%Y-%m-%d %H:%M:%S", &tm);
+ tmp = g_strdup_printf ("%s.%06ld: %s",
+ now_str, now.tv_usec, message);
+
+ g_log_default_handler (log_domain, log_level, tmp, NULL);
+
+ g_free (tmp);
+ }
+ else
+ {
+ g_log_default_handler (log_domain, log_level, message, NULL);
+ }
+
+ log_to_debug_sender (log_domain, log_level, message);
+}
+#endif /* ENABLE_DEBUG */
+
+
+static void
+account_prepare_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ PidginTestCaseFixture *fixture = user_data;
+ GError *error = NULL;
+
+ tp_proxy_prepare_finish (source, result, &error);
+ g_assert_no_error (error);
+
+ g_main_loop_quit (fixture->main_loop);
+}
+
+
+static void
+setup_service (PidginTestCaseFixture* fixture,
+ gconstpointer user_data)
+{
+ GQuark account_features[] = { TP_ACCOUNT_FEATURE_CORE, 0 };
+ const gchar *account_path;
+ GValue *boxed_params;
+ GHashTable *params = (GHashTable *) user_data;
+ GError *error = NULL;
+
+ g_assert (params != NULL);
+
+ fixture->dbus = tp_tests_dbus_daemon_dup_or_die ();
+ g_assert (fixture->dbus != NULL);
+
+ tp_dbus_daemon_request_name (fixture->dbus,
+ TP_ACCOUNT_MANAGER_BUS_NAME, FALSE, &error);
+ g_assert_no_error (error);
+
+ /* Create service-side Account object with the passed parameters */
+ fixture->account_service = g_object_new (TP_TESTS_TYPE_SIMPLE_ACCOUNT,
+ NULL);
+ g_assert (fixture->account_service != NULL);
+
+ /* account-path will be set-up as parameter as well, this is not an issue */
+ account_path = g_value_get_string (
+ (const GValue *) g_hash_table_lookup (params, "account-path"));
+ g_assert (account_path != NULL);
+
+ boxed_params = tp_g_value_slice_new_boxed (TP_HASH_TYPE_STRING_VARIANT_MAP,
+ params);
+ g_object_set_property (G_OBJECT (fixture->account_service),
+ "parameters", boxed_params);
+
+ tp_dbus_daemon_register_object (fixture->dbus, account_path,
+ fixture->account_service);
+
+ fixture->factory = _tpl_client_factory_dup (fixture->dbus);
+
+ fixture->account = tp_client_factory_ensure_account (fixture->factory,
+ account_path, NULL, NULL);
+ g_assert (fixture->account != NULL);
+
+ tp_proxy_prepare_async (fixture->account, account_features,
+ account_prepare_cb, fixture);
+ g_main_loop_run (fixture->main_loop);
+
+ g_assert (tp_proxy_is_prepared (fixture->account, TP_ACCOUNT_FEATURE_CORE));
+
+ tp_g_value_slice_free (boxed_params);
+}
+
+static void
+setup (PidginTestCaseFixture* fixture,
+ gconstpointer user_data)
+{
+ DEBUG ("setting up");
+
+ fixture->main_loop = g_main_loop_new (NULL, FALSE);
+ g_assert (fixture->main_loop != NULL);
+
+ fixture->basedir = g_build_path (G_DIR_SEPARATOR_S,
+ g_getenv ("TPL_TEST_LOG_DIR"), "purple", NULL);
+ DEBUG ("basedir is %s", fixture->basedir);
+
+ fixture->store = g_object_new (TPL_TYPE_LOG_STORE_PIDGIN,
+ "testmode", TRUE,
+ NULL);
+
+ fixture->room = tpl_entity_new_from_room_id (
+ "test@conference.collabora.co.uk");
+
+ fixture->irc_room = tpl_entity_new_from_room_id ("#telepathy");
+
+ fixture->contact = tpl_entity_new ("user2@collabora.co.uk",
+ TPL_ENTITY_CONTACT, NULL, NULL);
+
+ if (user_data != NULL)
+ setup_service (fixture, user_data);
+
+ DEBUG ("set up finished");
+}
+
+static void
+teardown_service (PidginTestCaseFixture* fixture,
+ gconstpointer user_data)
+{
+ GError *error = NULL;
+
+ g_assert (user_data != NULL);
+
+ if (fixture->account != NULL)
+ {
+ /* FIXME is it useful in this suite */
+ tp_tests_proxy_run_until_dbus_queue_processed (fixture->account);
+
+ g_object_unref (fixture->account);
+ fixture->account = NULL;
+ }
+
+ tp_dbus_daemon_unregister_object (fixture->dbus, fixture->account_service);
+ g_object_unref (fixture->account_service);
+ fixture->account_service = NULL;
+
+ tp_dbus_daemon_release_name (fixture->dbus, TP_ACCOUNT_MANAGER_BUS_NAME,
+ &error);
+ g_assert_no_error (error);
+
+ g_object_unref (fixture->dbus);
+ fixture->dbus = NULL;
+
+ g_clear_object (&fixture->factory);
+}
+
+static void
+teardown (PidginTestCaseFixture* fixture,
+ gconstpointer user_data)
+{
+ g_free (fixture->basedir);
+ fixture->basedir = NULL;
+
+ g_object_unref (fixture->store);
+ fixture->store = NULL;
+
+ g_object_unref (fixture->room);
+ g_object_unref (fixture->irc_room);
+ g_object_unref (fixture->contact);
+
+ if (user_data != NULL)
+ teardown_service (fixture, user_data);
+
+ g_main_loop_unref (fixture->main_loop);
+ fixture->main_loop = NULL;
+}
+
+static void
+test_basedir (PidginTestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ TplLogStorePidgin *store;
+ gchar *dir;
+
+ g_assert_cmpstr (log_store_pidgin_get_basedir (fixture->store), ==,
+ fixture->basedir);
+
+ /* try to instantiate the default store, without passing basedir, it has to
+ * match the real libpurple basedir */
+ store = g_object_new (TPL_TYPE_LOG_STORE_PIDGIN, NULL);
+ dir = g_build_path (G_DIR_SEPARATOR_S, g_get_home_dir (), ".purple",
+ "logs", NULL);
+ g_assert_cmpstr (log_store_pidgin_get_basedir (store), ==, dir);
+
+ g_object_unref (store);
+ g_free (dir);
+}
+
+static void
+test_get_dates_jabber (PidginTestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ GList *dates = NULL;
+ GDate *date = NULL;
+
+ /* Chatroom messages */
+ dates = log_store_pidgin_get_dates (TPL_LOG_STORE (fixture->store),
+ fixture->account, fixture->room, TPL_EVENT_MASK_ANY);
+
+ g_assert_cmpint (g_list_length (dates), ==, 2);
+
+ date = g_list_nth_data (dates, 0);
+ g_assert_cmpint (0, ==,
+ g_date_compare (date, g_date_new_dmy (12, G_DATE_APRIL, 2010)));
+
+ g_date_free (date);
+
+ date = g_list_nth_data (dates, 1);
+ g_assert_cmpint (0, ==,
+ g_date_compare (date, g_date_new_dmy (29, G_DATE_APRIL, 2010)));
+
+ g_date_free (date);
+ g_list_free (dates);
+
+ /* 1-1 messages */
+ dates = log_store_pidgin_get_dates (TPL_LOG_STORE (fixture->store),
+ fixture->account, fixture->contact, TPL_EVENT_MASK_ANY);
+
+ g_assert_cmpint (g_list_length (dates), ==, 1);
+
+ date = g_list_nth_data (dates, 0);
+ g_assert_cmpint (0, ==,
+ g_date_compare (date, g_date_new_dmy (10, G_DATE_DECEMBER, 2010)));
+
+ g_date_free (date);
+ g_list_free (dates);
+}
+
+static void
+test_get_dates_irc (PidginTestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ GList *dates = NULL;
+ GDate *date = NULL;
+
+ dates = log_store_pidgin_get_dates (TPL_LOG_STORE (fixture->store),
+ fixture->account,
+ fixture->irc_room,
+ TPL_EVENT_MASK_ANY);
+
+ g_assert_cmpint (g_list_length (dates), ==, 1);
+
+ date = g_list_nth_data (dates, 0);
+ g_assert_cmpint (0, ==,
+ g_date_compare (date, g_date_new_dmy (30, G_DATE_NOVEMBER, 2010)));
+
+ g_list_foreach (dates, (GFunc) g_date_free, NULL);
+ g_list_free (dates);
+}
+
+static void
+test_get_time (PidginTestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ GDate *date;
+
+ date = log_store_pidgin_get_time ("2010-04-29.140346+0100BST.html");
+
+ g_assert_cmpint (g_date_get_day (date), ==, 29);
+ g_assert_cmpint (g_date_get_month (date), ==, G_DATE_APRIL);
+ g_assert_cmpint (g_date_get_year (date), ==, 2010);
+
+ g_date_free (date);
+}
+
+static void
+test_get_name (PidginTestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ const gchar *name;
+
+ name = _tpl_log_store_get_name (TPL_LOG_STORE (fixture->store));
+
+ g_assert_cmpstr (name, ==, "Pidgin");
+}
+
+static void
+test_get_events_for_date_jabber (PidginTestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ GList *l;
+ TplTextEvent *msg = NULL;
+ GDate *date = g_date_new_dmy (12, G_DATE_APRIL, 2010);
+
+ /* chatroom messages */
+ l = log_store_pidgin_get_events_for_date (TPL_LOG_STORE (fixture->store),
+ fixture->account,
+ fixture->room,
+ TPL_EVENT_MASK_ANY,
+ date);
+
+ g_assert_cmpint (g_list_length (l), ==, 6);
+
+ msg = g_list_nth_data (l, 0);
+ g_assert (_tpl_event_target_is_room (TPL_EVENT (msg)) == TRUE);
+ g_assert_cmpstr (tpl_text_event_get_message (msg), ==, "1");
+
+ g_list_foreach (l, (GFunc) g_object_unref, NULL);
+ g_list_free (l);
+
+ /* 1-1 messages */
+ g_date_set_dmy (date, 10, G_DATE_DECEMBER, 2010);
+ l = log_store_pidgin_get_events_for_date (TPL_LOG_STORE (fixture->store),
+ fixture->account,
+ fixture->contact,
+ TPL_EVENT_MASK_ANY,
+ date);
+
+ g_assert_cmpint (g_list_length (l), ==, 2);
+
+ msg = g_list_nth_data (l, 0);
+ g_assert (_tpl_event_target_is_room (TPL_EVENT (msg)) == FALSE);
+ g_assert_cmpstr (tpl_text_event_get_message (msg), ==, "hi");
+
+ g_list_foreach (l, (GFunc) g_object_unref, NULL);
+ g_list_free (l);
+
+ g_date_free (date);
+}
+
+static int
+cmp_entities (gconstpointer a,
+ gconstpointer b)
+{
+ return -1 * g_strcmp0 (
+ tpl_entity_get_identifier (TPL_ENTITY (a)),
+ tpl_entity_get_identifier (TPL_ENTITY (b)));
+}
+
+static void
+test_get_entities_jabber (PidginTestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ GList *l = NULL;
+ TplEntity *entity;
+
+ l = log_store_pidgin_get_entities (TPL_LOG_STORE (fixture->store),
+ fixture->account);
+
+ g_assert_cmpint (g_list_length (l), ==, 3);
+
+ /* sort the entities, since their ordering depends on the file order */
+ l = g_list_sort (l, cmp_entities);
+
+ entity = g_list_nth_data (l, 0);
+ g_assert_cmpstr (tpl_entity_get_identifier (entity), ==,
+ "user5@collabora.co.uk");
+ g_assert (tpl_entity_get_entity_type (entity) == TPL_ENTITY_CONTACT);
+
+ entity = g_list_nth_data (l, 1);
+ g_assert_cmpstr (tpl_entity_get_identifier (entity), ==,
+ "user2@collabora.co.uk");
+ g_assert (tpl_entity_get_entity_type (entity) == TPL_ENTITY_CONTACT);
+
+ entity = g_list_nth_data (l, 2);
+ g_assert_cmpstr (tpl_entity_get_identifier (entity), ==,
+ "test@conference.collabora.co.uk");
+ g_assert (tpl_entity_get_entity_type (entity) == TPL_ENTITY_ROOM);
+
+ g_list_foreach (l, (GFunc) g_object_unref, NULL);
+ g_list_free (l);
+}
+
+static void
+test_search_new (PidginTestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ GList *l = NULL;
+
+ /* empty search */
+ l = log_store_pidgin_search_new (TPL_LOG_STORE (fixture->store),
+ "I do not exist in this log store data base!",
+ TPL_EVENT_MASK_ANY);
+
+ g_assert_cmpint (g_list_length (l), ==, 0);
+
+ tpl_log_manager_search_free (l);
+
+ /* non empty search matching 1-1 */
+ l = log_store_pidgin_search_new (TPL_LOG_STORE (fixture->store),
+ "hey you",
+ TPL_EVENT_MASK_ANY);
+
+ g_assert_cmpint (g_list_length (l), ==, 1);
+
+ tpl_log_manager_search_free (l);
+
+ /* non empty search, checking chatrooms are also searched */
+ l = log_store_pidgin_search_new (TPL_LOG_STORE (fixture->store),
+ "disco remote servers",
+ TPL_EVENT_MASK_ANY);
+
+ g_assert_cmpint (g_list_length (l), ==, 1);
+
+ tpl_log_manager_search_free (l);
+}
+
+static void
+test_get_events_for_empty_file (PidginTestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ GList *l = NULL;
+ TplEntity *entity;
+ GDate *date;
+
+ entity = tpl_entity_new ("87654321", TPL_ENTITY_CONTACT, NULL, NULL);
+
+ /* Check with empty file */
+ date = g_date_new_dmy (7, 2, 2010);
+
+ l = log_store_pidgin_get_events_for_date (TPL_LOG_STORE (fixture->store),
+ fixture->account, entity, TPL_EVENT_MASK_ANY, date);
+
+ g_assert_cmpint (g_list_length (l), ==, 0);
+ g_date_free (date);
+
+ /* Check with file that contains null bytes */
+ date = g_date_new_dmy (6, 2, 2010);
+
+ l = log_store_pidgin_get_events_for_date (TPL_LOG_STORE (fixture->store),
+ fixture->account, entity, TPL_EVENT_MASK_ANY, date);
+
+ g_assert_cmpint (g_list_length (l), ==, 0);
+ g_date_free (date);
+
+ g_object_unref (entity);
+}
+
+static void
+setup_debug (void)
+{
+ tp_debug_divert_messages (g_getenv ("TPL_LOGFILE"));
+
+#ifdef ENABLE_DEBUG
+ _tpl_debug_set_flags_from_env ();
+
+ stamp_logs = (g_getenv ("TPL_TIMING") != NULL);
+ debug_sender = tp_debug_sender_dup ();
+
+ g_log_set_default_handler (log_handler, NULL);
+#endif /* ENABLE_DEBUG */
+}
+
+
+int
+main (int argc, char **argv)
+{
+ GHashTable *params = NULL;
+ GList *l = NULL;
+ int retval;
+
+ g_type_init ();
+
+ setup_debug ();
+
+ /* no account tests */
+ g_test_init (&argc, &argv, NULL);
+ g_test_bug_base ("http://bugs.freedesktop.org/show_bug.cgi?id=");
+
+ g_test_add ("/log-store-pidgin/get-name",
+ PidginTestCaseFixture, NULL,
+ setup, test_get_name, teardown);
+
+ g_test_add ("/log-store-pidgin/get-time",
+ PidginTestCaseFixture, NULL,
+ setup, test_get_time, teardown);
+
+ /* this searches all over the account in the log stores */
+ g_test_add ("/log-store-pidgin/search-new",
+ PidginTestCaseFixture, NULL,
+ setup, test_search_new, teardown);
+
+ /* jabber account tests */
+ params = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
+ (GDestroyNotify) tp_g_value_slice_free);
+ g_assert (params != NULL);
+
+ l = g_list_prepend (l, params);
+
+ g_hash_table_insert (params, "account",
+ tp_g_value_slice_new_static_string ("user@collabora.co.uk"));
+ g_hash_table_insert (params, "account-path",
+ tp_g_value_slice_new_static_string (ACCOUNT_PATH_JABBER));
+
+ g_test_add ("/log-store-pidgin/basedir",
+ PidginTestCaseFixture, params,
+ setup, test_basedir, teardown);
+
+ g_test_add ("/log-store-pidgin/get-dates-jabber",
+ PidginTestCaseFixture, params,
+ setup, test_get_dates_jabber, teardown);
+
+ g_test_add ("/log-store-pidgin/get-events-for-date-jabber",
+ PidginTestCaseFixture, params,
+ setup, test_get_events_for_date_jabber, teardown);
+
+ g_test_add ("/log-store-pidgin/get-entities-jabber",
+ PidginTestCaseFixture, params,
+ setup, test_get_entities_jabber, teardown);
+
+ /* IRC account tests */
+ params = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
+ (GDestroyNotify) tp_g_value_slice_free);
+ g_assert (params != NULL);
+
+ l = g_list_prepend (l, params);
+
+ g_hash_table_insert (params, "account",
+ tp_g_value_slice_new_static_string ("user"));
+ g_hash_table_insert (params, "server",
+ tp_g_value_slice_new_static_string ("irc.freenode.net"));
+ g_hash_table_insert (params, "account-path",
+ tp_g_value_slice_new_static_string (ACCOUNT_PATH_IRC));
+
+ g_test_add ("/log-store-pidgin/get-dates-irc",
+ PidginTestCaseFixture, params,
+ setup, test_get_dates_irc, teardown);
+
+ /* Empty file */
+ params = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
+ (GDestroyNotify) tp_g_value_slice_free);
+ g_assert (params != NULL);
+
+ l = g_list_prepend (l, params);
+
+ g_hash_table_insert (params, "account",
+ tp_g_value_slice_new_static_string ("12345678"));
+ g_hash_table_insert (params, "account-path",
+ tp_g_value_slice_new_static_string (ACCOUNT_PATH_ICQ));
+
+ g_test_add ("/log-store-pidgin/get-event-for-empty-file",
+ PidginTestCaseFixture, params,
+ setup, test_get_events_for_empty_file, teardown);
+
+ retval = g_test_run ();
+
+ g_list_foreach (l, (GFunc) g_hash_table_unref, NULL);
+
+ return retval;
+}
diff --git a/tests/logger/dbus/test-tpl-log-store-sqlite.c b/tests/logger/dbus/test-tpl-log-store-sqlite.c
new file mode 100644
index 000000000..c65718a10
--- /dev/null
+++ b/tests/logger/dbus/test-tpl-log-store-sqlite.c
@@ -0,0 +1,40 @@
+#include "config.h"
+
+#include <telepathy-logger/log-store-sqlite-internal.h>
+#include <telepathy-logger/debug-internal.h>
+#include <telepathy-logger/client-factory-internal.h>
+
+int
+main (int argc, char **argv)
+{
+ TplLogStore *store;
+ TpDBusDaemon *bus;
+ TpAccount *account;
+ GError *error = NULL;
+ TpClientFactory* factory;
+
+ g_type_init ();
+
+ _tpl_debug_set_flags_from_env ();
+
+ bus = tp_dbus_daemon_dup (&error);
+ g_assert_no_error (error);
+
+ factory = _tpl_client_factory_dup (bus);
+
+ account = tp_client_factory_ensure_account (factory,
+ TP_ACCOUNT_OBJECT_PATH_BASE "gabble/jabber/danielle_2emadeley_40collabora_2eco_2euk0",
+ NULL, &error);
+ g_assert_no_error (error);
+
+ store = _tpl_log_store_sqlite_dup ();
+
+ g_print ("freq = %g\n",
+ _tpl_log_store_sqlite_get_frequency (TPL_LOG_STORE_SQLITE (store),
+ account, "dannielle.meyer@gmail.com"));
+
+ g_object_unref (store);
+ g_object_unref (account);
+ g_object_unref (bus);
+ g_object_unref (factory);
+}
diff --git a/tests/logger/dbus/test-tpl-log-store-xml.c b/tests/logger/dbus/test-tpl-log-store-xml.c
new file mode 100644
index 000000000..22c6d9cb5
--- /dev/null
+++ b/tests/logger/dbus/test-tpl-log-store-xml.c
@@ -0,0 +1,1120 @@
+#include "config.h"
+
+#include "telepathy-logger/log-store-xml.c"
+
+#include "lib/logger-test-helper.h"
+#include "lib/util.h"
+
+#include "telepathy-logger/debug-internal.h"
+#include "telepathy-logger/log-manager-internal.h"
+#include "telepathy-logger/log-store-internal.h"
+#include <telepathy-logger/client-factory-internal.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <glib.h>
+
+/* it was defined in telepathy-logger/log-store-xml.c */
+#undef DEBUG_FLAG
+#define DEBUG_FLAG TPL_DEBUG_TESTSUITE
+
+
+typedef struct
+{
+ GMainLoop *main_loop;
+ gchar *tmp_basedir;
+ TplLogStore *store;
+ TpDBusDaemon *bus;
+ TpClientFactory *factory;
+} XmlTestCaseFixture;
+
+
+static void
+setup (XmlTestCaseFixture* fixture,
+ gconstpointer user_data)
+{
+ GError *error = NULL;
+
+ fixture->main_loop = g_main_loop_new (NULL, FALSE);
+
+ fixture->store = g_object_new (TPL_TYPE_LOG_STORE_XML,
+ "testmode", TRUE,
+ NULL);
+
+ if (fixture->tmp_basedir != NULL)
+ log_store_xml_set_basedir (TPL_LOG_STORE_XML (fixture->store),
+ fixture->tmp_basedir);
+
+ fixture->bus = tp_tests_dbus_daemon_dup_or_die ();
+ g_assert (fixture->bus != NULL);
+
+ tp_dbus_daemon_request_name (fixture->bus,
+ TP_ACCOUNT_MANAGER_BUS_NAME,
+ FALSE,
+ &error);
+ g_assert_no_error (error);
+
+ fixture->factory = _tpl_client_factory_dup (fixture->bus);
+
+ tp_debug_divert_messages (g_getenv ("TPL_LOGFILE"));
+
+#ifdef ENABLE_DEBUG
+ _tpl_debug_set_flags_from_env ();
+#endif /* ENABLE_DEBUG */
+}
+
+
+static void
+setup_for_writing (XmlTestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ gchar *readonly_dir;
+ gchar *writable_dir;
+
+ readonly_dir = g_build_path (G_DIR_SEPARATOR_S,
+ g_getenv ("TPL_TEST_LOG_DIR"), "TpLogger", "logs", NULL);
+
+ writable_dir = g_build_path (G_DIR_SEPARATOR_S,
+ g_get_tmp_dir (), "logger-test-logs", NULL);
+
+ tp_tests_copy_dir (readonly_dir, writable_dir);
+ fixture->tmp_basedir = writable_dir;
+ g_free (readonly_dir);
+
+ setup (fixture, user_data);
+}
+
+
+static void
+teardown (XmlTestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ GError *error = NULL;
+
+ tp_dbus_daemon_release_name (fixture->bus, TP_ACCOUNT_MANAGER_BUS_NAME,
+ &error);
+ g_assert_no_error (error);
+
+ if (fixture->tmp_basedir != NULL)
+ {
+ gchar *command = g_strdup_printf ("rm -rf %s", fixture->tmp_basedir);
+
+ if (system (command) == -1)
+ g_warning ("Failed to cleanup tempory test log dir: %s",
+ fixture->tmp_basedir);
+
+ g_free (fixture->tmp_basedir);
+ }
+
+ if (fixture->store == NULL)
+ g_object_unref (fixture->store);
+
+ g_clear_object (&fixture->factory);
+}
+
+
+static void
+test_clear (XmlTestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ GList *hits;
+ hits = _tpl_log_store_search_new (fixture->store,
+ "user@collabora.co.uk",
+ TPL_EVENT_MASK_TEXT);
+
+ g_assert (hits != NULL);
+ g_assert_cmpint (g_list_length (hits), ==, 4);
+
+ tpl_log_manager_search_free (hits);
+
+ _tpl_log_store_clear (fixture->store);
+
+ hits = _tpl_log_store_search_new (fixture->store,
+ "user@collabora.co.uk",
+ TPL_EVENT_MASK_TEXT);
+
+ g_assert_cmpint (g_list_length (hits), ==, 0);
+}
+
+
+static void
+test_clear_account (XmlTestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ GList *hits;
+ TpAccount *account;
+ GError *error = NULL;
+ const gchar *kept = "user2@collabora.co.uk";
+ const gchar *cleared = "test2@collabora.co.uk";
+
+ hits = _tpl_log_store_search_new (fixture->store,
+ kept, TPL_EVENT_MASK_TEXT);
+
+ g_assert_cmpint (g_list_length (hits), ==, 4);
+
+ tpl_log_manager_search_free (hits);
+
+ hits = _tpl_log_store_search_new (fixture->store,
+ cleared, TPL_EVENT_MASK_TEXT);
+
+ g_assert_cmpint (g_list_length (hits), ==, 1);
+
+ tpl_log_manager_search_free (hits);
+
+ account = tp_client_factory_ensure_account (fixture->factory,
+ TP_ACCOUNT_OBJECT_PATH_BASE "gabble/jabber/test2_40collabora_2eco_2euk0",
+ NULL, &error);
+
+ g_assert_no_error (error);
+ g_assert (account != NULL);
+
+ _tpl_log_store_clear_account (fixture->store, account);
+ g_object_unref (account);
+
+ hits = _tpl_log_store_search_new (fixture->store, kept, TPL_EVENT_MASK_TEXT);
+
+ g_assert_cmpint (g_list_length (hits), ==, 4);
+
+ tpl_log_manager_search_free (hits);
+
+ hits = _tpl_log_store_search_new (fixture->store, cleared,
+ TPL_EVENT_MASK_TEXT);
+
+ g_assert_cmpint (g_list_length (hits), ==, 0);
+}
+
+
+static void
+test_clear_entity (XmlTestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ gboolean is_room = GPOINTER_TO_INT (user_data);
+ GList *hits;
+ TpAccount *account;
+ TplEntity *entity;
+ GError *error = NULL;
+ const gchar *always_kept, *kept, *cleared;
+
+ always_kept = "user2@collabora.co.uk";
+
+ if (is_room)
+ {
+ kept = "Hey, Just generating logs";
+ cleared = "meego@conference.collabora.co.uk/test2@collabora.co.uk";
+ }
+ else
+ {
+ kept = "meego@conference.collabora.co.uk/test2@collabora.co.uk";
+ cleared = "Hey, Just generating logs";
+ }
+
+ hits = _tpl_log_store_search_new (fixture->store, always_kept,
+ TPL_EVENT_MASK_TEXT);
+
+ g_assert_cmpint (g_list_length (hits), ==, 4);
+
+ tpl_log_manager_search_free (hits);
+
+ hits = _tpl_log_store_search_new (fixture->store, kept, TPL_EVENT_MASK_TEXT);
+
+ g_assert_cmpint (g_list_length (hits), ==, 1);
+
+ tpl_log_manager_search_free (hits);
+
+ hits = _tpl_log_store_search_new (fixture->store, cleared,
+ TPL_EVENT_MASK_TEXT);
+
+ g_assert_cmpint (g_list_length (hits), ==, 1);
+
+ tpl_log_manager_search_free (hits);
+
+ account = tp_client_factory_ensure_account (fixture->factory,
+ TP_ACCOUNT_OBJECT_PATH_BASE "gabble/jabber/test2_40collabora_2eco_2euk0",
+ NULL, &error);
+
+ g_assert_no_error (error);
+ g_assert (account != NULL);
+
+ if (is_room)
+ entity = tpl_entity_new_from_room_id ("meego@conference.collabora.co.uk");
+ else
+ entity = tpl_entity_new ("derek.foreman@collabora.co.uk",
+ TPL_ENTITY_CONTACT, NULL, NULL);
+
+ _tpl_log_store_clear_entity (fixture->store, account, entity);
+ g_object_unref (account);
+ g_object_unref (entity);
+
+ hits = _tpl_log_store_search_new (fixture->store,
+ always_kept, TPL_EVENT_MASK_TEXT);
+
+ g_assert_cmpint (g_list_length (hits), ==, 4);
+
+ tpl_log_manager_search_free (hits);
+
+ hits = _tpl_log_store_search_new (fixture->store, kept, TPL_EVENT_MASK_TEXT);
+
+ g_assert_cmpint (g_list_length (hits), ==, 1);
+
+ tpl_log_manager_search_free (hits);
+
+ hits = _tpl_log_store_search_new (fixture->store, cleared,
+ TPL_EVENT_MASK_TEXT);
+
+ g_assert_cmpint (g_list_length (hits), ==, 0);
+}
+
+
+static void
+assert_cmp_text_event (TplEvent *event,
+ TplEvent *stored_event)
+{
+ TplEntity *sender, *stored_sender;
+ TplEntity *receiver, *stored_receiver;
+
+ g_assert (TPL_IS_TEXT_EVENT (event));
+ g_assert (TPL_IS_TEXT_EVENT (stored_event));
+ g_assert_cmpstr (tpl_event_get_account_path (event), ==,
+ tpl_event_get_account_path (stored_event));
+
+ sender = tpl_event_get_sender (event);
+ stored_sender = tpl_event_get_sender (stored_event);
+
+ g_assert (_tpl_entity_compare (sender, stored_sender) == 0);
+ g_assert_cmpstr (tpl_entity_get_alias (sender), ==,
+ tpl_entity_get_alias (stored_sender));
+ g_assert_cmpstr (tpl_entity_get_avatar_token (sender), ==,
+ tpl_entity_get_avatar_token (stored_sender));
+
+ receiver = tpl_event_get_receiver (event);
+ stored_receiver = tpl_event_get_receiver (stored_event);
+
+ g_assert (_tpl_entity_compare (receiver, stored_receiver) == 0);
+ /* No support for receiver alias/token */
+
+ g_assert_cmpstr (tpl_text_event_get_message (TPL_TEXT_EVENT (event)),
+ ==, tpl_text_event_get_message (TPL_TEXT_EVENT (stored_event)));
+ g_assert_cmpint (tpl_text_event_get_message_type (TPL_TEXT_EVENT (event)),
+ ==, tpl_text_event_get_message_type (TPL_TEXT_EVENT (stored_event)));
+ g_assert_cmpstr (tpl_text_event_get_message_token (TPL_TEXT_EVENT (event)),
+ ==, tpl_text_event_get_message_token (TPL_TEXT_EVENT (stored_event)));
+ g_assert_cmpint (tpl_event_get_timestamp (event), ==,
+ tpl_event_get_timestamp (stored_event));
+ g_assert_cmpint (tpl_text_event_get_edit_timestamp (TPL_TEXT_EVENT (event)),
+ ==, tpl_text_event_get_edit_timestamp (TPL_TEXT_EVENT (stored_event)));
+}
+
+
+static void
+test_add_text_event (XmlTestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ TpAccount *account;
+ TplEntity *me, *contact, *room;
+ TplEvent *event;
+ GError *error = NULL;
+ GList *events;
+ gint64 timestamp = time (NULL);
+ TpTestsSimpleAccount *account_service;
+
+ tpl_test_create_and_prepare_account (fixture->bus, fixture->factory,
+ TP_ACCOUNT_OBJECT_PATH_BASE "idle/irc/me",
+ &account, &account_service);
+
+ me = tpl_entity_new ("bob.mcbadgers@example.com", TPL_ENTITY_SELF,
+ "my-alias", "my-avatar");
+ contact = tpl_entity_new ("contact", TPL_ENTITY_CONTACT, "contact-alias",
+ "contact-token");
+ room = tpl_entity_new_from_room_id ("room");
+
+
+ /* 1. Outgoing message to a contact */
+ event = g_object_new (TPL_TYPE_TEXT_EVENT,
+ /* TplEvent */
+ "account", account,
+ "sender", me,
+ "receiver", contact,
+ "timestamp", timestamp,
+ /* TplTextEvent */
+ "message-type", TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+ "message", "my message 1",
+ NULL);
+
+ _tpl_log_store_add_event (fixture->store, event, &error);
+ g_assert_no_error (error);
+
+ events = _tpl_log_store_get_filtered_events (fixture->store, account, contact,
+ TPL_EVENT_MASK_TEXT, 1, NULL, NULL);
+
+ g_assert_cmpint (g_list_length (events), ==, 1);
+ g_assert (TPL_IS_TEXT_EVENT (events->data));
+
+ assert_cmp_text_event (event, events->data);
+
+ g_object_unref (event);
+ g_object_unref (events->data);
+ g_list_free (events);
+
+ /* 2. Incoming message from contact (a /me action) */
+ event = g_object_new (TPL_TYPE_TEXT_EVENT,
+ /* TplEvent */
+ "account", account,
+ "sender", contact,
+ "receiver", me,
+ "timestamp", timestamp,
+ /* TplTextEvent */
+ "message-type", TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION,
+ "message", "my message 1",
+ NULL);
+
+ _tpl_log_store_add_event (fixture->store, event, &error);
+ g_assert_no_error (error);
+
+ events = _tpl_log_store_get_filtered_events (fixture->store, account, contact,
+ TPL_EVENT_MASK_TEXT, 1, NULL, NULL);
+
+ g_assert_cmpint (g_list_length (events), ==, 1);
+ g_assert (TPL_IS_TEXT_EVENT (events->data));
+
+ assert_cmp_text_event (event, events->data);
+
+ g_object_unref (event);
+ g_object_unref (events->data);
+ g_list_free (events);
+
+ /* 3. Outgoing message to a room */
+ event = g_object_new (TPL_TYPE_TEXT_EVENT,
+ /* TplEvent */
+ "account", account,
+ "sender", me,
+ "receiver", room,
+ "timestamp", timestamp,
+ /* TplTextEvent */
+ "message-type", TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+ "message", "my message 1",
+ NULL);
+
+ _tpl_log_store_add_event (fixture->store, event, &error);
+ g_assert_no_error (error);
+
+ events = _tpl_log_store_get_filtered_events (fixture->store, account, room,
+ TPL_EVENT_MASK_TEXT, 1, NULL, NULL);
+
+ g_assert_cmpint (g_list_length (events), ==, 1);
+ g_assert (TPL_IS_TEXT_EVENT (events->data));
+
+ assert_cmp_text_event (event, events->data);
+
+ g_object_unref (event);
+ g_object_unref (events->data);
+ g_list_free (events);
+
+ /* 4. Incoming message from a room that hit some network lag. */
+ event = g_object_new (TPL_TYPE_TEXT_EVENT,
+ /* TplEvent */
+ "account", account,
+ "sender", contact,
+ "receiver", room,
+ "timestamp", timestamp - 1,
+ /* TplTextEvent */
+ "message-type", TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+ "message", "my message 1",
+ NULL);
+
+ _tpl_log_store_add_event (fixture->store, event, &error);
+ g_assert_no_error (error);
+
+ events = _tpl_log_store_get_filtered_events (fixture->store, account, room,
+ TPL_EVENT_MASK_TEXT, 2, NULL, NULL);
+
+ /* Events appear in their dbus-order for the most part
+ * (ignoring timestamps). */
+ g_assert_cmpint (g_list_length (events), ==, 2);
+ g_assert (TPL_IS_TEXT_EVENT (g_list_last (events)->data));
+
+ assert_cmp_text_event (event, g_list_last (events)->data);
+
+ g_object_unref (event);
+ g_list_foreach (events, (GFunc) g_object_unref, NULL);
+ g_list_free (events);
+
+ /* 5. Delayed delivery of incoming message from a room */
+ event = g_object_new (TPL_TYPE_TEXT_EVENT,
+ /* TplEvent */
+ "account", account,
+ "sender", contact,
+ "receiver", room,
+ "timestamp", timestamp - (60 * 60 * 24),
+ /* TplTextEvent */
+ "message-type", TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+ "message", "my message 1",
+ NULL);
+
+ _tpl_log_store_add_event (fixture->store, event, &error);
+ g_assert_no_error (error);
+
+ /* Ask for all of the events to this room... */
+ events = _tpl_log_store_get_filtered_events (fixture->store, account, room,
+ TPL_EVENT_MASK_ANY, 1000000, NULL, NULL);
+
+ /* ... but there are only 3. */
+ g_assert_cmpint (g_list_length (events), ==, 3);
+ g_assert (TPL_IS_TEXT_EVENT (events->data));
+ /* Also, because of the day discrepancy, this event will not appear in the
+ * order it arrived (note that the order is actually undefined (the only
+ * invariant is that we don't lose the message), so don't cry if you break
+ * this assertion, as long as you don't break message edits). */
+ assert_cmp_text_event (event, events->data);
+
+ tpl_test_release_account (fixture->bus, account, account_service);
+ g_object_unref (event);
+ g_list_foreach (events, (GFunc) g_object_unref, NULL);
+ g_list_free (events);
+}
+
+static void
+test_add_superseding_event (XmlTestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ TpAccount *account;
+ TplEntity *me, *contact;
+ TplEvent *event;
+ TplTextEvent *new_event;
+ TplTextEvent *new_new_event;
+ TplTextEvent *late_event;
+ TplTextEvent *early_event;
+ GError *error = NULL;
+ GList *events;
+ GList *superseded;
+ gint64 timestamp = time (NULL);
+ TpTestsSimpleAccount *account_service;
+
+ tpl_test_create_and_prepare_account (fixture->bus, fixture->factory,
+ TP_ACCOUNT_OBJECT_PATH_BASE "idle/irc/me",
+ &account, &account_service);
+
+ me = tpl_entity_new ("me", TPL_ENTITY_SELF, "my-alias", "my-avatar");
+ contact = tpl_entity_new ("contact", TPL_ENTITY_CONTACT, "contact-alias",
+ "contact-token");
+
+ /* 1. Outgoing message to a contact. */
+ event = g_object_new (TPL_TYPE_TEXT_EVENT,
+ /* TplEvent */
+ "account", account,
+ "sender", me,
+ "receiver", contact,
+ "message-token", "OMGCOMPLETELYRANDOMSTRING1",
+ "timestamp", timestamp,
+ /* TplTextEvent */
+ "message-type", TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+ "message", "my message 1",
+ NULL);
+
+ /* add and re-retrieve the event */
+ _tpl_log_store_add_event (fixture->store, event, &error);
+ g_assert_no_error (error);
+ events = _tpl_log_store_get_filtered_events (fixture->store, account, contact,
+ TPL_EVENT_MASK_TEXT, 1, NULL, NULL);
+
+ g_assert_cmpint (g_list_length (events), ==, 1);
+ g_assert (TPL_IS_TEXT_EVENT (events->data));
+
+ assert_cmp_text_event (event, events->data);
+
+ g_object_unref (events->data);
+ g_list_free (events);
+
+ /* 2. Edit message 1. */
+ new_event = g_object_new (TPL_TYPE_TEXT_EVENT,
+ /* TplEvent */
+ "account", account,
+ "sender", me,
+ "receiver", contact,
+ "timestamp", timestamp,
+ /* TplTextEvent */
+ "edit-timestamp", timestamp + 1,
+ "message-token", "OMGCOMPLETELYRANDOMSTRING2",
+ "supersedes-token", "OMGCOMPLETELYRANDOMSTRING1",
+ "message-type", TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+ "message", "My message 1 [FIXED]",
+ NULL);
+
+ /* add and re-retrieve the event */
+ _tpl_log_store_add_event (fixture->store, TPL_EVENT (new_event), &error);
+ g_assert_no_error (error);
+ events = _tpl_log_store_get_filtered_events (fixture->store, account, contact,
+ TPL_EVENT_MASK_TEXT, 1, NULL, NULL);
+ assert_cmp_text_event (TPL_EVENT (new_event), events->data);
+
+ /* Check that the two events are linked */
+ superseded = tpl_text_event_get_supersedes (events->data);
+ g_assert (superseded != NULL);
+ assert_cmp_text_event (event, superseded->data);
+ g_assert (tpl_text_event_get_supersedes (superseded->data) == NULL);
+
+ g_list_foreach (events, (GFunc) g_object_unref, NULL);
+ g_list_free (events);
+
+ /* 3. Edit it again.
+ * Note that the (broken) edit-timestamp should not make any
+ * difference to the message processing, but it should be preserved.*/
+ new_new_event = g_object_new (TPL_TYPE_TEXT_EVENT,
+ /* TplEvent */
+ "account", account,
+ "sender", me,
+ "receiver", contact,
+ "timestamp", timestamp,
+ /* TplTextEvent */
+ "edit-timestamp", timestamp + (60 * 60 * 24),
+ "message-token", "OMGCOMPLETELYRANDOMSTRING3",
+ "supersedes-token", "OMGCOMPLETELYRANDOMSTRING1",
+ "message-type", TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+ "message", "My Message 1 [FIXED] [FIXED]",
+ NULL);
+
+ /* add and re-retrieve the event */
+ _tpl_log_store_add_event (fixture->store, TPL_EVENT (new_new_event), &error);
+ g_assert_no_error (error);
+ events = _tpl_log_store_get_filtered_events (fixture->store, account, contact,
+ TPL_EVENT_MASK_TEXT, 1, NULL, NULL);
+ assert_cmp_text_event (TPL_EVENT (new_new_event), events->data);
+
+ /* Check that the three events are linked */
+ superseded = tpl_text_event_get_supersedes (events->data);
+ g_assert (superseded != NULL);
+ assert_cmp_text_event (TPL_EVENT (new_event), superseded->data);
+ g_assert (superseded->next != NULL);
+ assert_cmp_text_event (event, superseded->next->data);
+ g_assert (tpl_text_event_get_supersedes (superseded->next->data) == NULL);
+
+ g_list_foreach (events, (GFunc) g_object_unref, NULL);
+ g_list_free (events);
+
+ /* Also note that the superseding events *replace* the old ones. */
+ events = _tpl_log_store_get_filtered_events (fixture->store, account, contact,
+ TPL_EVENT_MASK_TEXT, 1000000, NULL, NULL);
+ g_assert_cmpint (g_list_length (events), == , 1);
+ assert_cmp_text_event (TPL_EVENT (new_new_event), events->data);
+
+ g_list_foreach (events, (GFunc) g_object_unref, NULL);
+ g_list_free (events);
+
+ /* 4. Edit comes in with the wrong timestamp.
+ * Note that the (also broken) edit-timestamp should not make any
+ * difference to the message processing, but it should be preserved.*/
+ late_event = g_object_new (TPL_TYPE_TEXT_EVENT,
+ /* TplEvent */
+ "account", account,
+ "sender", me,
+ "receiver", contact,
+ "timestamp", timestamp + (60 * 60 * 24),
+ /* TplTextEvent */
+ "edit-timestamp", timestamp - (60 * 60 * 24),
+ "message-token", "OMGCOMPLETELYRANDOMSTRING4",
+ "supersedes-token", "OMGCOMPLETELYRANDOMSTRING1",
+ "message-type", TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+ "message", "My Message 1 [FIXED_LATE]",
+ NULL);
+
+ /* add and re-retrieve the event */
+ _tpl_log_store_add_event (fixture->store, TPL_EVENT (late_event), &error);
+ g_assert_no_error (error);
+ events = _tpl_log_store_get_filtered_events (fixture->store, account, contact,
+ TPL_EVENT_MASK_TEXT, 1, NULL, NULL);
+ assert_cmp_text_event (TPL_EVENT (late_event), events->data);
+
+ /* Check that the events are not linked (and a dummy was inserted instead)
+ * because the timestamp was wrong. */
+ superseded = tpl_text_event_get_supersedes (events->data);
+ g_assert (superseded != NULL);
+ g_assert_cmpstr (tpl_text_event_get_message (superseded->data), ==, "");
+
+ g_list_foreach (events, (GFunc) g_object_unref, NULL);
+ g_list_free (events);
+
+ /* And if we ask for all of the events, there will be 2 there. */
+ events = _tpl_log_store_get_filtered_events (fixture->store, account, contact,
+ TPL_EVENT_MASK_TEXT, 1000000, NULL, NULL);
+ g_assert_cmpint (g_list_length (events), == , 2);
+ assert_cmp_text_event (TPL_EVENT (new_new_event), events->data);
+ assert_cmp_text_event (TPL_EVENT (late_event), g_list_last (events)->data);
+
+ g_list_foreach (events, (GFunc) g_object_unref, NULL);
+ g_list_free (events);
+
+ /* 5. If we have an event that is broken in the other direction then it will
+ * also come out as a separate event (since each day is parsed on its own).
+ * Even though we don't currently omit edit-timestamp, we might as well
+ * see what happens if we forget it. */
+ early_event = g_object_new (TPL_TYPE_TEXT_EVENT,
+ /* TplEvent */
+ "account", account,
+ "sender", me,
+ "receiver", contact,
+ "timestamp", timestamp - (60 * 60 * 24),
+ /* TplTextEvent */
+ "message-token", "OMGCOMPLETELYRANDOMSTRING5",
+ "supersedes-token", "OMGCOMPLETELYRANDOMSTRING1",
+ "message-type", TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+ "message", "My Message 1 [FIXED_EARLY]",
+ NULL);
+
+ /* And if we ask for all of the events, there will be 3 there. */
+ _tpl_log_store_add_event (fixture->store, TPL_EVENT (early_event), &error);
+ g_assert_no_error (error);
+ events = _tpl_log_store_get_filtered_events (fixture->store, account, contact,
+ TPL_EVENT_MASK_TEXT, 1000000, NULL, NULL);
+ g_assert_cmpint (g_list_length (events), ==, 3);
+ assert_cmp_text_event (TPL_EVENT (early_event), events->data);
+ assert_cmp_text_event (TPL_EVENT (new_new_event), events->next->data);
+ assert_cmp_text_event (TPL_EVENT (late_event), g_list_last (events)->data);
+
+ tpl_test_release_account (fixture->bus, account, account_service);
+
+ g_list_foreach (events, (GFunc) g_object_unref, NULL);
+ g_list_free (events);
+
+ g_object_unref (event);
+ g_object_unref (new_event);
+ g_object_unref (new_new_event);
+ g_object_unref (late_event);
+ g_object_unref (early_event);
+}
+
+static void
+assert_cmp_call_event (TplEvent *event,
+ TplEvent *stored_event)
+{
+ TplEntity *sender, *stored_sender;
+ TplEntity *receiver, *stored_receiver;
+ TplEntity *actor, *stored_actor;
+
+ g_assert (TPL_IS_CALL_EVENT (event));
+ g_assert (TPL_IS_CALL_EVENT (stored_event));
+ g_assert_cmpstr (tpl_event_get_account_path (event), ==,
+ tpl_event_get_account_path (stored_event));
+
+ sender = tpl_event_get_sender (event);
+ stored_sender = tpl_event_get_sender (stored_event);
+
+ g_assert (_tpl_entity_compare (sender, stored_sender) == 0);
+ g_assert_cmpstr (tpl_entity_get_alias (sender), ==,
+ tpl_entity_get_alias (stored_sender));
+ g_assert_cmpstr (tpl_entity_get_avatar_token (sender), ==,
+ tpl_entity_get_avatar_token (stored_sender));
+
+ receiver = tpl_event_get_receiver (event);
+ stored_receiver = tpl_event_get_receiver (stored_event);
+
+ g_assert (_tpl_entity_compare (receiver, stored_receiver) == 0);
+ /* No support for receiver alias/token */
+
+ g_assert_cmpint (tpl_event_get_timestamp (event), ==,
+ tpl_event_get_timestamp (stored_event));
+
+ g_assert_cmpint (tpl_call_event_get_duration (TPL_CALL_EVENT (event)),
+ ==, tpl_call_event_get_duration (TPL_CALL_EVENT (stored_event)));
+
+ actor = tpl_call_event_get_end_actor (TPL_CALL_EVENT (event));
+ stored_actor = tpl_call_event_get_end_actor (TPL_CALL_EVENT (stored_event));
+
+ g_assert (_tpl_entity_compare (actor, stored_actor) == 0);
+ g_assert_cmpstr (tpl_entity_get_alias (actor), ==,
+ tpl_entity_get_alias (stored_actor));
+ g_assert_cmpstr (tpl_entity_get_avatar_token (actor), ==,
+ tpl_entity_get_avatar_token (stored_actor));
+ g_assert_cmpstr (
+ tpl_call_event_get_detailed_end_reason (TPL_CALL_EVENT (event)),
+ ==,
+ tpl_call_event_get_detailed_end_reason (TPL_CALL_EVENT (stored_event)));
+}
+
+
+static void
+test_add_call_event (XmlTestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ TpAccount *account;
+ TplEntity *me, *contact, *room;
+ TplEvent *event;
+ GError *error = NULL;
+ GList *events;
+ gint64 timestamp = time (NULL);
+ TpTestsSimpleAccount *account_service;
+
+ tpl_test_create_and_prepare_account (fixture->bus, fixture->factory,
+ TP_ACCOUNT_OBJECT_PATH_BASE "gabble/jabber/me",
+ &account, &account_service);
+
+ me = tpl_entity_new ("bob.mcbadgers@example.com", TPL_ENTITY_SELF,
+ "my-alias", "my-avatar");
+ contact = tpl_entity_new ("contact", TPL_ENTITY_CONTACT, "contact-alias",
+ "contact-token");
+ room = tpl_entity_new_from_room_id ("room");
+
+ /* 1. Outgoing call to a contact */
+ event = g_object_new (TPL_TYPE_CALL_EVENT,
+ /* TplEvent */
+ "account", account,
+ "sender", me,
+ "receiver", contact,
+ "timestamp", timestamp,
+ /* TplCallEvent */
+ "duration", (gint64) 1234,
+ "end-actor", me,
+ "end-reason", TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED,
+ "detailed-end-reason", TP_ERROR_STR_CANCELLED,
+ NULL);
+
+ _tpl_log_store_add_event (fixture->store, event, &error);
+ g_assert_no_error (error);
+
+ events = _tpl_log_store_get_filtered_events (fixture->store, account, contact,
+ TPL_EVENT_MASK_CALL, 1, NULL, NULL);
+
+ g_assert_cmpint (g_list_length (events), ==, 1);
+ g_assert (TPL_IS_CALL_EVENT (events->data));
+
+ assert_cmp_call_event (event, events->data);
+
+ g_object_unref (event);
+ g_object_unref (events->data);
+ g_list_free (events);
+
+ /* 2. Incoming call from contact */
+ event = g_object_new (TPL_TYPE_CALL_EVENT,
+ /* TplEvent */
+ "account", account,
+ "sender", contact,
+ "receiver", me,
+ "timestamp", timestamp,
+ /* TplCallEvent */
+ "duration", (gint64) 2345,
+ "end-actor", contact,
+ "end-reason", TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED,
+ "detailed-end-reason", TP_ERROR_STR_TERMINATED,
+ NULL);
+
+ _tpl_log_store_add_event (fixture->store, event, &error);
+ g_assert_no_error (error);
+
+ events = _tpl_log_store_get_filtered_events (fixture->store, account, contact,
+ TPL_EVENT_MASK_CALL, 1, NULL, NULL);
+
+ g_assert_cmpint (g_list_length (events), ==, 1);
+ g_assert (TPL_IS_CALL_EVENT (events->data));
+
+ assert_cmp_call_event (event, events->data);
+
+ g_object_unref (event);
+ g_object_unref (events->data);
+ g_list_free (events);
+
+ /* 3. Outgoing call to a room */
+ event = g_object_new (TPL_TYPE_CALL_EVENT,
+ /* TplEvent */
+ "account", account,
+ "sender", me,
+ "receiver", room,
+ "timestamp", timestamp,
+ /* TplCallEvent */
+ "duration", (gint64) 3456,
+ "end-actor", room,
+ "end-reason", TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED,
+ "detailed-end-reason", TP_ERROR_STR_CHANNEL_KICKED,
+ NULL);
+
+ _tpl_log_store_add_event (fixture->store, event, &error);
+ g_assert_no_error (error);
+
+ events = _tpl_log_store_get_filtered_events (fixture->store, account, room,
+ TPL_EVENT_MASK_CALL, 1, NULL, NULL);
+
+ g_assert_cmpint (g_list_length (events), ==, 1);
+ g_assert (TPL_IS_CALL_EVENT (events->data));
+
+ assert_cmp_call_event (event, events->data);
+
+ g_object_unref (event);
+ g_object_unref (events->data);
+ g_list_free (events);
+
+ /* 4. Incoming missed call from a room */
+ event = g_object_new (TPL_TYPE_CALL_EVENT,
+ /* TplEvent */
+ "account", account,
+ "sender", contact,
+ "receiver", room,
+ "timestamp", timestamp,
+ /* TplCallEvent */
+ "duration", (gint64) -1,
+ "end-actor", room,
+ "end-reason", TP_CALL_STATE_CHANGE_REASON_NO_ANSWER,
+ "detailed-end-reason", "",
+ NULL);
+
+ _tpl_log_store_add_event (fixture->store, event, &error);
+ g_assert_no_error (error);
+
+ events = _tpl_log_store_get_filtered_events (fixture->store, account, room,
+ TPL_EVENT_MASK_CALL, 1, NULL, NULL);
+
+ g_assert_cmpint (g_list_length (events), ==, 1);
+ g_assert (TPL_IS_CALL_EVENT (events->data));
+
+ assert_cmp_call_event (event, events->data);
+
+ tpl_test_release_account (fixture->bus, account, account_service);
+ g_object_unref (event);
+ g_object_unref (events->data);
+ g_list_free (events);
+}
+
+static void
+test_exists (XmlTestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ TpAccount *account1, *account2;
+ TplEntity *user2, *user3;
+ GError *error = NULL;
+
+ account1 = tp_client_factory_ensure_account (fixture->factory,
+ TP_ACCOUNT_OBJECT_PATH_BASE "gabble/jabber/test2_40collabora_2eco_2euk0",
+ NULL, &error);
+ g_assert_no_error (error);
+ g_assert (account1 != NULL);
+
+ account2 = tp_client_factory_ensure_account (fixture->factory,
+ TP_ACCOUNT_OBJECT_PATH_BASE "gabble/jabber/user_40collabora_2eco_2euk",
+ NULL, &error);
+ g_assert_no_error (error);
+ g_assert (account1 != NULL);
+
+ user2 = tpl_entity_new ("user2@collabora.co.uk", TPL_ENTITY_CONTACT,
+ "User2", "");
+
+ user3 = tpl_entity_new ("user3@collabora.co.uk", TPL_ENTITY_CONTACT,
+ "User3", "");
+
+ g_assert (_tpl_log_store_exists (fixture->store, account1, NULL, TPL_EVENT_MASK_ANY));
+ g_assert (_tpl_log_store_exists (fixture->store, account1, NULL, TPL_EVENT_MASK_TEXT));
+ g_assert (!_tpl_log_store_exists (fixture->store, account1, NULL, TPL_EVENT_MASK_CALL));
+
+ g_assert (_tpl_log_store_exists (fixture->store, account2, NULL, TPL_EVENT_MASK_ANY));
+ g_assert (_tpl_log_store_exists (fixture->store, account2, NULL, TPL_EVENT_MASK_TEXT));
+ g_assert (_tpl_log_store_exists (fixture->store, account2, NULL, TPL_EVENT_MASK_CALL));
+
+ g_assert (!_tpl_log_store_exists (fixture->store, account1, user2, TPL_EVENT_MASK_ANY));
+ g_assert (!_tpl_log_store_exists (fixture->store, account1, user2, TPL_EVENT_MASK_TEXT));
+ g_assert (!_tpl_log_store_exists (fixture->store, account1, user2, TPL_EVENT_MASK_CALL));
+
+ g_assert (_tpl_log_store_exists (fixture->store, account2, user2, TPL_EVENT_MASK_ANY));
+ g_assert (_tpl_log_store_exists (fixture->store, account2, user2, TPL_EVENT_MASK_TEXT));
+ g_assert (!_tpl_log_store_exists (fixture->store, account2, user2, TPL_EVENT_MASK_CALL));
+
+ g_assert (_tpl_log_store_exists (fixture->store, account2, user3, TPL_EVENT_MASK_ANY));
+
+ g_assert (!_tpl_log_store_exists (fixture->store, account2, user3, TPL_EVENT_MASK_TEXT));
+ g_assert (_tpl_log_store_exists (fixture->store, account2, user3, TPL_EVENT_MASK_CALL));
+
+ g_object_unref (account1);
+ g_object_unref (account2);
+ g_object_unref (user2);
+ g_object_unref (user3);
+}
+
+
+static void
+test_get_events_for_date (XmlTestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ TpAccount *account;
+ TplEntity *user2, *user3, *user4, *user5;
+ GList *events;
+ GDate *date;
+ gint idx;
+ TpTestsSimpleAccount *account_service;
+
+ tpl_test_create_and_prepare_account (fixture->bus, fixture->factory,
+ TP_ACCOUNT_OBJECT_PATH_BASE "gabble/jabber/user_40collabora_2eco_2euk",
+ &account, &account_service);
+
+ date = g_date_new_dmy (13, 1, 2010);
+
+ user2 = tpl_entity_new ("user2@collabora.co.uk", TPL_ENTITY_CONTACT,
+ "User2", "");
+
+ user3 = tpl_entity_new ("user3@collabora.co.uk", TPL_ENTITY_CONTACT,
+ "User3", "");
+
+ user4 = tpl_entity_new ("user4@collabora.co.uk", TPL_ENTITY_CONTACT,
+ "User4", "");
+
+ user5 = tpl_entity_new ("user5@collabora.co.uk", TPL_ENTITY_CONTACT,
+ "User5", "");
+
+ /* Check that text event and call event are merged properly, call events
+ * should come after any older or same timestamp event. */
+ events = _tpl_log_store_get_events_for_date (fixture->store, account, user4,
+ TPL_EVENT_MASK_ANY, date);
+
+ g_assert_cmpint (g_list_length (events), ==, 6);
+ idx = -1;
+
+ g_assert (TPL_IS_TEXT_EVENT (g_list_nth_data (events, ++idx)));
+ g_assert_cmpstr (
+ tpl_text_event_get_message (TPL_TEXT_EVENT (g_list_nth_data (events, idx))),
+ ==, "7");
+
+ g_assert (TPL_IS_TEXT_EVENT (g_list_nth_data (events, ++idx)));
+ g_assert_cmpstr (
+ tpl_text_event_get_message (TPL_TEXT_EVENT (g_list_nth_data (events, idx))),
+ ==, "8");
+
+ g_assert (TPL_IS_CALL_EVENT (g_list_nth_data (events, ++idx)));
+ g_assert_cmpint (
+ tpl_call_event_get_duration (TPL_CALL_EVENT (g_list_nth_data (events, idx))),
+ ==, 1);
+ g_assert_cmpint (
+ tpl_call_event_get_end_reason (TPL_CALL_EVENT (g_list_nth_data (events, idx))),
+ ==, TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED);
+ g_assert_cmpstr (tpl_call_event_get_detailed_end_reason (TPL_CALL_EVENT (g_list_nth_data (events, idx))),
+ ==, TP_ERROR_STR_CANCELLED);
+
+ g_assert (TPL_IS_CALL_EVENT (g_list_nth_data (events, ++idx)));
+ g_assert_cmpint (
+ tpl_call_event_get_duration (TPL_CALL_EVENT (g_list_nth_data (events, idx))),
+ ==, 2);
+ g_assert_cmpint (
+ tpl_call_event_get_end_reason (TPL_CALL_EVENT (g_list_nth_data (events, idx))),
+ ==, TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED);
+ g_assert_cmpstr (tpl_call_event_get_detailed_end_reason (TPL_CALL_EVENT (g_list_nth_data (events, idx))),
+ ==, TP_ERROR_STR_CANCELLED);
+
+ g_assert (TPL_IS_CALL_EVENT (g_list_nth_data (events, ++idx)));
+ g_assert_cmpint (
+ tpl_call_event_get_duration (TPL_CALL_EVENT (g_list_nth_data (events, idx))),
+ ==, 3);
+ g_assert_cmpint (
+ tpl_call_event_get_end_reason (TPL_CALL_EVENT (g_list_nth_data (events, idx))),
+ ==, TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED);
+ g_assert_cmpstr (tpl_call_event_get_detailed_end_reason (TPL_CALL_EVENT (g_list_nth_data (events, idx))),
+ ==, TP_ERROR_STR_CANCELLED);
+
+ g_assert (TPL_IS_TEXT_EVENT (g_list_nth_data (events, ++idx)));
+ g_assert_cmpstr (
+ tpl_text_event_get_message (TPL_TEXT_EVENT (g_list_nth_data (events, idx))),
+ ==, "9");
+
+ g_list_foreach (events, (GFunc) g_object_unref, NULL);
+ g_list_free (events);
+
+ /* Check that a call older then any text event is sorted first */
+ events = _tpl_log_store_get_events_for_date (fixture->store, account, user5,
+ TPL_EVENT_MASK_ANY, date);
+
+ g_assert_cmpint (g_list_length (events), ==, 2);
+ idx = -1;
+
+ g_assert (TPL_IS_CALL_EVENT (g_list_nth_data (events, ++idx)));
+ g_assert_cmpint (
+ tpl_call_event_get_duration (TPL_CALL_EVENT (g_list_nth_data (events, idx))),
+ ==, 1);
+
+ g_assert (TPL_IS_TEXT_EVENT (g_list_nth_data (events, ++idx)));
+ g_assert_cmpstr (
+ tpl_text_event_get_message (TPL_TEXT_EVENT (g_list_nth_data (events, idx))),
+ ==, "9");
+
+ g_list_foreach (events, (GFunc) g_object_unref, NULL);
+ g_list_free (events);
+
+ /* Check that call mask work */
+ events = _tpl_log_store_get_events_for_date (fixture->store, account, user4,
+ TPL_EVENT_MASK_CALL, date);
+
+ g_assert_cmpint (g_list_length (events), ==, 3);
+ g_assert (TPL_IS_CALL_EVENT (g_list_nth_data (events, 0)));
+ g_assert_cmpint (
+ tpl_call_event_get_duration (TPL_CALL_EVENT (g_list_nth_data (events, 0))),
+ ==, 1);
+
+ g_list_foreach (events, (GFunc) g_object_unref, NULL);
+ g_list_free (events);
+
+ /* Check that text mask work */
+ events = _tpl_log_store_get_events_for_date (fixture->store, account, user4,
+ TPL_EVENT_MASK_TEXT, date);
+
+ g_assert_cmpint (g_list_length (events), ==, 3);
+
+ g_assert (TPL_IS_TEXT_EVENT (g_list_nth_data (events, 0)));
+ g_assert_cmpstr (
+ tpl_text_event_get_message (TPL_TEXT_EVENT (g_list_nth_data (events, 0))),
+ ==, "7");
+
+ g_list_foreach (events, (GFunc) g_object_unref, NULL);
+ g_list_free (events);
+
+ /* Check that getting empty list is working */
+ events = _tpl_log_store_get_events_for_date (fixture->store, account, user2,
+ TPL_EVENT_MASK_CALL, date);
+ g_assert_cmpint (g_list_length (events), ==, 0);
+
+ events = _tpl_log_store_get_events_for_date (fixture->store, account, user3,
+ TPL_EVENT_MASK_TEXT, date);
+ g_assert_cmpint (g_list_length (events), ==, 0);
+
+ tpl_test_release_account (fixture->bus, account, account_service);
+ g_object_unref (user2);
+ g_object_unref (user3);
+ g_object_unref (user4);
+ g_object_unref (user5);
+ g_date_free (date);
+}
+
+
+gint main (gint argc, gchar **argv)
+{
+ g_type_init ();
+
+ g_test_init (&argc, &argv, NULL);
+ g_test_bug_base ("http://bugs.freedesktop.org/show_bug.cgi?id=");
+
+ g_test_add ("/log-store-xml/clear",
+ XmlTestCaseFixture, NULL,
+ setup_for_writing, test_clear, teardown);
+
+ g_test_add ("/log-store-xml/clear-account",
+ XmlTestCaseFixture, NULL,
+ setup_for_writing, test_clear_account, teardown);
+
+ g_test_add ("/log-store-xml/clear-entity",
+ XmlTestCaseFixture, GINT_TO_POINTER (FALSE),
+ setup_for_writing, test_clear_entity, teardown);
+
+ g_test_add ("/log-store-xml/clear-entity-room",
+ XmlTestCaseFixture, GINT_TO_POINTER (TRUE),
+ setup_for_writing, test_clear_entity, teardown);
+
+ g_test_add ("/log-store-xml/add-text-event",
+ XmlTestCaseFixture, NULL,
+ setup_for_writing, test_add_text_event, teardown);
+
+ g_test_add ("/log-store-xml/add-superseding-event",
+ XmlTestCaseFixture, NULL,
+ setup_for_writing, test_add_superseding_event, teardown);
+
+ g_test_add ("/log-store-xml/add-call-event",
+ XmlTestCaseFixture, NULL,
+ setup_for_writing, test_add_call_event, teardown);
+
+ g_test_add ("/log-store-xml/exists",
+ XmlTestCaseFixture, NULL,
+ setup, test_exists, teardown);
+
+ g_test_add ("/log-store-xml/get-events-for-date",
+ XmlTestCaseFixture, NULL,
+ setup, test_get_events_for_date, teardown);
+
+ return g_test_run ();
+}
diff --git a/tests/logger/dbus/test-tpl-log-walker.c b/tests/logger/dbus/test-tpl-log-walker.c
new file mode 100644
index 000000000..b924a1292
--- /dev/null
+++ b/tests/logger/dbus/test-tpl-log-walker.c
@@ -0,0 +1,463 @@
+#include "config.h"
+
+#include <string.h>
+
+#include "lib/simple-account.h"
+#include "lib/util.h"
+
+#include "telepathy-logger/call-event.h"
+#include "telepathy-logger/debug-internal.h"
+#include "telepathy-logger/log-manager.h"
+#include "telepathy-logger/text-event.h"
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/telepathy-glib-dbus.h>
+#include <glib.h>
+
+#define DEBUG_FLAG TPL_DEBUG_TESTSUITE
+
+
+typedef struct
+{
+ GList *events;
+ GMainLoop *main_loop;
+ TplLogManager *manager;
+ TpAccount *account;
+ TpDBusDaemon *bus;
+ TpClientFactory *factory;
+ TpTestsSimpleAccount *account_service;
+} WalkerTestCaseFixture;
+
+
+static void
+account_prepare_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WalkerTestCaseFixture *fixture = user_data;
+ GError *error = NULL;
+
+ tp_proxy_prepare_finish (source, result, &error);
+ g_assert_no_error (error);
+
+ g_main_loop_quit (fixture->main_loop);
+}
+
+
+static void
+setup (WalkerTestCaseFixture* fixture,
+ gconstpointer user_data)
+{
+ GArray *features;
+ GError *error = NULL;
+ GHashTable *params = (GHashTable *) user_data;
+ GValue *boxed_params;
+ const gchar *account_path;
+
+ fixture->main_loop = g_main_loop_new (NULL, FALSE);
+ g_assert (fixture->main_loop != NULL);
+
+ fixture->manager = tpl_log_manager_dup_singleton ();
+
+ fixture->bus = tp_tests_dbus_daemon_dup_or_die ();
+ g_assert (fixture->bus != NULL);
+
+ tp_dbus_daemon_request_name (fixture->bus,
+ TP_ACCOUNT_MANAGER_BUS_NAME,
+ FALSE,
+ &error);
+ g_assert_no_error (error);
+
+ /* Create service-side Account object with the passed parameters */
+ fixture->account_service = g_object_new (TP_TESTS_TYPE_SIMPLE_ACCOUNT,
+ NULL);
+ g_assert (fixture->account_service != NULL);
+
+ /* account-path will be set-up as parameter as well, this is not an issue */
+ account_path = tp_asv_get_string (params, "account-path");
+ g_assert (account_path != NULL);
+
+ boxed_params = tp_g_value_slice_new_boxed (TP_HASH_TYPE_STRING_VARIANT_MAP,
+ params);
+ g_object_set_property (G_OBJECT (fixture->account_service),
+ "parameters",
+ boxed_params);
+ tp_g_value_slice_free (boxed_params);
+
+ tp_dbus_daemon_register_object (fixture->bus,
+ account_path,
+ fixture->account_service);
+
+ fixture->factory = tp_client_factory_new (fixture->bus);
+ g_assert (fixture->factory != NULL);
+
+ fixture->account = tp_client_factory_ensure_account (fixture->factory,
+ tp_asv_get_string (params, "account-path"),
+ params,
+ &error);
+ g_assert_no_error (error);
+ g_assert (fixture->account != NULL);
+
+ features = tp_client_factory_dup_account_features (fixture->factory,
+ fixture->account);
+
+ tp_proxy_prepare_async (fixture->account,
+ (GQuark *) features->data,
+ account_prepare_cb,
+ fixture);
+ g_free (features->data);
+ g_array_free (features, FALSE);
+
+ g_main_loop_run (fixture->main_loop);
+
+ tp_debug_divert_messages (g_getenv ("TPL_LOGFILE"));
+
+#ifdef ENABLE_DEBUG
+ _tpl_debug_set_flags_from_env ();
+#endif /* ENABLE_DEBUG */
+}
+
+
+static void
+teardown (WalkerTestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ GError *error = NULL;
+
+ tp_dbus_daemon_release_name (fixture->bus,
+ TP_ACCOUNT_MANAGER_BUS_NAME,
+ &error);
+ g_assert_no_error (error);
+
+ g_clear_object (&fixture->account);
+ g_clear_object (&fixture->factory);
+
+ tp_dbus_daemon_unregister_object (fixture->bus, fixture->account_service);
+ g_clear_object (&fixture->account_service);
+
+ g_clear_object (&fixture->bus);
+ g_clear_object (&fixture->manager);
+ g_main_loop_unref (fixture->main_loop);
+}
+
+
+static gboolean
+filter_events (TplEvent *event, gpointer user_data)
+{
+ const gchar *message;
+
+ message = tpl_text_event_get_message (TPL_TEXT_EVENT (event));
+ return strstr (message, "'") == NULL;
+}
+
+
+static void
+rewind_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WalkerTestCaseFixture *fixture = user_data;
+ GError *error = NULL;
+
+ tpl_log_walker_rewind_finish (TPL_LOG_WALKER (source),
+ result,
+ &error);
+ g_assert_no_error (error);
+
+ g_main_loop_quit (fixture->main_loop);
+}
+
+
+static void
+rewind (WalkerTestCaseFixture *fixture,
+ TplLogWalker *walker,
+ guint num_events)
+{
+ tpl_log_walker_rewind_async (walker, num_events, rewind_cb, fixture);
+ g_main_loop_run (fixture->main_loop);
+}
+
+
+static void
+get_events_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WalkerTestCaseFixture *fixture = user_data;
+ GError *error = NULL;
+
+ tpl_log_walker_get_events_finish (TPL_LOG_WALKER (source),
+ result,
+ &fixture->events,
+ &error);
+ g_assert_no_error (error);
+
+ g_main_loop_quit (fixture->main_loop);
+}
+
+
+static void
+get_events (WalkerTestCaseFixture *fixture,
+ TplLogWalker *walker,
+ guint num_events)
+{
+ tpl_log_walker_get_events_async (walker, num_events, get_events_cb, fixture);
+ g_main_loop_run (fixture->main_loop);
+}
+
+
+static void
+test_get_events_call (WalkerTestCaseFixture *fixture,
+ TplLogWalker *walker,
+ guint num_events,
+ gint64 timestamp,
+ GTimeSpan duration)
+{
+ GList *events;
+
+ get_events (fixture, walker, num_events);
+
+ events = fixture->events;
+ g_assert (events != NULL);
+ g_assert_cmpuint (g_list_length (events), ==, num_events);
+ g_assert_cmpint (tpl_event_get_timestamp (TPL_EVENT (events->data)),
+ ==,
+ timestamp);
+ g_assert_cmpint (tpl_call_event_get_duration (TPL_CALL_EVENT (events->data)),
+ ==,
+ duration);
+ g_list_free_full (events, g_object_unref);
+}
+
+
+static void
+test_get_events_text (WalkerTestCaseFixture *fixture,
+ TplLogWalker *walker,
+ guint num_events,
+ gint64 timestamp,
+ const gchar *message)
+{
+ GList *events;
+
+ get_events (fixture, walker, num_events);
+
+ events = fixture->events;
+ g_assert (events != NULL);
+ g_assert_cmpuint (g_list_length (events), ==, num_events);
+ g_assert_cmpint (tpl_event_get_timestamp (TPL_EVENT (events->data)),
+ ==,
+ timestamp);
+ g_assert_cmpstr (tpl_text_event_get_message (TPL_TEXT_EVENT (events->data)),
+ ==,
+ message);
+ g_list_free_full (events, g_object_unref);
+}
+
+
+static void
+test_get_events (WalkerTestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ GList *events;
+ TplEntity *user5;
+ TplLogWalker *walker;
+
+ user5 = tpl_entity_new ("user5@collabora.co.uk", TPL_ENTITY_CONTACT,
+ "User5", "");
+
+ /* Both text and call events without a filter */
+ walker = tpl_log_manager_walk_filtered_events (fixture->manager,
+ fixture->account,
+ user5,
+ TPL_EVENT_MASK_ANY,
+ NULL,
+ NULL);
+
+ get_events (fixture, walker, 0);
+ test_get_events_text (fixture, walker, 2, 1263427264, "L''");
+ test_get_events_text (fixture, walker, 5, 1263427262, "J");
+ test_get_events_text (fixture, walker, 1, 1263427261, "I'''");
+ test_get_events_text (fixture, walker, 5, 1263427205, "12");
+ test_get_events_text (fixture, walker, 2, 1263427202, "11'");
+ test_get_events_call (fixture, walker, 4, 1263404881, 1);
+ test_get_events_text (fixture, walker, 4, 1263254401, "5''");
+ test_get_events_text (fixture, walker, 2, 1263254401, "5");
+ get_events (fixture, walker, 0);
+ test_get_events_text (fixture, walker, 3, 1263168066, "H'");
+ test_get_events_text (fixture, walker, 3, 1263168065, "G''");
+ test_get_events_text (fixture, walker, 6, 1263168063, "E");
+ test_get_events_text (fixture, walker, 1, 1263168062, "D''");
+ test_get_events_text (fixture, walker, 2, 1263168062, "D");
+ get_events (fixture, walker, 0);
+ test_get_events_text (fixture, walker, 4, 1263168005, "4");
+ test_get_events_text (fixture, walker, 2, 1263168003, "2");
+ test_get_events_text (fixture, walker, 4, 1263081661, "A");
+
+ tpl_log_walker_get_events_async (walker, 2, get_events_cb, fixture);
+ g_main_loop_run (fixture->main_loop);
+
+ events = fixture->events;
+ g_assert (events == NULL);
+
+ g_object_unref (walker);
+
+ /* Only text events with a filter */
+ walker = tpl_log_manager_walk_filtered_events (fixture->manager,
+ fixture->account,
+ user5,
+ TPL_EVENT_MASK_TEXT,
+ filter_events,
+ NULL);
+
+ get_events (fixture, walker, 0);
+ test_get_events_text (fixture, walker, 2, 1263427263, "K");
+ test_get_events_text (fixture, walker, 5, 1263427202, "11");
+ test_get_events_text (fixture, walker, 1, 1263427201, "10");
+ test_get_events_text (fixture, walker, 5, 1263254401, "5");
+ test_get_events_text (fixture, walker, 2, 1263168065, "G");
+ test_get_events_text (fixture, walker, 4, 1263168061, "C");
+ test_get_events_text (fixture, walker, 2, 1263168004, "3");
+ get_events (fixture, walker, 0);
+ test_get_events_text (fixture, walker, 3, 1263168001, "0");
+ test_get_events_text (fixture, walker, 2, 1263081661, "A");
+
+ tpl_log_walker_get_events_async (walker, 2, get_events_cb, fixture);
+ g_main_loop_run (fixture->main_loop);
+
+ events = fixture->events;
+ g_assert (events == NULL);
+
+ g_object_unref (walker);
+ g_object_unref (user5);
+}
+
+
+static void
+test_rewind (WalkerTestCaseFixture *fixture,
+ gconstpointer user_data)
+{
+ TplEntity *user5;
+ TplLogWalker *walker;
+
+ user5 = tpl_entity_new ("user5@collabora.co.uk", TPL_ENTITY_CONTACT,
+ "User5", "");
+
+ /* Both text and call events without a filter */
+ walker = tpl_log_manager_walk_filtered_events (fixture->manager,
+ fixture->account,
+ user5,
+ TPL_EVENT_MASK_ANY,
+ NULL,
+ NULL);
+
+ rewind (fixture, walker, 8);
+ get_events (fixture, walker, 0);
+ rewind (fixture, walker, 8);
+ get_events (fixture, walker, 2);
+ rewind (fixture, walker, 8);
+ test_get_events_text (fixture, walker, 8, 1263427261, "I'''");
+ rewind (fixture, walker, 3);
+ test_get_events_text (fixture, walker, 5, 1263427261, "I'");
+ rewind (fixture, walker, 1);
+ test_get_events_text (fixture, walker, 7, 1263427202, "11");
+ rewind (fixture, walker, 2);
+ test_get_events_call (fixture, walker, 5, 1263404881, 1);
+ rewind (fixture, walker, 2);
+ get_events (fixture, walker, 0);
+ test_get_events_text (fixture, walker, 1, 1263404950, "9");
+ rewind (fixture, walker, 0);
+ test_get_events_text (fixture, walker, 5, 1263254401, "5''");
+ rewind (fixture, walker, 1);
+ test_get_events_text (fixture, walker, 8, 1263168065, "G'''");
+ rewind (fixture, walker, 7);
+ test_get_events_text (fixture, walker, 7, 1263168065, "G'''");
+ test_get_events_text (fixture, walker, 7, 1263168063, "E");
+ rewind (fixture, walker, 2);
+ test_get_events_text (fixture, walker, 6, 1263168061, "C");
+ rewind (fixture, walker, 10);
+ rewind (fixture, walker, 0);
+ rewind (fixture, walker, 5);
+ test_get_events_text (fixture, walker, 16, 1263168005, "4''");
+ rewind (fixture, walker, 3);
+ test_get_events_text (fixture, walker, 6, 1263168004, "3");
+ rewind (fixture, walker, 1);
+ test_get_events_text (fixture, walker, 6, 1263081661, "A");
+
+ tpl_log_walker_get_events_async (walker, 2, get_events_cb, fixture);
+ g_main_loop_run (fixture->main_loop);
+ g_assert (fixture->events == NULL);
+
+ g_object_unref (walker);
+
+ /* Only text events with a filter */
+ walker = tpl_log_manager_walk_filtered_events (fixture->manager,
+ fixture->account,
+ user5,
+ TPL_EVENT_MASK_TEXT,
+ filter_events,
+ NULL);
+
+ rewind (fixture, walker, 8);
+ get_events (fixture, walker, 0);
+ rewind (fixture, walker, 8);
+ get_events (fixture, walker, 2);
+ rewind (fixture, walker, 8);
+ test_get_events_text (fixture, walker, 8, 1263427201, "10");
+ rewind (fixture, walker, 3);
+ test_get_events_text (fixture, walker, 5, 1263254406, "8");
+ rewind (fixture, walker, 1);
+ test_get_events_text (fixture, walker, 7, 1263168064, "F");
+ rewind (fixture, walker, 2);
+ test_get_events_text (fixture, walker, 5, 1263168061, "C");
+ rewind (fixture, walker, 2);
+ get_events (fixture, walker, 0);
+ test_get_events_text (fixture, walker, 1, 1263168062, "D");
+ rewind (fixture, walker, 0);
+ test_get_events_text (fixture, walker, 5, 1263168002, "1");
+ rewind (fixture, walker, 1);
+ test_get_events_text (fixture, walker, 4, 1263081661, "A");
+
+ tpl_log_walker_get_events_async (walker, 2, get_events_cb, fixture);
+ g_main_loop_run (fixture->main_loop);
+ g_assert (fixture->events == NULL);
+
+ g_object_unref (walker);
+ g_object_unref (user5);
+}
+
+
+gint main (gint argc, gchar **argv)
+{
+ GHashTable *params;
+ gint retval;
+
+ g_type_init ();
+
+ g_test_init (&argc, &argv, NULL);
+ g_test_bug_base ("http://bugs.freedesktop.org/show_bug.cgi?id=");
+
+ params = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
+ (GDestroyNotify) tp_g_value_slice_free);
+ g_assert (params != NULL);
+
+ g_hash_table_insert (params, "account",
+ tp_g_value_slice_new_static_string ("user@collabora.co.uk"));
+ g_hash_table_insert (params, "account-path",
+ tp_g_value_slice_new_static_string (
+ TP_ACCOUNT_OBJECT_PATH_BASE
+ "gabble/jabber/user_40collabora_2eco_2euk"));
+
+ g_test_add ("/log-walker/get-events",
+ WalkerTestCaseFixture, params,
+ setup, test_get_events, teardown);
+
+ g_test_add ("/log-walker/rewind",
+ WalkerTestCaseFixture, params,
+ setup, test_rewind, teardown);
+
+ retval = g_test_run ();
+
+ g_hash_table_unref (params);
+
+ return retval;
+}
diff --git a/tests/logger/dbus/test-tpl-observer.c b/tests/logger/dbus/test-tpl-observer.c
new file mode 100644
index 000000000..b1c687dce
--- /dev/null
+++ b/tests/logger/dbus/test-tpl-observer.c
@@ -0,0 +1,35 @@
+#include "config.h"
+
+#include <telepathy-logger/observer-internal.h>
+
+int
+main (int argc, char **argv)
+{
+ TplObserver *obs, *obs2;
+
+ g_type_init ();
+
+ obs = _tpl_observer_dup (NULL);
+
+ /* TplObserver is a singleton, be sure both references point to the same
+ * memory address */
+ obs2 = _tpl_observer_dup (NULL);
+ g_assert (obs == obs2);
+
+ /* unref the second singleton pointer and check that the it is still
+ * valid: checking correct object ref-counting after each _dup () call */
+ g_object_unref (obs2);
+ g_assert (TPL_IS_OBSERVER (obs));
+
+ /* it points to the same mem area, it should be still valid */
+ g_assert (TPL_IS_OBSERVER (obs2));
+
+
+ /* FIXME: This test does not actually test anything useful */
+
+ /* proper disposal for the singleton when no references are present */
+ g_object_unref (obs);
+
+ return 0;
+}
+
diff --git a/tests/logger/logs/Empathy/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100113.log b/tests/logger/logs/Empathy/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100113.log
new file mode 100644
index 000000000..ea42828e2
--- /dev/null
+++ b/tests/logger/logs/Empathy/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100113.log
@@ -0,0 +1,10 @@
+<?xml version='1.0' encoding='utf-8'?>
+<?xml-stylesheet type="text/xsl" href="empathy-log.xsl"?>
+<log>
+<message time='20100113T17:47:57' cm_id='123' id='user@collabora.co.uk' name='User1' token='' isuser='true' type='normal'>1</message>
+<message time='20100113T17:48:01' cm_id='123' id='user@collabora.co.uk' name='User1' token='' isuser='true' type='normal'>2</message>
+<message time='20100113T17:49:10' cm_id='123' id='user2@collabora.co.uk' name='User2' token='' isuser='false' type='normal'>3</message>
+<message time='20100113T17:51:55' cm_id='123' id='user@collabora.co.uk' name='User1' token='' isuser='true' type='normal'>4</message>
+<message time='20100113T17:52:58' cm_id='1263405178' id='user@collabora.co.uk' name='User1' token='' isuser='true' type='normal'>5</message>
+<message time='20100113T17:53:23' cm_id='1263405203' id='user2@collabora.co.uk' name='User2' token='' isuser='false' type='normal'>6</message>
+</log>
diff --git a/tests/logger/logs/Empathy/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100208.log b/tests/logger/logs/Empathy/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100208.log
new file mode 100644
index 000000000..c85340b07
--- /dev/null
+++ b/tests/logger/logs/Empathy/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100208.log
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='utf-8'?>
+<?xml-stylesheet type="text/xsl" href="empathy-log.xsl"?>
+<log>
+<message time='20100208T11:58:54' cm_id='1265630334' id='user@collabora.co.uk' name='User1' token='' isuser='true' type='normal'>1</message>
+</log>
diff --git a/tests/logger/logs/Empathy/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100216.log b/tests/logger/logs/Empathy/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100216.log
new file mode 100644
index 000000000..2aa9ee2f9
--- /dev/null
+++ b/tests/logger/logs/Empathy/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100216.log
@@ -0,0 +1,14 @@
+<?xml version='1.0' encoding='utf-8'?>
+<?xml-stylesheet type="text/xsl" href="empathy-log.xsl"?>
+<log>
+<message time='20100216T13:43:12' cm_id='0' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='false' type='normal'>fooooo</message>
+<message time='20100216T13:43:24' cm_id='0' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='false' type='normal'>123123</message>
+<message time='20100216T13:43:31' cm_id='0' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='false' type='normal'>123</message>
+<message time='20100216T13:45:44' cm_id='0' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='false' type='normal'>gna2</message>
+<message time='20100216T14:10:59' cm_id='0' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='false' type='normal'>prova 3</message>
+<message time='20100216T14:13:48' cm_id='0' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='false' type='normal'>123</message>
+<message time='20100216T15:51:28' cm_id='1266335488' id='user@collabora.co.uk' name='User1' token='b42b37774fe34e9891ab1ecd1f187062b7135e6a' isuser='true' type='normal'>1</message>
+<message time='20100216T15:52:36' cm_id='0' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='false' type='normal'>1</message>
+<message time='20100216T15:56:43' cm_id='1266335803' id='user@collabora.co.uk' name='User1' token='b42b37774fe34e9891ab1ecd1f187062b7135e6a' isuser='true' type='normal'>a</message>
+<message time='20100216T15:57:30' cm_id='0' id='user2@collabora.co.uk' name='User2' token='' isuser='false' type='normal'>bar</message>
+</log>
diff --git a/tests/logger/logs/Empathy/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100217.log b/tests/logger/logs/Empathy/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100217.log
new file mode 100644
index 000000000..46804f6d1
--- /dev/null
+++ b/tests/logger/logs/Empathy/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100217.log
@@ -0,0 +1,19 @@
+<?xml version='1.0' encoding='utf-8'?>
+<?xml-stylesheet type="text/xsl" href="empathy-log.xsl"?>
+<log>
+<message time='20100217T13:47:31' cm_id='1266414451' id='user@collabora.co.uk' name='User1' token='b42b37774fe34e9891ab1ecd1f187062b7135e6a' isuser='true' type='normal'>1</message>
+<message time='20100217T15:49:41' cm_id='0' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='true' type='normal'>123</message>
+<message time='20100217T15:49:57' cm_id='1266421797' id='user@collabora.co.uk' name='User1' token='b42b37774fe34e9891ab1ecd1f187062b7135e6a' isuser='true' type='normal'>321</message>
+<message time='20100217T16:52:45' cm_id='0' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='true' type='normal'>1</message>
+<message time='20100217T16:52:46' cm_id='1' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='true' type='normal'>2</message>
+<message time='20100217T16:52:46' cm_id='2' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='true' type='normal'>3</message>
+<message time='20100217T16:52:46' cm_id='3' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='true' type='normal'>4</message>
+<message time='20100217T16:52:46' cm_id='4' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='true' type='normal'>5</message>
+<message time='20100217T16:52:51' cm_id='1266425571' id='user@collabora.co.uk' name='User1' token='b42b37774fe34e9891ab1ecd1f187062b7135e6a' isuser='true' type='normal'>1</message>
+<message time='20100217T16:52:52' cm_id='1266425572' id='user@collabora.co.uk' name='User1' token='b42b37774fe34e9891ab1ecd1f187062b7135e6a' isuser='true' type='normal'>3</message>
+<message time='20100217T16:52:46' cm_id='1' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='true' type='normal'>2</message>
+<message time='20100217T16:52:46' cm_id='2' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='true' type='normal'>3</message>
+<message time='20100217T16:52:46' cm_id='3' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='true' type='normal'>4</message>
+<message time='20100217T16:52:46' cm_id='4' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='true' type='normal'>5</message>
+<message time='20100217T16:53:06' cm_id='0' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='true' type='normal'>3</message>
+</log>
diff --git a/tests/logger/logs/Empathy/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100218.log b/tests/logger/logs/Empathy/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100218.log
new file mode 100644
index 000000000..6cdb6276c
--- /dev/null
+++ b/tests/logger/logs/Empathy/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100218.log
@@ -0,0 +1,6 @@
+<?xml version='1.0' encoding='utf-8'?>
+<?xml-stylesheet type="text/xsl" href="empathy-log.xsl"?>
+<log>
+<message time='20100218T14:32:14' cm_id='0' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='false' type='normal'>now</message>
+<message time='20100218T14:35:11' cm_id='0' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='false' type='normal'>1</message>
+</log>
diff --git a/tests/logger/logs/TpLogger/logs/gabble_jabber_test2_40collabora_2eco_2euk0/chatrooms/meego@conference.collabora.co.uk/20110112.log b/tests/logger/logs/TpLogger/logs/gabble_jabber_test2_40collabora_2eco_2euk0/chatrooms/meego@conference.collabora.co.uk/20110112.log
new file mode 100644
index 000000000..b4b2a3be8
--- /dev/null
+++ b/tests/logger/logs/TpLogger/logs/gabble_jabber_test2_40collabora_2eco_2euk0/chatrooms/meego@conference.collabora.co.uk/20110112.log
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='utf-8'?>
+<?xml-stylesheet type="text/xsl" href="log-store-xml.xsl"?>
+<log>
+<message time='20110112T22:11:04' cm_id='8957fb4064049e7a1f9d8f84234d3bf09fb6778c' id='meego@conference.collabora.co.uk/test2@collabora.co.uk' name='test2@collabora.co.uk' token='' isuser='true' type='normal'>test</message>
+</log>
diff --git a/tests/logger/logs/TpLogger/logs/gabble_jabber_test2_40collabora_2eco_2euk0/derek.foreman@collabora.co.uk/20110210.log b/tests/logger/logs/TpLogger/logs/gabble_jabber_test2_40collabora_2eco_2euk0/derek.foreman@collabora.co.uk/20110210.log
new file mode 100644
index 000000000..aa63a5848
--- /dev/null
+++ b/tests/logger/logs/TpLogger/logs/gabble_jabber_test2_40collabora_2eco_2euk0/derek.foreman@collabora.co.uk/20110210.log
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='utf-8'?>
+<?xml-stylesheet type="text/xsl" href="log-store-xml.xsl"?>
+<log>
+<message time='20110210T11:21:01' cm_id='f95e605a3ae97c463b626a3538567bc90fc58730' id='test2@test.collabora.co.uk' name='test2@test.collabora.co.uk' token='' isuser='true' type='normal'>Hey, Just generating logs, don&apos;t bother replying ;)</message>
+</log>
diff --git a/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100113.log b/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100113.log
new file mode 100644
index 000000000..ea42828e2
--- /dev/null
+++ b/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100113.log
@@ -0,0 +1,10 @@
+<?xml version='1.0' encoding='utf-8'?>
+<?xml-stylesheet type="text/xsl" href="empathy-log.xsl"?>
+<log>
+<message time='20100113T17:47:57' cm_id='123' id='user@collabora.co.uk' name='User1' token='' isuser='true' type='normal'>1</message>
+<message time='20100113T17:48:01' cm_id='123' id='user@collabora.co.uk' name='User1' token='' isuser='true' type='normal'>2</message>
+<message time='20100113T17:49:10' cm_id='123' id='user2@collabora.co.uk' name='User2' token='' isuser='false' type='normal'>3</message>
+<message time='20100113T17:51:55' cm_id='123' id='user@collabora.co.uk' name='User1' token='' isuser='true' type='normal'>4</message>
+<message time='20100113T17:52:58' cm_id='1263405178' id='user@collabora.co.uk' name='User1' token='' isuser='true' type='normal'>5</message>
+<message time='20100113T17:53:23' cm_id='1263405203' id='user2@collabora.co.uk' name='User2' token='' isuser='false' type='normal'>6</message>
+</log>
diff --git a/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100208.log b/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100208.log
new file mode 100644
index 000000000..c85340b07
--- /dev/null
+++ b/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100208.log
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='utf-8'?>
+<?xml-stylesheet type="text/xsl" href="empathy-log.xsl"?>
+<log>
+<message time='20100208T11:58:54' cm_id='1265630334' id='user@collabora.co.uk' name='User1' token='' isuser='true' type='normal'>1</message>
+</log>
diff --git a/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100216.log b/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100216.log
new file mode 100644
index 000000000..2aa9ee2f9
--- /dev/null
+++ b/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100216.log
@@ -0,0 +1,14 @@
+<?xml version='1.0' encoding='utf-8'?>
+<?xml-stylesheet type="text/xsl" href="empathy-log.xsl"?>
+<log>
+<message time='20100216T13:43:12' cm_id='0' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='false' type='normal'>fooooo</message>
+<message time='20100216T13:43:24' cm_id='0' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='false' type='normal'>123123</message>
+<message time='20100216T13:43:31' cm_id='0' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='false' type='normal'>123</message>
+<message time='20100216T13:45:44' cm_id='0' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='false' type='normal'>gna2</message>
+<message time='20100216T14:10:59' cm_id='0' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='false' type='normal'>prova 3</message>
+<message time='20100216T14:13:48' cm_id='0' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='false' type='normal'>123</message>
+<message time='20100216T15:51:28' cm_id='1266335488' id='user@collabora.co.uk' name='User1' token='b42b37774fe34e9891ab1ecd1f187062b7135e6a' isuser='true' type='normal'>1</message>
+<message time='20100216T15:52:36' cm_id='0' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='false' type='normal'>1</message>
+<message time='20100216T15:56:43' cm_id='1266335803' id='user@collabora.co.uk' name='User1' token='b42b37774fe34e9891ab1ecd1f187062b7135e6a' isuser='true' type='normal'>a</message>
+<message time='20100216T15:57:30' cm_id='0' id='user2@collabora.co.uk' name='User2' token='' isuser='false' type='normal'>bar</message>
+</log>
diff --git a/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100217.log b/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100217.log
new file mode 100644
index 000000000..46804f6d1
--- /dev/null
+++ b/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100217.log
@@ -0,0 +1,19 @@
+<?xml version='1.0' encoding='utf-8'?>
+<?xml-stylesheet type="text/xsl" href="empathy-log.xsl"?>
+<log>
+<message time='20100217T13:47:31' cm_id='1266414451' id='user@collabora.co.uk' name='User1' token='b42b37774fe34e9891ab1ecd1f187062b7135e6a' isuser='true' type='normal'>1</message>
+<message time='20100217T15:49:41' cm_id='0' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='true' type='normal'>123</message>
+<message time='20100217T15:49:57' cm_id='1266421797' id='user@collabora.co.uk' name='User1' token='b42b37774fe34e9891ab1ecd1f187062b7135e6a' isuser='true' type='normal'>321</message>
+<message time='20100217T16:52:45' cm_id='0' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='true' type='normal'>1</message>
+<message time='20100217T16:52:46' cm_id='1' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='true' type='normal'>2</message>
+<message time='20100217T16:52:46' cm_id='2' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='true' type='normal'>3</message>
+<message time='20100217T16:52:46' cm_id='3' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='true' type='normal'>4</message>
+<message time='20100217T16:52:46' cm_id='4' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='true' type='normal'>5</message>
+<message time='20100217T16:52:51' cm_id='1266425571' id='user@collabora.co.uk' name='User1' token='b42b37774fe34e9891ab1ecd1f187062b7135e6a' isuser='true' type='normal'>1</message>
+<message time='20100217T16:52:52' cm_id='1266425572' id='user@collabora.co.uk' name='User1' token='b42b37774fe34e9891ab1ecd1f187062b7135e6a' isuser='true' type='normal'>3</message>
+<message time='20100217T16:52:46' cm_id='1' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='true' type='normal'>2</message>
+<message time='20100217T16:52:46' cm_id='2' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='true' type='normal'>3</message>
+<message time='20100217T16:52:46' cm_id='3' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='true' type='normal'>4</message>
+<message time='20100217T16:52:46' cm_id='4' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='true' type='normal'>5</message>
+<message time='20100217T16:53:06' cm_id='0' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='true' type='normal'>3</message>
+</log>
diff --git a/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100218.log b/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100218.log
new file mode 100644
index 000000000..6cdb6276c
--- /dev/null
+++ b/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user2@collabora.co.uk/20100218.log
@@ -0,0 +1,6 @@
+<?xml version='1.0' encoding='utf-8'?>
+<?xml-stylesheet type="text/xsl" href="empathy-log.xsl"?>
+<log>
+<message time='20100218T14:32:14' cm_id='0' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='false' type='normal'>now</message>
+<message time='20100218T14:35:11' cm_id='0' id='user2@collabora.co.uk' name='User2' token='863d261bd11170b87b67ccb9c7ec90a5bd17e990' isuser='false' type='normal'>1</message>
+</log>
diff --git a/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user3@collabora.co.uk/20100113.call.log b/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user3@collabora.co.uk/20100113.call.log
new file mode 100644
index 000000000..7ab442ad5
--- /dev/null
+++ b/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user3@collabora.co.uk/20100113.call.log
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='utf-8'?>
+<?xml-stylesheet type="text/xsl" href="empathy-log.xsl"?>
+<log>
+<call time='20100113T23:11:15' id='user@collabora.co.uk' name='Nicolas1' isuser='false' token='' duration='12' actor='user@collabora.co.uk' actortype='contact' actorname='User1' actortoken='' reason='user-requested' detail='im.telepathy1.Error.Cancelled' />
+</log>
diff --git a/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user4@collabora.co.uk/20100113.call.log b/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user4@collabora.co.uk/20100113.call.log
new file mode 100644
index 000000000..dfb79cba6
--- /dev/null
+++ b/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user4@collabora.co.uk/20100113.call.log
@@ -0,0 +1,8 @@
+<?xml version='1.0' encoding='utf-8'?>
+<?xml-stylesheet type="text/xsl" href="empathy-log.xsl"?>
+<log>
+<call time='20100113T17:48:01' id='user@collabora.co.uk' name='Nicolas1' isuser='false' token='' duration='1' actor='user@collabora.co.uk' actortype='contact' actorname='User1' actortoken='' reason='user-requested' detail='im.telepathy1.Error.Cancelled' />
+<call time='20100113T17:48:01' id='user@collabora.co.uk' name='Nicolas1' isuser='false' token='' duration='2' actor='user@collabora.co.uk' actortype='contact' actorname='User1' actortoken='' reason='user-requested' detail='im.telepathy1.Error.Cancelled' />
+<!-- use the old Telepathy namespace for 'detail' to test backward compatibility -->
+<call time='20100113T17:48:01' id='user@collabora.co.uk' name='Nicolas1' isuser='false' token='' duration='3' actor='user@collabora.co.uk' actortype='contact' actorname='User1' actortoken='' reason='user-requested' detail='org.freedesktop.Telepathy.Error.Cancelled' />
+</log>
diff --git a/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user4@collabora.co.uk/20100113.log b/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user4@collabora.co.uk/20100113.log
new file mode 100644
index 000000000..f3cbd008c
--- /dev/null
+++ b/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user4@collabora.co.uk/20100113.log
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8'?>
+<?xml-stylesheet type="text/xsl" href="empathy-log.xsl"?>
+<log>
+<message time='20100113T17:47:57' cm_id='234' id='user3@collabora.co.uk' name='User3' token='' isuser='true' type='normal'>7</message>
+<message time='20100113T17:48:01' cm_id='345' id='user3@collabora.co.uk' name='User3' token='' isuser='true' type='normal'>8</message>
+<message time='20100113T17:49:10' cm_id='456' id='user1@collabora.co.uk' name='User1' token='' isuser='false' type='normal'>9</message>
+</log>
diff --git a/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user5@collabora.co.uk/20100111.log b/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user5@collabora.co.uk/20100111.log
new file mode 100644
index 000000000..1d3af7e45
--- /dev/null
+++ b/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user5@collabora.co.uk/20100111.log
@@ -0,0 +1,11 @@
+<?xml version='1.0' encoding='utf-8'?>
+<?xml-stylesheet type="text/xsl" href="empathy-log.xsl"?>
+<log>
+<message time='20100111T00:00:01' cm_id='456' id='user1@collabora.co.uk' name='User1' token='' isuser='false' type='normal'>0</message>
+<message time='20100111T00:00:02' cm_id='456' id='user1@collabora.co.uk' name='User1' token='' isuser='false' type='normal'>1</message>
+<message time='20100111T00:00:03' cm_id='456' id='user1@collabora.co.uk' name='User1' token='' isuser='false' type='normal'>2</message>
+<message time='20100111T00:00:04' cm_id='456' id='user1@collabora.co.uk' name='User1' token='' isuser='false' type='normal'>3</message>
+<message time='20100111T00:00:05' cm_id='456' id='user1@collabora.co.uk' name='User1' token='' isuser='false' type='normal'>4</message>
+<message time='20100111T00:00:05' cm_id='456' id='user1@collabora.co.uk' name='User1' token='' isuser='false' type='normal'>4'</message>
+<message time='20100111T00:00:05' cm_id='456' id='user1@collabora.co.uk' name='User1' token='' isuser='false' type='normal'>4''</message>
+</log>
diff --git a/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user5@collabora.co.uk/20100112.log b/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user5@collabora.co.uk/20100112.log
new file mode 100644
index 000000000..3f3a495e0
--- /dev/null
+++ b/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user5@collabora.co.uk/20100112.log
@@ -0,0 +1,10 @@
+<?xml version='1.0' encoding='utf-8'?>
+<?xml-stylesheet type="text/xsl" href="empathy-log.xsl"?>
+<log>
+<message time='20100112T00:00:01' cm_id='456' id='user1@collabora.co.uk' name='User1' token='' isuser='false' type='normal'>5</message>
+<message time='20100112T00:00:01' cm_id='456' id='user1@collabora.co.uk' name='User1' token='' isuser='false' type='normal'>5'</message>
+<message time='20100112T00:00:01' cm_id='456' id='user1@collabora.co.uk' name='User1' token='' isuser='false' type='normal'>5''</message>
+<message time='20100112T00:00:04' cm_id='456' id='user1@collabora.co.uk' name='User1' token='' isuser='false' type='normal'>6</message>
+<message time='20100112T00:00:05' cm_id='456' id='user1@collabora.co.uk' name='User1' token='' isuser='false' type='normal'>7</message>
+<message time='20100112T00:00:06' cm_id='456' id='user1@collabora.co.uk' name='User1' token='' isuser='false' type='normal'>8</message>
+</log>
diff --git a/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user5@collabora.co.uk/20100113.call.log b/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user5@collabora.co.uk/20100113.call.log
new file mode 100644
index 000000000..34e5e6aa0
--- /dev/null
+++ b/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user5@collabora.co.uk/20100113.call.log
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='utf-8'?>
+<?xml-stylesheet type="text/xsl" href="empathy-log.xsl"?>
+<log>
+<call time='20100113T17:48:01' id='user@collabora.co.uk' name='Nicolas1' isuser='false' token='' duration='1' actor='user@collabora.co.uk' actortype='contact' actorname='User1' actortoken='' reason='user-requested' detail='im.telepathy1.Error.Cancelled' />
+</log>
diff --git a/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user5@collabora.co.uk/20100113.log b/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user5@collabora.co.uk/20100113.log
new file mode 100644
index 000000000..23d6859c0
--- /dev/null
+++ b/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user5@collabora.co.uk/20100113.log
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='utf-8'?>
+<?xml-stylesheet type="text/xsl" href="empathy-log.xsl"?>
+<log>
+<message time='20100113T17:49:10' cm_id='456' id='user1@collabora.co.uk' name='User1' token='' isuser='false' type='normal'>9</message>
+</log>
diff --git a/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user5@collabora.co.uk/20100114.log b/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user5@collabora.co.uk/20100114.log
new file mode 100644
index 000000000..0ee06a3f8
--- /dev/null
+++ b/tests/logger/logs/TpLogger/logs/gabble_jabber_user_40collabora_2eco_2euk/user5@collabora.co.uk/20100114.log
@@ -0,0 +1,10 @@
+<?xml version='1.0' encoding='utf-8'?>
+<?xml-stylesheet type="text/xsl" href="empathy-log.xsl"?>
+<log>
+<message time='20100114T00:00:01' cm_id='456' id='user1@collabora.co.uk' name='User1' token='' isuser='false' type='normal'>10</message>
+<message time='20100114T00:00:02' cm_id='456' id='user1@collabora.co.uk' name='User1' token='' isuser='false' type='normal'>11</message>
+<message time='20100114T00:00:02' cm_id='456' id='user1@collabora.co.uk' name='User1' token='' isuser='false' type='normal'>11'</message>
+<message time='20100114T00:00:02' cm_id='456' id='user1@collabora.co.uk' name='User1' token='' isuser='false' type='normal'>11''</message>
+<message time='20100114T00:00:05' cm_id='456' id='user1@collabora.co.uk' name='User1' token='' isuser='false' type='normal'>12</message>
+<message time='20100114T00:00:06' cm_id='456' id='user1@collabora.co.uk' name='User1' token='' isuser='false' type='normal'>13</message>
+</log>
diff --git a/tests/logger/logs/purple/bonjour/user@host/user2@host2/2010-04-29.140346+0100BST.html b/tests/logger/logs/purple/bonjour/user@host/user2@host2/2010-04-29.140346+0100BST.html
new file mode 100644
index 000000000..1d8f02930
--- /dev/null
+++ b/tests/logger/logs/purple/bonjour/user@host/user2@host2/2010-04-29.140346+0100BST.html
@@ -0,0 +1,6 @@
+<html><head><meta http-equiv="content-type" content="text/html; charset=UTF-8"><title>Conversation</title></head><body><h3>Conversation (bonjour)</h3>
+<font color="#16569E"><font size="2">(14:03:48)</font> <b>user2:</b></font> 1<br/>
+<font color="#A82F2F"><font size="2">(14:03:54)</font> <b>user1:</b></font> 2<br/>
+<font color="#16569E"><font size="2">(14:03:56)</font> <b>user2:</b></font> 3<br/>
+<font color="#A82F2F"><font size="2">(14:03:58)</font> <b>user1:</b></font> 4<br/>
+</body></html>
diff --git a/tests/logger/logs/purple/icq/12345678/87654321/2010-02-06.130032+0000GMT.html b/tests/logger/logs/purple/icq/12345678/87654321/2010-02-06.130032+0000GMT.html
new file mode 100644
index 000000000..8e87f427d
--- /dev/null
+++ b/tests/logger/logs/purple/icq/12345678/87654321/2010-02-06.130032+0000GMT.html
Binary files differ
diff --git a/tests/logger/logs/purple/icq/12345678/87654321/2010-02-07.130033+0000GMT.html b/tests/logger/logs/purple/icq/12345678/87654321/2010-02-07.130033+0000GMT.html
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/logger/logs/purple/icq/12345678/87654321/2010-02-07.130033+0000GMT.html
diff --git a/tests/logger/logs/purple/icq/12345678/87654321/2010-02-08.130034+0000GMT.html b/tests/logger/logs/purple/icq/12345678/87654321/2010-02-08.130034+0000GMT.html
new file mode 100644
index 000000000..90ee742c6
--- /dev/null
+++ b/tests/logger/logs/purple/icq/12345678/87654321/2010-02-08.130034+0000GMT.html
@@ -0,0 +1,8 @@
+<html><head><meta http-equiv="content-type" content="text/html; charset=UTF-8"><title>Conversation with 87654321 at Mon 08 Feb 2010 13:00:34 GMT on 12345678 (icq)</title></head><body><h3>Conversation with 87654321 at Mon 08 Feb 2010 13:00:34 GMT on 12345678 (icq)</h3>
+<font color="#A82F2F"><font size="2">(13:00:34)</font> <b>87654321:</b></font> oi<br/>
+<font color="#16569E"><font size="2">(13:00:49)</font> <b>12345678:</b></font> oi<br/>
+<font color="#16569E"><font size="2">(13:00:56)</font> <b>12345678:</b></font> bla bla?<br/>
+<font color="#A82F2F"><font size="2">(13:01:19)</font> <b>87654321:</b></font> bla bla bla!<br/>
+<font size="2">(13:03:34)</font><b> 87654321 is now known as TheUser2
+</b><br/>
+</body></html>
diff --git a/tests/logger/logs/purple/icq/12345678/87654321/2010-02-08.134023+0000GMT.html b/tests/logger/logs/purple/icq/12345678/87654321/2010-02-08.134023+0000GMT.html
new file mode 100644
index 000000000..b81f991a8
--- /dev/null
+++ b/tests/logger/logs/purple/icq/12345678/87654321/2010-02-08.134023+0000GMT.html
@@ -0,0 +1,12 @@
+<html><head><meta http-equiv="content-type" content="text/html; charset=UTF-8"><title>Conversation with 87654321 at Mon 08 Feb 2010 13:40:23 GMT on 12345678 (icq)</title></head><body><h3>Conversation with 87654321 at Mon 08 Feb 2010 13:40:23 GMT on 12345678 (icq)</h3>
+<font color="#A82F2F"><font size="2">(13:40:23)</font> <b>87654321:</b></font> oi<br/>
+<font size="2">(13:40:37)</font><b> 87654321 is now known as TheUser2.
+</b><br/>
+<font color="#A82F2F"><font size="2">(13:40:43)</font> <b>TheUser2:</b></font> gna<br/>
+<font color="#16569E"><font size="2">(13:40:49)</font> <b>12345678:</b></font> gnagnagna<br/>
+<font color="#A82F2F"><font size="2">(13:41:08)</font> <b>TheUser2:</b></font> just gnagna<br/>
+<font color="#16569E"><font size="2">(13:41:35)</font> <b>12345678:</b></font> ok sorry<br/>
+<font color="#16569E"><font size="2">(13:41:48)</font> <b>12345678:</b></font> gnagna<br/>
+<font color="#A82F2F"><font size="2">(13:41:48)</font> <b>TheUser2:</b></font> np<br/>
+<font color="#16569E"><font size="2">(13:41:53)</font> <b>12345678:</b></font> so, gnagna?<br/>
+</body></html>
diff --git a/tests/logger/logs/purple/irc/user@irc.freenode.net/#telepathy.chat/2010-11-30.124947+0000GMT.html b/tests/logger/logs/purple/irc/user@irc.freenode.net/#telepathy.chat/2010-11-30.124947+0000GMT.html
new file mode 100644
index 000000000..f024a0c8a
--- /dev/null
+++ b/tests/logger/logs/purple/irc/user@irc.freenode.net/#telepathy.chat/2010-11-30.124947+0000GMT.html
@@ -0,0 +1,178 @@
+<html><head><meta http-equiv="content-type" content="text/html; charset=UTF-8"><title>Conversation with #telepathy at Tue 30 Nov 2010 12:49:47 GMT on KA__@irc.freenode.net (irc)</title></head><body><h3>Conversation with #telepathy at Tue 30 Nov 2010 12:49:47 GMT on KA__@irc.freenode.net (irc)</h3>
+<font size="2">(12:49:48)</font><b> The topic for #telepathy is: ICQ trouble? <a href="http://is.gd/hmEtM">http://is.gd/hmEtM</a> | Visit #empathy on GIMPnet for Empathy-specific discussion | Telepathy Real-time Communications Framework: <a href="http://telepathy.freedesktop.org">http://telepathy.freedesktop.org</a> | <a href="http://telepathy.freedesktop.org/wiki/FAQ">http://telepathy.freedesktop.org/wiki/FAQ</a> | <a href="http://git.collabora.co.uk/">http://git.collabora.co.uk/</a></b><br/>
+<font size="2">(12:56:01)</font><b> MattJ [<em>~MattJ@91.85.174.192</em>] entered the room.</b><br/>
+<font size="2">(13:10:27)</font><b> johanbr [<em>~j@142.12.7.28</em>] entered the room.</b><br/>
+<font size="2">(13:16:13)</font><b> seiflotfy_ [<em>~seiflotfy@ip-95-223-14-124.unitymediagroup.de</em>] entered the room.</b><br/>
+<font size="2">(13:16:16)</font><b> seiflotfy left the room (quit: Read error: Connection reset by peer).</b><br/>
+<font color="#A82F2F"><font size="2">(13:17:58)</font> <b>smcv:</b></font> those who like contact lists: https://bugs.freedesktop.org/show_bug.cgi?id=31997<br/>
+<font size="2">(13:18:39)</font><b> shiyee left the room (quit: Quit: Ex-Chat).</b><br/>
+<font color="#A82F2F"><font size="2">(13:22:05)</font> <b>smcv:</b></font> and relatedly https://bugs.freedesktop.org/show_bug.cgi?id=31998<br/>
+<font size="2">(13:25:18)</font><b> seb128 left the room (quit: Quit: Ex-Chat).</b><br/>
+<font size="2">(13:25:34)</font><b> seb128 [<em>~seb128@ANancy-258-1-45-93.w90-39.abo.wanadoo.fr</em>] entered the room.</b><br/>
+<font size="2">(13:25:34)</font><b> seb128 left the room (quit: Changing host).</b><br/>
+<font size="2">(13:25:34)</font><b> seb128 [<em>~seb128@ubuntu/member/seb128</em>] entered the room.</b><br/>
+<font size="2">(13:37:05)</font><b> bpepple [<em>~bpepple|l@99-186-52-80.lightspeed.clmboh.sbcglobal.net</em>] entered the room.</b><br/>
+<font size="2">(13:43:15)</font><b> seiflotfy_ left the room (quit: Remote host closed the connection).</b><br/>
+<font size="2">(13:44:12)</font><b> alsuren [<em>~alsuren@78-86-104-189.zone2.bethere.co.uk</em>] entered the room.</b><br/>
+<font size="2">(13:44:58)</font><b> seiflotfy [<em>~seiflotfy@ip-95-223-14-124.unitymediagroup.de</em>] entered the room.</b><br/>
+<font size="2">(13:47:43)</font><b> gkcn left the room (&quot;http://quassel-irc.org - Her yerden rahatça konuş.&quot;).</b><br/>
+<font size="2">(13:58:56)</font><b> seb128 left the room (quit: Quit: Ex-Chat).</b><br/>
+<font color="#A82F2F"><font size="2">(14:04:34)</font> <b>wjt:</b></font> hmm. what do we actually have in Gabble that actually needs server support? besides PEP and contact search and contact info.<br/>
+<font color="#A82F2F"><font size="2">(14:09:31)</font> <b>cassidy:</b></font> mucs ?<br/>
+<font color="#A82F2F"><font size="2">(14:09:38)</font> <b>cassidy:</b></font> invisible<br/>
+<font color="#A82F2F"><font size="2">(14:09:47)</font> <b>cassidy:</b></font> proxies<br/>
+<font color="#A82F2F"><font size="2">(14:10:06)</font> <b>sjoerd:</b></font> invisible is a good one<br/>
+<font color="#A82F2F"><font size="2">(14:11:22)</font> <b>cassidy:</b></font> XMPP ping maybe<br/>
+<font color="#A82F2F"><font size="2">(14:11:30)</font> <b>cassidy:</b></font> not sure if we implement this one<br/>
+<font size="2">(14:11:31)</font><b> seb128 [<em>~seb128@ANancy-258-1-45-93.w90-39.abo.wanadoo.fr</em>] entered the room.</b><br/>
+<font size="2">(14:11:31)</font><b> seb128 left the room (quit: Changing host).</b><br/>
+<font size="2">(14:11:31)</font><b> seb128 [<em>~seb128@ubuntu/member/seb128</em>] entered the room.</b><br/>
+<font size="2">(14:11:41)</font><b> mikhailz left the room (quit: Quit: Ушёл).</b><br/>
+<font color="#A82F2F"><font size="2">(14:11:48)</font> <b>cassidy:</b></font> the roster power saving thing<br/>
+<font size="2">(14:12:11)</font><b> kaserf [<em>~felix@ppp-93-104-18-213.dynamic.mnet-online.de</em>] entered the room.</b><br/>
+<font color="#A82F2F"><font size="2">(14:12:20)</font> <b>wjt:</b></font> oh yeah, dwd implemented google:queue in M-Link<br/>
+<font color="#A82F2F"><font size="2">(14:12:26)</font> <b>wjt:</b></font> invisible&apos;s a good idea. we do implement xmpp ping<br/>
+<font color="#A82F2F"><font size="2">(14:18:06)</font> <b>wjt:</b></font> we don&apos;t do the roster versioning thing though<br/>
+<font color="#A82F2F"><font size="2">(14:18:10)</font> <b>wjt:</b></font> maybe we should. how hard could it be.<br/>
+<font color="#A82F2F"><font size="2">(14:18:44)</font> <b>wjt:</b></font> there&apos;s not much to test in Ping :)<br/>
+<font color="#A82F2F"><font size="2">(14:19:01)</font> <b>sjoerd:</b></font> I&apos;d like to move to a WockyRoster first tbh, but....<br/>
+<font color="#A82F2F"><font size="2">(14:19:13)</font> <b>wjt:</b></font> cassidy: proxies as in Socks5 bytestream proxies?<br/>
+<font color="#A82F2F"><font size="2">(14:19:16)</font> <b>cassidy:</b></font> yep<br/>
+<font size="2">(14:20:22)</font><b> bcurtiswx [<em>~bcurtis@wx.mesa.gmu.edu</em>] entered the room.</b><br/>
+<font size="2">(14:20:22)</font><b> bcurtiswx left the room (quit: Changing host).</b><br/>
+<font size="2">(14:20:22)</font><b> bcurtiswx [<em>~bcurtis@ubuntu/member/bcurtiswx</em>] entered the room.</b><br/>
+<font size="2">(14:30:32)</font><b> jonkri [<em>~jonkri@dedikerad/admin/jonkri</em>] entered the room.</b><br/>
+<font size="2">(14:36:43)</font><b> stefw_ [<em>~sean@201.138.95.188</em>] entered the room.</b><br/>
+<font size="2">(14:53:14)</font><b> trupheenix left the room (quit: Ping timeout: 255 seconds).</b><br/>
+<font color="#A82F2F"><font size="2">(14:55:26)</font> <b>ptlo:</b></font> kkszysiu, heya; i seem to remember you were hacking on a im-via-web-using-telepathy stuff? how&apos;s that going? i&apos;d be interested in doing something along the same lines<br/>
+<font color="#A82F2F"><font size="2">(14:55:46)</font> <b>ptlo:</b></font> (or similar)<br/>
+<font color="#A82F2F"><font size="2">(15:10:05)</font> <b>wjt:</b></font> huh<br/>
+<font color="#A82F2F"><font size="2">(15:10:21)</font> <b>wjt:</b></font> setting my status on Google Talk seems to take a noticable amount of time since the Shared Status stuff went in<br/>
+<font color="#A82F2F"><font size="2">(15:10:43)</font> <b>sjoerd:</b></font> in gabble or in another location<br/>
+<font color="#A82F2F"><font size="2">(15:11:12)</font> <b>sjoerd:</b></font> Oh, i noticed that our iq request queue somethings fill up and then doesn&apos;t seem to get unstuck<br/>
+<font color="#A82F2F"><font size="2">(15:11:43)</font> <b>sjoerd:</b></font> quite noticable of you request user info<br/>
+<font color="#A82F2F"><font size="2">(15:12:06)</font> <b>wjt:</b></font> in Gabble. i didn&apos;t even have GMail open<br/>
+<font color="#A82F2F"><font size="2">(15:12:23)</font> <b>wjt:</b></font> invisibleness works on almost all of my accounts now! :D<br/>
+<font color="#A82F2F"><font size="2">(15:12:38)</font> <b>sjoerd:</b></font> i mean does it take gabble a while to send out the presence or gmail to update it to peers<br/>
+<font color="#A82F2F"><font size="2">(15:13:35)</font> <b>wjt:</b></font> MattJ: so about that xep-0186 support? ;-)<br/>
+<font color="#A82F2F"><font size="2">(15:14:03)</font> <b>wjt:</b></font> heh, oh dear<br/>
+<font color="#A82F2F"><font size="2">(15:14:22)</font> <b>wjt:</b></font> i fell out of jdev@ when I went invisible<br/>
+<font color="#A82F2F"><font size="2">(15:14:39)</font> <b>sjoerd:</b></font> buhboh<br/>
+<font color="#A82F2F"><font size="2">(15:15:10)</font> <b>wjt:</b></font> i guess the collabora xmpp server does privacy list-based invisibility, so it&apos;s only doing what i asked<br/>
+<font color="#A82F2F"><font size="2">(15:16:50)</font> <b>cassidy:</b></font> wjt, how can you test if you are actually invisible? The account presence is always sync with your real status?<br/>
+<font color="#A82F2F"><font size="2">(15:17:26)</font> <b>cassidy:</b></font> (I like this ghost icon :)<br/>
+<font color="#A82F2F"><font size="2">(15:17:28)</font> <b>wjt:</b></font> http://telepathy.freedesktop.org/spec/Account.html#im.telepathy1.Account.CurrentPresence is based on what the CM says it is<br/>
+<font color="#A82F2F"><font size="2">(15:17:37)</font> <b>wjt:</b></font> and that&apos;s what shows up in the account dialog<br/>
+<font color="#A82F2F"><font size="2">(15:17:46)</font> <b>wjt:</b></font> what does the ghost look like? I use a different theme.<br/>
+<font color="#A82F2F"><font size="2">(15:20:40)</font> <b>cassidy:</b></font> wjt, http://people.collabora.co.uk/~cassidy/status.jpg<br/>
+<font color="#A82F2F"><font size="2">(15:22:31)</font> <b>wjt:</b></font> haha<br/>
+<font color="#A82F2F"><font size="2">(15:22:39)</font> <b>wjt:</b></font> that&apos;s the only good icon in that set<br/>
+<font color="#A82F2F"><font size="2">(15:22:51)</font> <b>cassidy:</b></font> totally<br/>
+<font color="#A82F2F"><font size="2">(15:28:05)</font> <b>jonnylamb:</b></font> pessi: Hi, I fixed some bugs in ring: http://git.collabora.co.uk/?p=user/jonny/telepathy-ring.git;a=shortlog;h=refs/heads/trivia<br/>
+<font color="#A82F2F"><font size="2">(15:29:09)</font> <b>wjt:</b></font> no gitorious merge request? <br/>
+<font color="#A82F2F"><font size="2">(15:29:27)</font> <b>jonnylamb:</b></font> no gitorious merge request.<br/>
+<font color="#A82F2F"><font size="2">(15:29:42)</font> <b>wjt:</b></font> no biscuit.<br/>
+<font color="#A82F2F"><font size="2">(15:30:44)</font> <b>sjoerd:</b></font> bha, my register flag is sticky on an account again<br/>
+<font color="#A82F2F"><font size="2">(15:30:49)</font> <b>sjoerd:</b></font> i thought we fixed that...<br/>
+<font color="#A82F2F"><font size="2">(15:33:38)</font> <b>MattJ:</b></font> That&apos;s the #2 most common problem I help Empathy users with :)<br/>
+<font color="#A82F2F"><font size="2">(15:34:15)</font> <b>jonnylamb:</b></font> :-/<br/>
+<font color="#A82F2F"><font size="2">(15:34:21)</font> <b>jonnylamb:</b></font> What&apos;s number one?<br/>
+<font color="#A82F2F"><font size="2">(15:34:26)</font> <b>cassidy:</b></font> yeah that should have be fixed in MC<br/>
+<font color="#A82F2F"><font size="2">(15:34:48)</font> <b>sjoerd:</b></font> Good that a proper register interface is getting higher on the todo list<br/>
+<font color="#A82F2F"><font size="2">(15:35:24)</font> <b>MattJ:</b></font> jonnylamb: debugging &quot;Network Error&quot; messages<br/>
+<font color="#A82F2F"><font size="2">(15:35:35)</font> <b>wjt:</b></font> \o\ /o/<br/>
+<font color="#A82F2F"><font size="2">(15:35:45)</font> <b>jonnylamb:</b></font> :-(<br/>
+<font color="#A82F2F"><font size="2">(15:35:46)</font> <b>MattJ:</b></font> jonnylamb: which actually covers practically every account or server misconfiguration possible, so...<br/>
+<font color="#A82F2F"><font size="2">(15:35:50)</font> <b>sjoerd:</b></font> that&apos;s mostly fixed though<br/>
+<font color="#A82F2F"><font size="2">(15:36:00)</font> <b>smcv:</b></font> ConnectionError gives us extensible signalling<br/>
+<font color="#A82F2F"><font size="2">(15:36:09)</font> <b>smcv:</b></font> not all the CMs actually provide more info yet<br/>
+<font color="#A82F2F"><font size="2">(15:36:17)</font> <b>smcv:</b></font> we&apos;re getting there for Gabble<br/>
+<font color="#A82F2F"><font size="2">(15:36:18)</font> <b>MattJ:</b></font> Does Gabble?<br/>
+<font color="#A82F2F"><font size="2">(15:36:23)</font> <b>MattJ:</b></font> Good :)<br/>
+<font color="#A82F2F"><font size="2">(15:36:34)</font> <b>smcv:</b></font> Gabble provides the un-localized debug message in all cases<br/>
+<font color="#A82F2F"><font size="2">(15:36:41)</font> <b>wjt:</b></font> the UI doesn&apos;t show it though<br/>
+<font color="#A82F2F"><font size="2">(15:36:44)</font> <b>MattJ:</b></font> Please use &lt;text&gt; from XMPP errors<br/>
+<font color="#A82F2F"><font size="2">(15:36:46)</font> <b>smcv:</b></font> and a maximally good Telepathy error in some<br/>
+<font size="2">(15:36:50)</font><b> alsuren_ [<em>~alsuren@78-86-104-189.zone2.bethere.co.uk</em>] entered the room.</b><br/>
+<font color="#A82F2F"><font size="2">(15:36:56)</font> <b>MattJ:</b></font> I know other servers aren&apos;t helpful that way, but Prosody is<br/>
+<font color="#A82F2F"><font size="2">(15:37:17)</font> <b>smcv:</b></font> MattJ: aha, I spy a use-case for the hypothetical server-message field<br/>
+<font color="#A82F2F"><font size="2">(15:37:25)</font> <b>sjoerd:</b></font> dear ejabberd, why are you not showing your xep 55 in your disco response<br/>
+<font color="#A82F2F"><font size="2">(15:37:31)</font> <b>MattJ:</b></font> smcv: :)<br/>
+<font color="#A82F2F"><font size="2">(15:37:38)</font> <b>smcv:</b></font> (ConnectionError has a hash table of misc, debug-message is the only universally-provided key)<br/>
+<font color="#A82F2F"><font size="2">(15:38:00)</font> <b>wjt:</b></font> hey<br/>
+<font color="#A82F2F"><font size="2">(15:38:09)</font> <b>wjt:</b></font> you successfully convinced me to conflate the two<br/>
+<font color="#A82F2F"><font size="2">(15:38:13)</font> <b>smcv:</b></font> MattJ: what language is the &lt;text&gt; in btw?<br/>
+<font color="#A82F2F"><font size="2">(15:38:22)</font> <b>wjt:</b></font> if the server provides &lt;text/&gt;, use that; otherwise, use a locally-supplied debug string<br/>
+<font color="#A82F2F"><font size="2">(15:38:41)</font> <b>smcv:</b></font> did I? not sure if I intended to or not, tbh<br/>
+<font color="#A82F2F"><font size="2">(15:38:54)</font> <b>MattJ:</b></font> smcv: In Prosody, always (British!) English currently<br/>
+<font color="#A82F2F"><font size="2">(15:38:56)</font> <b>sjoerd:</b></font> I don&apos;t really trust servers to give something more useful then gabble can tbh<br/>
+<font color="#A82F2F"><font size="2">(15:38:57)</font> <b>wjt:</b></font> well, s/you/this channel/<br/>
+<font color="#A82F2F"><font size="2">(15:39:06)</font> <b>MattJ:</b></font> sjoerd: lies<br/>
+<font color="#A82F2F"><font size="2">(15:39:22)</font> <b>sjoerd:</b></font> MattJ: Prosody is usually the exception about being nice<br/>
+<font color="#A82F2F"><font size="2">(15:39:26)</font> <b>MattJ:</b></font> Heh<br/>
+<font color="#A82F2F"><font size="2">(15:39:34)</font> <b>smcv:</b></font> libpurple&apos;s localized debug messages aren&apos;t quite the same thing as &lt;text/&gt; from the server<br/>
+<font color="#A82F2F"><font size="2">(15:39:47)</font> <b>sjoerd:</b></font> ejabberd isn&apos;t even telling me why it&apos;s disconnecting some test accounts<br/>
+<font color="#A82F2F"><font size="2">(15:39:55)</font> <b>MattJ:</b></font> There are vague errors like &quot;bad-request&quot; or &quot;not-authorized&quot; where Prosody usually gives more specific information about why the error occured<br/>
+<font color="#A82F2F"><font size="2">(15:40:04)</font> <b>smcv:</b></font> tbh we should probably provide our (libpurple&apos;s/wocky&apos;s) interpretation, *and* the &lt;text/&gt; from the server in case it&apos;s informative<br/>
+<font size="2">(15:40:05)</font><b> bcurtiswx left the room (quit: Remote host closed the connection).</b><br/>
+<font color="#A82F2F"><font size="2">(15:40:14)</font> <b>sjoerd:</b></font> nod<br/>
+<font color="#A82F2F"><font size="2">(15:40:16)</font> <b>MattJ:</b></font> smcv: Yes, the specs say you should do that<br/>
+<font color="#A82F2F"><font size="2">(15:40:25)</font> <b>MattJ:</b></font> e.g. not present solely the &lt;text&gt;<br/>
+<font color="#A82F2F"><font size="2">(15:40:36)</font> <b>smcv:</b></font> oh I wasn&apos;t suggesting that<br/>
+<font color="#A82F2F"><font size="2">(15:40:55)</font> <b>smcv:</b></font> the primary thing to present is a D-Bus error code which UIs are expected to localize<br/>
+<font color="#A82F2F"><font size="2">(15:41:07)</font> <b>smcv:</b></font> one of whose possible values is the dreaded NetworkError<br/>
+<font color="#A82F2F"><font size="2">(15:41:12)</font> <b>MattJ:</b></font> But please don&apos;t do what Psi does and copy the XMPP spec&apos;s error text verbatim into the UI...<br/>
+<font size="2">(15:41:19)</font><b> alsuren left the room (quit: Ping timeout: 272 seconds).</b><br/>
+<font color="#A82F2F"><font size="2">(15:43:39)</font> <b>sjoerd:</b></font> hehe<br/>
+<font size="2">(15:43:48)</font><b> nekohayo [<em>~jeff@206-248-171-113.dsl.teksavvy.com</em>] entered the room.</b><br/>
+<font color="#A82F2F"><font size="2">(15:44:00)</font> <b>pessi:</b></font> jonnylamb: please rebase against latest master ... and see if e7470c3ab and f26a5c3298 are still applicable<br/>
+<font color="#A82F2F"><font size="2">(15:44:45)</font> <b>sjoerd:</b></font> wjt: hrm, can you disco remote servers for their jud and does gabble do that if needed or does it rely on the given server being the jud?<br/>
+<font color="#A82F2F"><font size="2">(15:45:34)</font> <b>jonnylamb:</b></font> pessi: Hm, I wonder why my checkout was so out-of-date. Did you just push those patches?<br/>
+<font color="#A82F2F"><font size="2">(15:45:43)</font> <b>jonnylamb:</b></font> pessi: \o/ TpBaseChannel \o/<br/>
+<font color="#A82F2F"><font size="2">(15:46:37)</font> <b>wjt:</b></font> sjoerd: oops. the latter.<br/>
+<font color="#A82F2F"><font size="2">(15:46:40)</font> <b>pessi:</b></font> jonnylamb: I was just waiting for our tester to get rid of oFono bugs ;)<br/>
+<font color="#A82F2F"><font size="2">(15:48:57)</font> <b>sjoerd:</b></font> wjt: we should probably cope with both cases.. i wonder if jud server correctly indicate in a disco response that they&apos;re the jud server<br/>
+<font size="2">(15:49:46)</font><b> jessevdk [<em>~jessevdk@grijpc10.epfl.ch</em>] entered the room.</b><br/>
+<font size="2">(15:50:09)</font><b> mattire left the room (quit: Ping timeout: 245 seconds).</b><br/>
+<font color="#A82F2F"><font size="2">(15:52:40)</font> <b>jonnylamb:</b></font> pessi: Well you&apos;ve destroyed my branch with your recent commits!<br/>
+<font color="#A82F2F"><font size="2">(15:52:44)</font> <b>jonnylamb:</b></font> pessi: http://git.collabora.co.uk/?p=user/jonny/telepathy-ring.git;a=shortlog;h=refs/heads/trivia<br/>
+<font size="2">(15:54:54)</font><b> bcurtiswx [<em>~bcurtis@wx.mesa.gmu.edu</em>] entered the room.</b><br/>
+<font size="2">(15:54:54)</font><b> bcurtiswx left the room (quit: Changing host).</b><br/>
+<font size="2">(15:54:54)</font><b> bcurtiswx [<em>~bcurtis@ubuntu/member/bcurtiswx</em>] entered the room.</b><br/>
+<font size="2">(15:55:54)</font><b> jessevdk left the room (&quot;Ex-Chat&quot;).</b><br/>
+<font size="2">(15:57:03)</font><b> kaserf left the room (quit: Read error: Operation timed out).</b><br/>
+<font size="2">(15:57:37)</font><b> ptlo left the room (quit: Quit: Ex-Chat).</b><br/>
+<font size="2">(15:58:14)</font><b> mlundblad left the room (quit: Ping timeout: 260 seconds).</b><br/>
+<font color="#A82F2F"><font size="2">(15:58:30)</font> <b>wjt:</b></font> so. if I were to propose adding a property alongside ValidAccounts and InvalidAccounts, which contains accounts which are not normal IM accounts—maybe they&apos;re just signed in for some kind of pubsub system—and thus should not normally be shown to the user, how many people would club me to death?<br/>
+<font color="#A82F2F"><font size="2">(15:59:18)</font> <b>wjt:</b></font> assumptions: such accounts never turn into normal accounts; there would be a separate interface for them on AM; but otherwise they would behave like normal accounts; the Account objects would also have a property on a separate interface to say they&apos;re non-user-visible service accounts?<br/>
+<font size="2">(15:59:33)</font><b> fledermaus left the room (quit: Quit: bbl).</b><br/>
+<font color="#A82F2F"><font size="2">(16:00:38)</font> <b>wjt:</b></font> alternative possibly less-beating-worthy proposals include just adding the flag to the account and then modifying tp-{glib,qt4,...} to hide &apos;em by default<br/>
+<font color="#A82F2F"><font size="2">(16:00:53)</font> <b>smcv:</b></font> wjt: if the account manager manages it, I think it&apos;s a valid account like any other<br/>
+<font color="#A82F2F"><font size="2">(16:01:32)</font> <b>smcv:</b></font> if the libraries hide those accounts by default, that&apos;s no more compatible than changing the D-Bus API<br/>
+<font color="#A82F2F"><font size="2">(16:01:44)</font> <b>smcv:</b></font> you&apos;re just moving the incompatibility into the client libraries<br/>
+<font color="#A82F2F"><font size="2">(16:01:46)</font> <b>wjt:</b></font> but if it&apos;s not meant to be used for IM, then IM UIs shouldn&apos;t show it<br/>
+<font color="#A82F2F"><font size="2">(16:01:59)</font> <b>wjt:</b></font> yes, that&apos;s why i didn&apos;t want to put it in the client libraries :)<br/>
+<font color="#A82F2F"><font size="2">(16:02:24)</font> <b>wjt:</b></font> i want to be able to make stealth service accounts exist without modifying literally every existing and future application not to show them<br/>
+<font color="#A82F2F"><font size="2">(16:02:57)</font> <b>smcv:</b></font> wjt: if you want them to be that stealthy, put the new property on a different interface<br/>
+<font color="#A82F2F"><font size="2">(16:03:11)</font> <b>smcv:</b></font> AccountManager.I.AndAlso.SecretNinjaAccounts<br/>
+<font color="#A82F2F"><font size="2">(16:03:37)</font> <b>wjt:</b></font> that&apos;s exactly what I just proposed? :)<br/>
+<font color="#A82F2F"><font size="2">(16:03:55)</font> <b>smcv:</b></font> oh right I thought by &quot;alongside&quot; you meant in o.fd.T.AM<br/>
+<font size="2">(16:03:59)</font><b> seb128 left the room (quit: Quit: Ex-Chat).</b><br/>
+<font color="#A82F2F"><font size="2">(16:04:07)</font> <b>wjt:</b></font> nono, i meant on ja.ninja.AccountManager<br/>
+<font color="#A82F2F"><font size="2">(16:04:57)</font> <b>smcv:</b></font> I think that&apos;s better than modifying the client libraries<br/>
+<font color="#A82F2F"><font size="2">(16:05:24)</font> <b>wjt:</b></font> but implemented in MC (yeah yeah, adding weird extensions considered harmful, but i also don&apos;t want to use the libmissioncontrol-server6 API and subclass shit)<br/>
+<font color="#A82F2F"><font size="2">(16:07:01)</font> <b>smcv:</b></font> oh yeah having weird extensions live in MC where we can keep an eye on them is better than having them in misc subclasses :-)<br/>
+<font color="#A82F2F"><font size="2">(16:07:34)</font> <b>smcv:</b></font> tbh it&apos;s not necessarily too niche to have in telepathy-spec<br/>
+<font color="#A82F2F"><font size="2">(16:07:41)</font> <b>smcv:</b></font> it&apos;s just too weird to have in the core interface<br/>
+<font color="#A82F2F"><font size="2">(16:07:45)</font> <b>wjt:</b></font> exactly :)<br/>
+<font color="#A82F2F"><font size="2">(16:08:09)</font> <b>wjt:</b></font> ugh. so after empathy crashed and this MUC i&apos;m in respawned, all the scrollback messages turned up in 1-1 channels<br/>
+<font size="2">(16:08:18)</font><b> seb128 [<em>~seb128@ANancy-258-1-45-93.w90-39.abo.wanadoo.fr</em>] entered the room.</b><br/>
+<font size="2">(16:08:18)</font><b> seb128 left the room (quit: Changing host).</b><br/>
+<font size="2">(16:08:18)</font><b> seb128 [<em>~seb128@ubuntu/member/seb128</em>] entered the room.</b><br/>
+<font size="2">(16:08:25)</font><b> seb128 left the room (quit: Remote host closed the connection).</b><br/>
+<font size="2">(16:09:15)</font><b> seb128 [<em>~seb128@ANancy-258-1-45-93.w90-39.abo.wanadoo.fr</em>] entered the room.</b><br/>
+<font size="2">(16:09:18)</font><b> seb128 left the room (quit: Changing host).</b><br/>
+<font size="2">(16:09:18)</font><b> seb128 [<em>~seb128@ubuntu/member/seb128</em>] entered the room.</b><br/>
+<font color="#A82F2F"><font size="2">(16:10:13)</font> <b>sjoerd:</b></font> wjt: yeah that still happens from time to time, although i thought it was fixed :(<br/>
+<font size="2">(16:10:27)</font><b> The account has disconnected and you are no longer in this chat. You will automatically rejoin the chat when the account reconnects.</b><br/>
+</body></html>
diff --git a/tests/logger/logs/purple/jabber/user@collabora.co.uk/.system/2010-12-10.162531+0000GMT.txt b/tests/logger/logs/purple/jabber/user@collabora.co.uk/.system/2010-12-10.162531+0000GMT.txt
new file mode 100644
index 000000000..e3dfac5fe
--- /dev/null
+++ b/tests/logger/logs/purple/jabber/user@collabora.co.uk/.system/2010-12-10.162531+0000GMT.txt
@@ -0,0 +1,5 @@
+System log for account ka.test2@test.collabora.co.uk/TEST (jabber) connected at Fri 10 Dec 2010 16:25:31 GMT
+---- +++ ka.test2@test.collabora.co.uk/TEST signed on @ 10/12/10 16:25:31 ----
+---- cosimo.alfarano@collabora.co.uk (cosimo.alfarano@collabora.co.uk) is now Offline @ 10/12/10 16:26:50 ----
+---- cosimo.alfarano@collabora.co.uk (cosimo.alfarano@collabora.co.uk) changed status from Offline to Available @ 10/12/10 16:26:54 ----
+---- +++ ka.test2@test.collabora.co.uk/TEST signed off @ 10/12/10 16:27:15 ----
diff --git a/tests/logger/logs/purple/jabber/user@collabora.co.uk/test@conference.collabora.co.uk.chat/2010-04-12.122703+0100BST.html b/tests/logger/logs/purple/jabber/user@collabora.co.uk/test@conference.collabora.co.uk.chat/2010-04-12.122703+0100BST.html
new file mode 100644
index 000000000..faf97fe95
--- /dev/null
+++ b/tests/logger/logs/purple/jabber/user@collabora.co.uk/test@conference.collabora.co.uk.chat/2010-04-12.122703+0100BST.html
@@ -0,0 +1,12 @@
+<html><head><meta http-equiv="content-type" content="text/html; charset=UTF-8"><title>Conversation with test@conference.collabora.co.uk at Thu 12 Apr 2010 12:27:03 BST on user@collabora.co.uk/TEST (jabber)</title></head><body><h3>Conversation with test@conference.collabora.co.uk at Thu 12 Apr 2010 12:27:03 BST on user@collabora.co.uk/TEST (jabber)</h3>
+<font size="2">(12:27:03)</font><b> user@collabora.co.uk/TEST</em>] entered the room.</b><br/>
+<font size="2">(12:28:22)</font><b> user@collabora.co.uk/empathy</em>] entered the room.</b><br/>
+<font color="#16569E"><font size="2">(12:28:46)</font> <b>user2:</b></font> <body>1</body><br/>
+<font color="#16569E"><font size="2">(12:29:31)</font> <b>user2:</b></font> <body>2</body><br/>
+<font color="#A82F2F"><font size="2">(12:30:37)</font> <b>user@collabora.co.uk:</b></font> <body>1</body><br/>
+<font color="#16569E"><font size="2">(12:30:47)</font> <b>user2:</b></font> <body>3</body><br/>
+<font color="#16569E"><font size="2">(12:32:36)</font> <b>user2:</b></font> <body>3</body><br/>
+<font color="#16569E"><font size="2">(12:33:25)</font> <b>user2:</b></font> <body>4</body><br/>
+<font size="2">(12:46:06)</font><b> user@collabora.co.uk left the room.</b><br/>
+<font size="2">(12:46:46)</font><b> user@collabora.co.uk/empathy</em>] entered the room.</b><br/>
+<font size="2">(13:49:01)</font><b> user@collabora.co.uk left the room.</b><br/>
diff --git a/tests/logger/logs/purple/jabber/user@collabora.co.uk/test@conference.collabora.co.uk.chat/2010-04-29.140846+0100BST.html b/tests/logger/logs/purple/jabber/user@collabora.co.uk/test@conference.collabora.co.uk.chat/2010-04-29.140846+0100BST.html
new file mode 100644
index 000000000..2018a1e3a
--- /dev/null
+++ b/tests/logger/logs/purple/jabber/user@collabora.co.uk/test@conference.collabora.co.uk.chat/2010-04-29.140846+0100BST.html
@@ -0,0 +1,11 @@
+<html><head><meta http-equiv="content-type" content="text/html; charset=UTF-8"><title>Conversation with test@conference.collabora.co.uk at Thu 29 Apr 2010 14:08:46 BST on user@collabora.co.uk/TEST (jabber)</title></head><body><h3>Conversation with test@conference.collabora.co.uk at Thu 29 Apr 2010 14:08:46 BST on user@collabora.co.uk/TEST (jabber)</h3>
+<font size="2">(14:08:46)</font><b> User1 [<em>user@collabora.co.uk/TEST</em>] entered the room.</b><br/>
+<font color="#16569E"><font size="2">(14:08:55)</font> <b>User1:</b></font> <body>retest</body><br/>
+<font color="#16569E"><font size="2">(14:09:04)</font> <b>User1:</b></font> <body>ahah</body><br/>
+<font size="2">(14:09:44)</font><b> User2 [<em>User2@collabora.co.uk/Kazoo</em>] entered the room.</b><br/>
+<font color="#A82F2F"><font size="2">(14:10:11)</font> <b>User2:</b></font> <body>1</body><br/>
+<font color="#A82F2F"><font size="2">(14:10:11)</font> <b>User2:</b></font> <body>2</body><br/>
+<font color="#A82F2F"><font size="2">(14:10:11)</font> <b>User2:</b></font> <body>3</body><br/>
+<font color="#A82F2F"><font size="2">(14:10:20)</font> <b>User2:</b></font> <body>4</body><br/>
+<font color="#16569E"><font size="2">(14:10:26)</font> <b>User1:</b></font> <body>5</body><br/>
+</body></html>
diff --git a/tests/logger/logs/purple/jabber/user@collabora.co.uk/user2@collabora.co.uk/2010-12-10.162702+0000GMT.txt b/tests/logger/logs/purple/jabber/user@collabora.co.uk/user2@collabora.co.uk/2010-12-10.162702+0000GMT.txt
new file mode 100644
index 000000000..8a1580105
--- /dev/null
+++ b/tests/logger/logs/purple/jabber/user@collabora.co.uk/user2@collabora.co.uk/2010-12-10.162702+0000GMT.txt
@@ -0,0 +1,3 @@
+Conversation with user2@collabora.co.uk at Fri 10 Dec 2010 16:27:02 GMT on user@test.collabora.co.uk/TEST (jabber)
+(16:27:02) User1: hi
+(16:27:07) user@test.collabora.co.uk/TEST: hey you
diff --git a/tests/logger/logs/purple/jabber/user@collabora.co.uk/user5@collabora.co.uk/2010-01-10.000101+0000GMT.txt b/tests/logger/logs/purple/jabber/user@collabora.co.uk/user5@collabora.co.uk/2010-01-10.000101+0000GMT.txt
new file mode 100644
index 000000000..d67ac56a2
--- /dev/null
+++ b/tests/logger/logs/purple/jabber/user@collabora.co.uk/user5@collabora.co.uk/2010-01-10.000101+0000GMT.txt
@@ -0,0 +1,3 @@
+Conversation with user5@collabora.co.uk at Sun 10 Jan 2010 00:01:01 GMT on user@test.collabora.co.uk/TEST (jabber)
+(00:01:01) User5: A
+(00:01:02) user@test.collabora.co.uk/TEST: B
diff --git a/tests/logger/logs/purple/jabber/user@collabora.co.uk/user5@collabora.co.uk/2010-01-11.000101+0000GMT.txt b/tests/logger/logs/purple/jabber/user@collabora.co.uk/user5@collabora.co.uk/2010-01-11.000101+0000GMT.txt
new file mode 100644
index 000000000..3bf802dd5
--- /dev/null
+++ b/tests/logger/logs/purple/jabber/user@collabora.co.uk/user5@collabora.co.uk/2010-01-11.000101+0000GMT.txt
@@ -0,0 +1,17 @@
+Conversation with user5@collabora.co.uk at Mon 11 Jan 2010 00:01:01 GMT on user@test.collabora.co.uk/TEST (jabber)
+(00:01:01) User5: C
+(00:01:02) user@test.collabora.co.uk/TEST: D
+(00:01:02) user@test.collabora.co.uk/TEST: D'
+(00:01:02) user@test.collabora.co.uk/TEST: D''
+(00:01:03) User5: E
+(00:01:04) User5: F
+(00:01:04) User5: F'
+(00:01:04) User5: F''
+(00:01:05) user@test.collabora.co.uk/TEST: G
+(00:01:05) user@test.collabora.co.uk/TEST: G'
+(00:01:05) user@test.collabora.co.uk/TEST: G''
+(00:01:05) user@test.collabora.co.uk/TEST: G'''
+(00:01:06) User5: H
+(00:01:06) User5: H'
+(00:01:06) User5: H''
+(00:01:06) User5: H'''
diff --git a/tests/logger/logs/purple/jabber/user@collabora.co.uk/user5@collabora.co.uk/2010-01-14.000101+0000GMT.txt b/tests/logger/logs/purple/jabber/user@collabora.co.uk/user5@collabora.co.uk/2010-01-14.000101+0000GMT.txt
new file mode 100644
index 000000000..fa0ff80fb
--- /dev/null
+++ b/tests/logger/logs/purple/jabber/user@collabora.co.uk/user5@collabora.co.uk/2010-01-14.000101+0000GMT.txt
@@ -0,0 +1,12 @@
+Conversation with user5@collabora.co.uk at Thu 14 Jan 2010 00:01:01 GMT on user@test.collabora.co.uk/TEST (jabber)
+(00:01:01) User5: I
+(00:01:01) User5: I'
+(00:01:01) User5: I''
+(00:01:01) User5: I'''
+(00:01:02) user@test.collabora.co.uk/TEST: J
+(00:01:02) user@test.collabora.co.uk/TEST: J'
+(00:01:03) User5: K
+(00:01:04) User5: L
+(00:01:04) User5: L'
+(00:01:04) User5: L''
+(00:01:04) User5: L'''
diff --git a/tests/logger/test-basic-connect.sh b/tests/logger/test-basic-connect.sh
new file mode 100644
index 000000000..93af74d54
--- /dev/null
+++ b/tests/logger/test-basic-connect.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+./run-with-tmp-session-bus.sh `pwd`/test-basic-connect.py BasicConnectTest
diff --git a/tests/logger/test-tpl-conf.c b/tests/logger/test-tpl-conf.c
new file mode 100644
index 000000000..57363560f
--- /dev/null
+++ b/tests/logger/test-tpl-conf.c
@@ -0,0 +1,31 @@
+#include "config.h"
+
+#include <telepathy-logger/conf-internal.h>
+
+int
+main (int argc, char **argv)
+{
+ TplConf *conf, *conf2;
+
+ g_type_init ();
+
+ conf = _tpl_conf_dup ();
+
+ /* TplConf is a singleton, be sure both point to the same memory */
+ conf2 = _tpl_conf_dup ();
+ g_assert (conf == conf2);
+
+ /* unref the second singleton pointer and check that the it is still
+ * valid: checking correct object ref-counting after each _dup () call */
+ g_object_unref (conf2);
+ g_assert (TPL_IS_CONF (conf));
+
+ /* it points to the same mem area, it should be still valid */
+ g_assert (TPL_IS_CONF (conf2));
+
+ /* proper disposal for the singleton when no references are present */
+ g_object_unref (conf);
+
+ return 0;
+}
+
diff --git a/tests/suppressions/Makefile.am b/tests/suppressions/Makefile.am
new file mode 100644
index 000000000..691047d03
--- /dev/null
+++ b/tests/suppressions/Makefile.am
@@ -0,0 +1 @@
+EXTRA_DIST = tpl.supp
diff --git a/tests/suppressions/tpl.supp b/tests/suppressions/tpl.supp
new file mode 100644
index 000000000..a395e82e6
--- /dev/null
+++ b/tests/suppressions/tpl.supp
@@ -0,0 +1,321 @@
+# Valgrind error suppression file
+
+
+# ============================= selinux ==================================
+
+{
+ selinux getdelim
+ Memcheck:Leak
+ fun:malloc
+ fun:getdelim
+ obj:/lib/libselinux.so.1
+ obj:/lib/libselinux.so.1
+ obj:/lib/libselinux.so.1
+}
+
+
+
+# ============================= libc ==================================
+
+{
+ ld.so initialization + selinux
+ Memcheck:Leak
+ ...
+ fun:_dl_init
+ obj:/lib/ld-*.so
+}
+
+{
+ 64bit ld.so initialization + selinux
+ Memcheck:Leak
+ ...
+ fun:_dl_init
+ obj:/lib64/ld-*.so
+}
+
+{
+ ld.so start
+ Memcheck:Cond
+ ...
+ fun:_dl_start
+ obj:/lib/ld-*.so
+}
+
+{
+ 64bit ld.so start
+ Memcheck:Cond
+ ...
+ fun:_dl_start
+ obj:/lib64/ld-*.so
+}
+
+{
+ dlopen initialization, triggered by handle-leak-debug code
+ Memcheck:Leak
+ ...
+ fun:__libc_dlopen_mode
+ fun:init
+ fun:backtrace
+ fun:handle_leak_debug_bt
+ fun:dynamic_ensure_handle
+ fun:tp_handle_ensure
+}
+
+# ============================= GLib ==================================
+
+{
+ g_set_prgname copies its argument
+ Memcheck:Leak
+ ...
+ fun:g_set_prgname
+}
+
+{
+ one g_get_charset per child^Wprocess
+ Memcheck:Leak
+ ...
+ fun:g_get_charset
+}
+
+{
+ GQuarks can't be freed
+ Memcheck:Leak
+ ...
+ fun:g_quark_from_static_string
+}
+
+{
+ GQuarks can't be freed
+ Memcheck:Leak
+ ...
+ fun:g_quark_from_string
+}
+
+{
+ interned strings can't be freed
+ Memcheck:Leak
+ ...
+ fun:g_intern_string
+}
+
+{
+ interned strings can't be freed
+ Memcheck:Leak
+ ...
+ fun:g_intern_static_string
+}
+
+{
+ shared global default g_main_context
+ Memcheck:Leak
+ ...
+ fun:g_main_context_new
+ fun:g_main_context_default
+}
+
+{
+ GTest initialization
+ Memcheck:Leak
+ ...
+ fun:g_test_init
+ fun:main
+}
+
+{
+ GTest admin
+ Memcheck:Leak
+ ...
+ fun:g_test_add_vtable
+}
+
+{
+ GTest pseudorandomness
+ Memcheck:Leak
+ ...
+ fun:g_rand_new_with_seed_array
+ fun:test_run_seed
+ ...
+ fun:g_test_run
+}
+
+{
+ GSLice initialization
+ Memcheck:Leak
+ ...
+ fun:g_malloc0
+ fun:g_slice_init_nomessage
+ fun:g_slice_alloc
+}
+
+# ============================= GObject ===============================
+
+{
+ g_type_init
+ Memcheck:Leak
+ ...
+ fun:g_type_init
+}
+
+{
+ g_type_init_with_debug_flags
+ Memcheck:Leak
+ ...
+ fun:g_type_init_with_debug_flags
+}
+
+{
+ g_type_register_static
+ Memcheck:Leak
+ ...
+ fun:g_type_register_static
+}
+
+{
+ g_type_create_instance
+ Memcheck:Leak
+ ...
+ fun:g_type_create_instance
+}
+
+{
+ g_type_add_interface_static
+ Memcheck:Leak
+ ...
+ fun:g_type_add_interface_static
+}
+
+{
+ g_object_do_class_init
+ Memcheck:Leak
+ ...
+ fun:g_object_do_class_init
+}
+
+# ============================= dbus-glib =============================
+
+{
+ dbus-glib, https://bugs.freedesktop.org/show_bug.cgi?id=14125
+ Memcheck:Addr4
+ fun:g_hash_table_foreach
+ obj:/usr/lib/libdbus-glib-1.so.2.1.0
+ fun:g_object_run_dispose
+}
+
+{
+ registering marshallers is permanent
+ Memcheck:Leak
+ ...
+ fun:dbus_g_object_register_marshaller_array
+ fun:dbus_g_object_register_marshaller
+}
+
+{
+ dbus-glib specialized GTypes are permanent
+ Memcheck:Leak
+ ...
+ fun:dbus_g_type_specialized_init
+}
+
+{
+ libdbus shared connection
+ Memcheck:Leak
+ ...
+ fun:dbus_g_bus_get
+}
+
+{
+ dbus-gobject registrations aren't freed unless we fall off the bus
+ Memcheck:Leak
+ ...
+ fun:g_slist_append
+ fun:dbus_g_connection_register_g_object
+}
+
+{
+ DBusGProxy slots aren't freed unless we fall off the bus
+ Memcheck:Leak
+ ...
+ fun:dbus_connection_allocate_data_slot
+ ...
+ fun:dbus_g_proxy_constructor
+}
+
+{
+ error registrations are for life, not just for Christmas
+ Memcheck:Leak
+ ...
+ fun:dbus_g_error_domain_register
+}
+
+# ============================= telepathy-glib ========================
+
+{
+ tp_dbus_daemon_constructor @daemons once per DBusConnection
+ Memcheck:Leak
+ ...
+ fun:g_slice_alloc
+ fun:tp_dbus_daemon_constructor
+}
+
+{
+ tp_proxy_subclass_add_error_mapping refs the enum
+ Memcheck:Leak
+ ...
+ fun:g_type_class_ref
+ fun:tp_proxy_subclass_add_error_mapping
+}
+
+{
+ tp_proxy_or_subclass_hook_on_interface_add never frees its list
+ Memcheck:Leak
+ ...
+ fun:tp_proxy_or_subclass_hook_on_interface_add
+}
+
+{
+ tp_dbus_daemon_constructor filter not freed til we fall off the bus
+ Memcheck:Leak
+ ...
+ fun:dbus_connection_add_filter
+ fun:tp_dbus_daemon_constructor
+}
+
+{
+ Leak in tp-glib 0.11.16 (Fedora 14)
+ Memcheck:Leak
+ ...
+ fun:g_simple_async_result_new
+ fun:tp_proxy_prepare_async
+}
+
+# ============================= tp-logger-tests ==========================
+
+{
+ tp_tests_object_new_static_class
+ Memcheck:Leak
+ ...
+ fun:tp_tests_object_new_static_class
+}
+
+# ============================= unclassified ==========================
+
+{
+ ld.so initialization on glibc 2.9
+ Memcheck:Cond
+ fun:_dl_relocate_object
+ fun:dl_main
+ fun:_dl_sysdep_start
+ fun:_dl_start
+ obj:/lib/ld-2.9.so
+}
+
+{
+ ld.so initialization on glibc 2.9
+ Memcheck:Cond
+ fun:strlen
+ fun:_dl_init_paths
+ fun:dl_main
+ fun:_dl_sysdep_start
+ fun:_dl_start
+ obj:/lib/ld-2.9.so
+}