diff options
author | Simon McVittie <simon.mcvittie@collabora.co.uk> | 2013-10-31 16:47:55 +0000 |
---|---|---|
committer | Simon McVittie <simon.mcvittie@collabora.co.uk> | 2013-10-31 16:47:55 +0000 |
commit | ae86b52c435aa39b78ae14cb23a75e4e639b0d5a (patch) | |
tree | b526eda7fda8d3469e1db44ea258f3065017d9a7 | |
parent | bd2903877be3a3bd38a2a3fff6bd6e8d030e7306 (diff) | |
parent | 301d3d7d9b0f025d71390dbc9211923308e4386b (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
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"><a href="</xsl:text> + + <xsl:value-of disable-output-escaping="yes" select="@href"/> + + <xsl:text disable-output-escaping="yes">"></xsl:text> + + <xsl:value-of select="@href"/> + <xsl:text disable-output-escaping="yes"></a></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 < $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}"> + <<xsl:value-of select="@name"/>> + </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 — $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>→</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()) → $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 — $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 — $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 — $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 — $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 — $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 — $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 — $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'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'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 "alongside" 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'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'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 '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't even telling me why it'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 <text/>, 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 <text> 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'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'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'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's that going? i'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'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'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'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'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 "bad-request" or " + ""not-authorized" 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'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'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'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 Binary files differnew file mode 100644 index 000000000..8e87f427d --- /dev/null +++ b/tests/logger/logs/purple/icq/12345678/87654321/2010-02-06.130032+0000GMT.html 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 ("http://quassel-irc.org - Her yerden rahatça konuş.").</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'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'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's not much to test in Ping :)<br/> +<font color="#A82F2F"><font size="2">(14:19:01)</font> <b>sjoerd:</b></font> I'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's that going? i'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'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'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'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'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'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'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'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 "Network Error" 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'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'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't show it though<br/> +<font color="#A82F2F"><font size="2">(15:36:44)</font> <b>MattJ:</b></font> Please use <text> 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'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 <text> in btw?<br/> +<font color="#A82F2F"><font size="2">(15:38:22)</font> <b>wjt:</b></font> if the server provides <text/>, 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'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's localized debug messages aren't quite the same thing as <text/> from the server<br/> +<font color="#A82F2F"><font size="2">(15:39:47)</font> <b>sjoerd:</b></font> ejabberd isn't even telling me why it'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 "bad-request" or "not-authorized" 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's/wocky's) interpretation, *and* the <text/> from the server in case it'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 <text><br/> +<font color="#A82F2F"><font size="2">(15:40:36)</font> <b>smcv:</b></font> oh I wasn'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't do what Psi does and copy the XMPP spec'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'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'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 ("Ex-Chat").</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'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'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 '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'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'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'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's not meant to be used for IM, then IM UIs shouldn't show it<br/> +<font color="#A82F2F"><font size="2">(16:01:59)</font> <b>wjt:</b></font> yes, that's why i didn'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'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 "alongside" 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'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'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'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'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'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 +} |