pax_global_header00006660000000000000000000000064123002137360014507gustar00rootroot0000000000000052 comment=bb73c877a650d17c2f7aa381ad11d11887cfca16 syncevolution_1.4/000077500000000000000000000000001230021373600143565ustar00rootroot00000000000000syncevolution_1.4/.gitignore000066400000000000000000000054211230021373600163500ustar00rootroot00000000000000# general *.la *.lo *.o *.pyc *~ .deps .dirstamp .libs Makefile Makefile.in # top level /INSTALL /autom4te.cache /aclocal.m4 /compile /config.guess /config.h /config.h.in /config.log /config.status /config.sub /configure /depcomp /INSTALL /install-sh /libtool /ltmain.sh /m4 /missing /mkinstalldirs /README /README.html /stamp-h1 /syncevolution.1 /syncevolution-*.tar.gz # for Maemo build /libsynthesis # po /po/*.gmo /po/.intltool-merge-cache /po/LINGUAS /po/Makefile.in.in /po/POTFILES /po/stamp-it # src /src/autotroll.mk /src/Client_Source_*.log /src/Client_Sync_*.log /src/Client_Sync_*.A /src/client-test /src/LogDirTest /src/LogRedirectTest_glib.out /src/N7SyncEvo*.log /src/synccompare /src/syncevo-dbus-server /src/syncevo-http-server /src/syncevolution /src/testcases/ # src/backends /src/backends/backends.am # src/backends/webdav /src/backends/webdav/syncevo-webdav-lookup # src/dbus/glib /src/dbus/glib/stamp-syncevo-connection-bindings.h /src/dbus/glib/stamp-syncevo-connection-glue.h /src/dbus/glib/stamp-syncevo-server-bindings.h /src/dbus/glib/stamp-syncevo-server-glue.h /src/dbus/glib/stamp-syncevo-session-bindings.h /src/dbus/glib/stamp-syncevo-session-glue.h /src/dbus/glib/syncevo-connection-glue.h /src/dbus/glib/syncevo-connection-bindings.h /src/dbus/glib/syncevo-connection.xml /src/dbus/glib/syncevo-dbus.pc /src/dbus/glib/syncevo-marshal.c /src/dbus/glib/syncevo-marshal.h /src/dbus/glib/syncevo-server-glue.h /src/dbus/glib/syncevo-server-bindings.h /src/dbus/glib/syncevo-server.xml /src/dbus/glib/syncevo-session-glue.h /src/dbus/glib/syncevo-session-bindings.h /src/dbus/glib/syncevo-session.xml /src/dbus/glib/test-syncevo-dbus # src/dbus/interfaces /src/dbus/interfaces/syncevo-connection-doc.xml /src/dbus/interfaces/syncevo-dbus-api-doc.html /src/dbus/interfaces/syncevo-dbus-api-doc.xml /src/dbus/interfaces/syncevo-server-doc.xml /src/dbus/interfaces/syncevo-session-doc.xml # src/dbus/qt /src/dbus/qt/autotroll.mk /src/dbus/qt/syncevolution-qt-dbus.pc /src/dbus/qt/stamp-connection /src/dbus/qt/stamp-server /src/dbus/qt/stamp-session /src/dbus/qt/syncevo-connection-full.cpp /src/dbus/qt/syncevo-connection-full.h /src/dbus/qt/syncevo-connection-full.moc.cpp /src/dbus/qt/syncevo-server-full.cpp /src/dbus/qt/syncevo-server-full.h /src/dbus/qt/syncevo-server-full.moc.cpp /src/dbus/qt/syncevo-session-full.cpp /src/dbus/qt/syncevo-session-full.h /src/dbus/qt/syncevo-session-full.moc.cpp # src/dbus/server /src/dbus/server/org.syncevolution.service /src/dbus/server/syncevo-dbus-server-startup.sh /src/dbus/server/syncevo-dbus-server.desktop # src/gdbus /src/gdbus/example # src/gtk-ui /src/gtk-ui/sync-ui /src/gtk-ui/sync.desktop /src/gtk-ui/ui.xml # src/syncevo /src/syncevo-phone-config /src/syncevo/CmdlineHelp.c /src/syncevo/SyncEvolutionXML.c /src/syncevo/syncevolution.pc syncevolution_1.4/AUTHORS000066400000000000000000000000431230021373600154230ustar00rootroot00000000000000Patrick Ohly syncevolution_1.4/AUTOTOOLS-TESTING000066400000000000000000000016251230021373600170110ustar00rootroot00000000000000Untested: - flags of configure script: --enable-mlite --enable-gui=all or --enable-gui=moblin --enable-akonadi --enable-kcalextended --enable-qtcontacts (see TODO) --enable-kwallet --enable-maemocal ... probably more. - make -j and make -jX where X>2, because gcc is being killed by oom-killer). Partially tested: --enable-gnome-bluetooth-panel-plugin (installation not tested - plugin is not installed under given prefix, but somewhere into /usr/lib...) Tested and passed: - flags of configure script: --enable-unit-tests --enable-integration-tests --enable-maemo --enable-libcurl --enable-libsoup --enable-bluetooth --enable-gui=gtk --enable-core --enable-dbus-service --enable-gnome-keyring --enable-notify --enable-doc --enable-sqlite --enable-dav --enable-xmlrpc --enable-qt-dbus --enable-static --disable-shared - make -jX - make V=0 - make -j2 distcheck syncevolution_1.4/AUTOTOOLS-TODO000066400000000000000000000057441230021373600164470ustar00rootroot00000000000000REGRESSIONS: - Port ActiveSync backend to non-recursive Automake. IMPROVEMENTS: - Add a check for qt-mobility for QtContacts backend. - Probably client test should be built only when unit tests or integration tests are enabled. - Review CLEANFILES, DISTCLEANFILES, MAINTAINERCLEANFILES and MOSTLYCLEANFILES. That is - check which files should be assigned to which of CLEAN variables. - Check why distcheck outputs: ================== All 0 tests passed ================== There should have been at least one test being run. The same behavior exists in old build system. This is because neither unit tests nor integration tests are enabled by default. If we would want to enable them then we should add --enable-unit-tests and --enable-integration-tests into DISTCHECK_CONFIGURE_FLAGS in toplevel Makefile.am. - Check why there are so many failed tests when running `make check' explicitly. The same number of failures exists in old build system: Run: 583 Failure total: 528 Failures: 206 Errors: 322 - Tidy up configure.ac and some .am files. - Maybe write scripts generating configs_xml.am, templates.am and profiles.am. QUESTIONS: - None. PERSISTENT: - Hunt for common variables clobbering. - Silence more verbose output if found. NITPICKS: - Check if SyncEvolutionXML.c should always be recreated, patches always checked and test-client always relinked. - Maybe do not create /share/man/man1 directory if we do not put there anything. - If backends are static libraries then do not create /lib/syncevolution, because nothing is put here. - Change $(foo) to @foo@ for all variables substituted by configure script. This might be useful when looking for actual value of variable appearing out of nowhere in .am file. Maybe make also all configure variables UPPER_CASE and all local Automake variables lower_case. - Lower autoreconf's warnings level later. Namely - don't warn about some portability issues. The warnings about obsolete stuff should rather remain. For now only some glib macros are using obsolete features. - Should stamp files be marked as intermediate or rather as secondary files? - Check why 'copying selected object files to avoid basename conflicts...' is displayed between linking src/dbus/server/libsyncevodbusserver.la and compiling src/gdbus/src_gdbus_libgdbussyncevo_la-debug.lo. This is probably harmless. TRACKING: - Handle nobase prefixes. Link to track: http://debbugs.gnu.org/cgi/bugreport.cgi?bug=9289 - Explanation why make -jX used to fail: It failed because usually Automake generates dependencies of a library/program by looking at its _LIBADD/_LDADD variable. But this generation doesn't work correctly when _LIBADD/_LDADD variable has AC_SUBSTed variable - automake then just discards them and resulting _DEPENDENCIES variable does not contain them. As a workaround _DEPENDENCIES variable have to be written explicitly. Link to track: http://debbugs.gnu.org/cgi/bugreport.cgi?bug=9320 syncevolution_1.4/COPYING000066400000000000000000000023641230021373600154160ustar00rootroot00000000000000SyncEvolution - the next generation data synchronization program using SyncML Copyright (C) 2005-2006 Patrick Ohly Copyright (C) 2007 Funambol (up to and including release 0.7, but no later version) Copyright (C) 2007-2008 Patrick Ohly This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ------------------------------------------------------------------- Some of the files included in the SyncEvolution distribution were written by other authors and/or are distributed under a different license. See the individual COPYING files in subdirectories. syncevolution_1.4/ChangeLog000066400000000000000000000003101230021373600161220ustar00rootroot000000000000002009-02-27 Patrick Ohly * SyncEvolution moved from Subversion to Git. If you are reading this then you should use `git log` to read the changes from the commit messages. syncevolution_1.4/Doxyfile000066400000000000000000001516411230021373600160740ustar00rootroot00000000000000# Doxyfile 1.5.3 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project # # All text after a hash (#) is considered a comment and will be ignored # The format is: # TAG = value [value, ...] # For lists items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (" ") #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file that # follow. The default is UTF-8 which is also the encoding used for all text before # the first occurrence of this tag. Doxygen uses libiconv (or the iconv built into # libc) for the transcoding. See http://www.gnu.org/software/libiconv for the list of # possible encodings. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded # by quotes) that should identify the project. PROJECT_NAME = "SyncEvolution and Funambol" # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or # if some version control system is used. PROJECT_NUMBER = $(VERSION) # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. # If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. OUTPUT_DIRECTORY = $(OUTPUT_DIRECTORY) # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create # 4096 sub-directories (in 2 levels) under the output directory of each output # format and will distribute the generated files over these directories. # Enabling this option can be useful when feeding doxygen a huge amount of # source files, where putting all generated files in the same directory would # otherwise cause performance problems for the file system. CREATE_SUBDIRS = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # The default language is English, other supported languages are: # Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, # Croatian, Czech, Danish, Dutch, Finnish, French, German, Greek, Hungarian, # Italian, Japanese, Japanese-en (Japanese with English messages), Korean, # Korean-en, Lithuanian, Norwegian, Polish, Portuguese, Romanian, Russian, # Serbian, Slovak, Slovene, Spanish, Swedish, and Ukrainian. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will # include brief member descriptions after the members that are listed in # the file and class documentation (similar to JavaDoc). # Set to NO to disable this. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend # the brief description of a member or function before the detailed description. # Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator # that is used to form the text in various listings. Each string # in this list, if found as the leading text of the brief description, will be # stripped from the text and the result after processing the whole list, is # used as the annotated text. Otherwise, the brief description is used as-is. # If left blank, the following values are used ("$name" is automatically # replaced with the name of the entity): "The $name class" "The $name widget" # "The $name file" "is" "provides" "specifies" "contains" # "represents" "a" "an" "the" ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full # path before files name in the file list and in the header files. If set # to NO the shortest path that makes the file name unique will be used. FULL_PATH_NAMES = YES # If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag # can be used to strip a user-defined part of the path. Stripping is # only done if one of the specified strings matches the left-hand part of # the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the # path to strip. STRIP_FROM_PATH = $(STRIP_FROM_PATH) # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of # the path mentioned in the documentation of a class, which tells # the reader which header file to include in order to use a class. # If left blank only the name of the header file containing the class # definition is used. Otherwise one should specify the include paths that # are normally passed to the compiler using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter # (but less readable) file names. This can be useful is your file systems # doesn't support long names like on DOS, Mac, or CD-ROM. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen # will interpret the first line (until the first dot) of a JavaDoc-style # comment as the brief description. If set to NO, the JavaDoc # comments will behave just like regular Qt-style comments # (thus requiring an explicit @brief command for a brief description.) JAVADOC_AUTOBRIEF = NO # If the QT_AUTOBRIEF tag is set to YES then Doxygen will # interpret the first line (until the first dot) of a Qt-style # comment as the brief description. If set to NO, the comments # will behave just like regular Qt-style comments (thus requiring # an explicit \brief command for a brief description.) QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen # treat a multi-line C++ special comment block (i.e. a block of //! or /// # comments) as a brief description. This used to be the default behaviour. # The new default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. MULTILINE_CPP_IS_BRIEF = NO # If the DETAILS_AT_TOP tag is set to YES then Doxygen # will output the detailed description near the top, like JavaDoc. # If set to NO, the detailed description appears after the member # documentation. DETAILS_AT_TOP = NO # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it # re-implements. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce # a new page for each member. If set to NO, the documentation of a member will # be part of the file/class/namespace that contains it. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. TAB_SIZE = 8 # This tag can be used to specify a number of aliases that acts # as commands in the documentation. An alias has the form "name=value". # For example adding "sideeffect=\par Side Effects:\n" will allow you to # put the command \sideeffect (or @sideeffect) in the documentation, which # will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. ALIASES = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C # sources only. Doxygen will then generate output that is more tailored for C. # For instance, some of the names that are used will be different. The list # of all members will be omitted, etc. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java # sources only. Doxygen will then generate output that is more tailored for Java. # For instance, namespaces will be presented as packages, qualified scopes # will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want to # include (a tag file for) the STL sources as input, then you should # set this tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); v.s. # func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. CPP_CLI_SUPPORT = NO # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES (the default) to allow class member groups of # the same type (for instance a group of public functions) to be put as a # subgroup of that type (e.g. under the Public Functions section). Set it to # NO to prevent subgrouping. Alternatively, this can be done per class using # the \nosubgrouping command. SUBGROUPING = YES #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. # Private class members and static file members will be hidden unless # the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES all private members of a class # will be included in the documentation. EXTRACT_PRIVATE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. EXTRACT_STATIC = NO # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) # defined locally in source files will be included in the documentation. # If set to NO only classes defined in header files are included. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. When set to YES local # methods, which are defined in the implementation section but not in # the interface are included in the documentation. # If set to NO (the default) only methods in the interface are included. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be extracted # and appear in the documentation as a namespace called 'anonymous_namespace{file}', # where file will be replaced with the base name of the file that contains the anonymous # namespace. By default anonymous namespace are hidden. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members of documented classes, files or namespaces. # If set to NO (the default) these members will be included in the # various overviews, but no documentation section is generated. # This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. # If set to NO (the default) these classes will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all # friend (class|struct|union) declarations. # If set to NO (the default) these declarations will be included in the # documentation. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. # If set to NO (the default) these blocks will be appended to the # function's detailed documentation block. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation # that is typed after a \internal command is included. If the tag is set # to NO (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate # file names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen # will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen # will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = YES # If the INLINE_INFO tag is set to YES (the default) then a tag [inline] # is inserted in the documentation for inline members. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen # will sort the (detailed) documentation of file and class members # alphabetically by member name. If set to NO the members will appear in # declaration order. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the # brief documentation of file, namespace and class members alphabetically # by member name. If set to NO (the default) the members will appear in # declaration order. SORT_BRIEF_DOCS = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be # sorted by fully-qualified names, including namespaces. If set to # NO (the default), the class list will be sorted only by class name, # not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the # alphabetical list. SORT_BY_SCOPE_NAME = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or # disable (NO) the todo list. This list is created by putting \todo # commands in the documentation. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or # disable (NO) the test list. This list is created by putting \test # commands in the documentation. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or # disable (NO) the bug list. This list is created by putting \bug # commands in the documentation. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or # disable (NO) the deprecated list. This list is created by putting # \deprecated commands in the documentation. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional # documentation sections, marked by \if sectionname ... \endif. ENABLED_SECTIONS = API # The MAX_INITIALIZER_LINES tag determines the maximum number of lines # the initial value of a variable or define consists of for it to appear in # the documentation. If the initializer consists of more lines than specified # here it will be hidden. Use a value of 0 to hide initializers completely. # The appearance of the initializer of individual variables and defines in the # documentation can be controlled using \showinitializer or \hideinitializer # command in the documentation regardless of this setting. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated # at the bottom of the documentation of classes and structs. If set to YES the # list will mention the files that were used to generate the documentation. SHOW_USED_FILES = YES # If the sources in your project are distributed over multiple directories # then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy # in the documentation. The default is NO. SHOW_DIRECTORIES = NO # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from the # version control system). Doxygen will invoke the program by executing (via # popen()) the command , where is the value of # the FILE_VERSION_FILTER tag, and is the name of an input file # provided by doxygen. Whatever the program writes to standard output # is used as the file version. See the manual for examples. FILE_VERSION_FILTER = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = YES # If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings # for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. WARN_IF_UNDOCUMENTED = NO # If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some # parameters in a documented function, or documenting parameters that # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be abled to get warnings for # functions that are documented, but have no documentation for their parameters # or return value. If set to NO (the default) doxygen will only warn about # wrong or incomplete parameter documentation, but not about the absence of # documentation. WARN_NO_PARAMDOC = NO # The WARN_FORMAT tag determines the format of the warning messages that # doxygen can produce. The string should contain the $file, $line, and $text # tags, which will be replaced by the file and line number from which the # warning originated and the warning text. Optionally the format may contain # $version, which will be replaced by the version of the file (if it could # be obtained via FILE_VERSION_FILTER) WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning # and error messages should be written. If left blank the output is written # to stderr. WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag can be used to specify the files and/or directories that contain # documented source files. You may enter file names like "myfile.cpp" or # directories like "/usr/src/myproject". Separate the files or directories # with spaces. INPUT = src test $(CLIENT_LIBRARY) # This tag can be used to specify the character encoding of the source files that # doxygen parses. Internally doxygen uses the UTF-8 encoding, which is also the default # input encoding. Doxygen uses libiconv (or the iconv built into libc) for the transcoding. # See http://www.gnu.org/software/libiconv for the list of possible encodings. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank the following patterns are tested: # *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx # *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py FILE_PATTERNS = # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used select whether or not files or # directories that are symbolic links (a Unix filesystem feature) are excluded # from the input. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. Note that the wildcards are matched # against the file with absolute path, so to exclude all test directories # for example use the pattern */test/* EXCLUDE_PATTERNS = **/.svn */src/client-api/ */src/core/vocl */src/core/boost/* */e-cal-check-timezones.* # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the output. # The symbol name can be a fully qualified name, a word, or if the wildcard * is used, # a substring. Examples: ANamespace, AClass, AClass::ANamespace, ANamespace::*Test EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or # directories that contain example code fragments that are included (see # the \include command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank all files are included. EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude # commands irrespective of the value of the RECURSIVE tag. # Possible values are YES and NO. If left blank NO is used. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or # directories that contain image that are included in the documentation (see # the \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command , where # is the value of the INPUT_FILTER tag, and is the name of an # input file. Doxygen will then use the output that the filter program writes # to standard output. If FILTER_PATTERNS is specified, this tag will be # ignored. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: # pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further # info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER # is applied to all files. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will be used to filter the input files when producing source # files to browse (i.e. when SOURCE_BROWSER is set to YES). FILTER_SOURCE_FILES = NO #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will # be generated. Documented entities will be cross-referenced with these sources. # Note: To get rid of all source code in the generated output, make sure also # VERBATIM_HEADERS is set to NO. If you have enabled CALL_GRAPH or CALLER_GRAPH # then you must also enable this option. If you don't then doxygen will produce # a warning and turn it on anyway SOURCE_BROWSER = YES # Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct # doxygen to hide any special comment blocks from generated source code # fragments. Normal C and C++ comments will always remain visible. STRIP_CODE_COMMENTS = NO # If the REFERENCED_BY_RELATION tag is set to YES (the default) # then for each documented function all documented # functions referencing it will be listed. REFERENCED_BY_RELATION = YES # If the REFERENCES_RELATION tag is set to YES (the default) # then for each documented function all documented entities # called/used by that function will be listed. REFERENCES_RELATION = YES # If the REFERENCES_LINK_SOURCE tag is set to YES (the default) # and SOURCE_BROWSER tag is set to YES, then the hyperlinks from # functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will # link to the source code. Otherwise they will link to the documentstion. REFERENCES_LINK_SOURCE = YES # If the USE_HTAGS tag is set to YES then the references to source code # will point to the HTML generated by the htags(1) tool instead of doxygen # built-in source browser. The htags tool is part of GNU's global source # tagging system (see http://www.gnu.org/software/global/global.html). You # will need version 4.8.6 or higher. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen # will generate a verbatim copy of the header file for each class for # which an include is specified. Set to NO to disable this. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index # of all compounds will be generated. Enable this if the project # contains a lot of classes, structs, unions or interfaces. ALPHABETICAL_INDEX = NO # If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then # the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns # in which this list will be split (can be a number in the range [1..20]) COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all # classes will be put under the same header in the alphabetical index. # The IGNORE_PREFIX tag can be used to specify one or more prefixes that # should be ignored while generating the index headers. IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `html' will be used as the default path. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for # each generated HTML page (for example: .htm,.php,.asp). If it is left blank # doxygen will generate files with .html extension. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a personal HTML header for # each generated HTML page. If it is left blank doxygen will generate a # standard header. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a personal HTML footer for # each generated HTML page. If it is left blank doxygen will generate a # standard footer. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading # style sheet that is used by each HTML page. It can be used to # fine-tune the look of the HTML output. If the tag is left blank doxygen # will generate a default style sheet. Note that doxygen will try to copy # the style sheet file to the HTML output directory, so don't put your own # stylesheet in the HTML output directory as well, or it will be erased! HTML_STYLESHEET = # If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, # files or namespaces will be aligned in HTML using tables. If set to # NO a bullet list will be used. HTML_ALIGN_MEMBERS = YES # If the GENERATE_HTMLHELP tag is set to YES, additional index files # will be generated that can be used as input for tools like the # Microsoft HTML help workshop to generate a compressed HTML help file (.chm) # of the generated HTML documentation. GENERATE_HTMLHELP = NO # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. For this to work a browser that supports # JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox # Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). HTML_DYNAMIC_SECTIONS = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can # be used to specify the file name of the resulting .chm file. You # can add a path in front of the file if the result should not be # written to the html output directory. CHM_FILE = # If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can # be used to specify the location (absolute path including file name) of # the HTML help compiler (hhc.exe). If non-empty doxygen will try to run # the HTML help compiler on the generated index.hhp. HHC_LOCATION = # If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag # controls if a separate .chi index file is generated (YES) or that # it should be included in the master .chm file (NO). GENERATE_CHI = NO # If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag # controls whether a binary table of contents is generated (YES) or a # normal table of contents (NO) in the .chm file. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the HTML help documentation and to the tree view. TOC_EXPAND = NO # The DISABLE_INDEX tag can be used to turn on/off the condensed index at # top of each HTML page. The value NO (the default) enables the index and # the value YES disables it. DISABLE_INDEX = NO # This tag can be used to set the number of enum values (range [1..20]) # that doxygen will group on one line in the generated HTML documentation. ENUM_VALUES_PER_LINE = 4 # If the GENERATE_TREEVIEW tag is set to YES, a side panel will be # generated containing a tree-like index structure (just like the one that # is generated for HTML Help). For this to work a browser that supports # JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, # Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are # probably better off using the HTML help feature. GENERATE_TREEVIEW = NO # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be # used to set the initial width (in pixels) of the frame in which the tree # is shown. TREEVIEW_WIDTH = 250 #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- # If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. GENERATE_LATEX = no # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `latex' will be used as the default path. LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. If left blank `latex' will be used as the default command name. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to # generate index for LaTeX. If left blank `makeindex' will be used as the # default command name. MAKEINDEX_CMD_NAME = makeindex # If the COMPACT_LATEX tag is set to YES Doxygen generates more compact # LaTeX documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_LATEX = NO # The PAPER_TYPE tag can be used to set the paper type that is used # by the printer. Possible values are: a4, a4wide, letter, legal and # executive. If left blank a4wide will be used. PAPER_TYPE = a4wide # The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for # the generated latex document. The header should contain everything until # the first chapter. If it is left blank doxygen will generate a # standard header. Notice: only use this tag if you know what you are doing! LATEX_HEADER = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated # is prepared for conversion to pdf (using ps2pdf). The pdf file will # contain links (just like the HTML output) instead of page references # This makes the output suitable for online browsing using a pdf viewer. PDF_HYPERLINKS = NO # If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of # plain latex in the generated Makefile. Set this option to YES to get a # higher quality PDF documentation. USE_PDFLATEX = NO # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. # command to the generated LaTeX files. This will instruct LaTeX to keep # running if errors occur, instead of asking the user for help. # This option is also used when generating formulas in HTML. LATEX_BATCHMODE = NO # If LATEX_HIDE_INDICES is set to YES then doxygen will not # include the index chapters (such as File Index, Compound Index, etc.) # in the output. LATEX_HIDE_INDICES = NO #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- # If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output # The RTF output is optimized for Word 97 and may not look very pretty with # other RTF readers or editors. GENERATE_RTF = NO # The RTF_OUTPUT tag is used to specify where the RTF docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `rtf' will be used as the default path. RTF_OUTPUT = rtf # If the COMPACT_RTF tag is set to YES Doxygen generates more compact # RTF documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_RTF = NO # If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated # will contain hyperlink fields. The RTF file will # contain links (just like the HTML output) instead of page references. # This makes the output suitable for online browsing using WORD or other # programs which support those fields. # Note: wordpad (write) and others do not support links. RTF_HYPERLINKS = NO # Load stylesheet definitions from file. Syntax is similar to doxygen's # config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an rtf document. # Syntax is similar to doxygen's config file. RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- # If the GENERATE_MAN tag is set to YES (the default) Doxygen will # generate man pages GENERATE_MAN = NO # The MAN_OUTPUT tag is used to specify where the man pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `man' will be used as the default path. MAN_OUTPUT = man # The MAN_EXTENSION tag determines the extension that is added to # the generated man pages (default is the subroutine's section .3) MAN_EXTENSION = .3 # If the MAN_LINKS tag is set to YES and Doxygen generates man output, # then it will generate one additional man file for each entity # documented in the real man page(s). These additional files # only source the real man page, but without them the man command # would be unable to find the correct page. The default is NO. MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- # If the GENERATE_XML tag is set to YES Doxygen will # generate an XML file that captures the structure of # the code including all documentation. GENERATE_XML = NO # The XML_OUTPUT tag is used to specify where the XML pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `xml' will be used as the default path. XML_OUTPUT = xml # The XML_SCHEMA tag can be used to specify an XML schema, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_SCHEMA = # The XML_DTD tag can be used to specify an XML DTD, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_DTD = # If the XML_PROGRAMLISTING tag is set to YES Doxygen will # dump the program listings (including syntax highlighting # and cross-referencing information) to the XML output. Note that # enabling this will significantly increase the size of the XML output. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will # generate an AutoGen Definitions (see autogen.sf.net) file # that captures the structure of the code including all # documentation. Note that this feature is still experimental # and incomplete at the moment. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- # If the GENERATE_PERLMOD tag is set to YES Doxygen will # generate a Perl module file that captures the structure of # the code including all documentation. Note that this # feature is still experimental and incomplete at the # moment. GENERATE_PERLMOD = NO # If the PERLMOD_LATEX tag is set to YES Doxygen will generate # the necessary Makefile rules, Perl scripts and LaTeX code to be able # to generate PDF and DVI output from the Perl module output. PERLMOD_LATEX = NO # If the PERLMOD_PRETTY tag is set to YES the Perl module output will be # nicely formatted so it can be parsed by a human reader. This is useful # if you want to understand what is going on. On the other hand, if this # tag is set to NO the size of the Perl module output will be much smaller # and Perl will parse it just the same. PERLMOD_PRETTY = YES # The names of the make variables in the generated doxyrules.make file # are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. # This is useful so different doxyrules.make files included by the same # Makefile don't overwrite each other's variables. PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will # evaluate all C-preprocessor directives found in the sources and include # files. ENABLE_PREPROCESSING = YES # If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro # names in the source code. If set to NO (the default) only conditional # compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. MACRO_EXPANSION = NO # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES # then the macro expansion is limited to the macros specified with the # PREDEFINED and EXPAND_AS_DEFINED tags. EXPAND_ONLY_PREDEF = NO # If the SEARCH_INCLUDES tag is set to YES (the default) the includes files # in the INCLUDE_PATH (see below) will be search if a #include is found. SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by # the preprocessor. INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the # directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that # are defined before the preprocessor is started (similar to the -D option of # gcc). The argument of the tag is a list of macros of the form: name # or name=definition (no spaces). If the definition and the = are # omitted =1 is assumed. To prevent a macro definition from being # undefined via #undef or recursively expanded use the := operator # instead of the = operator. PREDEFINED = $(PREDEFINED) # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. # The macro definition that is found in the sources will be used. # Use the PREDEFINED tag if you want to use a different macro definition. EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then # doxygen's preprocessor will remove all function-like macros that are alone # on a line, have an all uppercase name, and do not end with a semicolon. Such # function macros are typically used for boiler-plate code, and will confuse # the parser if not removed. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- # The TAGFILES option can be used to specify one or more tagfiles. # Optionally an initial location of the external documentation # can be added for each tagfile. The format of a tag file without # this location is as follows: # TAGFILES = file1 file2 ... # Adding location for the tag files is done as follows: # TAGFILES = file1=loc1 "file2 = loc2" ... # where "loc1" and "loc2" can be relative or absolute paths or # URLs. If a location is present for each tag, the installdox tool # does not have to be run to correct the links. # Note that each tag file must have a unique name # (where the name does NOT include the path) # If a tag file is not located in the directory in which doxygen # is run, you must also specify the path to the tagfile here. TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create # a tag file that is based on the input files it reads. GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES all external classes will be listed # in the class index. If set to NO only the inherited external classes # will be listed. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed # in the modules index. If set to NO, only the current project's groups will # be listed. EXTERNAL_GROUPS = YES # The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will # generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base # or super classes. Setting the tag to NO turns the diagrams off. Note that # this option is superseded by the HAVE_DOT option below. This is only a # fallback. It is recommended to install and use dot, since it yields more # powerful graphs. CLASS_DIAGRAMS = YES # You can define message sequence charts within doxygen comments using the \msc # command. Doxygen will then run the mscgen tool (see http://www.mcternan.me.uk/mscgen/) to # produce the chart and insert it in the documentation. The MSCGEN_PATH tag allows you to # specify the directory where the mscgen tool resides. If left empty the tool is assumed to # be found in the default search path. MSCGEN_PATH = # If set to YES, the inheritance and collaboration graphs will hide # inheritance and usage relations if the target is undocumented # or is not a class. HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz, a graph visualization # toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) HAVE_DOT = YES # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect inheritance relations. Setting this tag to YES will force the # the CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect implementation dependencies (inheritance, containment, and # class references variables) of the class with other documented classes. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = NO # If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. TEMPLATE_RELATIONS = NO # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT # tags are set to YES then doxygen will generate a graph for each documented # file showing the direct and indirect include dependencies of the file with # other documented files. INCLUDE_GRAPH = YES # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and # HAVE_DOT tags are set to YES then doxygen will generate a graph for each # documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = YES # If the CALL_GRAPH, SOURCE_BROWSER and HAVE_DOT tags are set to YES then doxygen will # generate a call dependency graph for every global function or class method. # Note that enabling this option will significantly increase the time of a run. # So in most cases it will be better to enable call graphs for selected # functions only using the \callgraph command. CALL_GRAPH = NO # If the CALLER_GRAPH, SOURCE_BROWSER and HAVE_DOT tags are set to YES then doxygen will # generate a caller dependency graph for every global function or class method. # Note that enabling this option will significantly increase the time of a run. # So in most cases it will be better to enable caller graphs for selected # functions only using the \callergraph command. CALLER_GRAPH = NO # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES # then doxygen will show the dependencies a directory has on other directories # in a graphical way. The dependency relations are determined by the #include # relations between the files in the directories. DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. Possible values are png, jpg, or gif # If left blank png will be used. DOT_IMAGE_FORMAT = png # The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the # \dotfile command). DOTFILE_DIRS = # The MAX_DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of # nodes that will be shown in the graph. If the number of nodes in a graph # becomes larger than this value, doxygen will truncate the graph, which is # visualized by representing a node as a red box. Note that doxygen if the number # of direct children of the root node in a graph is already larger than # MAX_DOT_GRAPH_NOTES then the graph will not be shown at all. Also note # that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. DOT_GRAPH_MAX_NODES = 50 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the # graphs generated by dot. A depth value of 3 means that only nodes reachable # from the root by following a path via at most 3 edges will be shown. Nodes # that lay further from the root node will be omitted. Note that setting this # option to 1 or 2 may greatly reduce the computation time needed for large # code bases. Also note that the size of a graph can be further restricted by # DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. MAX_DOT_GRAPH_DEPTH = 0 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent # background. This is disabled by default, which results in a white background. # Warning: Depending on the platform used, enabling this option may lead to # badly anti-aliased labels on the edges of a graph (i.e. they become hard to # read). DOT_TRANSPARENT = NO # Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) # support this, this feature is disabled by default. DOT_MULTI_TARGETS = NO # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES (the default) Doxygen will # remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES #--------------------------------------------------------------------------- # Configuration::additions related to the search engine #--------------------------------------------------------------------------- # The SEARCHENGINE tag specifies whether or not a search engine should be # used. If set to NO the values of all tags below this one will be ignored. SEARCHENGINE = NO syncevolution_1.4/HACKING000066400000000000000000000362631230021373600153570ustar00rootroot00000000000000Compiling from Source --------------------- To compile the code the source or an installation of the Synthesis SyncML engine is needed. A compatible snapshot of it is included in SyncEvolution source packages and will be used automatically. The section _`Checking out the source` explains how to work with sources obtained via the git repositories. Also needed are the Evolution (can be disabled), Boost (>= 1.35) and pcre development files. For HTTP, either Curl or libsoup can be used. For compiling libsynthesis, sqlite, expat, libz are needed. On Debian based systems, the required packages can be installed with apt-get install evolution-data-server-dev \ libglib2.0-dev \ libecal1.2-dev libebook1.2-dev \ libsoup2.4-dev \ libpcre3-dev \ libexpat-dev \ libz-dev \ libboost-dev If gio (part of libglib) is not available in version 2.26 or higher, then libdbus can be used as fallback (not needed otherwise): apt-get install libdbus-glib-1-dev libboost-dev >= 1.34, available as libboost1.35-dev backport for Debian Etch. Necessary on some distros due to bad dependencies (not needed by SyncEvolution itself): apt-get install libdb3-dev Optional (enables reading proxy settings from GNOME preferences): apt-get install libsoup-gnome2.4-dev Optional (enables direct sync with phones): apt-get install libopenobex-dev libbluetooth-dev Optional (only used for SHA-256 when glib is not already a dependency): apt-get install libnss3-dev The test framework also requires CPPUnit: apt-get install libcppunit-dev For the GUI and its D-Bus based service backend: apt-get install xsltproc \ libglib2.0-dev \ libgtk2.0-dev libglade2-dev \ libgnome-keyring-dev \ libgconf2-dev libgnomevfs2-dev Optional packages for GUI: apt-get install libunique-dev libunique = ensure that GTK GUI only runs once per user Optional packages for GNOME Bluetooth Panel plugin: apt-get install libgnome-bluetooth-dev The plugin adds a button to invoke sync-UI after a device was paired which supports SyncML. The build system is the normal autotools system. See INSTALL for general instructions how to use that and "./configure --help" for SyncEvolution specific options. Note that compiling without the Evolution development files is possible. But because this is usually not what people want, the configure script needs explicit --disable-ecal --disable-ebook parameters, otherwise it will refuse to compile without Evolution support. When compiling from a git checkout, remember to run "./autogen.sh". It depends on: apt-get install libtool intltool automake Checking out the Source ----------------------- SyncEvolution is hosted on moblin.org. Anonymous access is via git clone git://git.moblin.org/syncevolution.git Before using sources checked out from Subversion, invoke "sh autogen.sh" with appropriate autotools packages installed. When running the configure script, it can be told to compile a Synthesis library automatically via the --with-synthesis-src configure option. This has advantages when modifying the Synthesis source together with SyncEvolution (no need to install Synthesis and thus faster compilation) and will bundle the Synthesis sources in source .tar.gz archives. The upstream Synthesis source code is here: git://www.synthesis.ch/libsynthesis.git The staging area for patches developed as part of Moblin are in the following repository: git://git.moblin.org/libsynthesis.git The intention is to include all these patches upstream to prevent forking the code. If you want to get patches included in the Synthesis code base, then please work directly with the upstream branch. For doing development work the recommended configure line is: configure --with-synthesis-src= \ --enable-warnings=fatal \ --enable-unit-tests \ --enable-libcurl \ --disable-shared \ --enable-developer-mode Enabling libcurl explicitly ensures that it gets built even when not the default. --disable-shared results in easier to debug executables (no shell wrapper scripts, all symbols available before the program runs). Backend libraries are dynamically scannned and loaded into syncevolution, the library path defaults to $prefix/syncevolution/backends. When developer-mode is enabled, it will scan libraries in current build directory instead. -Wno-unknown-pragmas is required to avoid warnings triggered by '#pragma }', a trick to preserve indention after 'extern "C" {' in /usr/include/evolution-data-server-1.12/libical/ Working with the Code --------------------- The code follows the code formatting of the Funambol C++ client library. Just emulate the existing code when possible. Exceptions derived from std::exception are used to report errors. The EvolutionSyncSource::handleException() function deals with logging the exception. SyncEvolution uses the a CPPUnit based testing framework. Configure with --enable-integration-tests and (optionally) --enable-unit-tests, then run "src/client-test" as described in src/client-test-app.cpp. It understands several environment variables, among them: - CLIENT_TEST_SERVER = chooses config - CLIENT_TEST_SOURCES = comma separated list of enabled sources, identified by their name For Evolution: eds_contact,eds_event,eds_task,eds_memo For file backend: file_contact,file_event,file_task,file_memo (uses same test data and vCard flavor as Evolution) - CLIENT_TEST_EVOLUTION_PREFIX=[name|file://] overrides the evolutionsource setting in the configuration; if file:// is used then these database will be created automatically NOTE : The automatic database creation is not supported for EDS 2.32. We need to create manually the databases using Evolution. New database names must be named ${CLIENT_TEST_EVOLUTION_PREFIX}${CLIENT_TEST_SOURCES}_[12]. Example: if CLIENT_TEST_EVOLUTION_PREFIX=Test_ and Client::Sync::eds_contact is used, then addressbooks with the names 'Test_eds_contact_1' and 'Test_eds_contact_2' must exist. - CLIENT_TEST_XML=1 use XML format as default instead of the normal WBXML For unattended testing: - CLIENT_TEST_FAILURES = comma separated list of tests which are allowed to fail without affecting the return code of the test runner; each list entry is a regex which must match a complete test name - CLIENT_TEST_SKIP = comma separated list of tests or test groups which are not to be executed at all; for this to work the test or test group has to be passed through test.h's version of ADD_TEST or FilterTest, which is the case for most tests but not all; as in CLIENT_TEST_FAILURES, each entry is a regex - CLIENT_TEST_LOG = name of server log file, will be copied and reset after each sync - CLIENT_TEST_ALARM = number of seconds a single test is allowed to run before aborting it Most Client::Sync tests use the default encoding, usually WBXML unless changed via CLIENT_TEST_XML=1. Client::Sync::*::testItemsXML always uses XML and Client::Sync::*::testItems always WBXML. Here are step-by-step instructions to get started with testing, using ScheduleWorld as example: - CLIENT_TEST_SERVER=scheduleworld \ CLIENT_TEST_EVOLUTION_PREFIX=file:///tmp/testing/ \ ./client-test -h => creates ~/.config/syncevolution/scheduleworld_[12]/ configs which use data bases under /tmp/testing, then prints all available tests - edit ~/.config/syncevolution/scheduleworld_[12]/spds/syncml/config.txt and enter account data for ScheduleWorld in both configurations; check that the syncURL is correct - CLIENT_TEST_SERVER=scheduleworld \ CLIENT_TEST_EVOLUTION_PREFIX=file:///tmp/testing/ \ ./client-test Client::Source => runs alls tests involving just local operations - CLIENT_TEST_SERVER=scheduleworld \ CLIENT_TEST_EVOLUTION_PREFIX=file:///tmp/testing/ \ ./client-test Client::Sync::vcard30::testCopy => runs one test that checks that one contact can be copied to and from the server using the two configurations - CLIENT_TEST_SERVER=scheduleworld \ CLIENT_TEST_EVOLUTION_PREFIX=file:///tmp/testing/ \ ./client-test Client::Sync => runs all tests which involve the SyncML server; tests involving just one source are run first, followed by the same tests with all enabled sources in two different orders "make valgrind" runs the same tests inside valgrind [http://www.valgrind.org]. A suppression file is used to hide errors inside system libraries which are not caused by SyncEvolution or Synthesis library code. Most likely the suppressions are system dependent and might have to be updated from time to time. Committing Changes ------------------ Here are some guidelines for well-formed commits: - Avoid unnecessary white space changes in the source code. - Don't mix unrelated changes in one single commit. Every single commit should leave the code in a functional state (compiles, unit tests pass, ...). - Document new functions and structures with Doxygen comments. These comments should really add information, not just repeat the name of the entity. At least paraphrase the names. - Each commit message should follow the format : [(BMC #1234)] - here refers to the module, class or functionality modified in the commit, for example "autotools" for the build scripts, "EvolutionContactSource" for the EDS contact backend, etc. - should be consise enough to describe the patch on its own. - The optional bugs.meego.com (BMC) number in round brackets refers to the issue addressed by the commit. Most changes, in particular bug fixes, should have such an issue filed for tracking purposes. - The body (hard-wrapped, multiple paragraphs for long text) should include the following pieces of information, if applicable: - problem statement (*why* is the change relevant?) - summary of the solution (*how* is the problem solved?) - what other solutions were considered and rejected (*context* for the solution) - known gaps (in particularly useful during code review, which will start with the oldest, possibly incomplete patch first) When writing source code comments and commit messages, always remember that you are communicating with someone. This person might be even be you in some distant future, trying to remember why the heck something was done the way it was done... There might not be a chance to ask questions, so think about what such a person might want to know and answer it in advance. There is a certain redundancy between source code comments, commit messages and issue tracker comments. Whenever possible, the description of the solution should be in the source code itself, with only a brief summary and/or pointer in the commit message. It's fine to copy-and-paste to reduce the overall effort. Building a Release ------------------ - increase version number in configure-pre.in/AM_INIT_AUTOMAKE For prereleases use "old version"+"new prerelease"-1 as package version. That ensures that package versions are higher than the old release, but lower than the final release. Avoid hyphens as part of the release names, i.e. use "0.7+0.8beta1" instead of "0.7+0.8-beta1". - ensure files were updated: ./NEWS debian/changelog - update po/LINGUAS - check and if necessary, bump the Synthesis .so versions (libsynthesis/src/Makefile.am.in) - ensure that Synthesis source code is tagged: use + if local changes exist - commit and tag SyncEvolution source code - make distcheck - compile binary .tar.gz packages for different Evolution versions; done automatically by runtests.py on estamos.de (= Debian 3.0), using different Garnome installations, and with special configure options to ensure maximum portability (LDFLAGS=-Wl,--as-needed --enable-static-cxx) - compile .deb for Maemo - update entries on the web about the release: http://maemo.org/downloads/product/OS2008/syncevolution/ http://www.estamos.de/blog/wp-admin http://www.estamos.de/projects/SyncEvolution/Roadmap.html http://freshmeat.net/projects/syncevolution/ Compiling for Maemo ------------------- unpack source archive in Scratchbox (for maximum compatibility: use Chinook 4.0 rootstrap; for support of all backends: ensure that the EDS-DBus calendar dev packages are installed), DEB_BUILD_OPTIONS=maemo fakeroot dpkg-buildpackage NOTE: dpkg-buildpackage -rfakeroot does *not* work as it leads to strange problems executing a.out during the client-src configure. Maemo EDS-DBus calendar dev packages /etc/apt/source.list: deb http://maemo.o-hand.com/packages chinook/ Compiling for iPhone -------------------- Requires iPhone toolchain and a libcurl compiled for the iPhone. libcurl is ideally configured as small as possible and statically (to avoid packaging problems): ./configure --prefix=/usr/local/iphone --host=arm-apple-darwin --disable-shared \ --disable-crypto-auth --without-gnutls --without-ssl --without-zlib \ --without-libssh2 --disable-ipv6 --disable-manual --disable-telnet \ --disable-tftp --disable-ldap --disable-file --disable-ftp manually set HAVE_POSIX_STRERROR_R in lib/config.h Potential problems with toolchain: std++ not found: ln -s libstdc++.6.dylib /usr/local/iphone-filesystem/usr/lib/libstdc++.dylib AddressBook framework must be added to iphone-dev/include/install-headers.sh.in Compile with curl-config in the PATH: PATH=/usr/local/iphone/bin/:$PATH ~/projects/SyncEvolution/trunk/configure --host=arm-apple-darwin --with-funambol-src=/home/patrick/projects/native CXXFLAGS=-O0 --disable-ecal --disable-ebook --enable-addressbook --prefix=/usr PATH=/usr/local/iphone/bin/:$PATH make all Build a package with: make distbin BINSUFFIX="iphone" Compiling for Mac OS X ---------------------- Configuring for development: /configure --with-funambol-src= \ --enable-addressbook \ --disable-ecal --disable-ebook \ CXXFLAGS="-Wall -Werror -Wno-unknown-pragmas" \ LDFLAGS="-framework Addressbook -framework CoreServices" \ CXXFLAGS=-g \ CFLAGS=-g Compiling final release: ./configure --enable-addressbook \ --disable-ecal --disable-ebook \ CXXFLAGS="-O -g -arch i386 -arch ppc" \ CFLAGS="-O -g -arch i386 -arch ppc" \ LDFLAGS="-arch i386 -arch ppc -framework Addressbook -framework CoreServices" \ --disable-dependency-tracking make BINSUFFIX=mac-os-x distbin Fine-grained memory checking: MallocStackLogging=1 MallocStackLoggingNoCompact=1 \ MallocScribble=1 MallocPreScribble=1 MallocGuardEdges=1 \ MallocCheckHeapStart=1 MallocCheckHeapEach=100 Debugging --------- The following packages contain debug information for relevant libraries on Ubuntu 7.10: evolution-data-server-dbg libglib2.0-0-dbg evolution-dbg Normally, syncevolution redirects stderr to its own log file. In crash scenarios the final error messages may get lost. To debug such cases, disable redirection by setting the environment variable SYNCEVOLUTION_DEBUG (value doesn't matter) and capture the output normally. syncevolution_1.4/INSTALL-tar-gz000066400000000000000000000007501230021373600166130ustar00rootroot00000000000000This archive contains all files necessary to run SyncEvolution *as part of the system*. This means that the files have to be placed in the root file system. This is necessary because some system services (D-Bus server) would not be able to find them without further hacks otherwise. To install the files, unpack them and then run: sudo cp -a usr/* /usr *** Warning: *** this bypasses the system's package management. If there are packages for your system, then better install those. syncevolution_1.4/LICENSE.LGPL-21000066400000000000000000000635041230021373600163500ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! syncevolution_1.4/LICENSE.txt000066400000000000000000000354451230021373600162140ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS syncevolution_1.4/Makefile.am000066400000000000000000000446771230021373600164340ustar00rootroot00000000000000include $(top_srcdir)/setup-variables.am include $(top_srcdir)/autotroll.am include $(top_srcdir)/build/build.am AUTOMAKE_OPTIONS = subdir-objects ## Process this file with automake to produce Makefile.in ACLOCAL_AMFLAGS = -I m4 -I m4-repo ${ACLOCAL_FLAGS} if COND_CORE SUBDIRS += $(SYNTHESIS_SUBDIR) else SUBDIRS += $(SYNTHESIS_SUBDIR_INCLUDES) endif SUBDIRS += . # choose D-Bus implementation if COND_GIO_GDBUS gdbus_dir = $(top_srcdir)/src/gdbusxx gdbus_build_dir = src/gdbusxx else gdbus_dir = $(top_srcdir)/src/gdbus gdbus_build_dir = src/gdbus endif disted_docs = distbin_docs = man_MANS = include src/src.am if COND_CORE include test/test.am SUBDIRS += po disted_docs += README NEWS COPYING $(TEST_README_FILES) test/syncevo-http-server-logging.conf distbin_docs += $(disted_docs) dist_doc_DATA += $(disted_docs) if COND_HTML_README distbin_docs += README.html # do not distribute in tarball. doc_DATA += README.html endif if COND_MAN_PAGES man_MANS += syncevolution.1 endif endif DIST_SUBDIRS += po dist_noinst_DATA += \ HACKING \ LICENSE.txt \ LICENSE.LGPL-21 \ README.rst \ description \ autogen.sh \ Doxyfile \ po/LINGUAS.README MAINTAINERCLEANFILES += Makefile.in config.h.in config.guess config.sub configure depcomp install-sh ltmain.sh missing mkinstalldirs TEST_README_FILES = $(wildcard $(top_srcdir)/test/README.*) # files and file patterns which are not meant to be packaged. DEV_FILE_PATTERN = $(1)$(2)/include $(1)$(2)/lib/*.so $(1)$(2)/lib/*.a $(1)$(2)/lib/*.la $(1)$(2)/lib/*/*.la $(1)$(2)/lib/pkgconfig # Exclude syncactivesync.so from bundle package because that needs to # be packaged separately. DEV_FILE_PATTERN += $(1)$(2)/lib/syncevolution/backends/syncactivesync.so # binary distribution as .tar.gz if COND_DBUS # when building with D-Bus, we have no choice: the service has to go into /usr # in order to be found distbin : $(distbin_docs) INSTALL-tar-gz all @ [ "$(BINSUFFIX)" ] || (echo "please invoke with e.g. 'make distbin BINSUFFIX=debian-3.1'"; exit 1 ) @ [ "$(prefix)" = "/usr" ] || (echo "please reconfigure with --prefix=/usr"; exit 1 ) rm -rf $(distdir) $(MAKE) DESTDIR=`pwd`/$(distdir) install $(MAKE) DESTDIR=`pwd`/$(distdir) installcheck rm -rf $(call DEV_FILE_PATTERN,$(distdir),/usr) for i in `find $(distdir) -type d | sort -r`; do rmdir $$i 2>/dev/null || true; done mkdir -p $(distdir)/usr/share/doc/syncevolution cp $(srcdir)/INSTALL-tar-gz $(distdir)/INSTALL cp $(filter-out all, $+) $(distdir)/usr/share/doc/syncevolution tar zcf $(distdir)-$(BINSUFFIX).tar.gz $(distdir) rm -rf $(distdir) else # without D-Bus, we can simply create an archive with a bin directory # and everything works distbin : $(distbin_docs) all @ [ "$(BINSUFFIX)" ] || (echo "please invoke with e.g. 'make distbin BINSUFFIX=debian-3.1'"; exit 1 ) rm -rf $(distdir) $(MAKE) prefix=`pwd`/$(distdir) install @echo removing developer files and empty directories rm -rf $(call DEV_FILE_PATTERN,$(distdir),/) for i in `find $(distdir) -type d | sort -r`; do rmdir $$i 2>/dev/null || true; done cp $(filter-out all, $+) $(distdir) tar zcf $(distdir)-$(BINSUFFIX).tar.gz $(distdir) rm -rf $(distdir) endif iphone : SyncEvolution.plist IPHONE_FILENAME = syncevolution-$(VERSION)-iphone.zip SyncEvolution.plist : SyncEvolution.plist.in $(IPHONE_FILENAME) $(AM_V_GEN)sed -e 's/__FILENAME__/$(IPHONE_FILENAME)/' \ -e 's/__VERSION__/$(VERSION)/' \ -e 's/__SIZE__/$(shell ls -l $(IPHONE_FILENAME) | sed -e 's/ */ /g' | cut -d ' ' -f 5)/' \ $< >$@ $(IPHONE_FILENAME) : all rm -rf syncevolution-iphone $@ $(MAKE) DESTDIR=`pwd`/syncevolution-iphone install rm -rf `ls -1 -d syncevolution-iphone/usr/share/doc/syncevolution/*/spds/sources/* | grep -v addressbook` perl -pi -e 's;SyncEvolution test #1;;;' \ -e 's;^type = text/(x-)?vcard;type = addressbook;m;' \ syncevolution-iphone/usr/share/doc/syncevolution/*/spds/sources/addressbook/config.txt cd syncevolution-iphone && zip -r ../$(IPHONE_FILENAME) . TYPE_deb = -D TYPE_rpm = -R # Dependency calculation is intentionally incomplete: # - don't force dependency on specific EDS libs via backends, their versions change too much (handled via --enable-evolution-compatibility and dynamic loading of the backends) # - ignore client-test dependencies (because users typically don't run it) # - ignore backend dependencies (should never prevent installing the bundle) # - be more flexible about kdelibs5 than dpkg-shlibdeps: it is found as package # for libkdeui.so.5 and libkdecore.so.5 on Ubuntu Lucid, but after Debian # Squeeze the package was replaced by individual library packages. On such # distros, libkdeui5 is what we need. # - same for kdepimlibs5 -> libakonadi-kde4 # - kdebase-runtime became kde-runtime in Debian Wheezy REQUIRES_SED_KDE = -e 's/kdelibs5 ([^,]*),/kdelibs5 | libkdeui5,/g' -e 's/kdepimlibs5 ([^,]*),/kdepimlibs5 | libakonadi-kde4,/g' -e 's/kdebase-runtime/kdebase-runtime | kde-runtime/g' REQUIRES_deb = --requires="'$(shell set -x; cd checkinstall/dist; LD_LIBRARY_PATH=$(distdir)/usr/lib:$(distdir)/usr/lib/syncevolution dpkg-shlibdeps -L$(srcdir)/src/shlibs.local --ignore-missing-info -O $$(for i in $$(find $(distdir) -type f -perm /u+x | grep -v -e client-test -e lib/syncevolution/backends/); do if file $$i | grep ELF >/dev/null; then echo $$i; fi; done) | sed $(REQUIRES_SED_KDE) -e 's/[^=]*=//')$(REQUIRES_deb_neon)$(REQUIRES_deb_ical)'" if NEON_COMPATIBILITY # --enable-neon-compatibility in src/backends/webdav: # replace dependencies from linking with hard-coded dlopen() dependencies REQUIRES_deb_neon = , libneon27 (>= 0.29.0) | libneon27-gnutls (>= 0.29.0) else REQUIRES_deb_neon = endif if ENABLE_ICAL REQUIRES_deb_ical = , libical0 | libical1 endif VERSION_deb = 1:$(STABLE_VERSION)$(VERSION) VERSION_rpm = `echo $(VERSION) | sed -e s/-/_/g` RELEASE = 2 # The package name: BINSUFFIX is used to distinguish binaries # for different Evolution releases. PKGNAME=syncevolution$(patsubst %,-%,$(BINSUFFIX)) # This is a list of packages (potentially) provided on estamos.de. # The current package conflicts with any of them, but not itself. PKGS = $(addprefix syncevolution-evolution-, 2.6 2.8 2.12) # When calling checkinstall we cannot install into /tmp # because any file created there will be excluded: that makes # sense, because "make install" might create temporary files # there. The current directory might be in /tmp, so use $HOME. # # --replaces is necessary for migrating from syncevolution-evolution- # to syncevolution-evolution (as per http://wiki.debian.org/Renaming_a_Package) # # When we build shared objects, then conflict with the corresponding # system libs. The assumption is that the system library is named # after the lib and its major version, which holds for libsmltk and # libsynthesis in Debian. deb rpm : checkinstall/dist/$(distdir) checkinstall/dist/debian/control (echo "SyncEvolution - synchronizing personal information management data" && cat $(srcdir)/description) >checkinstall/description-pak echo "/sbin/ldconfig" >checkinstall/postremove-pak echo "/sbin/ldconfig" >checkinstall/postinstall-pak conflicts=`ls -1 checkinstall/dist/$(distdir)/usr/lib/*.so.[0123456789] | sed -e 's;.*/;;' -e 's/\.so\.//' -e 's/$$/, /'` && \ tmpdir=`mktemp -d $$HOME/syncevolution.XXXXXXXXXX` && \ trap "rm -rf $$tmpdir" EXIT && \ cd checkinstall && \ fakeroot checkinstall checkinstall/description-pak tmpdir=`mktemp -d $$HOME/syncevolution.XXXXXXXXXX` && \ trap "rm -rf $$tmpdir" EXIT && \ cd checkinstall && \ fakeroot checkinstall /usr/share/doc/syncevolution-$*/README' && \ mv syncevolution-$**.deb .. all_phonies += checkinstall/dist/$(distdir) clean_dist checkinstall/dist/$(distdir): all rm -rf $@ $(MAKE) install DESTDIR=`pwd`/$@ $(MAKE) installcheck DESTDIR=`pwd`/$@ rm -rf $(call DEV_FILE_PATTERN,$@,/usr) clean-local: clean_dist clean_dist: rm -rf checkinstall # required by dpkg-shlibdeps checkinstall/dist/debian/control: mkdir -p ${dir $@} touch $@ # Build "html" inside the build dir, using source files # from the SyncEvolution source directory and (if built) # the installed client-api.build directory. all_phonies += doc clean-html doc: rm -rf html export VERSION="SyncEvolution $(VERSION)"; \ export OUTPUT_DIRECTORY="`pwd`"; \ export PREDEFINED="@BACKEND_DEFINES@ ENABLE_UNIT_TESTS ENABLE_INTEGRATION_TESTS"; \ export CLIENT_LIBRARY="@FUNAMBOL_SUBDIR@/test @FUNAMBOL_SUBDIR@/include "; \ export STRIP_FROM_PATH="$(srcdir) `dirname @FUNAMBOL_SUBDIR@`"; \ cd $(srcdir); doxygen clean-local: clean-html clean-html: rm -rf html all_dist_hooks += dot_dist_hook dot_dist_hook: @if test -d "$(srcdir)/.git"; \ then \ printf '%s' 'Creating ChangeLog...' && \ ( cd "$(top_srcdir)" && \ printf '%s\n\n' '# Generated by configure. Do not edit.'; \ $(top_srcdir)/missing --run perl $(top_srcdir)/build/gen-changelog.pl ) > ChangeLog.tmp && \ ( mv -f ChangeLog.tmp $(top_distdir)/ChangeLog && \ printf '%s\n' ' done.' ) || \ ( rm -f ChangeLog.tmp ; \ printf '%s\n' ' failed.'; \ echo Failed to generate ChangeLog >&2 ); \ else \ echo 'A git checkout is required to generate a ChangeLog.' >&2; \ fi if ENABLE_EVOLUTION_COMPATIBILITY # check .so (relevant for modular builds) and main syncevolution binary # (relevant in that case and for static builds) for dependencies on # problematic libraries and symbols # # Exclude *-2.so, these are EXTRA_BACKENDS for which other rules apply. # # ical_strdup is an exception because it is in SyncEvolution. all_local_installchecks += toplevel_so_check toplevel_so_check: for i in `find $(DESTDIR)/$(libdir)/syncevolution $(DESTDIR)/$(libdir)/libsyncevo* $(DESTDIR)/$(libdir)/libsynthesis* -name *.so | grep -v -2.so` $(DESTDIR)/$(bindir)/syncevolution; \ do \ if objdump -T -C $$i | grep -v :: | grep '\*UND\*' | sort | grep -v -w ical_strdup | grep -e ical -e " e_"; then \ echo "$$i should not depend on EDS, libical or libbluetooth"; \ exit 1; \ fi; \ if ldd $$i | grep -e libecal -e libebook -e libedata -e libical -e libbluetooth; then \ echo "$$i should not be linked against EDS, libical or libbluetooth"; \ exit 1; \ fi; \ done endif # Check that no executable or shared object depends on symbols in # libraries that it does not link against. Unnecessarily linking # against libs is okay, that can be caught and fixed by # -Wl,--as-needed. Depends on dpkg-shlibdeps, skipped if that is # not available. all_local_installchecks += toplevel_link_check toplevel_link_check: set -x; cd $(DESTDIR) && \ mkdir debian && \ touch debian/control && \ trap "rm -rf debian" EXIT && \ files=$$(find $(DESTDIR)/$(prefix) $(DESTDIR)/$(libdir) $(DESTDIR)/$(bindir) $(DESTDIR)/$(libexecdir) -type f -perm /u+x | sort -u) && \ files=$$(for i in $$files; do if file $$i | grep ELF >/dev/null; then echo $$i; fi; done) && \ if ! dpkg-shlibdeps --version; then \ echo "dpkg-shlibdeps not found, skipping link check"; \ elif LD_LIBRARY_PATH=usr/lib:usr/lib/syncevolution dpkg-shlibdeps \ --ignore-missing-info -O $$files \ 2>&1 >/dev/null | \ grep -v $(LINK_CHECK_ALLOWED) | \ grep -e "symbol .* found in none of the libraries" \ -e "contains an unresolvable reference to symbol" \ ; then \ echo "linking must be fixed"; false; \ else \ echo "linking is okay"; \ fi # Some exceptions for the link check above (= symbol may be used without linking). LINK_CHECK_ALLOWED = -e xxxxxxxx # SySync_ConsolePrintf is expected by libsmltk and has to be provided by caller. LINK_CHECK_ALLOWED += -e 'SySync_ConsolePrintf.*libsmltk.so' if NEON_COMPATIBILITY # libneon is intentionally not linked against, to choose between # GNUTLS and OpenSSL at runtime. LINK_CHECK_ALLOWED += -e 'symbol ne_.*syncdav.so' # Allow undefined references to libstdcxx. This happens when # adding backends compiled on more recent Linux distros into # the release archive. LINK_CHECK_ALLOWED += -e '@GLIBCXX_[^ ]* used by' endif # Be strict about running 'syncevolution' only when not doing # cross-compilation: in that case, if running 'syncevolution' fails, # abort the build process. Otherwise proceed with the fallback below, # which is to keep the "see --sync/source-property ?" placeholders in # the README. if COND_CROSS_COMPILING RUN_SYNCEVOLUTION_CHECK=if ($$?) { return ""; } else { return $$buffer; } else RUN_SYNCEVOLUTION_CHECK=die if $$?; return $$buffer; endif # patch README.rst properties on-the-fly README.patched.rst: README.rst src/syncevolution $(AM_V_GEN)perl -e '$$syncfound=0; $$sourcefound=0; $$res=0;' \ -e 'sub run { $$cmd = shift; $$buffer = `env LD_LIBRARY_PATH=src/syncevo/.libs:src/gdbus/.libs:src/gdbusxx/.libs:src/build-synthesis/src/.libs:$$ENV{LD_LIBRARY_PATH} $$cmd`; $(RUN_SYNCEVOLUTION_CHECK) }' \ -e 'while (<>) {' \ -e 's/^:Version: .*/:Version: $(VERSION)/;' \ -e 's/:Date: .*/":Date: " . `date +%Y-%m-%d`/e;' \ -e 'if (s;(<< see "syncevolution --sync-property ." >>\n);run("src/syncevolution --daemon=no --sync-property ?") || $$1;e) { $$syncfound=1; }' \ -e 'if (s;(<< see "syncevolution --source-property ." >>\n);run("src/syncevolution --daemon=no --source-property ?") || $$1;e) { $$sourcefound=1; }' \ -e 'print;' \ -e '}' \ -e 'die "<> tag not in README.rst?!" unless $$syncfound;' \ -e 'die "<> tag not in README.rst?!" unless $$sourcefound;' \ -e 'exit $$res;' \ $< >$@ CLEANFILES += README.patched.rst # produce man pages syncevolution.1: README.patched.rst $(AM_V_GEN)$(RST2MAN) --exit-status=3 $< >$@ CLEANFILES += syncevolution.1 # README is the traditional name in the distribution, # continue using it instead of README.rst. # TODO: replace some of the RST syntax README: README.patched.rst $(AM_V_GEN)cp $< $@ CLEANFILES += README # The README.html is also used on syncevolution.org as "Usage" page, # therefore we must use

headers and lower to fit into the page. README.html: README.patched.rst $(AM_V_GEN)$(RST2HTML) --initial-header-level=3 --exit-status=3 $< >$@ CLEANFILES += README.html .PHONY: $(all_phonies) ; installcheck-local: $(all_local_installchecks) ; dist-hook: $(all_dist_hooks) install-exec-hook: $(all_install_exec_hooks) uninstall-hook: $(all_uninstall_hooks) # Force sequential installation. This is a workaround for relinking failures # during concurrent distcheck (a backend was relinked against not yet installed # libsyncevolution.la). # # Also used to add additional backends. EXTRA_BACKENDS must already contain # renamed files, by convention using -2.so, -3.so, etc as suffix instead of the # base file's .so. install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am @$(MAKE) $(AM_MAKEFLAGS) install-data-am for i in $(EXTRA_BACKENDS); do $(INSTALL) $$i $(DESTDIR)/$(BACKENDS_DIRECTORY)/; done # Necessary for "make distcheck": must not leave files behind. uninstall-local: rm -f $(DESTDIR)/$(BACKENDS_DIRECTORY)/*-[0-9].so .DELETE_ON_ERROR: syncevolution_1.4/NEWS000066400000000000000000011272351230021373600150700ustar00rootroot00000000000000SyncEvolution 1.3.2 -> 1.4, 16.02.2014 ====================================== The 1.4 relase of SyncEvolution is the first stable version with the in-vehicle infotainment (IVI) PIM Manager included. GENIVI Diagnostic Log and Trace (DLT) is also supported. For more information about this aspect of SyncEvolution, see the PBAP and PIM entries in the 1.3.99.x release notes and https://syncevolution.org/blogs/pohly/2013/pim-its-all-about-contacts The biggest change for normal Linux users is Google CalDAV/CardDAV authentication with OAuth2. These are the open protocol that Google currently supports and thus the recommended way of syncing with Google, replacing ActiveSync and SyncML (both no longer available to all Google customers). Support for Google CardDAV is new. Like Evolution, SyncEvolution does not yet support some of the advanced features of the server, in particular custom labels for phone numbers, emails and addresses. Likewise, some client properties are not supported by the server: CALURI, CATEGORIES, FBURL, GEO and ROLE are not supported. Of ORG, only the first two components are supported. Currently, properties not supported by one side get lost in a full roundtrip sync. SyncEvolution depends on external components for OAuth2. It can be compiled to use gSSO [1] or GNOME Online Accounts [2]. The latter is enabled in binaries from syncevolution.org. GNOME Online Accounts >= 3.10 works out of the box for CalDAV and CardDAV. 3.8 is guaranteed to work for CardDAV and may also work for CalDAV, if the Linux distribution ships a patched version (like Debian Wheezy does). If it does not, then GNOME Online Accounts 3.8 binary can be patched to also support CalDAV, see [2]. Anything older than 3.8 does not work. Support for Ubuntu Online Accounts is available when compiling from source. For setup instructions see these READMEs. [1] https://01.org/gsso and http://cgit.freedesktop.org/SyncEvolution/syncevolution/plain/src/backends/signon/README [2] http://cgit.freedesktop.org/SyncEvolution/syncevolution/plain/src/backends/goa/README Binary packages of 1.4 on syncevolution.org have enhanced support for recent distros. They now work with EDS >= 3.6 *and* < 3.6. Distros with libical1 like Ubuntu Saucy are also supported. The HTTP server became better at handling message resends when the server is slow with processing a message. The server is able to keep a sync session alive while loading the initial data set by sending acknowledgement replies before the client times out. Some issues in CalDAV, WebDAV and SyncML were fixed. Graham R. Cobb contributed several patches for enhancing ActiveSync support and making it work with Exchange 2010. Guido Günther provided some patches addressing problems when compiling SyncEvolution for Maemo. Details: * D-Bus server: support DLT (FDO #66769) Diagnostic Log and Trace (DLT) manages a sequence of log messages, with remote controllable level of detail. SyncEvolution optionally (can be chosen at compile time and again at runtime) uses DLT instead of its own syncevolution-log.html files. See README-DLT.rst for more information. To use the feature, configure SyncEvolution with "--enable-dbus-server=--dlt --no-syslog" * D-Bus server: fix abort when mixing auto-sync and manual operations (FDO #73562) When enabling auto-sync for a config and then accessing or syncing the config manually via the command line tool, the server would abort at the time when the auto-sync was originally scheduled. * D-Bus server: accept WBXML with charset in incoming connections A user reported via email that the Nokia 515 sends 'application/vnd.syncml+wbxml; charset=UTF-8' as type of its messages this tripped up the syncevo-http-server, leading to: [ERROR] syncevo-dbus-server: /org/syncevolution/Server: message type 'application/vnd.syncml+wbxml; charset=UTF-8' not supported for starting a sync * D-Bus server: command line options for controlling output and startup The system log is used by default now. New command line options can be used to change this: -d, --duration=seconds/'unlimited' Shut down automatically when idle for this duration (default 300 seconds) -v, --verbosity=level Choose amount of output, 0 = no output, 1 = errors, 2 = info, 3 = debug; default is 1. --dbus-verbosity=level Choose amount of output via D-Bus signals, 0 = no output, 1 = errors, 2 = info, 3 = debug; default is 2. -o, --stdout Enable printing to stdout (result of operations) and stderr (errors/info/debug). -s, --no-syslog Disable printing to syslog. -p, --start-pim Activate the PIM Manager (= unified address book) immediately. * D-Bus: missing out parameters in D-Bus introspection XML (FDO #57292) The problem was in the C++ D-Bus binding. If the method that gets bound to D-Bus returns a value, that value was ignored in the signature: int foo() => no out parameter It works when the method was declared as having a retval: void foo (int &result) => integer out parameter This problem existed for both the libdbus and the GIO D-Bus bindings. In SyncEvolution it affected methods like GetVersions(). * D-Bus server: avoid progress outside of 0-100% range For example in the new TestLocalCache.testItemDelete100, the percentage value in the ProgressChanged signal become larger than 100 and then revert to 100 at the end of the sync. Seems the underlying calculation is faulty or simply inaccurate. This is not fixed. Instead the result is just clipped to the valid range. * sync: less verbose output, shorter runtime For each incoming change, one INFO line with "received x[/out of y]" was printed, immediately followed by another line with total counts "added x, updated y, removed z". For each outgoing change, a "sent x[/out of y]" was printed. In addition, these changes were forwarded to the D-Bus server where a "percent complete" was calculated and broadcasted to clients. All of that caused a very high overhead for every single change, even if the actual logging was off. The syncevo-dbus-server was constantly consuming CPU time during a sync when it should have been mostly idle. To avoid this overhead, the updated received/sent numbers that come from the Synthesis engine are now cached and only processed when done with a SyncML message or some other event happens (whatever happens first). To keep the implementation simple, the "added x, updated y, removed z" information is ignored completely and no longer appears in the output. * command line: implement --create/remove-database Creating a database is only possible with a chosen name. The UID is chosen automatically by the storage. Only implemented in the EDS backend. * command line: execute --export and --print-items while the source is still reading Instead of reading all item IDs, then iterating over them, process each new ID as soon as it is available. With sources that support incremental reading (only the PBAP source at the moment) that provides output sooner and is a bit more memory efficient. * command line: recover from slow sync with new sync modes The error message for an unexpected slow sync still mentioned the old and obsolete "refresh-from-client/server" sync modes. Better mention "refresh-from-local/remote". * command line: show backend error when listing databases fails The command line swallowed errors thrown by the backend while listing databases. Instead it just showed ": backend failed". The goal was to not distract users who accidentally access a non-functional backend. But the result is that operations like --configure or --print-databases could fail without giving the user any hint about the root cause of the issue. Now the error explanation in all its gory details is included. For example, not having activesyncd running leads to: INFO] eas_contact: backend failed: fetching folder list: GDBus.Error:org.freedesktop.DBus.Error.ServiceUnknown: The name org.meego.activesyncd was not provided by any .service files And running activesyncd without the necessary gconf keys shows up as: [INFO] eas_contact: backend failed: fetching folder list: GDBus.Error:org.meego.activesyncd.Error.AccountNotFound: Failed to find account [syncevolution@lists.intel.com] * password handling: fix usage of GNOME Keyring and KWallet (FDO #66110) When clients like the GTK sync-ui stored a password, it was always stored as plain text in the config.ini file by the syncevo-dbus-server. The necessary code for redirecting the password storage in a keyring (GNOME or KWallet) simply wasn't called in that case. The command line tool, even when using the D-Bus server to run the operation, had the necessary code active and thus was not affected. Now all SyncEvolution components use the same default: use safe password storage if either GNOME Keyring or KWallet were enabled during compilation, don't use it if not. Fixing this revealed other problems, like not being able to store certain passwords that lacked the necessary lookup criteria (like syncURL and/or username). To address this, the lookup criteria where extended and a new check was added to avoid accidentally removing other passwords. As a result, it may be possible that SyncEvolution no longer finds passwords that were stored with older versions of SyncEvolution. In such a case the passwords must be set again. * GNOME: clean up keyring access and require libgnome-keyring >= 2.20 The updated error messages now always include information about the password and libgnome-keyring error texts. A workaround is used for the "Error communicating with gnome-keyring-daemon" problem that started to appear fairly frequently in the automated testing once the keyring was actually used. The problem shows up with some additional debug messages: Gkr: received an invalid, unencryptable, or non-utf8 secret Gkr: call to daemon returned an invalid response: (null).(null)() It seems that sometimes setting up a session with GNOME keyring fails such that all further communication leads to decoding problem. There is an internal method to reset the session, but it cannot be called directly. As a workaround, fake the death of the GNOME keyring daemon and thus trigger a reconnect when retrying the GNOME keyring access. This is done by sending a D-Bus message, which will also affect other clients of GNOME keyring, but hopefully without user-visible effects. * config: enhanced password handling It is possible to configure a plain username/password combination once in SyncEvolution and then use references to it in other configurations, instead of having to set (and update) the credentials in different places. This is useful in particular with WebDAV, where credentials had to be repeated several times (target config, in each database when used as part of SyncML) or when using a service which requires several configs (Google via SyncML and CalDAV). To use this, create a sync config for a normal peer or a dedicated config just for the credentials, with "username/password/syncURL" set. The "syncURL" must be set to something identifying the peer if GNOME Keyring is used for the password storage. Then set "username", "databaseUser" and "proxyUser" properties to "id:" and all read and write access to those properties will be redirected by SyncEvolution into that other configuration. This even works in the GTK UI. For user names which contain colons, the new "user:" format must be used. Strings without colons are assumed to be normal user names, so most old configurations should continue to work. * signon: new backend using libgsignond-glib + libaccounts-glib The code works with gSSO (https://01.org/gsso) and Ubuntu Online Accounts. * GOA: get OAuth2 tokens out of GNOME Online Accounts "username = goa:..." selects an account in GOA and retrieves the OAuth2 token from that. * WebDAV: support OAuth2 If given an authentication configuration which can handle OAuth2, then OAuth2 is used instead of plain username/password authentication. * WebDAV: support Google CardDAV, break Yahoo Google CardDAV has one peculiarity: it renames new contacts during PUT without returning the new path to the client. See also http://lists.calconnect.org/pipermail/caldeveloper-l/2013-July/000524.html SyncEvolution already had a workaround for that (PROPGET on old path, extract new path from response) which happened to work. This workaround was originally added for Yahoo, which sometimes merges contacts into existing ones. In contrast to Yahoo, Google really seems to create new items. Without some server specific hacks, the client cannot tell what happened. Because Google is currently supported and Yahoo is not, let's change the hard-coded behavior to "renamed items are new". * WebDAV: started testing with owndrive.com = OwnCloud * WebDAV: avoid segfault during collection lookup Avoid referencing pathProps->second when the set of paths that PROPFINDs returns is empty. Apparently this can happen in combination with Calypso. * CalDAV: more workarounds for Google CalDAV + unique IDs Google became even more strict about checking REV. Tests which reused a UID after deleting the original item started to fail sometime since middle of December 2012. * CalDAV: work around Google server regression (undeclared namespace prefix in XML) Google CalDAV for a while (December 2012 till January 2013) sent invalid XML back when asked to include CardDAV properties in a PROPFIND. This got rejected in the XML parser, which prevents syncing calendar data: Neon error code 1: XML parse error at line 55: undeclared namespace prefix In the meantime Google fixed the issue in response to a bug report via email. But the workaround, only asking for the properties which are really needed, still makes sense and thus is kept. * WebDAV: auto-discovery fix With Google Contact + CardDAV the auto-discovery failed after finding the default address book, without reporting that result. * WebDAV: don't send Basic Auth via http proactively (FDO #57248) Sending basic authentication headers via http is insecure. Only do it proactively when the connection is encrypted and thus protects the information or when the server explicitly asks for it. * file backend: sub-second mod time stamps Change tracking in the file backend used to be based on the modification time in seconds. When running many syncs quickly (as in testing), that can lead to changes not being detected when they happen within a second. Now the file backend also includes the sub-second part of the modification time stamp, if available. This change is relevant when upgrading SyncEvolution: most of the items will be considered "updated" once during the first sync after the upgrade (or a downgrade) because the revision strings get calculated differently. * GTK UI: fixed two crashes - running a sync with no service selected and a 64 bit pointer problem recently discovered by Tino Keitel when compiling the Debian package with -fPIE. * packaging: compatible with EDS up to and including 3.10 and both libical.so.0 and libical.so.1 The binary packages now contain different versions of syncecal.so and syncebooks.so to cover different combinations of EDS and libical. * libical: compatibiliy mode for libical.so.0 and libical.so.1 libical 1.0 broke the ABI, leading to libical.so.1. The only relevant change for SyncEvolution is the renumbering of ICAL_*_PROPERTY enum values. We can adapt to that change at runtime, which allows us to compile once with libical.so.0, then patch executables or use dynamic loading to run with the more recent libical.so.1 if we add 1 to the known constants. * packaging: fix rpm (FDO #73347) After installing the syncevolution.org rpm on OpenSUSE, SyncEvolution was not starting because its shared libraries were not found unless "ldconfig" was called manually. Now the package does that automatically. * packaging: fix description The syncevolution-bundle description of both rpm and deb packagesaccidentally used the same description as syncevolution-evolution. * glib: fix double-free of source tags glib 2.39.0 (aka GNOME 3.10) as found in Ubuntu Trusty introduces warnings when g_source_remove() is passed an unknown tag. SyncEvolution did this in two cases: in both, the source callback returned false and thus caused the source to be removed by the caller. In that case, the explicit g_source_remove() is redundant and must be avoided. Such a call is faulty and might accidentally remove a new source with the same tag (unlikely though, given that tags seem to get assigned incrementally). The only noticable effect were additional error messages with different numbers: [ERROR] GLib: Source ID 9 was not found when attempting to remove it * EDS: fix compile problem with boost and EDS > 3.36 This fixes the following problem, seen with Boost 1.53.0 on altlinux when compiling for EDS >= 3.6: /usr/include/boost/smart_ptr/shared_ptr.hpp: In instantiation of 'typename boost::detail::sp_array_access::type boost::shared_ptr::operator[](std::ptrdiff_t) const [with T = char*; typename boost::detail::sp_array_access::type = void; std::ptrdiff_t = long int]': src/backends/evolution/EvolutionSyncSource.cpp:163:38: required from here /usr/include/boost/smart_ptr/shared_ptr.hpp:663:22: error: return-statement with a value, in function returning 'void' [-fpermissive] make[2]: *** [src/backends/evolution/src_backends_evolution_syncecal_la-EvolutionSyncSource.lo] * EDS contacts: avoid unnecessary DB writes during slow sync Traditionally, contacts were modified shortly before writing into EDS to match with Evolution expectations (must have N, only one CELL TEL, VOICE flag must be set). During a slow sync, the engine compare the modified contacts with the unmodified, incoming one. This led to mismatches and/or merge operations which end up not changing anything in the DB because the only difference would be removed again before writing. * EDS contacts: read-ahead cache Performance is improved by requesting multiple contacts at once and overlapping reading with processing. On a fast system (SSD, CPU fast enough to not be the limiting factor), testpim.py's testSync takes 8 seconds for a "match" sync where 1000 contacts get loaded and compared against the same set of contacts. Read-ahead with only 1 contact per query speeds that up to 6.7s due to overlapping IO and processing. Read-ahead with the default 50 contacts per query takes 5.5s. It does not get much faster with larger queries. * PBAP: add support for obexd 0.47, 0.48 and Bluez 5 obexd 0.48 is almost the same as obexd 0.47, except that it dropped the SetFilter and SetFormat methods in favor of passing a Bluex 5-style filter parameter to PullAll. * PBAP: various enhancements for efficient caching of contacts * HTTP server: handle message resends If a client gave up waiting for the server's response and resent its message while the server was still processing the message, syncing failed with "protocol error: already processing a message" raised by the syncevo-dbus-server because it wasn't prepared to handle that situation. The right place to handle this is inside the syncevo-http-server, because it depends on the protocol (HTTP in this case) whether resending is valid or not. It handles that now by tracking the message that is currently in processing and matching it against each new message. If it matches, the new request replaces the obsolete one without sending the message again to syncevo-dbus-server. When syncevo-dbus-server replies to the old message, the reply is used to finish the newer request. * engine: prevent timeouts in HTTP server mode HTTP SyncML clients give up after a certain timeout (SyncEvolution after RetryDuration = 5 minutes by default, Nokia e51 after 15 minutes) when the server fails to respond. This can happen with SyncEvolution as server when it uses a slow storage with many items, for example via WebDAV. In the case of slow session startup, multithreading is now used to run the storage initializing in parallel to sending regular "keep-alive" SyncML replies to the client. By default, these replies are sent every 2 minutes. This can be configured with another extensions of the SyncMLVersion property: SyncMLVersion = REQUESTMAXTIME=5m Other modes do not use multithreading by default, but it can be enabled by setting REQUESTMAXTIME explicitly. It can be disabled by setting the time to zero. The new feature depends on a libsynthesis with multithreading enabled and glib >= 2.32.0, which is necessary to make SyncEvolution itself thread-safe. With an older glib, multithreading is disabled, but can be enabled as a stop-gap measure by setting REQUESTMAXTIME explicitly. * Various testing and stability enhancements. SyncEvolution had to be made thread-safe for the HTTP timeout prevention. * Nokia: always add TYPE=INTERNET to EMAIL (FDO #61784) Without the explicit TYPE=INTERNET, email addresses sent to a Nokia e51 were not shown by the phone and even got lost eventually (when syncing back). This commit ensures that the type is set for all emails sent to any Nokia phone, because there may be other phones which need it and phones which don't, shouldn't mind. This was spot-checked with a N97 mini, which works fine with and without the INTERNET type. This behavior can be disabled again for specific Nokia phones by adding a remote rule which sets the addInternetEmail session variable to FALSE again. Non-Nokia phones can enable the feature in a similar way, by setting the variable to TRUE. * SyncML: config option for broken peers Some peers have problems with meta data (CtCap, old Nokia phones) and the sync mode extensions required for advertising the restart capability (Oracle Beehive). The default in SyncEvolution is to advertise the capability, so manual configuration is necessary when working with a peer that fails in that mode. Because the problem occurs when SyncEvolution contacts the peers before it gets the device information from the peer, dynamic rules based on the peer identifiers cannot be used. Instead the local config must already disable these extra features in advance. The "SyncMLVersion" property gets extended for this. Instead of just "SyncMLVersion = 1.0" (as before) it now becomes possible to say "SyncMLVersion = 1.0, noctcap, norestart". "noctcap" disables sending CtCap. "norestart" disables the sync mode extensions and thus doing multiple sync cycles in the same session (used between SyncEvolution instances in some cases to get client and server into sync in one session). Both keywords are case-insensitive. There's no error checking for typos, so beware! The "SyncMLVersion" property was chosen because it was already in use for configuring SyncML compatibility aspects and adding a new property would have been harder. * ActiveSync: added support for specifying folder names Previously, the database field was interpreted as a Collection ID. This adds logic to allow the database to be interpreted as a folder path. The logic is: 1) If the database is an empty string, pass it through (this is the most common case as it is interpreted as "use the default folder for the source type"). 2) If the database matches a Collection ID, use the ID (this is the same as the previous behaviour). 3) If the database matches a folder path name, with an optional leading "/", use the Collection ID for the matching folder. 4) Otherwise, force a FolderSync to get the latest folder changes from the server and repeat steps 2 and 3 5) If still no match, throw an error. * ActiveSync: support for listing databases Now --print-databases scans folders on the ActiveSync server and shows suitable folders for the ActiveSync backends instead of the previous, hard-coded help text. Invoking --print-databases can be used as a workaround for "SyncFolder error: Invalid synchronization key" errors. A better solution would be to do that automatically, but there was no time to implement that. See FDO #61869 and "[SyncEvolution] Activesync server losing state" http://thread.gmane.org/gmane.comp.mobile.syncevolution/4295 * SyncML: workarounds for broken peers Some peers have problems with meta data (CtCap, old Nokia phones) and the sync mode extensions required for advertising the restart capability (Oracle Beehive). Because the problem occurs when SyncEvolution contacts the peers before it gets the device information from the peer, dynamic rules based on the peer identifiers cannot be used. Instead the local config must already disable these extra features in advance. The "SyncMLVersion" property gets extended for this. Instead of just "SyncMLVersion = 1.0" (as before) it now becomes possible to say "SyncMLVersion = 1.0, noctcap, norestart". "noctcap" disables sending CtCap. "norestart" disables the sync mode extensions and thus doing multiple sync cycles in the same session (used between SyncEvolution instances in some cases to get client and server into sync in one session). Both keywords are case-insensitive. There's no error checking for typos, so beware! The "SyncMLVersion" property was chosen because it was already in use for configuring SyncML compatibility aspects and adding a new property would have been harder. * engine: local cache sync mode This patch introduces support for true one-way syncing ("caching"): the local datastore is meant to be an exact copy of the data on the remote side. The assumption is that no modifications are ever made locally outside of syncing. This is different from one-way sync modes, which allows local changes and only temporarily disables sending them to the remote side. Another goal of the new mode is to avoid data writes as much as possible. This new mode only works on the server side of a sync, where the engine has enough control over the data flow. Setting "sync" to: - "local-cache-incremental" will do an incremental sync (if possible) or a slow sync (otherwise). This is usually the right mode to use, and thus has "local-cache" as alias. - "local-cache-slow" will always do a slow sync. Useful for debugging or after (accidentally) making changes on the local side. An incremental sync will ignore such changes because they are not meant to happen, aren't checked for to improve performance and thus will leave client and server out-of-sync! Both modes are recorded in the sync report of the local side. The target side is the client and records the normal "two-way" or "slow" sync modes. With the current SyncEvolution contact field list, first, middle and last name are used to find matches for contacts. For events, tasks and memos, time, summary and description are used. * Minor memory leak fix when using GDBus GIO: GDBusMethodInfo Also depends on a glib fix, see https://bugzilla.gnome.org/show_bug.cgi?id=695376 * build fixes Avoid -lrt in make dependencies. Add missing pcre libs to syncevo-dbus-server. sqlite backend needs "#include " (patch from Mario Kicherer). * autotools: fix temp file vulnerability during compilation (CVE-2014-1639) We must use the temporary file that was created for us securily, not a temp file named after that file. This caused a temp file vulnerability and the real temporary files were not deleted by the script. * workarounds for warnings from g++ 4.5 Upgrading from releases <= 1.3.99.4: If the value of "username/databaseUser/proxyUser" contains a colon, the "user:" prefix must be added to the value, to continue treating it like a plain user name and not some reference to an unknown identity provider (like "id:", "goa:", "signon:", etc.). The lookup of passwords in GNOME Keyring was updated slightly in 1.3.99.5. It may be necessary to set passwords anew if the old one is no longer found. Upgrading from release 1.2.x: The sync format of existing configurations for Mobical (aka Everdroid) must be updated manually, because the server has encoding problems when using vCard 3.0 (now the default for Evolution contacts): syncevolution --configure \ syncFormat=text/x-vcard \ mobical addressbook The Funambol template explicitly enables usage of the "refresh-from-server" sync mode to avoid getting throttled with 417 'retry later' errors. The same must be added to existing configs manually: syncevolution --configure \ enableRefreshSync=TRUE \ funambol Upgrading from releases before 1.2: Old configurations can still be read. But writing, as it happens during a sync, must migrate the configuration first. Releases >= 1.2 automatically migrates configurations. The old configurations will still be available (see "syncevolution --print-configs") but must be renamed manually to use them again under their original names with older SyncEvolution releases. SyncEvolution 1.3.99.7 -> 1.4, 16.02.2014 ========================================= Compared to the pre-release, 1.4 mostly just enhanced the testing. Compatibility with GNOME 3.10 and a glib-related issue that existed almost forever without causing obvious problems were fixed. syncevolution.org binaries now finally work with distros using libical.so.1 (for example, Ubuntu Saucy and Trusty). Details: * autotools: fix temp file vulnerability during compilation (CVE-2014-1639) We must use the temporary file that was created for us securily, not a temp file named after that file. This caused a temp file vulnerability and the real temporary files were not deleted by the script. * glib: fix double-free of source tags glib 2.39.0 (aka GNOME 3.10) as found in Ubuntu Trusty introduces warnings when g_source_remove() is passed an unknown tag. SyncEvolution did this in two cases: in both, the source callback returned false and thus caused the source to be removed by the caller. In that case, the explicit g_source_remove() is redundant and must be avoided. Such a call is faulty and might accidentally remove a new source with the same tag (unlikely though, given that tags seem to get assigned incrementally). The only noticable effect were additional error messages with different numbers: [ERROR] GLib: Source ID 9 was not found when attempting to remove it * libical: compatibiliy mode for libical.so.0 and libical.so.1 libical 1.0 broke the ABI, leading to libical.so.1. The only relevant change for SyncEvolution is the renumbering of ICAL_*_PROPERTY enum values. We can adapt to that change at runtime, which allows us to compile once with libical.so.0, then patch executables or use dynamic loading to run with the more recent libical.so.1 if we add 1 to the known constants. SyncEvolution 1.3.99.7, 22.01.2014 ================================== Final release candidate for 1.4. No further changes planned unless new problems are found. Details: * SSO: support Ubuntu Online Accounts When compiling from source on recent Ubuntu it becomes possible to use Ubuntu Online Accounts for authenticating against Google's CalDAV and CardDAV servers. * D-Bus server: fix abort when mixing auto-sync and manual operations (FDO #73562) When enabling auto-sync for a config and then accessing or syncing the config manually via the command line tool, the server would abort at the time when the auto-sync was originally scheduled. * D-Bus server: accept WBXML with charset in incoming connections A user reported via email that the Nokia 515 sends 'application/vnd.syncml+wbxml; charset=UTF-8' as type of its messages this tripped up the syncevo-http-server, leading to: [ERROR] syncevo-dbus-server: /org/syncevolution/Server: message type 'application/vnd.syncml+wbxml; charset=UT We need to strip the '; charset=UTF-8' suffix also when checking for WBXML. * packaging: compatible with EDS up to and including 3.10 The packages now contain three versions of syncecal.so: - one for EDS < 3.6 - one for EDS >= 3.6 < 3.10 - one for EDS >= 3.10 with the libecal-1.2 soname patched The third flavor became necessary because EDS 3.10 accidentally changed the soname. The API and ABI actually is the same. Package meta-data was fixed to reflect the extended range of compatible EDS libraries, so syncevolution-evolution can be installed again with recent EDS. * packaging: update syncevolution-kde dependencies kdebase-runtime became kde-runtime in Debian Wheezy. Accept both as prerequisite of syncevolution-kde to allow installation on newer distros without pulling in the transitional kdebase-runtime package. * packaging: fix rpm (FDO #73347) After installing the syncevolution.org rpm on OpenSUSE, SyncEvolution was not starting because its shared libraries were not found unless "ldconfig" was called manually. Now the package does that automatically. * packaging: fix description The syncevolution-bundle description of both rpm and deb packagesaccidentally used the same description as syncevolution-evolution. * test improvements, integration of cppcheck and clang's scan-build SyncEvolution 1.3.99.6, 04.12.2013 ================================== This update focuses on SyncEvolution in IVI again. It adds support for GENIVI Diagnostic Log and Trace (DLT) and enhances searching in the unified address book. The biggest change for normal Linux desktop users is enhanced support for recent distros. Binaries on syncevolution.org now work with EDS >= 3.6 *and* < 3.6. Distros with libical1 like Ubuntu Saucy are also supported. Automated testing was updated to cover these newer platforms more thoroughly. Details: * GNOME Online Accounts: fix D-Bus problem in syncevolution.org binaries Support was included in syncevolution.org binaries, but was not tested and did not actually work due to some issue accessing the D-Bus session. * libsynthesis: partial fix batching of items The batching of contact writes introduced with SyncEvolution 1.3.99.4 caused problems with non-SyncEvolution SyncML peers when syncing contacts stored in EDS >= 3.6. EDS < 3.6 was not affected. That part is fixed. However, even in SyncEvolution<->SyncEvolution syncs another crash was found. This will require more investigation. Clearly the feature is not ready yet for general sync, so for now it is disabled by default and only enabled in the simpler PBAP sync. * libsynthesis: avoid redundant (and sometimes slow) getaddrbyname() (FDO #70771) The network lookup of the hostname can be slow (10 second delay when not connected) and shouldn't be necessary anyway, so disable it. * PIM Manager: case-insensitive and transliterated search (FDO #56524) * PIM: accent-insensitive and transliterated search (FDO #56524) Accent-insensitive search ignores accents, using the same code as in EDS. Transliterated search ignores foreign scripts by transliterating search term and contact properties to Latin first. That one is using ICU directly in the same way as EDS, but doesn't use the EDS ETransliterator class to avoid extra string copying. This commit changes the default behavior such that searching is by default most permissive (case- and accent-insensitive, does transliteration). Flags exist to restore more restrictive matching. * PIM: relax phone number matching Previously, the current default country was used to turn phone numbers without an explicit country code into full E164 numbers, which then had to match the search term when doing a caller ID lookup. This was inconsistent with EDS, where a weaker EQUALS_NATIONAL_PHONE_NUMBER was done. The difference is that a comparison between a number with country code matches one without if the national number of the same, regardless of the current default country. This is better because it reduces the influence of the hard to guess default country on matching. Another advantage of this change is the lower memory consumption and faster comparison, because strings are now stored in 4 + 8 byte numbers instead of strings of varying length. * PIM: fix incorrect write into pim-manager.ini (FDO #70772) Removing a peer accidentally wrote the updated list of active address books into the "sort" property of pim-manager.ini, which then prevented starting the PIM Manager. * PIM: ignore broken sort order in config (FDO #70772) Failure to set the sort order from pim-manager.ini should not prevent the startup of the PIM Manager because the client cannot really diagnose and fix the problem. It is better to try again with the default sort order. * PIM: adapt to locale changes at runtime (FDO #66618) Listen to signals from localed D-Bus system service and update all internal state which depends on the current locale. This state includes: - pre-computed data in all loaded contacts - filtering (for example, case sensitivity is locale dependent) - the sort order This feature can be controlled by setting the SYNCEVOLUTION_LOCALED env variable: - "session" - use a localed instance on the D-Bus session bus instead of the system instance. This was originally meant for testing, but might also be useful for per-user setting changes. - "none" - disables the feature * PIM: fix sync.py + multiple peers Due to overwriting a variable, configuring multiple different peers did not work. * D-Bus server: support DLT (FDO #66769) Diagnostic Log and Trace (DLT) manages a sequence of log messages, with remote controllable level of detail. SyncEvolution optionally (can be chosen at compile time and again at runtime) uses DLT instead of its own syncevolution-log.html files. See README-DLT.rst for more information. To use the feature, configure SyncEvolution with "--enable-dbus-server=--dlt --no-syslog" * EDS: enhanced compatibility mode SyncEvolution compiled for EDS < 3.6 can now also load EDS backends compiled for EDS >= 3.6. The packaging for syncevolution.org uses that to bundle EDS backends compiled on different distros in the same package. * EDS: SYNCEVOLUTION_EBOOK_QUERY env variable Setting the SYNCEVOLUTION_EBOOK_QUERY env variable to a valid EBook query string limits the results to contacts matching that query. Useful only in combination with --print-items or --export. Only implemented for EDS >= 3.6. * EDS: fix compile problem with boost and EDS > 3.36 This fixes the following problem, seen with Boost 1.53.0 on altlinux when compiling for EDS >= 3.6: /usr/include/boost/smart_ptr/shared_ptr.hpp: In instantiation of 'typename boost::detail::sp_array_access::type boost::shared_ptr::operator[](std::ptrdiff_t) const [with T = char*; typename boost::detail::sp_array_access::type = void; std::ptrdiff_t = long int]': src/backends/evolution/EvolutionSyncSource.cpp:163:38: required from here /usr/include/boost/smart_ptr/shared_ptr.hpp:663:22: error: return-statement with a value, in function returning 'void' [-fpermissive] make[2]: *** [src/backends/evolution/src_backends_evolution_syncecal_la-EvolutionSyncSource.lo] * PBAP: add support for obexd 0.48 obexd 0.48 is almost the same as obexd 0.47, except that it dropped the SetFilter and SetFormat methods in favor of passing a Bluex 5-style filter parameter to PullAll. SyncEvolution now supports 4, in words, four different obexd APIs. Sigh. This feature was originally announced for SyncEvolution 1.3.99.5, but not actually included in the code yet. SyncEvolution 1.3.99.5, 01.10.2013 ================================== SyncEvolution now supports Google CalDAV/CardDAV with OAuth2 authentication. These are the open protocol that Google currently supports and thus the recommended way of syncing with Google, replacing ActiveSync and SyncML (both no longer available to all Google customers). Support for Google CardDAV is new. Because of a vCard encoding issue on the server side, spaces in long notes may get removed. Like Evolution, SyncEvolution does not yet support some of the advanced features of the server, in particular custom labels for phone numbers, emails and addresses. Likewise, some client properties are not supported by the server: CALURI, CATEGORIES, FBURL, GEO and ROLE are not supported. Of ORG, only the first two components are supported. Currently, properties not supported by one side get lost in a full roundtrip sync. SyncEvolution depends on external components for OAuth2. It can be compiled to use gSSO [1] or GNOME Online Accounts. GNOME Online Accounts >= 3.10 works out of the box for CalDAV and CardDAV, 3.8 only for CardDAV (but the GNOME Online Accounts binary can be patched to also support CalDAV, see [2]), anything older than 3.8 does not work. Support for Ubuntu Online Accounts should not be hard to add, but is not available yet [3]. [1] https://01.org/gsso and http://cgit.freedesktop.org/SyncEvolution/syncevolution/plain/src/backends/signon/README [2] http://cgit.freedesktop.org/SyncEvolution/syncevolution/plain/src/backends/goa/README [3] http://thread.gmane.org/gmane.comp.mobile.syncevolution/4353/focus=4490 Details: * GTK UI: fixed two crashes - running a sync with no service selected and a 64 bit pointer problem recently discovered by Tino Keitel when compiling the Debian package with -fPIE. * password handling: fix usage of GNOME Keyring and KWallet (FDO #66110) When clients like the GTK sync-ui stored a password, it was always stored as plain text in the config.ini file by the syncevo-dbus-server. The necessary code for redirecting the password storage in a keyring (GNOME or KWallet) simply wasn't called in that case. The command line tool, even when using the D-Bus server to run the operation, had the necessary code active and thus was not affected. Now all SyncEvolution components use the same default: use safe password storage if either GNOME Keyring or KWallet were enabled during compilation, don't use it if not. Fixing this revealed other problems, like not being able to store certain passwords that lacked the necessary lookup criteria (like syncURL and/or username). To address this, the lookup criteria where extended and a new check was added to avoid accidentally removing other passwords. As a result, it may be possible that SyncEvolution no longer finds passwords that were stored with older versions of SyncEvolution. In such a case the passwords must be set again. * GNOME: clean up keyring access and require libgnome-keyring >= 2.20 The updated error messages now always include information about the password and libgnome-keyring error texts. A workaround is used for the "Error communicating with gnome-keyring-daemon" problem that started to appear fairly frequently in the automated testing once the keyring was actually used. The problem shows up with some additional debug messages: Gkr: received an invalid, unencryptable, or non-utf8 secret Gkr: call to daemon returned an invalid response: (null).(null)() It seems that sometimes setting up a session with GNOME keyring fails such that all further communication leads to decoding problem. There is an internal method to reset the session, but it cannot be called directly. As a workaround, fake the death of the GNOME keyring daemon and thus trigger a reconnect when retrying the GNOME keyring access. This is done by sending a D-Bus message, which will also affect other clients of GNOME keyring, but hopefully without user-visible effects. * config: enhanced password handling It is possible to configure a plain username/password combination once in SyncEvolution and then use references to it in other configurations, instead of having to set (and update) the credentials in different places. This is useful in particular with WebDAV, where credentials had to be repeated several times (target config, in each database when used as part of SyncML) or when using a service which requires several configs (Google via SyncML and CalDAV). To use this, create a sync config for a normal peer or a dedicated config just for the credentials, with "username/password/syncURL" set. The "syncURL" must be set to something identifying the peer if GNOME Keyring is used for the password storage. Then set "username", "databaseUser" and "proxyUser" properties to "id:" and all read and write access to those properties will be redirected by SyncEvolution into that other configuration. This even works in the GTK UI. For user names which contain colons, the new "user:" format must be used. Strings without colons are assumed to be normal user names, so most old configurations should continue to work. * signon: new backend using libgsignond-glib + libaccounts-glib The code works with gSSO (https://01.org/gsso). With some tweaks to the configure check and some ifdefs it probably could be made to work with Ubuntu Online Accounts. The code depends on an account accessible via libaccounts-glib which has a provider and and (optionally) services enabled for that provider. It is not necessary that the account already has a signon identity ID, the backend will create that for the provider (and thus shared between all services) if necessary. Therefore it is possible to use the ag-tool to create and enable the account and services. Provider and service templates are in the next commit. * WebDAV: support OAuth2 If given an authentication configuration which can handle OAuth2, then OAuth2 is used instead of plain username/password authentication. * WebDAV: support Google CardDAV, break Yahoo Google CardDAV has one peculiarity: it renames new contacts during PUT without returning the new path to the client. See also http://lists.calconnect.org/pipermail/caldeveloper-l/2013-July/000524.html SyncEvolution already had a workaround for that (PROPGET on old path, extract new path from response) which happened to work. This workaround was originally added for Yahoo, which sometimes merges contacts into existing ones. In contrast to Yahoo, Google really seems to create new items. Without some server specific hacks, the client cannot tell what happened. Because Google is currently supported and Yahoo is not, let's change the hard-coded behavior to "renamed items are new". * WebDAV: started testing with owndrive.com = OwnCloud * GOA: get OAuth2 tokens out of GNOME Online Accounts "username = goa:..." selects an account in GOA and retrieves the OAuth2 token from that. The implementation uses the GOA D-Bus API directly, because our C++ D-Bus bindings are easier to use and this avoids an additional library dependency. * PIM: fix UID usage in sync.py example Using the underscore in the UID has been wrong all along, it only happened to work because UID sanity checking was missing. After adding it, the example broke. Now simply remove the colon. It makes the UID less readable, but it doesn't have to be, and ensures that file names and database names contain the UID as-is. * PIM: if busy, don't shut down While there are sessions pending or active, the server should not shut down. It did that while executing a long-running PIM Manager SyncPeer() operations, by default after 10 minutes. This was not a problem elsewhere because other operations are associated with a client, whose presence also prevents shutdowns. Perhaps PIM Manager should also track the caller and treat it like a client. * PBAP: do not end Bluez5 transfer prematurely A transfer was marked as finished prematurely when encountering the "active" Status value, which can happen for longer transfers. * updated tests Upgrading from releases <= 1.3.99.4: If the value of "username/databaseUser/proxyUser" contains a colon, the "user:" prefix must be added to the value, to continue treating it like a plain user name and not some reference to an unknown identity provider (like "id:", "goa:", "signon:", etc.). The lookup of passwords in GNOME Keyring was updated slightly in 1.3.99.5. It may be necessary to set passwords anew if the old one is no longer found. Upgrading from release 1.2.x: The sync format of existing configurations for Mobical (aka Everdroid) must be updated manually, because the server has encoding problems when using vCard 3.0 (now the default for Evolution contacts): syncevolution --configure \ syncFormat=text/x-vcard \ mobical addressbook The Funambol template explicitly enables usage of the "refresh-from-server" sync mode to avoid getting throttled with 417 'retry later' errors. The same must be added to existing configs manually: syncevolution --configure \ enableRefreshSync=TRUE \ funambol Upgrading from releases before 1.2: Old configurations can still be read. But writing, as it happens during a sync, must migrate the configuration first. Releases >= 1.2 automatically migrates configurations. The old configurations will still be available (see "syncevolution --print-configs") but must be renamed manually to use them again under their original names with older SyncEvolution releases. SyncEvolution 1.3.99.4, 12.07.2013 ================================== The focus of this development snapshot is enhanced performance of syncing. With EDS, contacts get added, updated or loaded with batch operations, which led to 4x runtime improvements when importing PBAP address book for the first time. Removing unnecessary work from any following PBAP sync led to more than a 6x improvement. These improvements also benefit non-PBAP syncing and could in theory work with any SyncML peer. In practice, batching of items is currently limited to SyncEvolution as peer. The PBAP backend itself was rewritten such that data gets transferred from a phone in parallel to processing the already transferred data. The effect is that on a sufficiently fast system, a sync takes about the same time as downloading all contacts. To get the text-only part of the contacts even faster, PBAP syncing can be done such that it first syncs the text-only parts (without removing existing photos), then in a second round adds or modifies photos. The PIM Manager uses this incremental mode by default, in the command line it can be chose with the SYNCEVOLUTION_PBAP_SYNC env variable. The HTTP server became better at handling message resends when the server is slow with processing a message. The server is able to keep a sync session alive while loading the initial data set by sending acknowledgement replies before the client times out. Guido Günther provided some patches addressing problems when compiling SyncEvolution for Maemo. Details: * sync: less verbose output, shorter runtime For each incoming change, one INFO line with "received x[/out of y]" was printed, immediately followed by another line with total counts "added x, updated y, removed z". For each outgoing change, a "sent x[/out of y]" was printed. In addition, these changes were forwarded to the D-Bus server where a "percent complete" was calculated and broadcasted to clients. All of that caused a very high overhead for every single change, even if the actual logging was off. The syncevo-dbus-server was constantly consuming CPU time during a sync when it should have been mostly idle. To avoid this overhead, the updated received/sent numbers that come from the Synthesis engine are now cached and only processed when done with a SyncML message or some other event happens (whatever happens first). To keep the implementation simple, the "added x, updated y, removed z" information is ignored completely and no longer appears in the output. * HTTP server: handle message resends If a client gave up waiting for the server's response and resent its message while the server was still processing the message, syncing failed with "protocol error: already processing a message" raised by the syncevo-dbus-server because it wasn't prepared to handle that situation. The right place to handle this is inside the syncevo-http-server, because it depends on the protocol (HTTP in this case) whether resending is valid or not. It handles that now by tracking the message that is currently in processing and matching it against each new message. If it matches, the new request replaces the obsolete one without sending the message again to syncevo-dbus-server. When syncevo-dbus-server replies to the old message, the reply is used to finish the newer request. * PBAP: incremental sync (FDO #59551) Depending on the SYNCEVOLUTION_PBAP_SYNC env variable, syncing reads all properties as configured ("all"), excludes photos ("text") or first text, then all ("incremental"). When excluding photos, only known properties get requested. This avoids issues with phones which reject the request when enabling properties via the bit flags. This also helps with "databaseFormat=^PHOTO". * PIM: use incremental sync for PBAP by default (FDO #59551) When doing a PBAP sync, PIM manager asks the D-Bus sync helper to set its SYNCEVOLUTION_PBAP_SYNC to "incremental". If the env variable is already set, it does not get overwritten, which allows overriding this default. * PIM: set debug level in peer configs via env variable Typically the peer configs get created from scratch, in particular when testing with testpim.py. In that case the log level cannot be set in advance and doing it via the D-Bus API is also not supported. Therefore, for debugging, use SYNCEVOLUTION_LOGLEVEL= to create peers with a specific log level. * PIM: include pim-manager-api.txt in source distro (FDO #62516) The text file must be listed explicitly to be included by "make dist". * PIM: "full name" -> "fullname" fix in documentation (FDO #62515) Make the documentation match the code. A single word without space makes more sense, so let's go with what the code already used. * PIM: enhanced searching (search part of FDO #64177) Search terms now also include 'is/contains/begins-with/ends-with' and they can be combined with 'and' and 'or', also recursively. * PIM: Pinyin sorting for zh languages (part of FDO #64173) Full interleaving of Pinyin transliterations of Chinese names with Western names can be done by doing an explicit Pinyin transliteration as part of computing the sort keys. This is done using ICU's Transliteration("Han-Latin"), which we have to call directly because boost::locale does not expose that API. We hard-code this behavior for all "zh" languages (as identified by boost::locale), because by default, ICU would sort Pinyin separately from Western names when using the "pinyin" collation. * PIM: new return value for SyncPeer(), new SyncProgress signal (FDO #63417) The SyncPeer() result is derived from the sync statistics. To have them available, the "sync done" signal must include the SyncReport. Start and end of a sync could already be detected; "modified" signals while a sync runs depends on a new signal inside the SyncContext when switching from one cycle to the next and at the end of the last one. * PIM: allow removal of data together with database removal (part of FDO #64835) There is a difference in EDS between removing the database definition from the ESourceRegistry (which makes the data unaccessible via EDS) and removing the actual database. EDS itself only removes the definition and leaves the data around to be garbage-collected eventually. This is not what we want for the PIM Manager API; the API makes a stronger guarantee that data is really gone. Fixed by introducing a new mode flag for the deleteDatabase() method and deleting the directory of the source directly in the EDS backend, if requested by the caller. The syncevolution command line tool will use the default mode and thus keep the data around, while the PIM Manager forces the removal of data. * EDS: create new databases by cloning the builtin ones (FDO #64176) Instead of hard-coding a specific "Backend Summary Setup" in SyncEvolution, copy the config of the system database. That way special flags (like the desired "Backend Summary Setup" for local address books) can be set on a system-wide basis and without having to modify or configure SyncEvolution. Because EDS has no APIs to clone an ESource or turn a .source file into a new ESource, SyncEvolution has to resort to manipulating and creating the keyfile directly. * EDS contacts: update PHOTO+GEO during slow sync, avoid rewriting PHOTO file If PHOTO and/or GEO were the only modified properties during a slow sync, the updated item was not written into local storage because they were marked as compare="never" = "not relevant". For PHOTO this was intentional in the sample config, with the rationale that local storages often don't store the data exactly as requested. When that happens, comparing the data would lead to unnecessary writes. But EDS and probably all other local SyncEvolution storages (KDE, file) store the photo exactly as requested, so not considering changes had the undesirable effect of not always writing new photo data. For GEO, ignoring it was accidental. * EDS contacts: avoid unnecessary DB writes during slow sync Traditionally, contacts were modified shortly before writing into EDS to match with Evolution expectations (must have N, only one CELL TEL, VOICE flag must be set). During a slow sync, the engine compare the modified contacts with the unmodified, incoming one. This led to mismatches and/or merge operations which end up not changing anything in the DB because the only difference would be removed again before writing. * EDS contacts: read-ahead cache Performance is improved by requesting multiple contacts at once and overlapping reading with processing. On a fast system (SSD, CPU fast enough to not be the limiting factor), testpim.py's testSync takes 8 seconds for a "match" sync where 1000 contacts get loaded and compared against the same set of contacts. Read-ahead with only 1 contact per query speeds that up to 6.7s due to overlapping IO and processing. Read-ahead with the default 50 contacts per query takes 5.5s. It does not get much faster with larger queries. * command line: execute --export and --print-items while the source is still reading Instead of reading all item IDs, then iterating over them, process each new ID as soon as it is available. With sources that support incremental reading (only the PBAP source at the moment) that provides output sooner and is a bit more memory efficient. * WebDAV: avoid segfault during collection lookup Avoid referencing pathProps->second when the set of paths that PROPFINDs returns is empty. Apparently this can happen in combination with Calypso. * engine: prevent timeouts in HTTP server mode HTTP SyncML clients give up after a certain timeout (SyncEvolution after RetryDuration = 5 minutes by default, Nokia e51 after 15 minutes) when the server fails to respond. This can happen with SyncEvolution as server when it uses a slow storage with many items, for example via WebDAV. In the case of slow session startup, multithreading is now used to run the storage initializing in parallel to sending regular "keep-alive" SyncML replies to the client. By default, these replies are sent every 2 minutes. This can be configured with another extensions of the SyncMLVersion property: SyncMLVersion = REQUESTMAXTIME=5m Other modes do not use multithreading by default, but it can be enabled by setting REQUESTMAXTIME explicitly. It can be disabled by setting the time to zero. The new feature depends on a libsynthesis with multithreading enabled and glib >= 2.32.0, which is necessary to make SyncEvolution itself thread-safe. With an older glib, multithreading is disabled, but can be enabled as a stop-gap measure by setting REQUESTMAXTIME explicitly. * Various testing and stability enhancements. SyncEvolution had to be made thread-safe for the HTTP timeout prevention. SyncEvolution 1.3.99.3, 06.03.2013 ================================== Another development snapshot, with a particular focus on enhancing (and in some cases, fixing) searching in the PIM Manager. The PIM Manager in this snapshot depends on folks 0.9.x and thus gee 0.8. Support for Bluez 5 was added. The PIM Manager API was extended by addding CreatePeer and ReplaceSearch. The previous methods, SetPeer and RefineSearch, are still supported. Some issues in CalDAV, WebDAV and SyncML were fixed. Graham R. Cobb contributed several patches for enhancing ActiveSync support and making it work with Exchange 2010. Details: * PIM Manager: add ReplaceSearch, always allow it The new ReplaceSearch is more flexible than RefineSearch. It can handle both tightening the search and relaxing it. The downside of it is the more expensive implementation (must check all contacts again, then find minimal set of change signals to update view). Previously, a search which had no filter set at all at the begining could not be refined. This limitation of the implementation gets removed by always using a FilteredView, even if the initial filter is empty. * PIM Manager: introduce CreateConfig() That SetPeer() allows modifying and creating a config leads to race conditions when multiple clients want to create a config. The new CreateConfig() avoids that by atomically checking that a config does not exist yet and creating it. SetPeer() is still available for backwards compatibility. It continues to be used for modifying an existing config in TestContacts.testSync to check the effect of the logging settings. * PIM Manager: fix double entries in filtered search with limit Stressing the FilteredView by using it in tests originally written for the FullView showed that the filling up a view may have used data while it was inconsistent internally, leading to contacts being present multiple times. * PIM Manager and sync: support location = GEO property (FDO #60373) Exposed as "location" -> (lat, long) in the D-Bus bindings. Reading, writing and updating are supported. * PIM Manager: support groups = CATEGORIES (FDO #60380) Allow reading and writing of groups (folks terminology), aka CATEGORIES in vCard. * PIM Manager: intelligent phone search in EDS (part of FDO #59571) If phone number search is enabled in EDS, then the direct search in EDS now uses the more accurate E_BOOK_QUERY_EQUALS_NATIONAL_PHONE_NUMBER comparison, with the E164 formatted caller ID as value to compare against. This gives semantically correct results. The previous solution (now the fallback) had to use substring searches, which did not match if the contact's phone number was not formatted according to E164 and which may have matched the wrong contacts if the trailing numbers are the same. * PIM Manager : use pre-computed normalized phone numbers from EDS (part of FDO #59571) When available, the pre-computed E164 number from EDS will be used instead of doing one libphonebook parser run for each telephone number while reading. Benchmarking showed that this parsing was the number one hotspot, so this is a considerable improvement. * PIM Manager: fix error messages Ensure and check that no unnecessary ERROR messages are printed. libfolks was used slightly incorrectly, leading to several harmless error messages (glib asserts). libphonenumber printed its error messages to stdout. * PIM Manager: fix memory leaks during writing of contacts Constructing the GValues created additional references instead of taking over ownership as intended. * D-Bus server: fix read-after-free bug when using syslog openlog() expects the string to remain valid. Must ensure that in LoggerSyslog by making a copy. Found with valgrind. * PIM Manager: make implementation of some of the D-Bus methods thread-safe The goal is to make it easier to extend syncevo-dbus-server with other IPC mechanisms, which then can call the native C++ code directly. That code was not prepared to handle calls in threads other than the main one. Now this is checked when entering the methods and work is shifted to the main thread if necessary. In the meantime the calling thread waits for completion. * PIM Manager: check responsiveness (part of FDO #60851) Enhanced the testActive test so that it can detect when the D-Bus server stops responding for too long. One major reason for that was event processing in folks, which got improved as part of https://bugzilla.gnome.org/show_bug.cgi?id=694385 * PIM Manager: adapt to gee 0.8 Changed the code to compile with gee 0.8, as used by folks 0.9.x. Older versions of folks are no longer supported. * PBAP: support Bluez 5 The new Bluez 5 API is the third supported API for doing PBAP transfers. It gets checked first, then the PBAB backend falls back to new-style obexd (file based, similar to Bluez 5, but not quite the same) and finally old-style obexd (data transfer via D-Bus). In contrast to previous APIs, Bluez 5 does not report the reason for a failed PBAP transfer. SyncEvolution then throws a generic "transfer failed" error with "reason unknown" as message. * command line: recover from slow sync with new sync modes The error message for an unexpected slow sync still mentioned the old and obsolete "refresh-from-client/server" sync modes. Better mention "refresh-from-local/remote". * CalDAV: more workarounds for Google CalDAV + unique IDs Google became even more strict about checking REV. Tests which reused a UID after deleting the original item started to fail sometime since middle of December 2012. * CalDAV: work around Google server regression (undeclared namespace prefix in XML) Google CalDAV for a while (December 2012 till January 2013) sent invalid XML back when asked to include CardDAV properties in a PROPFIND. This got rejected in the XML parser, which prevents syncing calendar data: Neon error code 1: XML parse error at line 55: undeclared namespace prefix In the meantime Google fixed the issue in response to a bug report via email. But the workaround, only asking for the properties which are really needed, still makes sense and thus is kept. * WebDAV: don't send Basic Auth via http proactively (FDO #57248) Sending basic authentication headers via http is insecure. Only do it proactively when the connection is encrypted and thus protects the information or when the server explicitly asks for it. * Nokia: always add TYPE=INTERNET to EMAIL (FDO #61784) Without the explicit TYPE=INTERNET, email addresses sent to a Nokia e51 were not shown by the phone and even got lost eventually (when syncing back). This commit ensures that the type is set for all emails sent to any Nokia phone, because there may be other phones which need it and phones which don't, shouldn't mind. This was spot-checked with a N97 mini, which works fine with and without the INTERNET type. This behavior can be disabled again for specific Nokia phones by adding a remote rule which sets the addInternetEmail session variable to FALSE again. Non-Nokia phones can enable the feature in a similar way, by setting the variable to TRUE. * SyncML: config option for broken peers Some peers have problems with meta data (CtCap, old Nokia phones) and the sync mode extensions required for advertising the restart capability (Oracle Beehive). The default in SyncEvolution is to advertise the capability, so manual configuration is necessary when working with a peer that fails in that mode. Because the problem occurs when SyncEvolution contacts the peers before it gets the device information from the peer, dynamic rules based on the peer identifiers cannot be used. Instead the local config must already disable these extra features in advance. The "SyncMLVersion" property gets extended for this. Instead of just "SyncMLVersion = 1.0" (as before) it now becomes possible to say "SyncMLVersion = 1.0, noctcap, norestart". "noctcap" disables sending CtCap. "norestart" disables the sync mode extensions and thus doing multiple sync cycles in the same session (used between SyncEvolution instances in some cases to get client and server into sync in one session). Both keywords are case-insensitive. There's no error checking for typos, so beware! The "SyncMLVersion" property was chosen because it was already in use for configuring SyncML compatibility aspects and adding a new property would have been harder. * ActiveSync: added support for specifying folder names Previously, the database field was interpreted as a Collection ID. This adds logic to allow the database to be interpreted as a folder path. The logic is: 1) If the database is an empty string, pass it through (this is the most common case as it is interpreted as "use the default folder for the source type"). 2) If the database matches a Collection ID, use the ID (this is the same as the previous behaviour). 3) If the database matches a folder path name, with an optional leading "/", use the Collection ID for the matching folder. 4) Otherwise, force a FolderSync to get the latest folder changes from the server and repeat steps 2 and 3 5) If still no match, throw an error. * ActiveSync: support for listing databases Now --print-databases scans folders on the ActiveSync server and shows suitable folders for the ActiveSync backends instead of the previous, hard-coded help text. Invoking --print-databases can be used as a workaround for "SyncFolder error: Invalid synchronization key" errors. A better solution would be to do that automatically, but there was no time to implement that. See FDO #61869 and "[SyncEvolution] Activesync server losing state" http://thread.gmane.org/gmane.comp.mobile.syncevolution/4295 * command line: show backend error when listing databases fails The command line swallowed errors thrown by the backend while listing databases. Instead it just showed ": backend failed". The goal was to not distract users who accidentally access a non-functional backend. But the result is that operations like --configure or --print-databases could fail without giving the user any hint about the root cause of the issue. Now the error explanation in all its gory details is included. For example, not having activesyncd running leads to: INFO] eas_contact: backend failed: fetching folder list: GDBus.Error:org.freedesktop.DBus.Error.ServiceUnknown: The name org.meego.activesyncd was not provided by any .service files And running activesyncd without the necessary gconf keys shows up as: [INFO] eas_contact: backend failed: fetching folder list: GDBus.Error:org.meego.activesyncd.Error.AccountNotFound: Failed to find account [syncevolution@lists.intel.com] * Minor memory leak fix when using GDBus GIO: GDBusMethodInfo Also depends on a glib fix, see https://bugzilla.gnome.org/show_bug.cgi?id=695376 * build fixes Avoid -lrt in make dependencies. Add missing pcre libs to syncevo-dbus-server. sqlite backend needs "#include " (patch from Mario Kicherer). SyncEvolution 1.3.99.2, 13.12.2012 ================================== Another development snapshot. Includes all fixes that went into 1.3.2 and several improvements to the PIM Manager. Documentation was updated and extended considerably. The pim-manager-api.txt now describes the abstract API while src/dbus/server/pim/README explains how SyncEvolution implements it. Details: * PIM Manager searches for a caller ID ('phone' search) in EDS directly while folks still starts up. No unification is done of these results. Intermediate results are replaced by the final ones from folks once those are ready. * PIM Manager: allow configuration of session directories (part of FDO #55921) Useful for moving the session directories to a temporary file system. They are essentially just useful for debugging when used as part of PIM Manager. - "logdir" - a directory in which directories are created with debug information about sync session - "maxsessions" - number of sessions that are allowed to exist after a sync (>= 0): 0 is special and means unlimited, 1 for just the latest, etc.; old sessions are pruned heuristically (for example, keep sessions where something changed instead of some where nothing changed), so there is no hard guarantee that the last n sessions are present. * PIM Manager: write less data to disk (part of FDO #55921) Avoid writing config file changes to disk by enabling a new "ephemeral" mode for syncing via the PIM Manager. In this mode, config file changes are not flushed resp. discarded directly. This prevents writing to .ini files in ~/.config. The "synthesis" binfile client files are still written, but they get redirected into the session directory, which can (and should) be set to a temp file system and get deleted again quickly. Data dumps are turned off now in the configs created by the PIM Manager. * syncevo-dbus-server: use syslog instead of standard output by default * syncevo-dbus-server: command line options for controlling output and startup -d, --duration=seconds/'unlimited' Shut down automatically when idle for this duration (default 300 seconds) -v, --verbosity=level Choose amount of output, 0 = no output, 1 = errors, 2 = info, 3 = debug; default is 1. -o, --stdout Enable printing to stdout (result of operations) and stderr (errors/info/debug). -s, --no-syslog Disable printing to syslog. -p, --start-pim Activate the PIM Manager (= unified address book) immediately. * PIM Manager: store set of active address books persistently (FDO #56334) Together with storing the sort order persistently, this allows restarting the daemon and have it create the same unified address book again. * PIM Manager: remove colon from valid peer UID character set (FDO #56436) Using the UID as part of file names gets more problematic when allowing colons. Remove that character from the API and enforce the format in the source code. * PIM Manager API: introduce contact ID and use it for reading This makes it easier for a client to fully polulate its view with contact data. Previously it could happen that due to concurrent changes in the server, a client was returned data for the same contact multiple times. A client had to detect that and re-issue read requests. * PIM Manager API: optional ViewAgent.Quiescent() (FDO #56428) The callback is guaranteed to be invoked once when a search has finished sending its initial results, and not sooner. This makes it possible to check whether the current data contains some contact or not. * PIM Manager: limit number of search results (FDO #56142) A 'limit' search term with a number as parameter (formatted as string) can be added to a 'phone' or 'any-contains' search term to truncate the search results after a certain number of contacts. Example: Search([['any-contains', 'Joe'], ['limit', '10']]) => return the first 10 Joes. As with any other search, the resulting view will be updated if contact data changes. The limit must not be changed in a RefineSearch(). A 'limit' term may (but doesn't have to) be given. If it is given, its value must match the value set when creating the search. This limitation simplifies the implementation and its testing. The limitation could be removed if there is sufficient demand. * PIM Manager: fix refining a search Due to not mapping the local index in the view to the parent's index, refining only worked in views where parent and child had the same index for the contacts in the search view. * PIM Manager: fix starting when done via search When the unified address book (= FullView) was not running yet at the time when a client wanted to search it, the unified address book was not started and thus the search never returned results. * PIM Manager: fix writing contact, support photo and notes folks and EDS do not support writing properties in parallel (https://bugzilla.gnome.org/show_bug.cgi?id=652659). Must serialize setting of modified properties. * PIM Manager: fix incorrect contact removal signals in filtered view The filtered view did not check whether a parent's removed contact was really part of the view before sending a removal signal for it. * D-Bus: missing out parameters in D-Bus introspection XML (FDO #57292) The problem was in the C++ D-Bus binding. If the method that gets bound to D-Bus returns a value, that value was ignored in the signature: int foo() => no out parameter It works when the method was declared as having a retval: void foo (int &result) => integer out parameter This problem existed for both the libdbus and the GIO D-Bus bindings. In SyncEvolution it affected methods like GetVersions(). * PIM Manager performance: pre-compute normalized telephone numbers Looking up by phone number spends most of its cycles in normalizing of the phone numbers in the unified address book. Instead of doing that work over and over again during the search, do it once while loading. Looking up a phone number only once does not gain from this change, it even gets slower (more memory intensive, less cache locality). Only searching multiple times becomes faster. Ultimately it would be best to store the normalized strings together with the telephone number inside EDS when the contact gets created. Work on that is in progress. * PIM Manager: improve performance of FullView sorting This fixes the hotspot during populating the FullView content: moving contacts around required copying IndividualData and thus copying complex C++ structs and strings. Storing pointers and moving those avoids that, with no lack of convenience thanks to boost::ptr_vector. Reordering also becomes faster, because the intermediate copy only needs to be of the pointers instead of the full content. * PIM Manager example: add benchmarking The new "checkpoints" split up the whole script run into pieces which are timed separately, with duration printed to stdout. In addition, tools like "perf" can be started for the duration of one phase. * SyncML: workarounds for broken peers Some peers have problems with meta data (CtCap, old Nokia phones) and the sync mode extensions required for advertising the restart capability (Oracle Beehive). Because the problem occurs when SyncEvolution contacts the peers before it gets the device information from the peer, dynamic rules based on the peer identifiers cannot be used. Instead the local config must already disable these extra features in advance. The "SyncMLVersion" property gets extended for this. Instead of just "SyncMLVersion = 1.0" (as before) it now becomes possible to say "SyncMLVersion = 1.0, noctcap, norestart". "noctcap" disables sending CtCap. "norestart" disables the sync mode extensions and thus doing multiple sync cycles in the same session (used between SyncEvolution instances in some cases to get client and server into sync in one session). Both keywords are case-insensitive. There's no error checking for typos, so beware! The "SyncMLVersion" property was chosen because it was already in use for configuring SyncML compatibility aspects and adding a new property would have been harder. * EDS: fix creating databases --create-database was broken in combination with the final code in EDS 3.6 because it passed NULL for the UID to e_source_new_with_uid(), which is considered an error by the implementation of that method. Must use e_source_new() if we don't have a UID. * fixed some memory leaks, extended tests to cover new features and bugs SyncEvolution 1.3.99.1, 25.10.2012 ================================== Development snapshot. The PIM Manager API implementation is fully implemented, see src/dbus/server/pim/README for an introduction. The PBAP backend together with a new one-way caching sync mode provides an efficient way of keeping a local database in sync via Bluetooth with a phone which does not implement SyncML. Other changes: * workarounds for warnings from g++ 4.5 * engine: : local cache sync mode This patch introduces support for true one-way syncing ("caching"): the local datastore is meant to be an exact copy of the data on the remote side. The assumption is that no modifications are ever made locally outside of syncing. This is different from one-way sync modes, which allows local changes and only temporarily disables sending them to the remote side. Another goal of the new mode is to avoid data writes as much as possible. This new mode only works on the server side of a sync, where the engine has enough control over the data flow. Setting "sync" to: - "local-cache-incremental" will do an incremental sync (if possible) or a slow sync (otherwise). This is usually the right mode to use, and thus has "local-cache" as alias. - "local-cache-slow" will always do a slow sync. Useful for debugging or after (accidentally) making changes on the local side. An incremental sync will ignore such changes because they are not meant to happen, aren't checked for to improve performance and thus will leave client and server out-of-sync! Both modes are recorded in the sync report of the local side. The target side is the client and records the normal "two-way" or "slow" sync modes. With the current SyncEvolution contact field list, first, middle and last name are used to find matches for contacts. For events, tasks and memos, time, summary and description are used. * HTTP proxy: useProxy=0 overrides http_* env variables Previously, if http_proxy was set, a proxy was used even if explicitly disabled. This prevented disabling the use of a proxy which only made sense in some cases, like accessing something that runs locally. Explicitly telling SyncEvolution to ignore http_proxy is necessary because it doesn't support no_proxy. * WebDAV: auto-discovery fix With Google Contact + CardDAV the auto-discovery failed after finding the default address book, without reporting that result. * command line: implement --create/remove-database Creating a database is only possible with a chosen name. The UID is chosen automatically by the storage. Only implemented in the EDS backend. * file backend: sub-second mod time stamps Change tracking in the file backend used to be based on the modification time in seconds. When running many syncs quickly (as in testing), that can lead to changes not being detected when they happen within a second. Now the file backend also includes the sub-second part of the modification time stamp, if available. This change is relevant when upgrading SyncEvolution: most of the items will be considered "updated" once during the first sync after the upgrade (or a downgrade) because the revision strings get calculated differently. * D-Bus server: avoid progress outside of 0-100% range For example in the new TestLocalCache.testItemDelete100, the percentage value in the ProgressChanged signal become larger than 100 and then revert to 100 at the end of the sync. Seems the underlying calculation is faulty or simply inaccurate. This is not fixed. Instead the result is just clipped to the valid range. * code cleanup + improvements in testing SyncEvolution 1.3.1 -> 1.3.2, 05.11.2012 ======================================== Minor (or major, if you depend on auto syncing) bug fix release. Details: * auto sync: only synced once (FDO #56667) A successful sync was incorrectly treated like a sync with a permanent failure, which prevents further automatic syncing. * auto sync: notifications were not translated The code which enabled localization of messages created by the D-Bus server was incomplete. Localization was only enabled accidentally through KDE if the KDE platform modules was enabled during compilation and installed. * HTTP Proxy: useProxy=0 overrides http_* env variables Previously, if http_proxy was set, a proxy was used even if explicitly disabled. This prevented disabling the use of a proxy which only made sense in some cases, like accessing something that runs locally. Explicitly telling SyncEvolution to ignore http_proxy is necessary because it doesn't support no_proxy. * minor changes in testing and autotools files (missing Boost search path in gdbus* libs might have caused compile problems) SyncEvolution 1.3 -> 1.3.1, 05.10.2012 ====================================== Minor bug fix release. Details: * command line: fix output of --import for directories The running count at the start of the line (#0, #1, ...) was not incremented when reading individual files from a directory. * Funambol: work around PHOTO TYPE=image/jpeg, part II The final version of the fix hadn't made it into the source code. * vCalendar 1.0 + tasks: DUE date could be shifted by a day (FDO #55238) Because of incomplete support for time conversion, the due date could get mixed up when phone and PC were set to something other than UTC. Reported and fixed by Peter Jan. * syncevolution.org: syncevolution-evolution had incorrect dependencies Installation on older Linux distros was not possible because the ebook/ecal package dependencies were named incorrectly, for example libebook-1.2-10 instead of libebook1.2-10. Only more recent packages have the extra dash, for example libebook-1.2-12. Reported by Mariusz Sokolowski. * GTK-3 UI: fixed compile problem The GTK-3 UI depends on a class from gio-unix-2.0 and failed to compile on Fedora Core 16 because the configure checks for that lib (and thus the compiler flags) were missing. Reported by Peter Robinson. * Curl: allow using it in the D-Bus server In the past, using curl as HTTP transport in the syncevo-dbus-server was prevented, leading to "unsupported transport type is specified in the configuration". The reason was that using curl would block the server and make it unresponsive on D-Bus. This reason has gone away, because now the HTTP traffic happens in a separate process. Thus now it is allowed to use curl in the syncevo-dbus-server. * fix for false negative in syncevo-dbus-server testing SyncEvolution 1.2.2 -> 1.3, 10.09.2012 ====================================== After almost three months of public beta testing the next major version of SyncEvolution is ready for release. The pre-releases did have the desired effect of flushing out bugs not found by nightly testing alone. Thanks everyone for packaging, downloading and testing them! New features are KDE/Akonadi and ActiveSync support, not only in the source code but also in syncevolution.org binaries. ActiveSync is the recommended way of synchronizing contacts with Google: https://syncevolution.org/wiki/google-contacts-activesync The D-Bus server and local sync were rewritten considerably, to make the code cleaner and more robust. The CalDAV backend now also supports tasks and memos. CalDAV and CardDAV can be used in combination with a SyncML peer ("bridging SyncML and WebDAV"), thus allowing a device which only supports SyncML to talk to a WebDAV service without any intermediate storage. 1.3 contains bug fixes that were not backported to 1.2.x, so upgrading is recommended. For example, SyncEvolution 1.3 is required for Evolution 3.4, otherwise photos are not exported properly. Support for Evolution >= 3.6 is in the source code, but not in syncevolution.org binaries. Further workarounds for recent changes in Google CalDAV and Funambol One Media were added. Details: * ActiveSync: updated to work with latest activesyncd and Google, package binaries Syncing Google contacts was added to the nightly testing. Syncing contacts and events with Exchange 2012 was already working. Setup instructions and known issues are described here: https://syncevolution.org/wiki/google-contacts-activesync * phone sync: delete<->delete conflict + phone calendar+todo sync (BMC #23744) When deleting an item on phone and locally, the next sync failed with ERROR messages about "object not found". This has several reasons: - libsynthesis super data store attempts to read items which may or may not exist (triggers ERROR message) - it checks for 404 but Evolution backends only return a generic database error (causes sync to fail) * phone sync: get phone vendor and model from Device ID profile (BMC #736) In the past we have relied on the user-modifiable device name to be the fingerprint for matching a phone to a template which is unreliable. This release changes this in the cases where the phone supports the Device ID profile (DIP). If support for DIP is detected, then we extract the vendor and product ids and attempt to associate them with a product and vendor name by using a newly added lookup table. This lookup table has to be maintained manually and depends on contributions by users to cover more devices. See http://blixtra.org/blog/2011/09/22/syncevolution-needs-you-or-at-least-your-bluetooth-phones/ * vCalendar 1.0: fixed recurring all-day event support vCalendar 1.0 cannot represent all-day events. The workarounds for mapping iCalendar 2.0 all-day events into vCalendar 1.0 was incomplete, leading to effects like shifting EXDATEs and end times. * Funambol: ignore UID Funambol's OneMedia sends UID, but not RECURRENCE-ID. That becomes a problem when multiple events of the same event series are added to a backend which follows the iCalendar 2.0 standard (CalDAV, EDS, KDE), because these events all look like the master event, and there can be only one of those. SyncEvolution now strips the UID from all events coming from any Funambol server (regardless of the version). If a future Funambol server release adds support for both UID and RECURRENCE-ID, then SyncEvolution will have to be updated to take advantage of the improved server. Because the RECURRENCE-ID is also getting stripped (despite not being set at the moment), SyncEvolution should continue to work as it does now even if the server changes. It would have been nice to limit this workaround to affected Funambol server versions, but an inquiry on the Funambol mailing list didn't get a reply, therefore SyncEvolution is playing it safe and assumes that all future Funambol releases will have the same problem. * Funambol: work around PHOTO TYPE=image/jpeg A combination of Funambol Android and Funambol server recently led to the Funambol server sending PHOTO data with TYPE=image/jpeg. This is invalid and caused EDS to reject the photo (Vladimir Elisseev, "[SyncEvolution] issues with syncing photos"). Work around the problem by only keeping the part of the type after the last slash, if there is any. For image/jpeg and similar types that leads to the desired value and does not affect valid values, because those do not contain a slash (http://www.iana.org/assignments/media-types/image/index.html). * Funambol: avoid slow syncs in refresh from server libsynthesis has traditionally implemented "refresh-from-server" as "delete local data" plus "slow" sync. This is more compatible, because some servers (like Google) do not support "refresh-from-server". But it has the downside that the server cannot know that the client won't send any data, and Funambol's OneMedia now only allows one slow sync before blocking the next one for a certain period of time. This is done to prevent excessive resource usage by badly behaving clients. To accomodate both kinds of servers, the new "enableRefreshSync" sync property can be set set to explicitly allow the usage of the "refresh-from-server" sync mode. It's off by default. The Funambol template has it turned on, existing configs must be updated manually (see upgrading comments below). * Mobical (aka Everdroid): stopped testing memo syncing Memos used to work, but now only trigger an unspecific 400 error on the server side. * GTK-UI: accept service config with a username again (BMC#23106) Suppressing configs with empty username had undesired side effects: modifying configs for direct syncing with a device incorrectly triggered the same error message, without any means of entering a username. The faulty check was removed without replacement. * GTK-UI: added GTK 3 version of UI When GTK 3 is found during compilation, a GTK 3 version of the UI is built. The source code of both is different to avoid excessive use of ifdefs. At the moment, both versions offer the same features. In the long run, the GTK 3 version will replace the GTK 2 version. * command line: added refresh/one-way-from-local/remote (BMC #23537) The -from-client/server sync modes are confusing because the direction of the data exchange depends on which side acts as SyncML server or client. This release introduces new modes which use -from-local/remote instead. The statistics and messages also use these variants now. The old modes are still understood, but are declared as "not recommended" in the documentation. * command line: config and source names are optional (BMC #23783) The need to add "foo" and "bar" pseudo config and source names to the command line even when all parameters for the operation where explicitly specified on the command line was confusing. Now it is possible to invoke item operations without the config and source name. Names which refer to non-existent configs are still accepted, as in previous releases. Typos are handled better by producing a detailed error report which includes (as applicable): - config doesn't exist - source doesn't exist or not selected - backend property not set Because luids used to be positional arguments after and , a new --luids keyword is necessary to indicate that the ensuing parameters are luids and not and . * command line: introduced --print-databases, supported for CalDAV/CardDAV Listing databases is now a dedicated operation, instead of being done whenever syncevolution was invoked without parameters. Advantages: - can be combined with property assignments for backends which do not work without that additional information, for example CalDAV/CardDAV: syncevolution --print-databases \ backend=[caldav|carddav] \ syncURL=... \ username=... \ password=... - can be done for configured sources * command line: use both stdout and stderr Traditionally, the "syncevolution" command line tool mixed its INFO/ERROR/DEBUG messages into the normal stdout. This has the major drawback that error messages get lost during operations like syncevolution --export - @default addressbook | grep "John Doe" Now anything which is not the expected result of the operation is always sent to stderr. Obviously this includes ERROR messages. INFO and DEBUG are harder to decide. Because they usually convey meta information about the running operation, they are also sent to stderr. The output of running a sync goes to both stdout (summary) and stderr (progress). * command line: allow setting empty properties Due to the way how properties were handled internally, it wasn't possible to explicitly set a property to its default value. Instead the property was unset. For example, explicitly setting database= was not possible. This is necessary for client-test and ActiveSync, because client-test needs to know that the testing is expected to run with the default databases (something which normally is avoided by overwriting empty database properties). Now the "is set" state is tracked explicitly in the config storage and command line property APIs. Unsetting a property via the command line could be implemented with an explicit command line option, but is not supported at the moment. * command line: fixed --export When exporting items into a file, the delimiter between items was missing. * command line + local sync: fixed erroneous "Comparison impossible" output. "Comparison impossible" was incorrectly printed after a successful comparison on the target side of local sync. * local sync: fix timeout with local sync with libdbus When using libdbus instead of GIO D-Bus (as done by syncevolution.org binaries and SyncEvolution on Maemo), local sync may have aborted after 25 seconds when syncing many items with a D-Bus timeout error: [ERROR] sending message to child failed: org.freedesktop.DBus.Error.NoReply: Did not receive a reply. Possible ca Reported by Toke Høiland-Jørgensen for Harmattan. Somehow not encountered elsewhere. * synccompare: shorter data dump of PHOTO A full comparison of the base64 PHOTO data can be very long. Now some key characteristics of the PHOTO data (number of characters in base64 encoding, number of bytes in decoded data, md5sum of decoded data) are printed instead. That way, unintended changes of the data (different encoding, different content) should still be found while testing and added/removed photos are nicely visible in synccompare diffs. * synccompare: fixed output for byte-identical duplicates If database dumps contained byte-identical duplicates, they were treated as a single item on the left side of a comparison. This caused erroneous "added" entries on the right side. * secure password storage: usage of GNOME Keyring vs. KDE KWallet configurable Automatically detecting KDE users is not possible at the moment. Instead KDE users have to manually set the new "keyring" global config property to "KDE" (case insensitive) if the SyncEvolution installation supports both, because GNOME Keyring is the default to avoid surprises for traditional users. If only KWallet support is enabled, then this is not necessary. "GNOME" and "true/false/1/0/yes/no" can also be set. This has the advantage that keyring usage can be enabled permanently for the command line in --daemon=no mode; normally keyrings are not used in that mode because accessing them can bring up UI dialogs. It also becomes possible to disable keyring usage in syncevo-dbus-server, something which couldn't be done before. The --keyring command line option is still supported, as an alias for "[--sync-property] keyring=". The default value for --keyring is true, to match the traditional behavior. In contrast to other sync properties, setting "keyring" does not require an explicit --run parameter. Again this is done to mirror traditional usage. * config: improved 'maxlogdirs' documentation The old explanation made it sound like nothing would get deleted by default ("If set, ..."). That's not correct, by default only 10 sessions are kept. Also explain the behavior of deleting intermediate sessions first. * Evolution: always create databases (PTCOM-113) Always try to create address book or calendar database, because even if there is a source there's no guarantee that the actual database was created already; the original logic for only setting this when explicitly requesting a new database therefore failed in some cases. This problem affected users who had never created anything locally and wanted to use SyncEvolution to migrate their data. Now that works without having to create dummy entries first. * Evolution contacts: changed default sync format to vCard 3.0 vCard 3.0 is the better default because it has saner encoding rules and defines more properties, thus avoiding the need for non-standard extensions. However, Mobical has problems with the new default. See upgrade instructions below. * Evolution: added support for EDS 3.5.x When compiled against EDS 3.5.x or later, SyncEvolution now uses the backend code originally written for the EClient API introduced in EDS 3.2. That code was changed so that it works with the new include file rules and ESourceRegistry in EDS 3.5.x. Support for using the EClient API with EDS 3.4 was removed because maintaining three different flavors of the EDS backend code would be too much work and not gain much (just the possibility to test the EDSClient code with 3.4). At the moment, this is a compile time choice made automatically by configure. syncevolution.org binaries are compiled against an older EDS and thus do not work with EDS 3.5.x or later. EDS 3.5.x handles authentication itself, using a standard system prompt if necessary. SyncEvolution can no longer provide the password, and thus the "databaseUser/Password" options have no effect when using EDS 3.5.x. * D-Bus server: fixed HTTP presence for recent libdbus Testing with libdbus 1.6.0 on Debian Testing failed because the lib changed some behavior: instead of looking up the owner of a certain bus name immediately, it now does that when invoking a method. Therefore the check for "have connection" in SyncEvolution was too simplistic and missed the fact that both were not usable, causing the server to assume that HTTP was down while in reality it should have assumed it to be up. This prevented auto-syncing and manually clicking "Sync" in the GTK UI. * D-Bus server: made notification verbosity configurable with "notifyLevel" The new "notifyLevel" per-peer configuration option allows users to control how many desktop notifications the D-Bus server produces while executing an automatic sync: 0 - suppress all notifications 1 - show only errors 2 - show information about changes and errors (in practice currently the same as level 3) 3 - show all notifications, including starting a sync (default) * WebDAV: fixed data corruption issue when uploading item with long UID In some cases data with a very long UID wasn't handled correctly, causing the out-going data to be malformed and probably causing a rejection by the server. The root cause is incorrect string handling. In releases before 1.2.99.1, only the --import operation of contacts into CardDAV were affected. In 1.2.99.1, the same code also got used for calendar items and then could also affect syncing. * CalDAV: updated Google workarounds Google started sending empty items (VCALENDAR with no VEVENT inside) which cannot be removed. SyncEvolution 1.3 ignores such items. The workaround for a 404 from Google Calendar for a GET (sending a REPORT request matching the item's UID) was broken: first, processing the result ended up calling the unset responseEnd boost function pointer, which caused the request to fail. Second, getting multiple items wasn't handled (data from all items concatenated together was used). That can happen in the somewhat unlike case that some items have a UID which is a complete superset of the requested UID - not realistic in real life, but happens during testing. * Google Calendar: updated URL redirect handling Google Calendar sometimes returns redirect requests to human-readable web sites (an "unavailable" page, a login form). This is of course bogus when the client is an automated CalDAV client. The "unavailable.html" case was already handled. Made it a bit more flexible to also catch possible variations of it (additional parameters, https instead of http). Added the https://accounts.google.com/ServiceLogin case. Not sure whether retrying will help in that case, but there's not much else that SyncEvolution can do. * WebDAV: bridge with SyncML Now a peer accessed via SyncML can read/write data stored in a CalDAV/CardDAV server directly. This can be used to connect a device which only supports SyncML to a CalDAV/CardDAV server, or sync data between a SyncML server and a CalDAV/CardDAV server. See "CalDAV and CardDAV" in the README for details. * WebDAV: improved --configure Added INFO output about checking sources. This helps with WebDAV when the server cannot be contacted (dead, misconfigured) because otherwise there would be no indication at all why the --configure operation seems to hang. Here is some example output, including aborting: $ syncevolution --configure --template webdav \ syncURL=http://192.168.1.100:9000/ \ username=foo password=bar retryDuration=2s \ target-config@webdav-temp [INFO] creating configuration target-config@webdav-temp [INFO] addressbook: looking for databases... [INFO] addressbook: no database to synchronize [INFO] calendar: looking for databases... [INFO] calendar: no database to synchronize [INFO] memo: looking for databases... [INFO] memo: no database to synchronize [INFO] todo: looking for databases... [INFO] todo: no database to synchronize It timed out fairly quickly here because of the retryDuration=2s. That also gets placed in the resulting config, which is probably not desired. Aborting the operation is now supported: $ syncevolution --configure \ --template webdav \ syncURL=http://192.168.1.100:9000/ \ username=foo password=bar \ target-config@webdav-temp [INFO] creating configuration target-config@webdav-temp [INFO] addressbook: looking for databases... ^C[INFO] Asking to suspend... [INFO] Press CTRL-C again quickly (within 2s) to stop immediately (can cause problems in the future!) ^C[INFO] Aborting immediately ... [ERROR] error code from SyncEvolution aborted on behalf of user (local, status 20017): aborting as requested by user It would be good to make the CTRL-C handling code aware that it can abort immediately instead of doing the intermediate "asking to suspend" step, which only makes sense for sync sessions. * WebDAV: support tasks and memos (BMC #24893) The new backend property values "CalDAVTodo" and "CalDAVJournal" select tasks resp. memos stored in a CalDAV collection. "CalDAV" continues to select events. Events, tasks and journals can be mixed in the same resource (= URL). However, this is less efficient than storing them separately. A good CalDAV server allows filtering items by type, and SyncEvolution uses that. However, it was found that Radicale 0.7 ignores this filtering, which could have led to data loss (SyncEvolution asks for all VTODOs in preparation for a "delete all items" operation in a "CalDAVTodo" source, gets also VJOURNALs, then deletes them). Therefore SyncEvolution plays it safe and downloads the VTODO and VJOURNAL data to double-check that it is working on the right items. This causes additional traffic for well-behaving servers; currently it cannot be turned off. Tasks are exchanged as vCalendar 1.0 or iCalendar 2.0 VJOURNAL. Memos are exchanged as VTODO or plain text. The logic for storing incoming plain text is slightly different compared to the way how the EDS memo backend did it: instead of copying the first line from the text into the summary, it is now moved. In other words, the first line gets stripped. The change is primarily technically motivated; both approaches have pros and cons. * WebDAV: improved Radicale support Radicale > 0.7 will return status 200 for delete requests; is now treated like 204 by SyncEvolution. 412 'Preconditiona Failed' when asking to delete an already removed item is treated like the more common 404 'not found'. Same with 410 'gone' instead of 404 when trying to read a non-existent item. * CalDAV/CardDAV sync: improved target side output Added a "target side of local sync ready" INFO message to introduce the output which has the target context in the [INFO] tag. The sync report from the target side now has the target context embedded in brackets after the "Changes applied during synchronization" header, to avoid ambiguities. Sometimes the backend has to resend requests because of temporary issues. If the problem turned out to be permanent, there was a long period of time, retryDuration=5 minutes to be precice, in which no visible progress happened. Now SyncEvolution's WebDAV backend will print a message like this before going to sleep until it is time to retry: [INFO @googlecalendar] operation temporarily (?) failed, going to retry in 5.0s before giving up in 18.4s: PROPFIND: Neon error code 1: 401 Unauthorized The uncertainty comes from several factors. In this example, the 401 might indicate a permanent problem (wrong credentials), or it could be Google reporting a temporary authorization problem which is (probably) meant to slow down the client while it asks the user to re-enter the password. SyncEvolution only asks for passwords once, so it tries again with the same password if it was successful with it in the past. Otherwise it gives up immediately. Another dubious example are name server lookup errors. They can be permanent (wrong host name) or temporary (name server down). SyncEvolution errs on the side of retrying, to avoid interrupting an operation which still has a chance to continue. Output from the target side of a local sync was passed through stderr redirection as chunks of text to the frontends. This had several drawbacks: - forwarding only happened when the local sync parent was processing the output redirection, which (due to limitations of the implementation) only happens when it needs to print something itself - debug messages were not forwarded - message boundaries might have been lost In particular the new INFO messages are relevant while the sync runs and need to be shown immediately. * WebDAV: --status for WebDAV source aborted The command line --status operation did not complete when applied to a CalDAV/CardDAV source. Instead it aborted because the operation took a code path where the backend was not fully initialized. * file backend: more flexible sync support for memos The databaseFormat=text/calendar for memos did not support synchronizing as plain text. When using the new databaseFormat=text/calendar+plain, vCalendar/iCalendar/plain text are all valid sync formats; the storage is iCalendar 2.0 VJOURNAL in all cases. * WebDAV: avoid potential crash during database detection When a server responds to a PROPFIND for a path with results for some other path, then SyncEvolution crashed during the search for the default calendar or address book because of a bug in the code which was meant to handle that kind of response. Apparently Yahoo Calendar did that. Now seen again in combination with Radicale 0.6.4. In general, the code was made more robust to cope with bugs in Radicale 0.6.4. Later Radicale versions fixed these issues and also worked with SyncEvolution 1.2.2 without client-side workarounds. * WebDAV: better path normalization "syncURL" and "database" properties had to end in a trailing slash, otherwise items were not found (404 errors). Now the necessary slash is added automatically. * Curl transport: support SSLServerCertificates= When the setting refers to a directory, then CURLOPT_CAINFO doesn't work (must be a file). Check this and use CURLOPT_CAPATH instead. Caveat: there are some comments in the API documentation about "NSS enabled libcurl" which supports a directory in CURLOPT_CAINFO. Hopefully providing an explicit path in CURLOPT_CAPATH also works in that configuration. * code cleanup + rewrite: syncing done in separate process syncevo-dbus-server now runs syncing in a separate process. Local sync also uses a second helper process. This makes the D-Bus server more responsive via D-Bus (no more blocking operations) and minimizes the effect of bugs in code involved with syncing (backends, system libraries, etc.). In the long term this restructuring will also allow more advanced features, like monitoring local or remote storage for changes. * SyncEvolution <-> SyncEvolution sync: multiple cycles per session SyncML only allows one send/receive cycle per session. There are cases (for example, client side merges data that a dumber server failed to match correctly) where client and server are still out of sync at the end of a cycle. When SyncEvolution syncs with another SyncEvolution instance (locally or remotely), both sides detect that the peer can continue syncing in the same session and start over automatically when needed. Previously the user had to start another sync session manually. To the user this is shown as "number of cycles" in a sync session in the sync report. "Restart" is the process of entering a new cycle. The cycles are also visible in the command line output as multiple INFO lines: [INFO] eds_contact: starting first time sync from client (peer is server) [INFO] creating complete data backup of source eds_contact before sync (enabled with dumpData and needed for prin Local data changes to be applied during synchronization: *** eds_contact *** no changes [INFO] eds_contact: sent 1/1 [INFO] eds_contact: started [INFO] eds_contact: first time sync done successfully [INFO] eds_contact: starting normal sync from client (peer is server) <=== [INFO] eds_contact: started <=== [INFO] eds_contact: normal sync done successfully <=== [INFO] creating complete data backup after sync (enabled with dumpData and needed for printChanges) Synchronization successful. Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | LOCAL | REMOTE | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | eds_contact | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | | refresh-from-local, 2 cycles, 0 KB sent by client, 0 KB received | ^^^^^^^^ | item(s) in database backup: 1 before sync, 1 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Tue Feb 7 17:07:49 2012, duration 0:03min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ * SyncEvolution <-> SyncEvolution sync: negotiate UID support via SyncCap (BMC #22783) The semantic of UID/RECURRENCE-ID in calendar data is now tracked per data store involved in a sync. If full iCalendar 2.0 semantic (= IDs are globally unique) is guaranteed, then pairs are found based on these IDs. Otherwise pairs must be found by looking at item attributes. Previously a hack was used to detect this kind of support (any kind of SyncEvolution instance was assumed to support it, although some backends do not). * engine: add DTSTAMP+LAST-MODIFIED before writing calendar items When writing calendar items into a backend storage as iCalendar 2.0 or vCalendar 1.0, they should have DTSTAMP and LAST-MODIFIED values. DTSTAMP is expected by some CalDAV servers (like Apple). LAST-MODIFIED is usually added by the storage, but not always. In the text/plain -> syncevolution -> text/calendar -> Radicale -> EDS -> syncevolution chain the LAST-MODIFIED was not added by Radicale, which caused problems for change tracking in an EDS-based SyncEvolution. Also necessary when importing from a phone using vCalendar without DTSTAMP directly into CalDAV. * autotools: ensure that link lines are complete As mentioned by Tino Keitel on the mailing list, some libs and executables were only implicitly linked against libraries that they called directly. This happened to work by chance because these libraries ended up in the running executable anyway, due to indirect loading. Now there is a "make installcheck" test for this kind of defect and the makefiles were updated to avoid it. One exception is libsmltk, which depends on the caller providing SySync logging support. * syncevolution.org packages: fixed D-Bus server autostart in .deb and .rpm packages syncevo-dbus-server wasn't started automatically as part of a user session because /etc/xdg/autostart/syncevo-dbus-server.desktop wasn't included in the packages. This broke auto syncing after a session restart (required manually starting SyncEvolution). * syncevolution.org packages: support KDE The traditional "syncevolution-evolution" package was replaced with "syncevolution-bundle". A meta "syncevolution-evolution" package depends on it, to support seamless updates for users who have "syncevolution-evolution" installed. Binary dependencies of the main .deb are ignored for backends because loading them is optional. The new "syncevolution-kde" package has the right dependencies for KDE/Akonadi, while "syncevolution-evolution" mostly just lists standard libs if the "EDS compatibility" mode is used, where libebook/libecal are loaded dynamically. Platform specific code (GNOME keyring, KDE wallet) was moved into loadable, optional modules, to allow installation of the SyncEvolution bundle without forcing the installation of unused system components. * D-Bus: use GIO D-Bus instead of libdbus if available When compiling from source, the more modern GIO D-Bus is used instead of libdbus if available and recent enough (>= 2.30). syncevolution.org binaries still use libdbus, to stay compatible with older Linux distros. * several minor bug fixes syncevo-dbus-server now runs under valgrind in the nightly testing, plus several more test scenarios were added. This helped to find and fix various minor memory handling issues. * developers: backend API changes beginSync/endSync() (aka m_startDataRead/m_endDataWrite) may now be called multiple times per SyncSource instance life cycle. SyncSources derived from TrackingSyncSource should work without changes. Use the Client::Source::*::testChangesMultiCycles test to check whether your backend supports this correctly. Reading and deleting must throw a 404 status exception when an item is not found. The Client::Source::*::*404 tests cover this. The special semantic of the former RegisterSyncSource::InactiveSource (invalid pointer of value 1) caused bugs, like using it in --print-databases (=> segfault) or not being able to store the result of a createSource() directly in a smart pointer (=> potential leak in SyncSource::createSource()). Obviously a bad idea to start with. Replaced with a RegisterSyncSource::InactiveSource() method which creates a real, inactive SyncSource instance which can and must be deleted by the caller. This is a SyncSource API change for backend developers. Instead of RegisterSyncSource::InactiveSource, return RegisterSyncSource::InactiveSource(). Comparisons against RegisterSyncSource::InactiveSource needs to be replaced with a call to the new SyncSource::isInactive(). Long-running backend calls are encouraged to check for events on the main glib context (either in a loop or with g_main_context_iteration(NULL)) and abort when SuspendFlags::getSuspendFlags().getState() returns SuspendFlags::ABORT. Implementing the improved local sync output required extending the D-Bus API. The Server.LogOutput signal now has an additional "process name" parameter. Normally it is empty. For messages originating from the target side, it carries that extra target context string. This D-Bus API change is backward compatible. Older clients can still subscribe to and decode the LogOutput messages, they'll simply ignore the extra parameter. Newer clients expecting that extra parameter won't work with an older D-Bus daemon: they'll fail to decode the D-Bus message. * packagers: libgdbussyncevo is now installed as a normal library in /usr/lib, even though SyncEvolution is the only user. pcrecpp is now a new hard dependency. Upgrading from release 1.2.x: The sync format of existing configurations for Mobical (aka Everdroid) must be updated manually, because the server has encoding problems when using vCard 3.0 (now the default for Evolution contacts): syncevolution --configure \ syncFormat=text/x-vcard \ mobical addressbook The Funambol template explicitly enables usage of the "refresh-from-server" sync mode to avoid getting throttled with 417 'retry later' errors. The same must be added to existing configs manually: syncevolution --configure \ enableRefreshSync=TRUE \ funambol Upgrading from releases before 1.2: Old configurations can still be read. But writing, as it happens during a sync, must migrate the configuration first. Releases >= 1.2 automatically migrates configurations. The old configurations will still be available (see "syncevolution --print-configs") but must be renamed manually to use them again under their original names with older SyncEvolution releases. SyncEvolution 1.2.99.3 -> 1.3, 10.09.2012 ========================================= Final SyncEvolution 1.3 release. The pre-releases did have the desired effect of flushing out bugs not found by nightly testing alone. Thanks everyone for packaging, downloading and testing them! Time to get it out officially as the next stable release. * D-Bus server + GIO D-Bus: shutdown fix When compiled against GIO D-Bus (not the case in syncevolution.org binaries), the syncevo-dbus-server occasionally shut down before sending out all pending D-Bus messages. Showed up only in nightly testing. * D-Bus server + GIO D-Bus: fix auto-activation (Debian bug #599247) When syncevo-dbus-server was started on demand by the D-Bus daemon, then it registered itself with the daemon before it was ready to serve requests. Only happened in combination with GIO D-Bus and thus was not a problem before 1.2.99.x. One user-visible effect was that the GTK UI did not select the default service when it was started for the first time, because it could not retrieve that information from syncevo-dbus-server. * local sync: fix timeout with local sync with libdbus When using libdbus instead of GIO D-Bus (as done by syncevolution.org binaries and SyncEvolution on Maemo), local sync may have aborted after 25 seconds when syncing many items with a D-Bus timeout error: [ERROR] sending message to child failed: org.freedesktop.DBus.Error.NoReply: Did not receive a reply. Possible ca Reported by Toke Høiland-Jørgensen for Harmattan. Somehow not encountered elsewhere. * KDE: check for D-Bus to avoid crash in KApplication (BMC #25596) Some unnamed version of KDE crashes in KApplication when invoked without a D-Bus session. The reporter ran into this when compiling from source, because the SyncEvolution binary is invoked as part of the build process, which ran outside of a D-Bus session. Avoid the crash by checking for a D-Bus session bus before instantiating KApplication. Instantiating KApplication was added for KWallet support. Without D-Bus, KWallet does not work either, therefore throw an explicit error when the lack of D-Bus is detected. * Funambol: work around PHOTO TYPE=image/jpeg A combination of Funambol Android and Funambol server recently led to the Funambol server sending PHOTO data with TYPE=image/jpeg. This is invalid and caused EDS to reject the photo (Vladimir Elisseev, "[SyncEvolution] issues with syncing photos"). Work around the problem by only keeping the part of the type after the last slash, if there is any. For image/jpeg and similar types that leads to the desired value and does not affect valid values, because those do not contain a slash (http://www.iana.org/assignments/media-types/image/index.html). * syncevo-http-server: fixed printing of server debug output Python failed to call logSyncEvoOutput() after adding the additional 'process' parameter to LogOutput because it extracts all four parameters and then cannot pass them to logSyncEvoOutput(). Now logSyncEvoOutput() uses the new process information to instantiate a logger with the right prefix, using 'sync' as fallback for messages without that information (as before). * Some minor code and test cleanup. SyncEvolution 1.2.99.3 -> 1.2.99.4, 07.08.2012 ============================================== Another release candidate for SyncEvolution 1.3. Lesson learned: declaring a snapshot as "final" is a good way of luring the hidden bugs into the light. Of course, then another snapshot is needed... Details: * D-Bus server: fix support for autoSyncDelay > 0 Auto syncing was not getting triggered when using an autoSyncDelay > 0; by default it is 5 minutes. Thanks to Vladimir Elisseev for reporting this problem. * command line: fixed --export When exporting items into a file, the delimiter between items was missing. * config: improved 'maxlogdirs' documentation The old explanation made it sound like nothing would get deleted by default ("If set, ..."). That's not correct, by default only 10 sessions are kept. Also explain the behavior of deleting intermediate sessions first. * developers: fixed D-Bus interface XML Reverted to Qt 4.x compatible annotations and changed "templateName" to "getTemplate" to make it more obvious what the parameter does. Only relevant for the out-of-tree Qt UI. Fixed accidental removal of the "template" parameter in Session.GetNamedConfig(). Was not used in practice, but has to be correct in case that someone wants to use it. SyncEvolution 1.2.99.2 -> 1.2.99.3, 24.07.2012 ============================================== Final release candidate for SyncEvolution 1.3 - fingers crossed, knock on wood, etc. ActiveSync is now available in binaries from syncevolution.org and becomes the recommended way of synchronizing contacts with Google. EDS 3.5.x and later are supported when compiling from source; syncevolution.org binaries continue to support only EDS up to 3.4. Details: * EDS: added support for EDS 3.5.x When compiled against EDS 3.5.x or later, SyncEvolution now uses the backend code originally written for the EClient API introduced in EDS 3.2. That code was changed so that it works with the new include file rules and ESourceRegistry in EDS 3.5.x. Support for using the EClient API with EDS 3.4 was removed because maintaining three different flavors of the EDS backend code would be too much work and not gain much (just the possibility to test the EDSClient code with 3.4). At the moment, this is a compile time choice made automatically by configure. syncevolution.org binaries are compiled against an older EDS and thus do not work with EDS 3.5.x or later. EDS 3.5.x handles authentication itself, using a standard system prompt if necessary. SyncEvolution can no longer provide the password, and thus the "databaseUser/Password" options have no effect when using EDS 3.5.x. * ActiveSync: updated to work with latest activesyncd and Google, package binaries Syncing Google contacts was added to the nightly testing. Syncing contacts and events with Exchange 2012 was already working. Setup instructions and known issues are described here: https://syncevolution.org/wiki/google-contacts-activesync * local sync: don't drop data comparison output on target side synccompare on the target side of a local sync was invoked with its output being redirected via an unreliable socket to the local sync parent. When the output was large, some of it might have been lost. * local sync: fixed crash When processing stdout from syncevo-local-child in syncevo-dbus-helper, the LogRedirect class was invoked recursively and tried to print the same stdout data repeatedly until the syncevo-dbus-helper crashed due to the infinite recurssion. * local sync: fixed helper process shutdown in case of parent failure The helper process only detected that the parent failed when it tried to log something while the parent had already shut down the D-Bus connection. Even that did not work reliably and differed between D-Bus libdbus and GIO. Added several test cases and fixes for "process died prematurely" error scenarios. * Mobical (aka Everdroid): stopped testing memo syncing Memos used to work, but now only trigger an unspecific 400 error on the server side. * autotools: ensure that link lines are complete As mentioned by Tino Keitel on the mailing list, some libs and executables were only implicitly linked against libraries that they called directly. This happened to work by chance because these libraries ended up in the running executable anyway, due to indirect loading. Now there is a "make installcheck" test for this kind of defect and the makefiles were updated to avoid it. One exception is libsmltk, which depends on the caller providing SySync logging support. * D-Bus server: fixed HTTP presence for recent libdbus Testing with libdbus 1.6.0 on Debian Testing failed because the lib changed some behavior: instead of looking up the owner of a certain bus name immediately, it now does that when invoking a method. Therefore the check for "have connection" in SyncEvolution was too simplistic and missed the fact that both were not usable, causing the server to assume that HTTP was down while in reality it should have assumed it to be up. This prevented auto-syncing and manually clicking "Sync" in the GTK UI. * syncevolution.org: declare dependencies on libical and EDS Let the bundle .deb depend on libical if the lib was enabled during compilation (for example, for CalDAV). This ensures that it gets installed on systems which otherwise don't have it. "syncevolution-evolution" is compatible (and depends on) EDS up to and including 3.4. The package now declares that dependency and conflicts with more recent EDS, because even if the older EDS libs are still installed they won't work when the rest of EDS was updated. * CalDAV + syncevolution.org: fixed segfault without libical+libecal When libical and libecal were not installed, trying to use the CalDAV backend for VEVENTs segfaulted because it depends on libical and did not check properly for it. Only affected syncevolution.org binaries. SyncEvolution 1.2.99.1 -> 1.2.99.2, 04.07.2012 ============================================== Next step towards SyncEvolution 1.3. It adds a workaround for Funambol's OneMedia and fixes an old bug which became more severe in 1.2.99.1. Also has some usability improvements for CalDAV/CardDAV. Hopefully it will not take long to stabilize the code, so test it now while it is still hot :-) Details: * Funambol: ignore UID Funambol's OneMedia sends UID, but not RECURRENCE-ID. That becomes a problem when multiple events of the same event series are added to a backend which follows the iCalendar 2.0 standard (CalDAV, EDS, KDE), because these events all look like the master event, and there can be only one of those. SyncEvolution now strips the UID from all events coming from any Funambol server (regardless of the version). If a future Funambol server release adds support for both UID and RECURRENCE-ID, then SyncEvolution will have to be updated to take advantage of the improved server. Because the RECURRENCE-ID is also getting stripped (despite not being set at the moment), SyncEvolution should continue to work as it does now even if the server changes. It would have been nice to limit this workaround to affected Funambol server versions, but an inquiry on the Funambol mailing list didn't get a reply, therefore SyncEvolution is playing it safe and assumes that all future Funambol releases will have the same problem. * WebDAV: fixed data corruption issue when uploading item with long UID In some cases data with a very long UID wasn't handled correctly, causing the out-going data to be malformed and probably causing a rejection by the server. The root cause is incorrect string handling. In releases before 1.2.99.1, only the --import operation of contacts into CardDAV were affected. In 1.2.99.1, the same code also got used for calendar items and then could also affect syncing. * engine: add DTSTAMP+LAST-MODIFIED before writing calendar items When writing calendar items into a backend storage as iCalendar 2.0 or vCalendar 1.0, they should have DTSTAMP and LAST-MODIFIED values. DTSTAMP is expected by some CalDAV servers (like Apple). LAST-MODIFIED is usually added by the storage, but not always. In the text/plain -> syncevolution -> text/calendar -> Radicale -> EDS -> syncevolution chain the LAST-MODIFIED was not added by Radicale, which caused problems for change tracking in an EDS-based SyncEvolution. Also necessary when importing from a phone using vCalendar without DTSTAMP directly into CalDAV. * Google Calendar: updated URL redirect handling Google Calendar sometimes returns redirect requests to human-readable web sites (an "unavailable" page, a login form). This is of course bogus when the client is an automated CalDAV client. The "unavailable.html" case was already handled. Made it a bit more flexible to also catch possible variations of it (additional parameters, https instead of http). Added the https://accounts.google.com/ServiceLogin case. Not sure whether retrying will help in that case, but there's not much else that SyncEvolution can do. * CalDAV + VJOURNAL: handle UID conflicts When asked to insert a VJOURNAL which already existed (= same UID), CalDAV servers respond with a 412 "Precondition failed" error. This needs to be detected and translated into an "item needs to be merged" result so that the engine can load the existing item, merge the data, and then write back. * WebDAV: --status for WebDAV source aborted The command line --status operation did not complete when applied to a CalDAV/CardDAV source. Instead it aborted because the operation took a code path where the backend was not fully initialized. * CalDAV/CardDAV sync: improved target side output Added a "target side of local sync ready" INFO message to introduce the output which has the target context in the [INFO] tag. The sync report from the target side now has the target context embedded in brackets after the "Changes applied during synchronization" header, to avoid ambiguities. Sometimes the backend has to resend requests because of temporary issues. If the problem turned out to be permanent, there was a long period of time, retryDuration=5 minutes to be precice, in which no visible progress happened. Now SyncEvolution's WebDAV backend will print a message like this before going to sleep until it is time to retry: [INFO @googlecalendar] operation temporarily (?) failed, going to retry in 5.0s before giving up in 18.4s: PROPFIND: Neon error code 1: 401 Unauthorized The uncertainty comes from several factors. In this example, the 401 might indicate a permanent problem (wrong credentials), or it could be Google reporting a temporary authorization problem which is (probably) meant to slow down the client while it asks the user to re-enter the password. SyncEvolution only asks for passwords once, so it tries again with the same password if it was successful with it in the past. Otherwise it gives up immediately. Another dubious example are name server lookup errors. They can be permanent (wrong host name) or temporary (name server down). SyncEvolution errs on the side of retrying, to avoid interrupting an operation which still has a chance to continue. Output from the target side of a local sync was passed through stderr redirection as chunks of text to the frontends. This had several drawbacks: - forwarding only happened when the local sync parent was processing the output redirection, which (due to limitations of the implementation) only happens when it needs to print something itself - debug messages were not forwarded - message boundaries might have been lost In particular the new INFO messages are relevant while the sync runs and need to be shown immediately. * command line: fixed password + property lookup during --print-databases --print-databases for an existing configuration did not look up passwords stored in a keyring, causing the operation to fail for backends like CalDAV/CardDAV where credentials are required. Overriding source properties in that case also only worked when using the unqualified property name ("databasePassword=foo") but not when using the source name as prefix ("calendar/databasePassword=foo"). * Developers: Implementing the improved local sync output required extending the D-Bus API. The Server.LogOutput signal now has an additional "process name" parameter. Normally it is empty. For messages originating from the target side, it carries that extra target context string. This D-Bus API change is backward compatible. Older clients can still subscribe to and decode the LogOutput messages, they'll simply ignore the extra parameter. Newer clients expecting that extra parameter won't work with an older D-Bus daemon: they'll fail to decode the D-Bus message. SyncEvolution 1.2.2 -> 1.2.99.1, 22.06.2012 =========================================== First pre-release of SyncEvolution 1.3. Contains bug fixes that were not backported to 1.2.x, so upgrading is recommended. For example, SyncEvolution 1.3 is required for Evolution 3.4, otherwise photos are not exported properly. Further workarounds for recent changes in Google CalDAV were added. Major new features are KDE/Akonadi support in the syncevolution.org binaries and ActiveSync support (only in the source code). The D-Bus server and local sync were rewritten considerably, to make the code cleaner and more robust. The CalDAV backend now also supports tasks and memos. Details: * phone sync: delete<->delete conflict + phone calendar+todo sync (BMC #23744) When deleting an item on phone and locally, the next sync failed with ERROR messages about "object not found". This has several reasons: - libsynthesis super data store attempts to read items which may or may not exist (triggers ERROR message) - it checks for 404 but Evolution backends only return a generic database error (causes sync to fail) * phone sync: get phone vendor and model from Device ID profile (BMC #736) In the past we have relied on the user-modifiable device name to be the fingerprint for matching a phone to a template which is unreliable. This release changes this in the cases where the phone supports the Device ID profile (DIP). If support for DIP is detected, then we extract the vendor and product ids and attempt to associate them with a product and vendor name by using a newly added lookup table. This lookup table has to be maintained manually and depends on contributions by users to cover more devices. See http://blixtra.org/blog/2011/09/22/syncevolution-needs-you-or-at-least-your-bluetooth-phones/ * vCalendar 1.0: fixed recurring all-day event support vCalendar 1.0 cannot represent all-day events. The workarounds for mapping iCalendar 2.0 all-day events into vCalendar 1.0 was incomplete, leading to effects like shifting EXDATEs and end times. * GTK-UI: accept service config with a username again (BMC#23106) Suppressing configs with empty username had undesired side effects: modifying configs for direct syncing with a device incorrectly triggered the same error message, without any means of entering a username. The faulty check was removed without replacement. * GTK-UI: added GTK 3 version of UI When GTK 3 is found during compilation, a GTK 3 version of the UI is built. The source code of both is different to avoid excessive use of ifdefs. At the moment, both versions offer the same features. In the long run, the GTK 3 version will replace the GTK 2 version. * command line: added refresh/one-way-from-local/remote (BMC #23537) The -from-client/server sync modes are confusing because the direction of the data exchange depends on which side acts as SyncML server or client. This release introduces new modes which use -from-local/remote instead. The statistics and messages also use these variants now. The old modes are still understood, but are declared as "not recommended" in the documentation. * command line: config and source names are optional (BMC #23783) The need to add "foo" and "bar" pseudo config and source names to the command line even when all parameters for the operation where explicitly specified on the command line was confusing. Now it is possible to invoke item operations without the config and source name. Names which refer to non-existent configs are still accepted, as in previous releases. Typos are handled better by producing a detailed error report which includes (as applicable): - config doesn't exist - source doesn't exist or not selected - backend property not set Because luids used to be positional arguments after and , a new --luids keyword is necessary to indicate that the ensuing parameters are luids and not and . * command line: introduced --print-databases, supported for CalDAV/CardDAV Listing databases is now a dedicated operation, instead of being done whenever syncevolution was invoked without parameters. Advantages: - can be combined with property assignments for backends which do not work without that additional information, for example CalDAV/CardDAV: syncevolution --print-databases \ backend=[caldav|carddav] \ syncURL=... \ username=... \ password=... - can be done for configured sources * command line: use both stdout and stderr Traditionally, the "syncevolution" command line tool mixed its INFO/ERROR/DEBUG messages into the normal stdout. This has the major drawback that error messages get lost during operations like syncevolution --export - @default addressbook | grep "John Doe" Now anything which not the expected result of the operation is always sent to stderr. Obviously this includes ERROR messages. INFO and DEBUG are harder to decide. Because they usually convey meta information about the running operation, they are also sent to stderr. The output of running a sync goes to both stdout (summary) and stderr (progress). * command line: allow setting empty properties Due to the way how properties were handled internally, it wasn't possible to explicitly set a property to its default value. Instead the property was unset. For example, explicitly setting database= was not possible. This is necessary for client-test and ActiveSync, because client-test needs to know that the testing is expected to run with the default databases (something which normally is avoided by overwriting empty database properties). Now the "is set" state is tracked explicitly in the config storage and command line property APIs. Unsetting a property via the command line could be implemented with an explicit command line option, but is not supported at the moment. * command line + local sync: fixed erroneous "Comparison impossible" output. "Comparison impossible" was incorrectly printed after a successful comparison on the target side of local sync. * synccompare: shorter data dump of PHOTO A full comparison of the base64 PHOTO data can be very long. Now some key characteristics of the PHOTO data (number of characters in base64 encoding, number of bytes in decoded data, md5sum of decoded data) are printed instead. That way, unintended changes of the data (different encoding, different content) should still be found while testing and added/removed photos are nicely visible in synccompare diffs. * synccompare: fixed output for byte-identical duplicates If database dumps contained byte-identical duplicates, they were treated as a single item on the left side of a comparison. This caused erroneous "added" entries on the right side. * secure password storage: usage of GNOME Keyring vs. KDE KWallet configurable Automatically detecting KDE users is not possible at the moment. Instead KDE users have to manually set the new "keyring" global config property to "KDE" (case insensitive) if the SyncEvolution installation supports both, because GNOME Keyring is the default to avoid surprises for traditional users. If only KWallet support is enabled, then this is not necessary. "GNOME" and "true/false/1/0/yes/no" can also be set. This has the advantage that keyring usage can be enabled permanently for the command line in --daemon=no mode; normally keyrings are not used in that mode because accessing them can bring up UI dialogs. It also becomes possible to disable keyring usage in syncevo-dbus-server, something which couldn't be done before. The --keyring command line option is still supported, as an alias for "[--sync-property] keyring=". The default value for --keyring is true, to match the traditional behavior. In contrast to other sync properties, setting "keyring" does not require an explicit --run parameter. Again this is done to mirror traditional usage. * Evolution: always create databases (PTCOM-113) Always try to create address book or calendar database, because even if there is a source there's no guarantee that the actual database was created already; the original logic for only setting this when explicitly requesting a new database therefore failed in some cases. This problem affected users who had never created anything locally and wanted to use SyncEvolution to migrate their data. Now that works without having to create dummy entries first. * Evolution contacts: changed default sync format to vCard 3.0 vCard 3.0 is the better default because it has saner encoding rules and defines more properties, thus avoiding the need for non-standard extensions. However, Mobical has problems with the new default. See upgrade instructions below. * D-Bus server: made notification verbosity configurable with "notifyLevel" The new "notifyLevel" per-peer configuration option allows users to control how many desktop notifications the D-Bus server produces while executing an automatic sync: 0 - suppress all notifications 1 - show only errors 2 - show information about changes and errors (in practice currently the same as level 3) 3 - show all notifications, including starting a sync (default) * CalDAV: updated Google workarounds Google started sending empty items (VCALENDAR with no VEVENT inside) which cannot be removed. SyncEvolution 1.3 ignores such items. The workaround for a 404 from Google Calendar for a GET (sending a REPORT request matching the item's UID) was broken: first, processing the result ended up calling the unset responseEnd boost function pointer, which caused the request to fail. Second, getting multiple items wasn't handled (data from all items concatenated together was used). That can happen in the somewhat unlike case that some items have a UID which is a complete superset of the requested UID - not realistic in real life, but happens during testing. * WebDAV: bridge with SyncML Now a peer accessed via SyncML can read/write data stored in a CalDAV/CardDAV server directly. This can be used to connect a device which only supports SyncML to a CalDAV/CardDAV server, or sync data between a SyncML server and a CalDAV/CardDAV server. See "CalDAV and CardDAV" in the README for details. * WebDAV: improved --configure Added INFO output about checking sources. This helps with WebDAV when the server cannot be contacted (dead, misconfigured) because otherwise there would be no indication at all why the --configure operation seems to hang. Here is some example output, including aborting: $ syncevolution --configure --template webdav \ syncURL=http://192.168.1.100:9000/ \ username=foo password=bar retryDuration=2s \ target-config@webdav-temp [INFO] creating configuration target-config@webdav-temp [INFO] addressbook: looking for databases... [INFO] addressbook: no database to synchronize [INFO] calendar: looking for databases... [INFO] calendar: no database to synchronize [INFO] memo: looking for databases... [INFO] memo: no database to synchronize [INFO] todo: looking for databases... [INFO] todo: no database to synchronize It timed out fairly quickly here because of the retryDuration=2s. That also gets placed in the resulting config, which is probably not desired. Aborting the operation is now supported: $ syncevolution --configure \ --template webdav \ syncURL=http://192.168.1.100:9000/ \ username=foo password=bar \ target-config@webdav-temp [INFO] creating configuration target-config@webdav-temp [INFO] addressbook: looking for databases... ^C[INFO] Asking to suspend... [INFO] Press CTRL-C again quickly (within 2s) to stop immediately (can cause problems in the future!) ^C[INFO] Aborting immediately ... [ERROR] error code from SyncEvolution aborted on behalf of user (local, status 20017): aborting as requested by user It would be good to make the CTRL-C handling code aware that it can abort immediately instead of doing the intermediate "asking to suspend" step, which only makes sense for sync sessions. * WebDAV: support tasks and memos (BMC #24893) The new backend property values "CalDAVTodo" and "CalDAVJournal" select tasks resp. memos stored in a CalDAV collection. "CalDAV" continues to select events. Events, tasks and journals can be mixed in the same resource (= URL). However, this is less efficient than storing them separately. A good CalDAV server allows filtering items by type, and SyncEvolution uses that. However, it was found that Radicale 0.7 ignores this filtering, which could have led to data loss (SyncEvolution asks for all VTODOs in preparation for a "delete all items" operation in a "CalDAVTodo" source, gets also VJOURNALs, then deletes them). Therefore SyncEvolution plays it safe and downloads the VTODO and VJOURNAL data to double-check that it is working on the right items. This causes additional traffic for well-behaving servers; currently it cannot be turned off. Tasks are exchanged as vCalendar 1.0 or iCalendar 2.0 VJOURNAL. Memos are exchanged as VTODO or plain text. The logic for storing incoming plain text is slightly different compared to the way how the EDS memo backend did it: instead of copying the first line from the text into the summary, it is now moved. In other words, the first line gets stripped. The change is primarily technically motivated; both approaches have pros and cons. * WebDAV: improved Radicale support Radicale > 0.7 will return status 200 for delete requests; is now treated like 204 by SyncEvolution. 412 'Preconditiona Failed' when asking to delete an already removed item is treated like the more common 404 'not found'. Same with 410 'gone' instead of 404 when trying to read a non-existent item. * file backend: more flexible sync support for memos The databaseFormat=text/calendar for memos did not support synchronizing as plain text. When using the new databaseFormat=text/calendar+plain, vCalendar/iCalendar/plain text are all valid sync formats; the storage is iCalendar 2.0 VJOURNAL in all cases. * WebDAV: avoid potential crash during database detection When a server responds to a PROPFIND for a path with results for some other path, then SyncEvolution crashed during the search for the default calendar or address book because of a bug in the code which was meant to handle that kind of response. Apparently Yahoo Calendar did that. Now seen again in combination with Radicale 0.6.4. In general, the code was made more robust to cope with bugs in Radicale 0.6.4. Later Radicale versions fixed these issues and also worked with SyncEvolution 1.2.2 without client-side workarounds. * WebDAV: better path normalization "syncURL" and "database" properties had to end in a trailing slash, otherwise items were not found (404 errors). Now the necessary slash is added automatically. * Funambol: avoid slow syncs in refresh from server libsynthesis has traditionally implemented "refresh-from-server" as "delete local data" plus "slow" sync. This is more compatible, because some servers (like Google) do not support "refresh-from-server". But it has the downside that the server cannot know that the client won't send any data, and Funambol's OneMedia now only allows one slow sync before blocking the next one for a certain period of time. This is done to prevent excessive resource usage by badly behaving clients. To accomodate both kinds of servers, the new "enableRefreshSync" sync property can be set set to explicitly allow the usage of the "refresh-from-server" sync mode. It's off by default. The Funambol template has it turned on, existing configs must be updated manually (see upgrading comments below). * Curl transport: support SSLServerCertificates= When the setting refers to a directory, then CURLOPT_CAINFO doesn't work (must be a file). Check this and use CURLOPT_CAPATH instead. Caveat: there are some comments in the API documentation about "NSS enabled libcurl" which supports a directory in CURLOPT_CAINFO. Hopefully providing an explicit path in CURLOPT_CAPATH also works in that configuration. * code cleanup + rewrite: syncing done in separate process syncevo-dbus-server now runs syncing in a separate process. Local sync also uses a second helper process. This makes the D-Bus server more responsive via D-Bus (no more blocking operations) and minimizes the effect of bugs in code involved with syncing (backends, system libraries, etc.). In the long term this restructuring will also allow more advanced features, like monitoring local or remote storage for changes. * SyncEvolution <-> SyncEvolution sync: multiple cycles per session SyncML only allows one send/receive cycle per session. There are cases (for example, client side merges data that a dumber server failed to match correctly) where client and server are still out of sync at the end of a cycle. When SyncEvolution syncs with another SyncEvolution instance (locally or remotely), both sides detect that the peer can continue syncing in the same session and start over automatically when needed. Previously the user had to start another sync session manually. To the user this is shown as "number of cycles" in a sync session in the sync report. "Restart" is the process of entering a new cycle. The cycles are also visible in the command line output as multiple INFO lines: [INFO] eds_contact: starting first time sync from client (peer is server) [INFO] creating complete data backup of source eds_contact before sync (enabled with dumpData and needed for prin Local data changes to be applied during synchronization: *** eds_contact *** no changes [INFO] eds_contact: sent 1/1 [INFO] eds_contact: started [INFO] eds_contact: first time sync done successfully [INFO] eds_contact: starting normal sync from client (peer is server) <=== [INFO] eds_contact: started <=== [INFO] eds_contact: normal sync done successfully <=== [INFO] creating complete data backup after sync (enabled with dumpData and needed for printChanges) Synchronization successful. Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | LOCAL | REMOTE | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | eds_contact | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | | refresh-from-local, 2 cycles, 0 KB sent by client, 0 KB received | ^^^^^^^^ | item(s) in database backup: 1 before sync, 1 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Tue Feb 7 17:07:49 2012, duration 0:03min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ * SyncEvolution <-> SyncEvolution sync: negotiate UID support via SyncCap (BMC #22783) The semantic of UID/RECURRENCE-ID in calendar data is now tracked per data store involved in a sync. If full iCalendar 2.0 semantic (= IDs are globally unique) is guaranteed, then pairs are found based on these IDs. Otherwise pairs must be found by looking at item attributes. Previously a hack was used to detect this kind of support (any kind of SyncEvolution instance was assumed to support it, although some backends do not). * syncevolution.org packages: fixed D-Bus server autostart in .deb and .rpm packages syncevo-dbus-server wasn't started automatically as part of a user session because /etc/xdg/autostart/syncevo-dbus-server.desktop wasn't included in the packages. This broke auto syncing after a session restart (required manually starting SyncEvolution). * syncevolution.org packages: support KDE The traditional "syncevolution-evolution" package was replaced with "syncevolution-bundle". A meta "syncevolution-evolution" package depends on it, to support seamless updates for users who have "syncevolution-evolution" installed. Binary dependencies of the main .deb are ignored for backends because loading them is optional. The new "syncevolution-kde" package has the right dependencies for KDE/Akonadi, while "syncevolution-evolution" mostly just lists standard libs if the "EDS compatibility" mode is used, where libebook/libecal are loaded dynamically. Platform specific code (GNOME keyring, KDE wallet) was moved into loadable, optional modules, to allow installation of the SyncEvolution bundle without forcing the installation of unused system components. * D-Bus: use GIO D-Bus instead of libdbus if available When compiling from source, the more modern GIO D-Bus is used instead of libdbus if available and recent enough (>= 2.30). syncevolution.org binaries still use libdbus, to stay compatible with older Linux distros. * several minor bug fixes syncevo-dbus-server now runs under valgrind in the nightly testing, plus several more test scenarios were added. This helped to find and fix various minor memory handling issues. * developers: backend API changes beginSync/endSync() (aka m_startDataRead/m_endDataWrite) may now be called multiple times per SyncSource instance life cycle. SyncSources derived from TrackingSyncSource should work without changes. Use the Client::Source::*::testChangesMultiCycles test to check whether your backend supports this correctly. Reading and deleting must throw a 404 status exception when an item is not found. The Client::Source::*::*404 tests cover this. The special semantic of the former RegisterSyncSource::InactiveSource (invalid pointer of value 1) caused bugs, like using it in --print-databases (=> segfault) or not being able to store the result of a createSource() directly in a smart pointer (=> potential leak in SyncSource::createSource()). Obviously a bad idea to start with. Replaced with a RegisterSyncSource::InactiveSource() method which creates a real, inactive SyncSource instance which can and must be deleted by the caller. This is a SyncSource API change for backend developers. Instead of RegisterSyncSource::InactiveSource, return RegisterSyncSource::InactiveSource(). Comparisons against RegisterSyncSource::InactiveSource needs to be replaced with a call to the new SyncSource::isInactive(). Long-running backend calls are encouraged to check for events on the main glib context (either in a loop or with g_main_context_iteration(NULL)) and abort when SuspendFlags::getSuspendFlags().getState() returns SuspendFlags::ABORT. * packagers: libgdbussyncevo is now installed as a normal library in /usr/lib, even though SyncEvolution is the only user. pcrecpp is now a new hard dependency. Upgrading from release 1.2.x: The sync format of existing configurations for Mobical (aka Everdroid) must be updated manually, because the server has encoding problems when using vCard 3.0 (now the default for Evolution contacts): syncevolution --configure \ syncFormat=text/x-vcard \ mobical addressbook The Funambol template explicitly enables usage of the "refresh-from-server" sync mode to avoid getting throttled with 417 'retry later' errors. The same must be added to existing configs manually: syncevolution --configure \ enableRefreshSync=TRUE \ funambol Upgrading from releases before 1.2: Old configurations can still be read. But writing, as it happens during a sync, must migrate the configuration first. Releases >= 1.2 automatically migrates configurations. The old configurations will still be available (see "syncevolution --print-configs") but must be renamed manually to use them again under their original names with older SyncEvolution releases. SyncEvolution 1.2.1 -> 1.2.2, 13.01.2012 ======================================== Maintenance release with various bug fixes. * syncevo-dbus-server + ConnMan: fixed "online" detection (BMC #21541, BMC #24587) SyncEvolution did not recognize any cellular connectivity as suitable for syncing. The strict check for certain "connected technology" is unnecessary, anything which makes the computer "online" should be good enough. So now it just uses the ConnMan "State" property. Additional benefit: will continue to work with ConnMan 1.0, which won't have the "ConnectedTechnologies" property anymore. The Bluetooth available check was also (incorrectly) using the ConnMan API. Now asssume that OBEX/Bluetooth is always available. * automatic backups: added INFO messages and fixed dumpData/printChanges (BMC #24619) Point out that backups are created (user might be unaware otherwise and wonder about the delay), explain why (so that users know how to turn it off). Turning these backups off with dumpData=0 printChanges=0 had to be fixed, backups were always written previously. * EDS compatibility: bumped version check for EDS 3.2 SyncEvolution is known to work with EDS 3.2. Therefore use the libebook/ecal/edataserver libs from 3.2 if available, without warnings in the --version output. Also happens with inconsistent distro setups where the old libs are available and would have been prefered by SyncEvolution 1.2.1 even though the old libs no longer work with EDS 3.2. * GTK-UI: do not accept service config without a username (BMC#23106) Instead of creating such a config, an error dialog is shown. * GTK-UI: updated translations * fixed various compile issues, primarily on Fedora Core 17 (unistd.h/ssize_t, invoking syncevolution during compilation, missing src/dbus/qt/configure-sub.in) SyncEvolution 1.2 -> 1.2.1, 25.11.2011 ====================================== Maintenance release with various bug fixes. * GTK UI + config: fix "custom server" setup (BMC #13511) When the "default" config template (= ScheduleWorld) was downgraded to "not consumer ready" in SyncEvolution 1.1.0.99.1, setting up a custom SyncML service in the GTK UI stopped working because the UI wouldn't show the "not consumer ready" config. The problem described above is deterministic and fixed now. Initially the problem seemed to be random. So perhaps there is also another, related issue. * phone sync: delete<->delete conflict + phone calendar+todo sync (BMC #23744) When deleting an item on phone and locally, the next sync failed with ERROR messages about "object not found". Retrying the sync then worked. * Nokia: prevent accidental usage of "calendar" or "todo" sources Nokia phones use a combined "calendar+todo" source for syncing. The "calendar" and "todo" sources also exist because that is where local databases are configured. In such a setup, syncing always has to use "calendar+todo". For example, to refresh from the Linux desktop to the phone, use: --sync refresh-from-server calendar+todo To work with items (restore, show local content), use the underlying sources, as in: --print-items calendar It was possible to accidentally sync with the "calendar". This commit prevents that by adding an invalid URI setting to the "calendar" and "todo" sources in the Nokia and Ovi templates. Existing configs are not touched, so beware when you already have configured your Nokia phone. * vCard: X- chat extensions were limited to one instance per kind For example, only one Jabber account could be synchronized. This was caused by an incomplete definition of the conversion to and from vCard. * syncevo-dbus-server + phone sync: catch SIGPIPE to avoid premature exit Frederik Elwert reported that running a local sync with a phone via Bluetooth caused the syncevo-dbus-server to shut down during a sync. Explicitly telling the process to ignore the SIGPIPE signal solved that problem. * syncevo-http-server: support chained SSL certificates So far, the file pointed to by --certificate-file had to contain the server certificated (signed by a CA known to the client) and (optionally) a client certificate. Now the file may also contain additional intermediate certificates which will be sent to the client (chained certificates). * documentation: added glossary and command line conventions sections, improved listing of properties, embedd property definitions in man page, README and README.html * EDS compatibility: fixed inconsistency in libecal check The check for the _r variants in libical still used an older max version. This might have prevented using them (if not found) or could have led to a mixture of old and new libecal in the same process (probably crashed). * glib: avoid including glib/*.h headers directly Recent glib deprecates the direct inclusion of some of its headers, in favor of including glib.h. Doing that here whenever possible, so perhaps it now compiles on Fedora 17 (untested). SyncEvolution 1.1.1 -> 1.2, 13.10.2011 ====================================== The major new feature of the 1.2 release is support for non-SyncML protocols in general and CalDAV/CardDAV in particular. ActiveSync support is in development and will be in 1.3. These protocols are implemented as backends which are combined with other backends by SyncEvolution in a so called "local sync". The GTK sync-ui does not yet support configuring non-SyncML protocols. See the README.rst and man page for more information on how to use the new feature via the command line. Properties not supported by SyncML servers can now be preserved locally in two-way synchronization (BMC #15030). This depends on information about what properties a SyncML server supports ("CtCap"), which is typically not provided by servers. SyncEvolution contains a copy of that information for Google Contacts (BMC #15029). Akonadi backend and KWallet support were merged. They are not included yet in syncevolution.org binaries. To use them compile from source. The configuration format was updated to solve a conceptual problem inherited with the legacy property names: the "type" property had multiple, sometimes conflicting roles. For example, setting the preferred data format for sync with one peer might have changed the backend selection for some other peer (BMC #1023). Now "backend/databaseFormat/syncFormat/forceSyncFormat" replace "type". "type" is still accepted by the command line as alias. Upgrading from releases before 1.2: Old configurations can still be read. But writing, as it happens during a sync, must migrate the configuration first. Release 1.2 automatically migrates configurations. The old configurations will still be available (see "syncevolution --print-configs") but must be renamed manually to use them again under their original names with older SyncEvolution releases. Other changes: * Using the --sync-property and --source-property command line options is optional, just specifying the property assignment is enough. * syncevo-http-server was enhanced considerably. See http://syncevolution.org/wiki/http-server-howto * support NetworkManager API >= 0.9 (BMC #19470) * syncevolution.org binaries: now compatible with Debian Testing/libnotify.so.4 (BMC #22668) libnotify is not linked directly into syncevo-dbus-server in the syncevolution.org binaries. Instead libnotify.so.1 till .so.4 (current Debian Testing) are opened opened dynamically and the necessary functions are looked up via dlsym(). Not finding the libraries or the functions silently disables this notification mechanism. * Sync mode is recorded when running in SyncML server mode (BMC #2786). * syncevo-dbus-server automatically stops when some of its libraries are updated and restarts if auto-syncing is on (BMC #14955). * Added code for Buteo, mKCal and QtContacts in MeeGo. Buteo and mKCal were removed again from MeeGo, so the code is obsolete. The QtContacts backend may be still be useful to access items via that API, but for syncing on MeeGo the normal EDS backend is used since MeeGo reverted back to EDS as PIM storage. * "databasePassword" source property: lookup failure in keyring (BMC #22937) The databasePassword also wasn't looked up at all when doing item operations via the command line. When configuring sources for an HTTP server, the config name typically is just the context (@foo). When using the config in the HTTP server, the config name is the peer inside that context (client@foo). Because the GNOME keyring lookup keys for the "databasePassword" (more specifically, the object name) contained the full config name which was different in both cases, looking up the saved password failed. The solution is to normalize the config name (to accomodate for different ways of spelling it) and use only the context, with @ as before. This will break existing setups where the object name in the keyring (incorrectly) includes the full config name. In that case just configure the source again to set the password anew. * Evolution Calendar: fixed detached recurrence support (BMC #22940) When manipulating a meeting series with more than one detached recurrence certain sequences of operations could incorrectly fail with "UID already exists". * iCalendar 2.0: must set VALUE in EXDATE (part of BMC #22940) EXDATE has a VALUE parameter, which wasn't defined in the XML profile. Didn't seem to matter at all in practice, but wasn't standard-compliant. * GTK sync-ui: wrap sync service descriptions (BMC #7199) Descriptions of different sync services are not fully visible unless word-wrapping gets enabled. * CalDAV/CardDAV + local storage: avoid empty properties The main motivation for this change is that a recent Apple Calendar server rejects vCards with empty BDAY property. Another reason is that keeping the data as small as possible is desirable by itself. Sending an empty property serves as a hint for the peer that the property is supported. This is not necessary when storing an item in a backend. Therefore this commit disables empty properties for all backends which do not themselves set the m_backendRule Synthesis info value. * Google Contacts: ensure that first/middle/name are set when storing in EDS (BMC #20864) Evolution and the MeeGo UX assume that first/middle/last name are set. That is not the case when a contact is created in the Google Contacts web interface. Such contacts are sent by Google without the N property. SyncEvolution now tries to recreate the name components from the FN string, by splitting at word boundaries and assuming " " or ", " format. Obviously this heuristic fails for some locales. * Evolution Calendar: fixed error handling for broken TZIDs * Sony Ericsson: use ISO-8859-1 for all devices (BMC #14414) Passing invalid UTF-8 strings into libecal caused glib to abort syncevo-dbus-server. * auto sync: show all failed syncs except for temporary network errors (BMC #21888) Notifications were meant to be shown for all errors except temporary ones. This has never been implemented correctly since the feature was introduced: instead of hiding known temporary errors, all errors except 500 (fatal error) were suppressed. * vCard: inline local photo data (BMC #19661) Some platforms (Maemo, MeeGo) store photos in separate files. Now SyncEvolution efficiently includes that photo data in the generated vCard right before sending it to a peer; previously it sent a useless local file:// URI. The Maemo port has a less efficient workaround for that which now should be obsolete. * syncevo-dbus-server: online status wrong without Network Manager or ConnMan (BMC #21543) When neither Network Manager nor ConnMan are running, network presence was "not online". This prevented running automatic syncs. For developers: * modified backend API - ClientTestConfig modernized - InsertItemResult::m_merged turned from boolean to enum * testing and compilation changes; for example, the minimum version of libsynthesis is now checked at configure time instead of failing at runtime due to missing features in the Synthesis engine SyncEvolution 1.1.99.7 -> 1.2, 13.10.2011 ========================================= Some more bug fixes and testing improvements. * fixed potential invalid memory access in add<->add conflict handling * fixed memory leak in workaround for EDS bug * CalDAV/CardDAV: handle ETags without quotation marks (eGroupware) * updated README: warning about sync direction moved to --sync option SyncEvolution 1.1.99.6 -> 1.1.99.7, 15.09.2011 ============================================== Mostly bug fixes again. Some are a bit more intrusive, thus another pre-release. * syncevolution.org binaries: now compatible with Debian Testing/libnotify.so.4 (BMC #22668) libnotify is not linked directly into syncevo-dbus-server in the syncevolution.org binaries. Instead libnotify.so.1 till .so.4 (current Debian Testing) are opened opened dynamically and the necessary functions are looked up via dlsym(). Not finding the libraries or the functions silently disables this notification mechanism. * calendar sync: better handling for add<->add conflicts (partly fixes BMC #22783) When both sides of a sync have added the same event, the sync must determine which one is more recent instead of blindly overwriting always the same side. Such conflicts are typically rare except for enterprise scenarios where meeting invitiations are processed automatically by a groupware (Exchange, Google Calendar/Mail, ...) and then the attendee status is updated on one side. SyncEvolution now does the necessary age comparison and preserves the more recent data for most properties. In some properties the data from both sides is preserved by concatenating the text (description, location, ...). It remains to be seen whether that is really desirable. Also, sync statistics are slightly off: the incoming item is counted as "added" even though it gets turned into an update. * item operations: authentication problem for WebDAV when using keyring (BMC #21311) The password still wasn't looked up in the keyring when using --import/export/delete-items. * "databasePassword" source property: lookup failure in keyring (BMC #22937) The databasePassword also wasn't looked up at all when doing item operations via the command line. When configuring sources for an HTTP server, the config name typically is just the context (@foo). When using the config in the HTTP server, the config name is the peer inside that context (client@foo). Because the GNOME keyring lookup keys for the "databasePassword" (more specifically, the object name) contained the full config name which was different in both cases, looking up the saved password failed. The solution is to normalize the config name (to accomodate for different ways of spelling it) and use only the context, with @ as before. This will break existing setups where the object name in the keyring (incorrectly) includes the full config name. In that case just configure the source again to set the password anew. * Evolution Calendar: fixed detached recurrence support (BMC #22940) When manipulating a meeting series with more than one detached recurrence certain sequences of operations could incorrectly fail with "UID already exists". * iCalendar 2.0: must set VALUE in EXDATE (part of BMC #22940) EXDATE has a VALUE parameter, which wasn't defined in the XML profile. Didn't seem to matter at all in practice, but wasn't standard-compliant. * GTK sync-ui: wrap sync service descriptions (BMC #7199) Descriptions of different sync services are not fully visible unless word-wrapping gets enabled. * source configs: don't check "backend" unless it is needed When using a config which has sources with a backend type set which is not currently available, an error was thrown even if those sources weren't even part of the current operation (for example, syncing another source which is currently supported). * config migration: avoid name conflicts and auto syncing of old configs (BMC #22691) When (auto-)migrating a config, it was possible that a name for the peer, say foo.old, was chosen for the renamed config although there was already such a config, for example foo.old in ~/.sync4j. Besides being confusing for users, this also led to a bug in the code where it copied from the older config with the foo.old name. The main problem fixed is the disabling of auto syncing in the old config. Otherwise it was still used by syncevo-dbus-server for syncing, which triggered another auto-migration, ad infinitum... * auto syncing: must check whether enabled when looking at unknown URLs (part of BMC #22691) "syncURL = insert your URL here" with "autoSync = 0" did lead to auto sync attempts although it wasn't enabled. A check for "auto syncing enabled" was missing for the "unknown transport" case. * CalDAV/CardDAV + local storage: avoid empty properties The main motivation for this change is that a recent Apple Calendar server rejects vCards with empty BDAY property. Another reason is that keeping the data as small as possible is desirable by itself. Sending an empty property serves as a hint for the peer that the property is supported. This is not necessary when storing an item in a backend. Therefore this commit disables empty properties for all backends which do not themselves set the m_backendRule Synthesis info value. * Apple CardDAV: apply PHOTO import/export scripts by default A recent Apple Calendar server (correctly) rejects the invalid PHOTO;TYPE=unknown: property in a vCard. This internal representation must be cleared before serializing the field list. * for developers: modified backend API - ClientTestConfig modernized - InsertItemResult::m_merged turned from boolean to enum * testing and compilation changes; for example, the minimum version of libsynthesis is now checked at configure time instead of failing at runtime due to missing features in the Synthesis engine SyncEvolution 1.1.99.5 -> 1.1.99.6, 17.08.2011 ============================================== Mostly bug fixes, some improvements in testing and packaging. This release was tested successfully with DAViCal 0.9.9.4. * CalDAV: fixed incorrect change tracking causing "event not found" (BMC #22329) * CalDAV: handle delete<->delete conflict during local sync (BMC #22327) If the same event was deleted both locally and in the CalDAV server, syncing failed with "event not found". * Google Contacts: ensure that first/middle/name are set when storing in EDS (BMC #20864) Evolution and the MeeGo UX assume that first/middle/last name are set. That is not the case when a contact is created in the Google Contacts web interface. Such contacts are sent by Google without the N property. SyncEvolution now tries to recreate the name components from the FN string, by splitting at word boundaries and assuming " " or ", " format. Obviously this heuristic fails for some locales. * CalDAV: continue despite Google Calendar access problems (see BMC #19484) An attempt to work around "403 You don't have access to change that event" errors, perhaps caused by http://code.google.com/p/google-caldav-issues/issues/detail?id=38 The problem is now recorded instead of aborting the sync. The sync then ends in a 22001 = "partial failure" error and the operation will be retried in the next sync. * CalDAV: transform UTC RECURRENCE-ID for Evolution (BMC #22594) Evolution showed a meeting twice on the day of a modified recurrence, if the meeting series was originally created and modified in Exchange, then imported into Google Calendar. * CalDAV syncevolution.org binaries now works when libneon.so.27 or libneon-gnutls.so.27 (Debian) are installed. Previously libneon.so.27 was required, which is no longer available in Debian Testing. * syncevo-dbus-server/gdbus: fixed segfault when asked for properties when none are available (BMC #22152) * Evolution Calendar: fixed error handling for broken TZIDs * Sony Ericsson: use ISO-8859-1 for all devices (BMC #14414) Passing invalid UTF-8 strings into libecal caused glib to abort syncevo-dbus-server. * item operations: authentication problem for WebDAV when using keyring (BMC #21311) The password wasn't looked up in the keyring when using --print-items/import/export/... * WebDAV: fixed item operations without configuration (BMC #22164) Previously failed with "[ERROR] : virtual read-only configuration node, cannot write property webDAVCredentialsOkay = 1". * auto sync: show all failed syncs except for temporary network errors (BMC #21888) Notifications were meant to be shown for all errors except temporary ones. This has never been implemented correctly since the feature was introduced: instead of hiding known temporary errors, all errors except 500 (fatal error) were suppressed. * vCard: inline local photo data (BMC #19661) Some platforms (Maemo, MeeGo) store photos in separate files. Now SyncEvolution efficiently includes that photo data in the generated vCard right before sending it to a peer; previously it sent a useless local file:// URI. The Maemo port has a less efficient workaround for that which now should be obsolete. * syncevo-dbus-server: online status wrong without Network Manager or ConnMan (BMC #21543) When neither Network Manager nor ConnMan are running, network presence was "not online". This prevented running automatic syncs. * fixed compile issues with Debian Testing/gcc 4.6.1 Known issues, might still be resolved for the final 1.2: -------------------------------------------------------- * syncevolution.org binaries: libnotify1 -> libnotify4 incompatibility (BMC #22668) Newer distros no longer have the libnotify.so.1 that syncevolution.org binaries depend on. As a workaround it is possible to install the libnotify1 package from older distro releases. * CalDAV: add<->add conflicts (BMC #22669) Suppose the same meeting invitation for event UID=FOO is processed in both Evolution and Google Calendar. This always happens when the meeting invitation emails is sent to Google Mail, then later viewed in Evolution. On the Evolution side, the invitation is accepted. In Google Calendar this is still open. When syncing in that state the sync engine does not recognize that both sides have added the same meeting and the "meeting accepted" information eventually gets lost. As a workaround, always synchronize the calendar before processing meeting invitation emails. SyncEvolution 1.1.99.1 -> 1.1.99.5, 13.07.2011 ============================================== Release 1.1.99.5 is the first release candidate for 1.2. It has gone through a long stabilization period and thus is suitable for normal users. The major new feature of the 1.2 release is support for non-SyncML protocols in general and CalDAV/CardDAV in particular. ActiveSync support is in development. These protocols are implemented as backends which are combined with other backends by SyncEvolution in a so called "local sync". The GTK sync-ui does not yet support configuring non-SyncML protocols. See the README.rst and man page for more information on how to use the new feature via the command line. Properties not supported by SyncML servers can now be preserved locally in two-way synchronization (BMC #15030). This depends on information about what properties a SyncML server supports ("CtCap"), which is typically not provided by servers. SyncEvolution contains a copy of that information for Google Contacts (BMC #15029). Akonadi backend and KWallet support were merged. They are not included yet in syncevolution.org binaries. To use them compile from source. The configuration format was updated to solve a conceptual problem inherited with the legacy property names: the "type" property had multiple, sometimes conflicting roles. For example, setting the preferred data format for sync with one peer might have changed the backend selection for some other peer (BMC #1023). Now "backend/databaseFormat/syncFormat/forceSyncFormat" replace "type". "type" is still accepted by the command line as alias. Old configurations can still be read. But writing, as it happens during a sync, must migrate the configuration first. In contrast to earlier, more experimental releases in the 1.2 series, 1.1.99.5 and later automatically migrate configurations. The old configurations will still be available (see "syncevolution --print-configs") but must be renamed manually to use them again under their original names with older SyncEvolution releases. Other changes: * syncevo-http-server was enhanced considerably. See http://syncevolution.org/wiki/http-server-howto * support NetworkManager API >= 0.9 (BMC #19470) * Sync mode is recorded when running in SyncML server mode (BMC #2786). * syncevo-dbus-server automatically stops when some of its libraries are updated and restarts if auto-syncing is on (BMC #14955). * Using the --sync-property and --source-property command line options is optional, just specifying the property assignment is enough. * Added support for Buteo, mKCal and QtContacts in MeeGo. Buteo and mKCal were removed again from MeeGo, so the code is obsolete. The QtContacts backend may be still be useful to access items via that API, but for syncing on MeeGo the normal EDS backend is used since MeeGo reverted back to EDS as PIM storage. * code cleanup and various minor fixes/improvements, see ChangeLog SyncEvolution 1.1 -> 1.1.1, 26.12.2010 ====================================== Maintenance release, in particular improving syncing with phones. There was a bug that could cause all kinds of weird behavior after a failed sync with a phone, so updating is highly recommended. * Synthesis engine: fixed a corruption issue in internal meta data which caused duplicates and other problems in a pretty indeterminstic way; apparently caused by failed syncs (BMC #11044). * Synthesis engine: recurrence rules with end date now sent correctly to phones (BMC #11241). The RRULE property was not encoded correctly previously during the iCalendar 2.0 -> vCalendar 1.0 conversion. Events with recurrence count were okay. Probably also affected SyncML servers without iCalendar 2.0 support. The fix was confirmed to work with Nokia phones. It also helps with Sony Ericsson phones, but at least the t700 still has a problem: depending on the phone's time zone, it repeats the event for one day too long (BMC #10092). * Synthesis engine: fixed broken time zone information when sending to phone; previously that broke sending calendar updates to Nokia phones (BMC #9600). iCalendar 2.0 time zone definitions imported from libical were not encoded correctly in vCalendar 1.0 items as sent to phones. Nokia phones accepted such data when part of a new event, but rejected updates of it. * Synthesis engine: shorter TZIDs, might help N900 calendar (BMC #6680). The shorter TZIDs will be included in iCalendar 2.0 data exported by libsyntesis and thus SyncEvolution. This change is motivated primarily by the observation that the N900 calendar storage can handle TZID=, but not TZID=/softwarestudio.org/Tzfile/. * ScheduleWorld: disable configuration template because service has shut down. The template is only hidden from the GTK sync-ui, but remains in SyncEvolution for the time being because it is referenced in several places. * Evolution CalDAV: added workaround for "must sync twice" (BMC #10265) The Evolution CalDAV backend seems to update its data when closing the database, not when opening it. As a result, syncevolution had to be run twice to see all data changes. The workaround is to open the database twice at the start of the sync. This is done for all calendar databases, regardless of which backend they use, in case that some other (yet unknown) backend needs the same workaround. * GTK sync-ui: workaround for "Sync Now" button not reacting to online status changed (BMC #9949). * Changed slow sync handling. Some users have complained about getting duplicated contacts (BMC #10081). The exact reason is not known (no useful logs provided yet), but it might be due to using "duplicate" as resolution strategy during slow syncs. This caused slightly different contacts to be duplicated instead of merging the two copies, reasoning that "no data loss" is better than "duplicates". This release switches to a mode where the engine tries harder to avoid duplicates by merging data if modification time stamps are available for contacts (usually they are). When fields differ, the more recent data is kept. * convert absolute alarm back to relative (BMC #11233) Experiments show that at least Nokia phones (and thus perhaps also Mobical.com) interpret a fixed alarm as "repeat alarm with the same relative offset as on first occurrence". The same transformation to relative alarm times is applied whenever the transformation to absolute alarm is enabled for a peer. * Sony Ericsson: enable conversion to absolute alarm times (BMC #10092) Like Nokia and Mobical.net, Sony Ericsson phones also seem to be unable to deal with relative alarm times - verified with t700. * Sony Ericsson C510: workaround for SyncML violation The phone does not sent identifiers for the target database; using the source identifier as fallback allows a sync to run. * Fixed a regression affecting users who had created a config with SyncEvolution < 1.0. Using the config worked once, then failed with "No configuration for ... found". Users must manually remove the empty "peers" directory inside their affected configuration, the fix only makes configs without that directory usable again (BMC #9381). * Removed obsolete workaround for older mKCal calendar storage. * Fixed error message in QtContacts backend. * Same SYNCEVOLUTION_DEBUG code as in master branch. * Some updates to synccompare, including a workaround for a Perl bug seen on Debian Testing with Perl 5.10.1-16 (Perl panic). * Fix compilation of syncevo-dbus-server with libnotify 0.7.0 (BMC #10453). * Fixed compilation on Debian GNU/Hurd (no MAX_PATH, Mac OS X confusion). SyncEvolution 1.0.1 -> 1.1, 26.10.2010 ====================================== An incremental update, resolving issues where the fixes would have been too intrusive for a 1.0.x release. In particular compatibility with Nokia phones was improved. Some new features were also included (command line options for manipulating items, backends for MeeGo PIM storages). Details: * bug fix in sync-ui: wrong direction of one-way data transfers with devices (BMC #7091) * bug fix in syncevo-dbus-server: incorrect Presence status after config change (BMC #8453) Shows up in sync-ui as "'Sync Now' button active after creating a config while offline". * sync-ui (GTK version): app is now listed as "SyncEvolution (GTK)" under "Office" * Nokia phones: avoid data loss in two-way sync due to X-EVOLUTION-UI-SLOT (BMC #2566) * Nokia phones: alarm times in UTC, sending PHOTO (BMC #1657, #5860) * included all phone templates submitted to syncevolution.org Wiki (BMC #5727) * syncevo-phone-config: set consumerReady in output, more useful for Wiki (BMC #3803) * workaround for D-Bus timeouts in EDS libecal/libebook (BMC #4026) * added generic command line options for importing, exporting, updating, listing and deleting items in the different backends (http://syncevolution.org/blogs/pohly/2010/manipulate-evolution-kcalextendedmkcal-qtcontacts-pim-items-uniform-command-line) * added backends for mKCal and QtContacts (MeeGo PIM storage), meant to be used for manipulating this data on the command line * enhanced D-Bus interface (BMC #3558, #3559, #3560, #3562, #3563, #7761, #7766) * the command line tool now warns when running against a different D-Bus daemon (BMC #3563) * creating and configuring sources in a context (without peer-specific properties) is now supported * improved documentation: README.rst, man page, and --help output * fixed some compile issues (BMC #6367), improved nightly testing SyncEvolution 1.0 -> 1.0.1, 16.07.2010 ====================================== A bug fix release. The main reason for releasing it is that SyncEvolution 1.0 no longer worked on recent distros (Fedora Core 13, Debian testing) because of a name clash between the Bluez D-Bus utility code and recent glib. Details: * compile fix for FC 13 (and possibly others): use private copy of gdbus (BMC #3556) * sync-ui: prevent overwriting device configs by accident (BMC #3566,1194) Setting up a phone used the template name as config name and overwrote an existing configuration of another phone that was created using that same template. Now the code uses the Bluetooth device name as set on the device and checks for (less likely) collisions. It also sanitizes the name to avoid complicated config names (only relevant when also using the command line). * syncevo-dbus-server: accept 'application/vnd.syncml+xml; charset=UTF-8' for starting an HTTP session (BMC #3554) The redundant charset specification was set by the Funambol Thunderbird client. Because of a literal comparison against 'application/vnd.syncml+xml' the messages were rejected. * config fix: operations on non-peer configs failed (BMC #3157) When running operations on a non-peer configuration (like --restore @default addressbook), the operation fails with [ERROR] : type 'select backend' not supported * ZYB.com: service goes away end of June 2010, template removed (BMC #3310) * some build (BMC #2586, BMC #3557) and language updates SyncEvolution 0.9.2 -> 1.0, 11.06.2010 ====================================== Major new features compared to previous stable release: * synchronize directly with a phone over Bluetooth/OBEX * accept Bluetooth/OBEX connections in cooperation with obexd >= 0.19 * run SyncEvolution as a rudimentary HTTP SyncML server The GTK sync-UI can be used to select a paired phone and create a configuration for it based on the bundled configuration templates. Configuration templates are included for Nokia phones; for other phones see the http://syncevolution.org/development/sync-phone HOWTO and check out the Wiki there. Some users have already reported success for Sony Ericsson phones and added setup instructions. New templates from the Wiki can be dropped into ~/.config/syncevolution-templates under an arbitrary file name. Unexpected slow syncs can be detected when running as client (MB #2416) and unless turned off (see "preventSlowSync"), SyncEvolution aborts the session so that the situation can be analyzed. A refresh from client or server might be more suitable. The command line tool provides instructions at the end of its output. The GTK sync-UI points towards its recovery dialog. Automatic synchronization is supported by the syncevo-dbus-server (MB #6378). When that is installed, it will be started as part of a user session and keep running to trigger syncs in the background. Notifications are emitted when syncs start, end or fail (MB #10000). Automatic synchronization can be enabled separately for each peer ("autoSync=0/1", off by default), will be done at regular intervals ("autoSyncInterval=30" minutes) when online long enough ("autoSyncDelay=5" minutes). That last option ensures that a) an automatic sync does not attempt to use a network connection unless it was already active and b) hopefully is also around long enough to complete the sync. The Synthesis XML configuration was split up into different parts which are assembled from /usr/share/syncevolution/xml. Files in ~/.config/syncevolution-xml override and extend the default files, which my be useful when adding support for a new phone. SyncML servers: * ZYB.com now works thanks to a workaround for anchor handling (MB #2424); only contacts tested because everything else is considered legacy by ZYB.com * Horde: avoid confusing the server with a deviceId that starts like the ones used in old Funambol clients, helps with calendar sync (MB #9347) * Mobical.net (and other, similar services): fix vCalendar 1.0 alarm properties before importing them (MB #10458) * desknow.com works when switching to SyncMLVersion = 1.1 * Funambol, Memotoo (and probably others): preserve meeting series when receiving update for detached recurrence (BMC #1916) Evolution: * addressbook backend: avoid picking CouchDB, second try (MB #7877) * calendar backend: minor fix for change tracking when deleting a single instance of a recurring event * workaround for Evolution 2.30: "timezone cannot be retrieved because it doesn't exist" is triggered incorrectly when importing non-standard timezone definitions because libecal changed an error code (MB #9820) Performance and reliability improvements (MB #7708): * synccompare much faster * database dumps consume less disk space * more intelligent about expiring obsolete session directories and backups * database accesses are reduced in several backends * shorter logs (MB #8092) * message resending helps under unreliable network connectivity ("RetryInterval") * full support for suspend&resume in SyncEvolution client to SyncEvolution or Synthesis server syncs * better handling of certain third-party time zone definitions (BMC #1332) Improved GTK sync-UI: * revised config screen: all in one list where entries can be expanded, integrated setup of sync with other devices * recovery support: restore from backup, unexpected slow sync handling * spinner while network is in use (MB #2229) * interactive password requests (MB #6376) * uses new D-Bus API Command line: * fixed printing of rejected items (MB #7755) * consistent logging of added/updated/deleted items with short description * improved error reporting (textual descriptions instead of plain error codes MB #2069, partial success MB #7755, record and show first ERROR encountered MB #7708) * can create new sources (MB #8424) * runs operations inside daemon and thus avoids conflicts with operations done by other clients; for testing purposes (like running a client which talks to a local server in the daemon) it is still possible to ignore the daemon (--daemon=no, MB #5043) * revised README, now also available as man page (BMC #690) Redesigned and reimplemented D-Bus API, used by sync-UI and command line: * central syncevo-dbus-server controls configurations and sync sessions: http://syncevolution.org/development/direct-synchronization-aka-syncml-server * accepts incoming SyncML connection requests and messages received by independent transport stubs (obexd, HTTP server, ...) * can be used by multiple user interfaces at once * fully documented, see src/dbus/interfaces and http://api.syncevolution.org * no longer depends on dbus-glib with hand-written glue code for C++, instead uses gdbus plus automatic C++ binding generated via C++ templates Revised configuration layout (MB #8048, design document at http://syncevolution.org/development/configuration-handling): * several peer-independent sync and source properties are shared between multiple peers * they can be accessed without selecting a specific peer, by using an empty config name or with the new "@" syntax * user interface of command line unchanged * old configurations can be read and written, without causing unwanted slow syncs when moving between stable and unstable SyncEvolution versions * old configurations can be migrated with the "--migrate" command line switch; however, then older SyncEvolution can no longer access them and migrating more than one old configuration causes the second or later configuration to loose its "deviceId" property (which is shared now), causing a slow sync once * config names may contain characters that are not allowed in the file names used for the underlying files; will be replaced with underscores automatically (MB #8350) Upgrading from 0.9.x: * Upgrading and downgrading should work seamlessly when using existing configurations. * The new configuration layout is only used when creating new configurations or explicitly invoking "syncevolution --migrate" (see above). Such configs cannot be used by older SyncEvolution releases. * The new "RetryInterval" property causes messages to be resent after 2 minutes (increased from 1 minute in previous 1.0 betas). At least the Funambol server is known to not handle this correctly in all cases (http://funzilla.funambol.com/show_bug.cgi?id=7910). So in the Funambol config template the interval is set to zero, disabling the feature. Disabling the feature must be done manually in existing Funambol configurations. SyncEvolution 1.0 beta 3 -> 1.0 final, 11.06.2010 ================================================= Bug fixes and new features: * Configuration templates are stored in a single file (BMC #1208). New templates (like something downloaded from http://syncevolution.org/wiki) can be dropped into $HOME/.config/syncevolution-templates using an arbitrary file name. * Progress and per-source status are now also reported and recorded when running in server mode (BMC #1359). There are still several limitations (sync mode not reported, no information about sent/received/processed items while the sync runs, see BMC #2786). * Better handling of certain third-party time zone definitions (BMC #1332). Better logging to track down such problems. * D-Bus server + command line: return error code when failed (BMC #2193) * syncevo-phone-config: simplified command line options, several bug fixes (syntax error, incorrect handling of calendar+todo, BMC #1197) * Revised README, now also available as man page (BMC #690). Conversion of D-Bus API documentation into .html page (BMC #1745). * Funambol, Memotoo (and probably others): preserve meeting series when receiving update for detached recurrence (BMC #1916) * Fix for potential out-of-bounds memory access (BMC #1007). * HTTP server: fix for potential crash when second session was requested while an older one was still running, initial sync was done without libical time zone information and thus may have mismatched times (BMC #2435) * Nokia E55: convert alarm times (BMC #1657). This is done via a new remote rule in /usr/share/syncevolution/xml/remoterules/server/46_E55.xml If another phone needs the same treatment, then copy that file to ~/.config/syncevolution-xml/remoterules/server and edit the element. * GTK GUI: styling fix (BMC #1372), updated toolbar for MeeGo 1.0 (BMC #1970), avoid duplicating configs when selecting a config created by syncevo-phone-config or the command line (BMC #1266), scroll bars for emergency window (BMC #1296), avoid compile problem on Fedora Core 13 due to name collision with system sync() call, updated translations. SyncEvolution 1.0 beta 2 -> beta 3, 20.04.2010 ============================================== One more step towards the long awaited 1.0. 0.1 was released over four years ago and the 1.0 cycle started some time last summer. Beta 3 is considered feature complete at this point. Automatic synchronization is supported by the syncevo-dbus-server (MB #6378). When that is installed, it will be started as part of a user session and keep running to trigger syncs in the background. Notifications are emitted when syncs start, end or fail (MB #10000). Automatic synchronization can be enabled separately for each peer ("autoSync=0/1", off by default), will be done at regular intervals ("autoSyncInterval=30" minutes) when online long enough ("autoSyncDelay=5" minutes). That last option ensures that a) an automatic sync does not attempt to use a network connection unless it was already active and b) hopefully is also around long enough to complete the sync. Detecting online status depends on ConnMan. Without it, SyncEvolution assumes that the network is available. For Bluetooth it is enough to have a peer paired. When SyncEvolution is compiled with a backend sync daemon ("syncevo-dbus-server"), then conceptually that daemon controls the configuration and coordinates manually and automatically started sync sessions. Previously, the command line tool bypassed the daemon by running operations itself. Now it can hand over the command line parameters to the daemon to be executed there ("--daemon=yes", the default if the daemon is available; MB #5043). Command line parameters and output of "syncevolution" are the same as before. Note that the daemon only runs one operation at a time, which delays the command line client when the daemon is busy. For testing purposes (like running a client which talks to a local server in the daemon) it is still possible to ignore the daemon (--daemon=no). Thanks to fixes and improvements in both Synthesis engine and SyncEvolution, suspend and resume are fully supported in client and server (MB #2425). Previously it failed in some cases, as mercilessly exposed by our automated testing. Now all of these tests pass. The HTTP server now also handles message resends by clients correctly. Direct synchronization with older phones (like Sony Ericsson K750i) can be started now by switching to an older version of the SyncML standard ("SyncMLVersion" property, MB #9312). No further interoperability testing with such phones has been done at this time. When acting as client, that same property allows talking to older SyncML servers, like desknow.com. A minor workaround and the right configuration make it possible to synchronize with Nokia N85 and probably also other S60 devices. Added a template for "Nokia S60". Also made the template for "Nokia N900" accessible in the GTK GUI. Because determining which configuration works for a phone involves a lot of trial-and-error, the new "syncevo-phone-config" script automates that process. Other changes: * Mobical.net (and other, similar services): fix vCalendar 1.0 alarm specifications before importing them (MB #10458) * Nokia N900: added a config template for it and disabled the redundant RespURI when using Bluetooth. Preliminary testing shows that this solves some of the issues seen before (MB #10224). * workaround for Evolution 2.30: "timezone cannot be retrieved because it doesn't exist" is triggered incorrectly when importing non-standard timezone definitions because libecal change an error code (MB #9820) * "syncevo-http-server" HTTP server script is included in normal install * syncevolution.org binaries: finally solved the libbluetooth3 incompatibility (MB #9289). Binaries of beta 2 crashed on more recent distros because of that. * SyncML client and Bluetooth: a mobile device running SyncEvolution creates a configuration automatically (MB #6175). The peer contacting us has to use the standard SyncEvolution URIs (addressbook, calendar, todo, memo). * command line: when dealing with the shared non-peer part of a config, it checks for properties which are unsuitable only prints those (MB #8048) * GTK GUI: improved setup of devices, automatic sync switch, some fixes for crashes and other tweaks * Nokia 7210c: send time as UTC instead of relying on time zone information (MB #9907). * command line: setting up a configuration for a "SyncEvolution" server on a client was not possible because the "SyncEvolutionClient" configuration was picked instead (MB #10004). The latter has to be used when configuring a SyncEvolution server to talk to a SyncEvolution client. * restore: no longer updates the time of the backup (MB #9963) * various minor improvements and fixes, see ChangeLog Upgrading: * The new "RetryInterval" property causes messages to be resent after 2 minutes (increased from 1 minute in previous 1.0 betas). At least the Funambol server is known to not handle this correctly in all cases (http://funzilla.funambol.com/show_bug.cgi?id=7910). So in the Funambol config template the interval is set to zero, disabling the feature. Enabling or disabling the feature must be done manually in existing configurations. SyncEvolution 1.0 beta 1 -> beta 2, 23.02.2010 ============================================== Several new features and some bug fixing. Despite some open issues (see below), this release is ready for getting packaged in staging areas of distros as replacement for 0.9.2. As before, documentation for 1.0 is only available in the "Development" section of syncevolution.org, including HOWTOs for setting up the HTTP SyncML server and phones manually. Setting up a phone became a bit easier with beta 2, because SyncEvolution is now integrated with the GNOME Bluetooth panel: once a device with SyncML client support is paired, a button offers to bring up the sync-UI and configure or synchronize with that device. We do a fuzzy match against the Bluetooth device name to find a suitable template (not manufacturer/model, because that is not readily available). Still not many (read: hardly any) templates available, though. The binaries on syncevolution.org are compiled with Bluetooth support. libbluetooth2 or libbluetooth3 should be installed, but are not essential. If there is no suitable version of it, the Bluetooth channel has to be selected manually as part of the syncURL. Unexpected slow syncs are prevented by default, in contrast to beta 1 where this feature was available but turned off. When an unexpected slow sync is detected in a client, users have to follow the instructions provided by the command line or sync-ui and choose how to proceed (explicitly request slow sync, refresh from server or client, restore from backup). SyncEvolution as server currently cannot prevent slow syncs, even when initiating the sync with a phone. In preparation for syncing automatically, logdir and database handling was improved considerably. Backups use less disk space because identical files share the same file content via hard links. This also speeds up the synccompare Perl script. Database dumps and the corresponding comparison are delayed until the session really runs, which avoids doing needless work a) when the server a client tries to contact is unreachable or down and b) by only including sources that are really in use during a sync on the server side. The Synthesis XML configuration was split up into different parts which are assembled from /usr/share/syncevolution/xml. Files in ~/.config/syncevolution-xml override and extend the default files, which my be useful when adding support for a new phone. Summary of changes since 1.0 beta 1: * sync-ui: recovery dialog (MB #8050), device setup, config usable with long strings (MB #9278), fixed displaying of source phases during sync (MB #9320) * sync-ui + syncevo-dbus-server: integration with Bluez to detect paired devices (MB #9216, MB #7089), select template based on device name (MB #7838), detect network and Bluetooth connectivity (only with ConnMan, MB #7700), passwords stored in GNOME keyring by syncevo-dbus-server are shown with dots in sync-ui (MB #9169) * Evolution addressbook backend: avoid picking CouchDB, second try (MB #7877) * Evolution calendar backend: minor fix for change tracking when deleting a single instance of a recurring event * build fixes: Bluetooth compatibility (MB #9289), use libical _r variant of calls because 0.43 has issues in the normal version, conflict with system libsynthesis and libsmltk (MB #9811) * Horde: avoid confusing the server with a deviceId that starts like the ones used in old Funambol clients, helps with calendar sync (MB #9347) * better reporting when SyncEvolution dies during a sync (only happend once when it wasn't installed properly, but still... MB #9844) * performance improvements: synccompare much faster/database dumps consume less disk space/more intelligent about expiring obsolete session directories and backups/database accesses are reduced in several backends (MB #7708), shorter logs (MB #8092) * slow sync detection: now also works in the case where the client detects an anchor mismatch and enabled by default (MB #2416) * OBEX transport: some error handling changes and removal of polling, now also possible via sync-ui + syncevo-dbus-server (MB #9436) * API changes: SyncSource introduces an "isEmpty" operation which is needed for the slow sync detection * SyncML: split up configuration (MB #7712), increased default message size because the old one might have been too small for large DevInf structures * several fixes for virtual data sources ("calendar+todo"): now works on client side, fixed naming on server (MB #9664), fixed error message for slow sync detection, supported in combination with sync-UI (MB #9535) * fixes for shared configuration layout: finding sessions of peers in non-default context, adding sources affected peers in the same context (MB #9329), wrong context during --configure when using shortcut for peers in non-default context (MB #9338) Known gaps for 1.0 final and beyond: Redesigned and reimplemented D-Bus API, required by sync-UI: - 'syncevolution' command line tool bypasses D-Bus server and runs sync sessions itself (MB #5043) - availability of peers not detected when using NetworkManager (connected for HTTP, paired for Bluetooth; MB #7700) SyncML server in general: - suspend/resume support is untested (MB #2425) - the progress events and statistics reported for a SyncML client are not generated when running as SyncML server, will require a fair amount of refactoring in the Synthesis engine (MB #7709) HTTP SyncML server: - a configuration must be created for each peer manually, including a remoteDeviceId value that contains the peer's SyncML device ID (MB #7838) OBEX SyncML server ("sync with phones"): - does not support phones which require a SAN 1.0 message (MB #9312) - determining a working configuration for an unknown phone requires a bit of experimenting, which should be automated (MB #9862) OBEX SyncML client: - parsing of SAN message is rudimentary and depends on an existing local configuration, needs to be refined depending on which SyncML server software it is meant to work with (MB #6175) Automatic sync (MB #6378): - no support for the various server push notification mechanisms - no intelligent detection of local changes - no regular background sync, development is in progress Upgrading from 1.0 beta 1: moving back and forth should work seamlessly Upgrading from 0.9.x: see under beta 1 SyncEvolution 0.9.2 -> 1.0 beta 1, 26.01.2010 ============================================== Compared to the current stable release, 0.9.2, this beta release can also: * synchronize directly with a phone over Bluetooth/OBEX * accept Bluetooth/OBEX connections in cooperation with obexd 0.19 * run SyncEvolution as a rudimentary HTTP SyncML server These feature were already available in a source-only 1.0 alpha release. For the beta, we fixed some issues (nothing major) and in addition to the source, also make binaries available. As before, we hope to get feedback on where we are going with 1.0 and its SyncML server and direct synchronization features. If you want to get involved, now is a good time because a) there is something which works and b) there is still time to influence the final 1.0, scheduled for March 2010. Documentation of the new features can be found in the "Development" section (http://syncevolution.org/development) for HOWTOs or ask on the mailing list (http://syncevolution.org/support). Here is a more complete list of features compared to the stable release. The full (and up-to-date) list can be retrieved from the Moblin Bugzilla (MB) issue tracking system with this query: http://bugzilla.moblin.org/showdependencytree.cgi?id=7892&hide_resolved=0 For changes compared to the 1.0 alpha please consult the change log. Implemented features are marked with a plus +, open ones with a minus -. ZYB.com + now works thanks to a workaround for anchor handling (MB #2424) - only contacts tested because everything is considered legacy by ZYB.com Slow sync handling (MB #2416) + Unexpected slow syncs can be detected when running as client and if configured (see "preventSlowSync"), abort the session so that the situation can be analyzed. A refresh from client or server might be more suitable. Because this required manual intervention by the user, the feature is off by default. - Catching slow syncs does not work yet when running as server and in one corner case in a client. Improved sync-UI: + settings for HTTP servers are now done inside the list of all configs and server templates instead of poping up a separate window + uses the new D-Bus API + no longer uses private gconf key to select default peer, replaced by "defaultPeer" in SyncEvolution config + added recovery features like handling of unexpected slow syncs (MB #2416) - restoring from backup only supported by command line (MB #8050) - spinner to indicate network activity missing (MB #2229) - interactive password request not implemented yet (MB #6376) Command line: + fixed printing of rejected items (MB #7755) + improved error reporting (textual descriptions instead of plain error codes MB #2069, partial success MB #7755, record and show first ERROR encountered MB #7708) + can create new sources (MB #8424) Redesigned and reimplemented D-Bus API, required by sync-UI: + central syncevo-dbus-server controls configurations and sync sessions: http://syncevolution.org/development/direct-synchronization-aka-syncml-server + accepts incoming SyncML connection requests and messages received by independent transport stubs (obexd, HTTP server, ...) + can be used by multiple user interfaces at once + fully documented, see src/dbus/interfaces + no longer depends on dbus-glib with hand-written glue code for C++, instead uses gdbus plus automatic C++ binding generated via C++ templates - 'syncevolution' command line tool bypasses D-Bus server and runs sync sessions itself (MB #5043) - availability of peers not detected (connected for HTTP, paired for Bluetooth; MB #7700) - Bluetooth peers can only be configured via command line (MB #9216) Revised configuration layout (MB #8048, design document at http://syncevolution.org/development/configuration-handling): + several peer-independent sync and source properties are shared between multiple peers + they can be accessed without selecting a specific peer, by using an empty config name or with the new "@" syntax + user interface in command line and D-Bus API unchanged + old configurations can be read and written, without causing unwanted slow syncs when moving between stable and unstable SyncEvolution versions + old configurations can be migrated with the "--migrate" command line switch; however, then older SyncEvolution can no longer access them and migrating more than one old configuration causes the second or later configuration to loose its "deviceId" property (which is shared now), causing a slow sync once + config names may contain characters that are not allowed in the file names used for the underlying files; will be replaced with underscores automatically (MB #8350) - users of the sync-ui will not know about the --migrate option, so if they have only one configuration, it should be migrated automatically SyncML server in general: + incoming connections are accepted by syncevo-dbus-server via the D-Bus Connection API; because this is a "personal SyncML server", all local data is meant to belong to a single user, and only one sync session can be active at any point in time + different users on the same machine can run their own server, as long as they ensure that listening for incoming connections does not conflict with each other (different port in HTTP) + the session of an HTTP client which stops sending messages expires after "RetryDuration" seconds instead of blocking the server forever (MB #7710) - suspend/resume support is untested (MB #2425) - automatic backup of server databases is inefficient (done even when client is not allowed to do a sync; always backs up all data, including sources which are not active; MB #7708) - the progress events and statistics reported for a SyncML client are not generated when running as SyncML server, will require a fair amount of refactoring in the Synthesis engine (MB #7709) - the Synthesis server example config contains workarounds for specific phones, but SyncEvolution does not currently use those; adding new workarounds should be made very simple (MB #7712) HTTP SyncML server: + test/syncevo-http-server.py provides an experimental HTTP server based on Python and Twisted - a configuration must be created for each peer manually, including a remoteDeviceId value that contains the peer's SyncML device ID (MB #7838) OBEX SyncML server ("sync with phones"): + peers are contacted via a builtin transport that uses libopenobex (MB #5188) + Server Alerted Notification (SAN) message triggers syncs; server ID and URI are configurable (MB #7871) - a configuration must be created for each peer manually, including a syncURL that contains the peer's MAC address (MB #7838) - should be integrated into the system's Bluetooth pairing (MB #7089) OBEX SyncML client: + obexd 0.19 contains a plugin which passes SyncML messages to syncevo-dbus-server - parsing of SAN message is rudimentary and depends on an existing local configuration, needs to be refined depending on which SyncML server software it is meant to work with (MB #6175) Automatic sync (MB #6378): - no support for the various server push notification mechanisms - no intelligent detection of local changes - no regular background sync - depends on safe handling of concurrent editing, which is blocked by merging of a new Evolution Data Server API (MB #3479) Upgrading from 0.9.x: * Upgrading and downgrading should work seamlessly when using existing configurations. But this being an alpha, better ensure that you have backups of both your data and your configurations in ~/.config/syncevolution. * The new configuration layout is only used when creating new configurations or explicitly invoking "syncevolution --migrate" (see above). Such configs cannot be used by older SyncEvolution releases. SyncEvolution 0.9.1 -> 0.9.2, 23.01.2010 ======================================== Synthesis SyncML Engine version: see src/synthesis/ChangeLog New Maemo 5/Nokia N900 calendar backend and packages, brought to you by Ove Kaaven. These packages are available via the Maemo extras-devel repository. Bug reports can be submitted both in http://bugs.maemo.org and http://bugzilla.moblin.org. The latter is the tracker that is monitored by the SyncEvolution team, which will also incorporate patches. In general, Ove is the main maintainer of the new backend. New XMLRPC backend, contributed by Franz Knipp/M-otion. It accesses data inside a web service via a SOUP API and thus allows synchronizing it via SyncML. See src/backends/xmlrpc/README for more information. Added templates for Oracle Beehive and Goosync. Both are not currently part of the regular testing. In addition to that, 0.9.2 is an incremental update, with several updated translations and addressing all of the issues reported by users for 0.9.1: - vCard dialects: added "X-GENDER/X-SIP" (used by Maemo) and X-SKYPE (used by Maemo and recent Evolution, MB #8948) - Evolution Address Book: avoid picking CouchDB by default (MB #7877, evolution-couchdb #479110) CouchDB address books are appended at the end of the local database list, otherwise preserving the order of address books. The initial release of evolution-couchdb in Ubuntu 9.10 is unusable because it does not support the REV property. Reordering the entries ensures that the CouchDB address book is not used as the default database by SyncEvolution, as it happened in Ubuntu 9.10. Users can still pick it intentionally via "evolutionsource". - installation: templates now in $(datadir)/syncevolution/templates (MB #7808) This are files used internally, meant to be extended by distributors. Storing them in /etc is no longer supported, but also unlikely to be needed. Added warnings that these files cannot simply be copied into .config because they are not complete configurations. - installation: "make install" populates $(docdir) (MB #7168) Previously README, COPYING, NEWS, and server READMEs were copied into syncevolution.org .tar.gz/.deb/.rpm archives as part of custom make rules and thus missing in other installations. - building: --with-boost had no effect (MB#7856), detect incorrect use of --with-synthesis-src, workaround for lack of --with-docdir in older autoconf, do not unnecessarily depend on CPPUnit header files and GNOME/EDS libs (MB#8338), workaround for libtool bug ("cannot install `syncecal.la' to a directory not ending in ..."), - clarified documentation of properties for file backend (MB#8146) - stderr redirection: detect "error" messages and show them (MB#7655) The "GConf Error: Failed to contact configuration server..." error message was suppressed by the code which catches noise from libraries invoked by SyncEvolution. Now it is printed as ERROR, making it easier to detect why running SyncEvolution inside cron needs additional changes: http://www.estamos.de/blog/2009/05/08/running-syncevolution-as-cron-job/ - importing contacts from SyncML server without full name (MB#5664): Evolution expects the name to be set and shows an empty string if it is missing. Now the name is re-added by appending first, middle and last name. - Evolution calendar: work around 'cannot encode item' problem (MB #7879) Happens when the calendar file contains broken events which reference a timezone that is not defined. Now the event is treated like one in the local timezone. - "http_proxy" env variable is supported regardless which HTTP transport is used (MB#8177). - avoid crashes when libecal sets neither error nor pointer (MB#8005) and when aborting a running sync in the syncevo-dbus-server (MB#8385) - "--status" output: fixed missing total item counts (MB #9097) Upgrading from 0.9.1: * nothing to do, upgrading and downgrading should work seamlessly SyncEvolution 0.9 -> 0.9.1, 26.10.2009 ====================================== Synthesis SyncML Engine version: see src/synthesis/ChangeLog Mobical and Memotoo are now officially supported. Memotoo uses vCard 2.1 with several Evolution specific extensions. It uses iCalendar 2.0, however, without actually supporting the advanced features of it. Times are converted to UTC and meeting information are lost. Mobical uses vCard 2.1 and vCalendar 1.0 as data formats, with the result that many properties used in Evolution are not supported by the server. In particular calendar support is very limited (known issues when events are in time zones different from the one selected locally and on the server, no support for meetings). For details see README.mobical. *** Beware *** that the Mobical SyncML password is *not* the same as the one for their web site. Log into mobical.net, then go to "my accounts >> configure new device >> manual settings" to find the SyncML credentials. It is now possible to compile database backends outside of SyncEvolution, install them and have SyncEvolution use them automatically like any other backend. The backend API has been enhanced considerably. For example, backend developers have access to a modular set of utility classes that can be mixed into a specific implementation. Backends can access the internal Synthesis representation directly and therefore no longer need their own vCard/vCalendar/iCalendar parser. The sqlite demo backend can be enabled and compiled again with --enable-sqlite. It demonstrates how to map directly from the Synthesis field list to some internal format (an SQLite database schema in this case). Other changes: * Resend messages to cope with intermittent loss of network connectivity (Moblin Bugzilla #3427). See the new "ResendDuration" and "ResendDelay" configuration properties for details. * SyncEvolution command line uses the GNOME keyring when the new --keyring option is given. * The logging of added and updated items was enhanced. Events, tasks and memos are logged with a short description instead of just the local ID. The description for contacts was improved. * Receiving photos from Mobical failed because Mobical does not quite follow the vCard 2.1 (Moblin Bugzilla #6668). Sending photos worked, but added a few bytes of garbage at the end of each photo (typically ignored when showing). Parser was made more tolerant by Synthesis and encoder bug was fixed. * Task priorities used by Mobical and Evolution did not match: vCalendar 1.0 uses 1-3, iCalendar 2.0 uses 1-9 (MB #6664). SyncEvolution now translates between the two ranges, with some information getting lost when talking to a peer which only supports the smaller range. * Importing work and home phone numbers from Google into desktop Evolution works better, because SyncEvolution now adds the "VOICE" flag expected by Evolution (MB#6501). * SSL certificate checking with Google is enabled by default and enabled in Moblin, because libsoup in that distro has the necessary fix. Without that fix, all connection attempts fail. The binaries on syncevolution.org are compiled with --disable-ssl-certificate-check, so users who want the additional security must enable it. * .rpms on syncevolution.org no longer specify a dependency on certain Perl features. This depencency was a problem on Mandriva. Unwanted hard dependencies on libecal in syncevolution.org binaries are avoided for real this time (MB#6552). * Some sync-UI enhancements (describe sync services, avoid crash with very long input in some of the text boxes (MB#5219), set application icon, improved some strings). * sync-UI: now disables sources which are not supported when setting up a configuration, like memos on Moblin (MB #6672). Previously the source was enabled, which prevented using using the configuration as-is on the command line. * The sync UI allowed to enable calendar and task synchronization with Google although Google does not support that (MB#5871). In new installations this is prevented by clearing the URI for those data categories. * Trying to remove a non-existent configuration via the command line now raises an error, to catch typos (MB #6673). * Improved checks which logs in the logdir belong to the current server (MB#5215). * Improved sanity checking of integer configuration parameters (MB#6500). * Spelling fix: "aboring" => "aborting" Known issue: * Mobical and Memotoo do not have a description in the GUI yet. * ZYB.com is not supported because of a known anchor handling problem in the server (MB#2424). Upgrading from 0.9: * nothing to do, upgrading and downgrading should work seamlessly SyncEvolution 0.9.1 beta 2 -> 0.9.1, 26.10.2009 =============================================== Synthesis SyncML Engine version: see src/synthesis/ChangeLog Minor changes: * spelling fixes in NEWS file (--source-type => --source-property) * update to zh_CN * improved autotools compilation of libsynthesis SyncEvolution 0.9.1 beta 1 -> 0.9.1 beta 2, 19.10.2009 ====================================================== Synthesis SyncML Engine version: see src/synthesis/ChangeLog Several fixes: * Receiving photos from Mobical failed because Mobical does not quite follow the vCard 2.1 (Moblin Bugzilla #6668). Sending photos worked, but added a few bytes of garbage at the end of each photo (typically ignored when showing). Parser was made more tolerant by Synthesis and encoder bug was fixed. * Task priorities used by Mobical and Evolution did not match: vCalendar 1.0 uses 1-3, iCalendar 2.0 uses 1-9 (MB #6664). SyncEvolution now translates between the two ranges, with some information getting lost when talking to a peer which only supports the smaller range. * The workaround for detecting an endless stream of Alert 222 messages (caused by misbehavior of certain servers when a specific message has to be resent) aborted certain valid (albeit somewhat pathologic) sync sessions. Improved the heuristic so that it still catches the real loop without aborting in that other case. * sync-ui: now disables sources which are not supported when setting up a configuration, like memos on Moblin (MB #6672). Previously the source was enabled, which prevented using using the configuration as-is on the command line. * .rpms on syncevolution.org no longer specify a dependency on certain Perl features. This depencency was a problem on Mandriva. Unwanted hard dependencies on libecal in syncevolution.org binaries are avoided for real this time (MB#6552). * Trying to remove a non-existent configuration via the command line now raises an error, to catch typos (MB #6673). * Message resend options: added sanity checks to catch negative values, clarified that duration is given in seconds, 0s resend interval disables resending (MB #6500). * Spelling fix: "aboring" => "aborting" SyncEvolution 0.9 -> 0.9.1 beta 1, 06.10.2009 ============================================= Synthesis SyncML Engine version: see src/synthesis/ChangeLog Mobical and Memotoo are now officially supported. Memotoo uses vCard 2.1 with several Evolution specific extensions. It uses iCalendar 2.0, however, without actually supporting the advanced features of it. Times are converted to UTC and meeting information are lost. Mobical uses vCard 2.1 and vCalendar 1.0 as data formats, with the result that many properties used in Evolution are not supported by the server. In particular calendar support is very limited (known issues when events are in time zones different from the one selected locally and on the server, no support for meetings). For details see README.mobical. *** Beware *** that the Mobical SyncML password is *not* the same as the one for their web site. Log into mobical.net, then go to "my accounts >> configure new device >> manual settings" to find the SyncML credentials. It is now possible to compile database backends outside of SyncEvolution, install them and have SyncEvolution use them automatically like any other backend. The backend API has been enhanced considerably. For example, backend developers have access to a modular set of utility classes that can be mixed into a specific implementation. Backends can access the internal Synthesis representation directly and therefore no longer need their own vCard/vCalendar/iCalendar parser. The sqlite demo backend can be enabled and compiled again with --enable-sqlite. It demonstrates how to map directly from the Synthesis field list to some internal format (an SQLite database schema in this case). Other changes: * Resend messages to cope with intermittent loss of network connectivity (Moblin Bugzilla #3427). See the new "ResendDuration" and "ResendDelay" configuration properties for details. * The logging of added and updated items was enhanced. Events, tasks and memos are logged with a short description instead of just the local ID. The description for contacts was improved. * The sync UI allowed to enable calendar and task synchronization with Google although Google does not support that (MB#5871). In new installations this is prevented by clearing the URI for those data categories. * Importing work and home phone numbers from Google into desktop Evolution works better, because SyncEvolution now adds the "VOICE" flag expected by Evolution (MB#6501). * SyncEvolution command line uses the GNOME keyring when the new --keyring option is given. * SSL certificate checking with Google is enabled by default and enabled in Moblin, because libsoup in that distro has the necessary fix. Without that fix, all connection attempts fail. The binaries on syncevolution.org are compiled with --disable-ssl-certificate-check, so users who want the additional security must enable it. * syncevolution.org binaries should be compatible with a wider range of Evolution releases again (MB#6552). * Some sync UI enhancements (describe sync services, avoid crash with very long input in some of the text boxes (MB#5219), set application icon, improved some strings). * Improved checks which logs in the logdir belong to the current server (MB#5215). * Improved sanity checking of integer configuration parameters (MB#6500). Known issue: * Mobical and Memotoo do not have a description in the GUI yet. * ZYB.com is not supported because of a known anchor handling problem in the server (MB#2424). SyncEvolution 0.8.1 -> 0.9, 12.08.2009 -------------------------------------- Synthesis SyncML Engine version: see src/synthesis/ChangeLog This is a major new release, with first steps towards further improvements. From this release on, the Synthesis SyncML engine will be the underlying SyncML and data conversion engine. A native GTK GUI is now included. The "sync-ui" program depends on a backend D-Bus service ("synevo-dbus-server") and several auxiliary files. Therefore, it only runs without hacks after installation in /usr (possible with .deb, .rpm and binary .tar.gz archives, and with "sudo make install", after compiling from source). The normal command line tool still works without being installed. In this release, the data handling model was changed from "all items are sent verbatim to the SyncML server" to "parse and convert". The argument for the former approach was that the SyncML server should be the only entity in the system which does data conversion. The previous releases already had to deviate from this approach to accommodate for minor client/server incompatibilities and for vCard 2.1 support, so the new approach just takes it one step further. The main reason for going to full semantic conversion is vCalendar 1.0 support. Support by servers for iCalendar 2.0, the only format supported by 0.8.1, is often still incomplete or even non-existent. By doing the conversion on the client side, SyncEvolution is now able to synchronize events and tasks with a wider variety of servers. It is still true that properties not supported by a server cannot be synchronized to other devices, so using a server with full iCalendar 2.0 support is recommended. But in contrast to 0.8.1, information that can be stored only locally is no longer lost when receiving an incomplete update from the SyncML server, thanks to intelligent merging, provided by the Synthesis engine. This depends on an accurate description of the server's capabilities, which might not be provided by all of them. This still needs to be tested in more detail. Interoperability with servers tested extensively in this release. The following servers are now supported: * ScheduleWorld. There is very complete support for Evolution data. The only known issues are around resuming from an interrupted sync. * Google contact sync. Google follows the vCard 2.1 specification, and thus does not support some of the vCard 3.0 additions, nor some of the common extensions. As a result, several properties are not synchronized (nickname, birthday, spouse/manager, URLs, ...). Only one top-level organization seems to be supported. For details, see README.google. Regarding Google's SyncML support, refresh-from-client and one-way-from-client sync modes are not supported. Deleting contacts moves them out of the main address without deleting them permanently. When adding such a contact again, the server discards the data sent by the client and recreates the contact with the data that it remembered. Because SSL certificate checking for Google works only with libsoup if the platform has a patched libsoup (http://bugzilla.gnome.org/show_bug.cgi?id=589323) or libsoup >= 2.28, certificate checking remains turned off by default for Google. If your platform has a suitable libsoup (like Moblin 2.0), then enable checking with: syncevolution --configure \ --sync-property SSLVerifyServer=true \ --sync-property SSLVerifyHost=true \ google * Funambol, with calendar and task support. Funambol supports iCalendar 2.0 in the current server, so this is enabled in the configuration template. Not all iCalendar 2.0 features are supported by the server, most notably support for meetings (drops attendees), meeting invitations (drops UID), detached recurrences (drops RECURRENCE-ID). See README.funambol for details. Interoperability with the Funambol server was improved by adding support for some vCard extensions (X-MANAGER/ASSISTANT/SPOUSE/ANNIVERSARY, #2418). Lost ACTION property has a work around (#2422). To enable that support in an existing configuration so that it exchanges items in the more suitable iCalendar 2.0 format, use: syncevolution --configure --source-property sync=two-way \ funambol calendar todo syncevolution --configure --source-property type='calendar:text/calendar!' \ funambol calendar syncevolution --configure --source-property type='todo:text/calendar!' \ funambol todo Without the exclamation mark, format auto-negotiation would pick the less capable vCalendar 1.0 format because that is marked as preferred by the server. *** WARNING ***: After switching from a previous release to the current one, or vice versa, do a "syncevolution --sync refresh-from-server" or "--sync refresh-from-client" (depending on which side has the authoritative copy of the data) once, to get client and server into a consistent state. Not doing so can result in applying the same changes to the server multiple times, and thus duplicates. Other changes in detail: * vCalendar 1.0 is now supported. * Both libcurl and libsoup can be selected at compile time as HTTP(S) transport mechanism. * SF #2101015: Expect: 100-continue header results in 417 Error with proxy. Should no longer occur with the HTTP transports in this release. * SF #1874805: Syncing with Funambol results in loosing all-day property. This now works thanks to the Synthesis data conversion rules. * SF #2586600: Synchronisation with mobical.net fails in 0.8.1. Works now, but there are some known issues (Bugzilla #3009) and therefore mobical.net is not officially supported yet. * SF #2542968: Separator for categories should not be escaped. Done correctly by the Synthesis vcard conversion. * bug fix: Evolution notes with only a summary and no description were not sent correctly to the server. Instead of sending the summary, an empty text was sent. * CTRL-C no longer kills SyncEvolution right away. Instead it asks the server to suspend the session. If that takes too long, then pressing CTRL-C twice quickly will abort the sync without waiting for the server (Warning, this may lead to a slow sync in the next session). * WBXML is enabled by default now, except for Funambol (#2415). Using WBXML reduces message sizes and increases parsing performance. * New configuration templates can be added to /etc/default/applications/syncevolution. These templates may contain icons, which are used by the GUI (no icons shipped right now). * Information about previous synchronization sessions is now stored in a machine-readable format and can be accessed using the new --print-sessions options. The output of this information is more complete and more nicely formatted. * --status now shows not only data changes since the last sync, but also item changes (see README for the difference between the two). * The new --restore option allows restoring local data to the state as it was before or after a sync. For this to work, "logdir" must be set (done by default for new configurations). The format of database dumps was changed to implement this feature. Instead of in a flat file, items are now saved as individual files in a directory. To get the previous format back (for example, to import as one .vcf or .ics file manually) concatenate these files. * With –-remove, one can remove configurations. It leaves data files and the local databases untouched. Known issues: * The GUI includes the number of locally deleted items during a refresh-from-server sync in the number of "received changes" (#5185), which is a bit misleading. This is a result of #3314, which introduced changes not "received" from the server. * When a network error occurs and the client never notices that the connection to the server was lost, it will hang forever, waiting for the server's reply (#3427). * The file backend now works only for data formats understood by SyncEvolution and the Synthesis engine. Items are parsed when exchanging them among the backend, engine, and server, in contrast to 0.8.1, where item content was not touched locally (#5046). * The ZYB.com server sends conflicting sync anchors, so most syncs don't work as expected (#2424). SyncEvolution 0.9 beta 3 hotfix -> 0.9 final, 12.08.2009 -------------------------------------------------------- Because SSL certificate checking for Google only works with libsoup if the platform has a patched libsoup (http://bugzilla.gnome.org/show_bug.cgi?id=589323) or libsoup >= 2.28, certificate checking remains turned off by default for Google. If your platform has a suitable libsoup (like Moblin 2.0), then enable checking with: syncevolution --configure \ --sync-property SSLVerifyServer=true \ --sync-property SSLVerifyHost=true \ google Only minor changes: * updated translations * refresh-from-server syncs now report how many items were deleted locally at the start of the sync (Bugzilla #3314). The GUI includes the number of locally deleted items during a refresh-from-server sync in the number of "received changes", which is a bit misleading (#5185). * fixed build issue on Fedora 11/g++ 4.4 (Bugzilla #5061) * some build and test improvements * proper fix for D-Bus error functions (#4919) * improve sync-ui startup time by avoiding an unnecessary copying of the sync config into itself (#5021) * adapted tooltip style (used for SyncML server links) to new Moblin theme, they weren't visible earlier (#5017) * notify zone selector in Moblin 2.0 about sync-ui startup (#4752) * sync-ui: minor layout change for fatal error situation SyncEvolution 0.9 beta 3 -> 0.9 beta 3 hotfix, 23.07.2009 --------------------------------------------------------- Found additional Google limitation: the server drops photos if they exceed a certain size. The limit is somewhere between 40KB (okay) and 80KB (dropped). The last-minute workaround for Google/libsoup/gnutls (using http) didn't work because apparently Google only supports SyncML over https (Bugzilla #4551). Now the default configuration template uses https with all certificate checking disabled. A patch for libsoup was submitted to upstream. Some error messages by the "syncevolution" command line tool were not printed (#4676). The root cause was the intentional interception of stderr to hide the noise printed by various system libraries (#1333). Unfortunately remarks about incorrect command line options were among swallowed messages. No good workaround available short of disabling the redirection with SYNCEVOLUTION_DEBUG=1, so let's release an update... Other changes: * updated translations SyncEvolution 0.9 beta 2 -> 0.9 beta 3, 21.07.2009 -------------------------------------------------- Synthesis SyncML Engine version: see src/synthesis/ChangeLog Enabled calendar and task synchronization for myFunambol.com. Not all iCalendar 2.0 features are supported by the server, most notably support for meetings (drops attendees), meeting invitations (drops UID), detached recurrences (drops RECURRENCE-ID). See README.funambol for details. Interoperability with the Funambol server was improved by adding support for some vCard extensions (X-MANAGER/ASSISTANT/SPOUSE/ANNIVERSARY, Bugzilla #2418). Lost ACTION property is worked around (#2422). Synchronization with Google Contacts was enabled and tested. A configuration template for that server is now provided. Google follows the vCard 2.1 specification and thus does not support some of the vCard 3.0 additions, nor some of the common extensions. As a result, several properties are not synchronized (nickname, birthday, spouse/manager, URLs, ...). Only one top-level organization seems to be supported. For details, see README.google. Regarding Google's SyncML support, refresh-from-client and one-way-from-client sync modes are not supported. Deleting contacts moves them out of the main address without deleting them permanently. When adding such a contact again, the server discards the data sent by the client and recreates the contact with the data that it remembered. SSL certificate checking with libsoup (the default transport) is now supported (#2431). However, libsoup/gnutls are very strict about SSL certificate checking and reject version 1 certificates, like the one used by Verisign for Google (#4551). At the moment the only solution is to fall back to plain http in the Google configuration template. CTRL-C no longer kills SyncEvolution right away. Instead it asks the server to suspend the session. If that takes too long, then pressing CTRL-C twice quickly will abort the sync without waiting for the server (warning, this may lead to a slow sync in the next session). WBXML is enabled by default now, except for Funambol (#2415). Using WBXML reduces message sizes and increases parsing performance. It was not enabled initially in the 0.9 releases in order to test this new feature more thoroughly. Old configs don't have an explicit enableWBXML setting and therefore will automatically use the new default. Various bug fixes and improvements: * only show servers in GUI which are tested and supported (Bugzilla #3336) * a single log file is written in .html format (#3474) * added several translations of the GUI * lots of testing improvements, build binary packages again UPGRADING When enabling calendar and todo synchronization with Funambol in an existing configuration, set the type so that iCalendar 2.0 is used: syncevolution --configure --source-property sync=two-way funambol calendar todo syncevolution --configure --source-property type='calendar:text/calendar!' funambol calendar syncevolution --configure --source-property type='todo:text/calendar!' funambol todo When creating a configuration anew, this is not necessary because the configuration template contains those types. SyncEvolution 0.9 beta 1 -> 0.9 beta 2, 12.06.2009 -------------------------------------------------- Synthesis SyncML Engine version: see src/synthesis/ChangeLog Major new feature: a GTK GUI! The "sync-ui" program depends on a backend D-Bus service ("synevo-dbus-server") and several auxiliary files. Therefore it only runs without hacks after "sudo make install", in contrast to the normal command line which can be invoked directly. New configuration templates can be added to /etc/default/applications/syncevolution. These templates may contain icons which are used by the GUI (no icons shipped right now). Information about previous synchronization sessions is now stored in a machine-readable format and can be accessed via the new --print-sessions options. The output of this information is more complete and nicer formatted. --status now not only shows data changes since the last sync, but also the item changes (see README for the difference between the two). The new --restore option allows restoring local data to the state as it was before or after a sync. For this to work, "logdir" must be set (done by default for new configurations). The format of database dumps was changed to implement this feature: instead of in a flat file, items are now saved as individual files in a directory. To get the previous format back (for example, to import as one .vcf or .ics file manually) concatenate these files. With --remove one can remove configurations. It leaves data files and the local databases untouched. Various bug fixes and improvements: * compiles and works again on Debian Etch if Boost 1.35 is installed from www.backports.org (without GUI, see Bugzilla #3358) * uses XDG_CACHE_HOME (= ~/.cache) for logs and database dumps to avoid interfering with .desktop search in XDG_DATA_HOME; the directory there is automatically moved when running syncevolution (Bugzilla #3309) * re-enabled certain config options (clientAuthType, maxMsgSize, maxObjSize); normally it shouldn't be necessary to modify those (Bugzilla #3242, #2784) * fixed error handling of unexpected server reply in libsoup transport (Bugzilla #3041) * message logging is enabled at logLevel 3 (XML translation) and 4 (also original XML or WBXML message) * GTK GUI fixes since initial Moblin 2.0 beta: only start it once if libunique is available (Bugzilla #3154), wrap text in change sync service" button (Bugzilla #2064), sort sources alphabetically in UI (Bugzilla #2070) SyncEvolution 0.8.1 -> 0.9 beta 1, 13.05.2009 --------------------------------------------- Synthesis SyncML Engine version: see src/synthesis/ChangeLog A major new release and the first step towards further improvements: from this release onwards, the Synthesis SyncML engine is used as the underlying SyncML and data conversion engine. The focus of this first beta was to reach the same level of functionality and stability as in 0.8.1. Therefore this beta does not yet bring much new features; this will be the focus of further beta releases until finally 0.9 will be a full replacement for 0.8.1. This release also switches from an "all items are sent verbatim to the SyncML server" to a "parse and convert" data handling model. The argument for the former approach was that the SyncML server should be the only entity in the system which does data conversion. The previous releases already had to deviate from this approach to accommodate for minor client/server incompatibilities and for vCard 2.1 support. The main reason for going to full semantic conversion is vCalendar 1.0 support. Support by servers for iCalendar 2.0, the only format supported by 0.8.1, is often still incomplete or even non-existent. By doing the conversion on the client side, SyncEvolution is now able to synchronize events and tasks with a wider variety of servers. It is still true that properties not supported by a server cannot be synchronized to other devices, so using a server with full iCalendar 2.0 support is recommended. But in contrast to 0.8.1, information that can only be stored locally is no longer lost when receiving an incomplete update from the SyncML server thanks to intelligent merging provided by the Synthesis engine. This depends on an accurate description of the server's capabilities, which might not be provided by all of them - still needs to be tested in more detail. *** WARNING ***: after switching from a previous release to the current one or vice versa, do a "syncevolution --sync refresh-from-server" or "--sync refresh-from-client" (depending on which side has the authoritative copy of the data) once to get client and server into a consistent state. Not doing so can result in applying the same changes to the server multiple times and thus duplicates. Changes in detail: * vCalendar 1.0 is now supported. Because this hasn't been tested that much yet, events and tasks are still disabled in the Funambol default configuration (SF #2635973). * Both libcurl and libsoup can be selected at compile time as HTTP(S) transport mechanism. * SF #2101015: Expect: 100-continue header results in 417 Error with proxy Should no longer occur with the HTTP transports in this release. * SF #1874805: Syncing with Funambol results in loosing all-day property This now works thanks to the Synthesis data conversion rules. * SF #2586600: Synchronisation with mobical.net fails Should work now because of the different SyncML implementation (untested). * SF #2542968: separator for categories should not be escaped Done correctly by the Synthesis vcard conversion. * bug fix: Evolution notes with only a summary and no description were not sent correctly to the server: an empty text was sent instead of sending the summary Known shortcomings in this release which will be fixed before the final 0.9: * Verbatim file backups of items on the SyncML server are currently not possible: the SyncEvolution "file" backend still exists, but all items are converted by the Synthesis engine and therefore must be in a format supported by the engine. * HTTPS can be used with libsoup, but certificate checking is always disabled. Need to find a portable way to determine where the certificate file is on various systems. * Log file handling is not yet unified: the traditional client.log contains only high-level SyncEvolution log entries. Low-level SyncML and engine log entries are in sysync_*.html files. * stdout and stderr messages from system libraries are visible on the console. 0.8.1 used to redirect those into the client.log to hide this noise; this will be added again. In the meantime, ignore messages like "Deadlock potential - avoiding evil bug!". This is liborbit telling us that it is (hopefully successfully) handling something nasty. SyncEvolution 0.8.1 -> 0.8.1a, 15.12.2008 ----------------------------------------- C++ client library: 7.0 plus some patches, see github repository referenced in configure script. A minor bug fix release, updating only necessary on Mac OS X. * #2307976 "Trace/BPT trap - sync failure": occurs randomly in Mac OS X specific transport layer of the Funambol C++ client library. Avoided in 0.8.1a by using libcurl as transport, as in 0.7. SyncEvolution 0.8 -> 0.8.1, 11.10.2008 -------------------------------------- C++ client library: 7.0 plus some patches, see github repository referenced in configure script. A minor bug fix release, updating not really necessary. The binary packages for Evolution are built now so that one package works for all compatible Evolution releases, including the new Evolution 2.24. * Evolution calendar: regression in 0.8: one-way sync of virtual birthday calendar (#2095433). "refresh-from-client" works again for the birthday calendar. Other modes are not supported. In contrast to previous releases SyncEvolution now does some sanity checks that the sync mode is right. * Mac OS X: removing old logdirs failed (#2087389). Fixed. * SyncML client library: "Expect: 100-continue" header resulted in 417 error with certain proxies (#2101015). Now this header is always disabled; it doesn't make much sense with SyncML anyway. * The development of the Funambol C++ client library is now tracked in a git repository on github.com. Modifications and tags for SyncEvolution are checked in there. The configure script checks out the right sources from there automatically; can be controlled via --with-funambol-src parameter. * Evolution desktop: the version of the used Evolution libraries is included in the "--version" output and log files. * Cleaned up README. Kudos to Martin Wetterstedt for pointing out mistakes in the README and the web site. SyncEvolution 0.7 -> 0.8, 29.08.2008 ------------------------------------ C++ client library: 7.0 plus compatibility patch for Synthesis Updating user configuration: this version introduces a new, simplified configuration layout. Old configurations still work. They can be converted to the new format via a new "--migrate" command line option. *** WARNING ***: this version uses a different change tracking for Mac OS X address book, Evolution calendars, task lists and memos. After switching from a previous release to the current one or vice versa, do a "syncevolution --sync refresh-from-server" once to reset the change tracking. Not doing so can result in applying the same changes to the server multiple times and thus duplicates. * New configuration file layout: following the freedesktop.org recommendation, new configurations are stored in $XDG_CONFIG_HOME/syncevolution or $HOME/.config/syncevolution if XDG_CONFIG_HOME is not set. The old layout under $HOME/.sync4j/evolution is still supported. * New command line options: new configurations can be created by syncevolution itself (--configure), including setting of all configuration properties (--sync-property, --source-property). The configuration can dumped to stdout (--print-config), with or without comments explaining each property (--quiet). See the README for details. * The "evolutionsource" source property no longer has to be configured. If left blank, the default client database will be synchronized. * Selecting which kind of data is to be synchronized under a specific source name is a lot easier now and the same on all supported platforms: the SyncEvolution backends can be selected via aliases (e.g. "contacts") and the format is specified via an optional MIME type (e.g. "contacts:text/x-vcard"). In the unlikely situation that multiple backends are active which can synchronize the same kind of data, then the right one can be selected by the unique name of the backend (e.g. "Evolution Address Book"). * New configurations automatically get a random client ID string. Setting it manually is still possible, but no longer necessary. Disabling unavailable data sources is also done automatically. SyncEvolution checks that the backend is available and there is at least one database (the first one will be synchronized unless explicitly changed). If these checks fail and the sync source was explicitly requested by the user by listing it after the server name, then an error is printed and no configuration is written. If the user wants the default setup, then the source is silently disabled. * All passwords can be read from stdin at runtime or an environment variable (see "--sync-property password=?" or README for details). Both avoids the less secure storing of plain text passwords in the configuration files (SF #1832458). * Detached recurrences: meeting series where some occurrences were modified are now supported. Previously only the main event was synchronized. All exceptions got lost when copying back from the server. Requires a SyncML server which supports this. ScheduleWorld was extended to do that. * Fixed segfaults caused by logging certain data. The reason was an API change in the client library's logging calls which the older SyncEvolution code hadn't been adapted to. Did not normally occur, but might have been the reason for SF #1830149 (unconfirmed). * Time zone support: the time zones of incoming events are mapped to native time zone definitions whenever possible. Currently this works if the TZID follows the Olson naming scheme with a location at the end. Matching the time zone has the advantage of being able to update the time zone definition without having to recreate the event. If matching fails and the VTIMEZONE definition differs from one already imported earlier, then SyncEvolution works arounds limitation in Evolution by renaming the time zone. Previously the new event used the old and most likely out-dated time zone definition. ***WARNING***: Evolution itself does not do either of these steps itself yet, thus importing meeting invitations via Evolution still fails in some cases. The code implementing the time zone handling described above was written with inclusion into Evolution itself in mind; a discussion with the Evolution developers about that is in progress. * On Maemo/Nokia Internet Tablets, calendar synchronization now works because the new calendar change tracking no longer depends on some of the backend calls which used to fail (SF #1734977). * Added SSL configuration options: certificate checking can be relaxed or disabled completely (SF #1852647). * Added a new file backend: stores each SyncML item as a separate file in a directory. The directory has to be specified via the database name, using [file://] as format. The file:// prefix is optional, but the directory is only created if it is used. Change tracking is done via the file systems modification time stamp: editing a file treats it as modified and then sends it to the server in the next sync. Removing and adding files also works. The local unique identifier for each item is its name in the directory. New files are created using a running count which initialized based on the initial content of the directory to "highest existing number + 1" and incremented to avoid collisions. Although this sync source itself does not care about the content of each item/file, the server needs to know what each item sent to it contains and what items the source is able to receive. Therefore the "type" property for this source must contain a data format specified, including a version for it. Here are some examples: - type=file:text/vcard:3.0 - type=file:text/plain:1.0 * Code restructuring: it is now possible to add new backends and thus write SyncML clients for other kinds of data without touching any line of code in SyncEvolution itself. All the required interfaces are documented inside SyncEvolution itself. A HTML documentation can be built via the new "make doc" target (requires Doxygen and dot). The SyncEvolution framework itself never depended on GNOME or Evolution, only the Evolution data sources did. If you want support for other ways of storing your data, consider writing a new data source - it is really easy. See EvolutionSyncSource or TrackingSyncSource for details. * Messages are printed to the screen immediately. More readable log file format. * Maemo: the useless ''list: unable to access calendars: failure' error message is avoided. It was triggered by not having memo support in Evolution Data Server. Cleaned up the code so that it properly distinguishes between 'calendar', 'memo list' and 'task list'. * added server template for MemoToo; note that the server has not been tested * added synchronization of Evolution memo summary Most devices only synchronize plain text and do not have a separate summary field. Such an extra summary field was added to Evolution after memo support was initially implemented in SyncEvolution, therefore SyncEvolution did not transmit that field. Added transmitting the summary by inserting it as first line of the plain text blob *if* it is not already identical with the first line. When receiving a memo, the summary is set from the first line *without* removing the first line because the first line might have been used as a normal part of the memo. * Various other minor changes, fixes and lots of code cleanups. * license cleanup: SyncEvolution is GPL v2 or later SyncEvolution 0.8 beta 2 -> 0.8 final, 29.08.2008 ------------------------------------------------- C++ client library: 7.0 plus compatibility patch for Synthesis * license cleanup: SyncEvolution is GPL v2 or later SyncEvolution 0.8 beta 2 -> 0.8 beta 3, 17.08.2008 -------------------------------------------------- C++ client library: 7.0 plus compatibility patch for Synthesis * Another revision of updating events in Evolution calendars: the method introduced in 0.8 beta 1 for dealing with detached recurrences did not work with the Evolution Exchange Connector. Now both Exchange and local calendars pass the unit tests again. * minor code cleanup (testing, writing additional backends) SyncEvolution 0.8 beta 1 -> 0,8 beta 2, 03.08.2008 -------------------------------------------------- C++ client library: 7.0 plus compatibility patch for Synthesis * To prevent accidental sync runs when a configuration change was intented, a new --run switch must be used when configuration properties are given on the command line. When neither --run nor --configure are specified, SyncEvolution prints an error and refuses to do anything. * Improved documentation for command line, in particular the synopsis. * Added a new file backend: stores each SyncML item as a separate file in a directory. The directory has to be specified via the database name, using [file://] as format. The file:// prefix is optional, but the directory is only created if it is used. Change tracking is done via the file systems modification time stamp: editing a file treats it as modified and then sends it to the server in the next sync. Removing and adding files also works. The local unique identifier for each item is its name in the directory. New files are created using a running count which initialized based on the initial content of the directory to "highest existing number + 1" and incremented to avoid collisions. Although this sync source itself does not care about the content of each item/file, the server needs to know what each item sent to it contains and what items the source is able to receive. Therefore the "type" property for this source must contain a data format specified, including a version for it. Here are some examples: - type=file:text/vcard:3.0 - type=file:text/plain:1.0 * Code restructuring: it is now possible to add new backends and thus write SyncML clients for other kinds of data without touching any line of code in SyncEvolution itself. All the required interfaces are documented inside SyncEvolution itself. A HTML documentation can be built via the new "make doc" target (requires Doxygen and dot). SyncEvolution 0.8 alpha 1 -> 0.8 beta 1, 12.07.2008 --------------------------------------------------- C++ client library: the frozen 7.0 code, but before the release * Added support for detached recurrences (aka modified instances of a recurring event). Requires a SyncML server which supports this. ScheduleWorld was extended to do that. * Fixed segfaults caused by logging certain data. The reason was an API change in the client library's logging calls which the older SyncEvolution code hadn't been adapted to. Did not normally occur, but might have been the reason for SF #1830149 (unconfirmed). * when creating a config for the first time, only enable sync sources which can be synchronized (SF #1991286) The check for that was completely missing. Now SyncEvolution checks that the backend is available and there is at least one database (the first one will be synchronized unless explicitly changed). If these checks fail and the sync source was explicitly requested by the user by listing it after the server name, then an error is printed and no configuration is written. If the user wants the default setup, then the source is silently disabled. * Fixed incorrect properties in some of the new server templates (ScheduleWorld syncURL + calender URI, Funambol syncURL, ScheduleWorld addressbook type) * Device IDs must start with the "sc-pim-" prefix, otherwise myFUNAMBOL may treat different devices as the single phone that myFUNAMBOL supports, leading to unwanted slow syncs. * Maemo package is build again so that backends are loaded dynamically: installing Dates application is as it was with the 0.7 release (SF #1993109). The useless ''list: unable to access calendars: failure' error message is avoided. It was triggered by not having memo support in Evolution Data Server. Cleaned up the code so that it properly distinguishes between 'calendar', 'memo list' and 'task list'. * added server template for MemoToo; note that the server has not been tested * added synchronization of Evolution memo summary Most devices only synchronize plain text and do not have a separate summary field. Such an extra summary field was added to Evolution after memo support was initially implemented in SyncEvolution, therefore SyncEvolution did not transmit that field. Added transmitting the summary by inserting it as first line of the plain text blob *if* it is not already identical with the first line. When receiving a memo, the summary is set from the first line *without* removing the first line because the first line might have been used as a normal part of the memo. * removed --properties option: it wasn't implemented yet and won't be in 0.8 * fixed regression in alpha 1: setting sync mode during status query or sync affected *all* sources, even the disabled ones. Now it only affects the enabled ones, as intended. To enable disabled sync sources, list them after the server name. *** WARNING ***: this version uses a different change tracking for for Mac OS X AddressBook. After switching from a previous release to the current one or vice versa, do a "syncevolution --sync refresh-from-server" once to reset the change tracking. Not doing so can result in applying the same changes to the server multiple times and thus duplicates. A similar change was necessary in 0.8 alpha 1 for Evolution calendar, tasks, and memos. When switching from a version >= 0.8 alpha 1 to an older version or vice versa also refresh the local databases. 0.8 alpha 1 did not create correct configurations. When you want to continue using such a configuration, make sure that in addition to the obviously wrong syncURLs also the less obvious ScheduleWorld config mistakes are fixed: * calendar: uri=cal2 * addressbook: type=addressbook:text/vcard * deviceId must start with "sc-pim-" if you synchronize with myFUNAMBOL, otherwise there may be unwanted slow syncs when multiple devices with a different deviceId connect. Note that changing the deviceId causes a slow sync, so you should get client and server in sync before changing the value, change it, then do a "--sync refresh-from-server". SyncEvolution 0.7 -> 0.8 alpha 1, 19.04.2008 -------------------------------------------- C++ client library: a snapshot of the development version Updating user configuration: this version introduces a new, simplified configuration layout. Old configurations still work. They can be converted to the new format via a new "--migrate" command line option. *** WARNING ***: this version uses a different change tracking for Evolution calendars, task lists and memos. After switching from a previous release to the current one or vice versa, do a "syncevolution --sync refresh-from-server" once to reset the change tracking. Not doing so can result in applying the same changes to the server multiple times and thus duplicates. * New configuration file layout: following the freedesktop.org recommendation, new configurations are stored in $XDG_CONFIG_HOME/syncevolution or $HOME/.config/syncevolution if XDG_CONFIG_HOME is not set. The old layout under $HOME/.sync4j/evolution is still supported. * New command line options: new configurations can be created by syncevolution itself (--configure), including setting of all configuration properties (--sync-property, --source-property). The configuration can dumped to stdout (--print-config), with or without comments explaining each property (--quiet). See the README for details. * The "evolutionsource" source property no longer has to be configured. If left blank, the default client database will be synchronized. * Selecting which kind of data is to be synchronized under a specific source name is a lot easier now and the same on all supported platforms: the SyncEvolution backends can be selected via aliases (e.g. "contacts") and the format is specified via an optional MIME type (e.g. "contacts:text/x-vcard"). In the unlikely situation that multiple backends are active which can synchronize the same kind of data, then the right one can be selected by the unique name of the backend (e.g. "Evolution Address Book"). * New configurations automatically get a random client ID string. Setting it manually is still possible, but no longer necessary. * All passwords can be read from stdin at runtime or an environment variable (see "--sync-property password=?" or README for details). Both avoids the less secure storing of plain text passwords in the configuration files (SF #1832458). * Detached recurrences: meeting series where some occurrences were modified are now supported. Previously only the main event was synchronized. All exceptions got lost when copying back from the server. ***WARNING***: such events are accepted by ScheduleWorld, but not propagated to other clients. Under investigation. * Time zone support: the time zones of incoming events are mapped to native time zone definitions whenever possible. Currently this works if the TZID follows the Olson naming scheme with a location at the end. Matching the time zone has the advantage of being able to update the time zone definition without having to recreate the event. If matching fails and the VTIMEZONE definition differs from one already imported earlier, then SyncEvolution works arounds limitation in Evolution by renaming the time zone. Previously the new event used the old and most likely out-dated time zone definition. ***WARNING***: Evolution itself does not do either of these steps itself yet, thus importing meeting invitations via Evolution still fails in some cases. The code implementing the time zone handling described above was written with inclusion into Evolution itself in mind; a discussion with the Evolution developers about that is in progress. * On Maemo/Nokia Internet Tablets, calendar synchronization now works because the new calendar change tracking no longer depends on some of the backend calls which used to fail (SF #1734977). * Added SSL configuration options: certificate checking can be relaxed or disabled completely (SF #1852647). * Adding support for new local data sources is easier now. The SyncEvolution frame work itself never depended on GNOME or Evolution, only the Evolution data sources did. If you want support for other ways of storing your data, consider writing a new data source - it is really easy. See EvolutionSyncSource or TrackingSyncSource for details. * Messages are printed to the screen immediately. More readable log file format. * Various other minor changes and fixes. SyncEvolution 0.6 -> 0.7, 17.12.2007 ------------------------------------ C++ client library: r_6_5_3_1 Updating user configuration: no relevant changes in this release. For those who haven't done so already, enabling large object support is recommended (see syncml/config.txt sample configs). * added port for iPhone and Mac OS X Address Book * fixed Nokia packaging problem which prevented installation via the package manager unless it was in "red pill" mode (SF #1781652) * sync with eGroupware - lost or messed up telephones: SyncEvolution incorrectly added TYPE=OTHER to phone numbers sent with e.g. CELL instead of TYPE=CELL (SF #1796086). Another patch was required for eGroupware itself to correctly map phone numbers as sent by SyncEvolution, see Compatibility web page. * added .deb packages * adapted calendar event insert/update to Evolution 2.12: the UID needs to be restored, otherwise the Evolution backend crashes (GNOME issue #488881) * new feature: if the previous log directory is still available, then local changes made since last sync can be queried before starting a sync (new option --status) and will be printed directly before a sync. Setting the "logdir" option will automatically keep the most recent logs and database dumps around. * added command line options: --sync|-s Temporarily synchronize the active sources in that mode. Useful for a 'refresh-from-server' or 'refresh-from-client' sync which clears all data at one end and copies all items from the other. --status|-t The changes made to local data since the last synchronization are shown without starting a new one. This can be used to see in advance whether the local data needs to be synchronized with the server. --quiet|-q Suppresses most of the normal output during a synchronization. The log file still contains all the information. --help|-h Prints usage information. --version Prints the SyncEvolution version. * default configurations now reference the normal Evolution databases ("Personal") thus requiring less changes to use. The account information is now clearly marked as placeholder which needs to be entered. * bugfix: vCard 3.0 with mixed case were not converted properly to vCard 2.1 by SyncEvolution (must convert to upper case because vCard 2.1 only allows that), leading to problems with mapping phone numbers in the Funambol server. Diagnosed and reported by Paul McDermott, thanks a lot! * support receiving plain text notes with \n and \r\n line breaks; always send with \r\n * added explicit error message when syncevolution is invoked with incorrect names in the list of sources to synchronize: previously it silently ignored unknown names * improved output: less verbose ("extracting" items is now logged at debug level and thus not normally shown) and more informative printing of changes (table summarizes number of changes on client and server, heading for comparison changed to make it clear that it shows changes on the client) * SyncCap is not generated unless syncModes are configured: added a comment to example config (SF #1764123) * improved error handling: catch errors during post-processing and continue SyncEvolution 0.7-pre2 -> 0.7, 17.12.2007 ----------------------------------------- C++ client library: r_6_5_3_1 * bugfix: vCard 3.0 with mixed case were not converted properly to vCard 2.1 by SyncEvolution (must convert to upper case because vCard 2.1 only allows that), leading to problems with mapping phone numbers in the Funambol server. Diagnosed and reported by Paul McDermott, thanks a lot! * support receiving plain text notes with \n and \r\n line breaks; always send with \r\n * added explicit error message when syncevolution is invoked with incorrect names in the list of sources to synchronize: previously it silently ignored unknown names * added stack dumping in case of premature abort; removed workaround for lost connection to Evolution Dataserver again because the workaround itself caused random segfaults inside glib SyncEvolution 0.7-pre1 -> 0.7-pre2, 08.11.2007 ---------------------------------------------- C++ client library: branch b_v65 Updating user configuration: no relevant changes in this release. For those who haven't done so already, enabling large object support is recommended (see syncml/config.txt sample configs). It is required for myFUNAMBOL to synchronize very large address books and some users have reported segfaults unless this option was enabled. * iPhone bug fix: syncing contacts with photos was unreliable (export) and crashed (import) because the API had not been called correctly * iPhone + ScheduleWorld: when configured to use vcard3 (recommended!) then contacts are exchanged as vCard 3.0 * iPhone + ScheduleWorld bugfix: importing vCard 3.0 did not correctly classify the phone numbers. A sync with the new "--sync refresh-from-server" option will fix this, assuming that the server has the correct data. * Evolution: detect a crashed backend and abort SyncEvolution instead of hanging forever. * adapted calendar event insert/update to Evolution 2.12: the UID needs to be restored, otherwise the Evolution backend crashes (GNOME issue #488881) * new feature: if the previous log directory is still available, then local changes made since last sync can be queried before starting a sync (new option --status) and will be printed directly before a sync. Setting the "logdir" option will automatically keep the most recent logs and database dumps around. * added command line options: --sync|-s Temporarily synchronize the active sources in that mode. Useful for a 'refresh-from-server' or 'refresh-from-client' sync which clears all data at one end and copies all items from the other. --status|-t The changes made to local data since the last synchronization are shown without starting a new one. This can be used to see in advance whether the local data needs to be synchronized with the server. --quiet|-q Suppresses most of the normal output during a synchronization. The log file still contains all the information. --help|-h Prints usage information. --version Prints the SyncEvolution version. * default configurations now reference the normal Evolution databases ("Personal") thus requiring less changes to use. The account information is now clearly marked as placeholder which needs to be entered. SyncEvolution 0.6 -> 0.7-pre1, 17.10.2007 ----------------------------------------- * C++ client library: tag "sdkcpp_6_0_9_1" (same as before) * added support for Mac OS X/iPhone address book * fixed Nokia packaging problem which prevented installation via the package manager unless it was in "red pill" mode * improved output: less verbose ("extracting" items is now logged at debug level and thus not normally shown) and more informative printing of changes (table summarizes number of changes on client and server, heading for comparison changed to make it clear that it shows changes on the client) * example configs were in share/share directory (SF #1767329) * Nokia 770/800: uninstallable package fixed by setting category (SF #1781652) * sync with eGroupware - lost or messed up telephones: SyncEvolution incorrectly added TYPE=OTHER to phone numbers sent with e.g. CELL instead of TYPE=CELL (SF #1796086). Another patch was required for eGroupware itself to correctly map phone numbers as sent by SyncEvolution, see Compatibility web page. * SyncCap is not generated unless syncModes are configured: added a comment to example config (SF #1764123) * improved error handling: catch errors during post-processing and continue SyncEvolution 0.5 -> 0.6, 13.07.2007 ------------------------------------ * C++ client library: tag "sdkcpp_6_0_9_1" * added support for synchronizing Evolution notes (aka memos) as plain text where the first line serves as summary; this is the format understood by ScheduleWorld * added support for synchronizing Evolution notes (aka memos) as iCal 2.0 journal; not currently supported by any server and untested * revamped example configs and documentation: only one set of config files for each server is provided, because this is more likely to be needed by users * example configs are now installed in share/doc/syncevolution, enabled message limit and large object support in them * added support for Nokia 770/800 (aka Maemo): built with loadable modules so that it works with whatever backends are installed, improved log handling to accomodate for limited space on filesystem (see below), some workarounds * added workaround for Nokia 770: contacts are not really deleted unless the EDS-Sync with instant messaging servers is activated; now SyncEvolution will delete contacts marked as deleted by the GUI before a sync if it finds any. WARNING: if you use EDS-Sync and SyncEvolution, then give EDS-Sync enough time after going online to finish its own synchronization of modified/deleted contacts before starting SyncEvolution. * improved log handling: writing log and database dumps can be disabled with "logdir=none", verbosity of log is controlled by "loglevel", better handling of errors during initial database access * added workaround for Evolution bug #455274: the separator for multiple categories in events and tasks is not generated and interpreted according to iCalendar 2.0 by Evolution; as a consequence of that items sent to the server had all categories merged into one and items imported into Evolution only used one of the catories http://bugzilla.gnome.org/show_bug.cgi?id=455274 * fixed off-by-one counting of months in backup directory names * fixed error handling: a failed source was not forced into a slow sync as required; one failed source prevented saving configs of not-failed ones and thus forced those into an unnecessary slow sync * uses the Funambol C++ testing framework (which is based on the previous SyncEvolution testing); now creates its configs and (when using CLIENT_TEST_EVOLUTION_PREFIX=file://) also the Evolution databases automatically * implemented synccompare as pure Perl script using Algorithm::Diff instead of external diff tool * synccompare did not figure out width of shell window as it should have * better error handling if creating the before/after database dumps fails (SF #1685637) * workaround for Funambol 3.0 trailing = parser bug UPGRADING Old config files from 0.5 or older continue to work, but it is recommended to set the following options to enable message size limits: maxMsgSize = 8192 maxObjSize = 500000 loSupport = 1 SyncEvolution 0.6pre2 -> 0.6, 13.07.2007 ---------------------------------------- * improved README/HACKING documents * fixed the new example configs: use event/task for Funambol 6.0, name was wrong * added workaround for Evolution bug #455274: the separator for multiple categories in events and tasks is not generated and interpreted according to iCalendar 2.0 by Evolution; as a consequence of that items sent to the server had all categories merged into one and items imported into Evolution only used one of the catories http://bugzilla.gnome.org/show_bug.cgi?id=455274 * added workaround for Nokia 770: contacts are not really deleted unless the EDS-Sync with instant messaging servers is activated; now SyncEvolution will delete contacts marked as deleted by the GUI before a sync if it finds any. WARNING: if you use EDS-Sync and SyncEvolution, then give EDS-Sync enough time after going online to finish its own synchronization of modified/deleted contacts before starting SyncEvolution. SyncEvolution 0.6pre1 -> 0.6pre2, 23.04.2006 -------------------------------------------- * C++ client library: tag "sdkcpp_6_0_7" + revision 1.7 of build/autotools/test/Makefile.am * added support for synchronizing Evolution notes (aka memos) as plain text where the first line serves as summary; this is the format understood by ScheduleWorld, not the iCal 2.0 format added in 0.6pre1 * improved log handling: writing log and database dumps can be disabled with "logdir=none", verbosity of log is controled by "loglevel", better handling of errors during initial database access * fixed off-by-one counting of months in backup directory names * fixed error handling: a failed source was not forced into a slow sync as required; one failed source prevented saving configs of not-failed ones and thus forced those into an unnecessary slow sync * revamped example configs: only one set of config files for each server is provided, because this is more likely to be needed by users * uses the Funambol C++ testing framework (which is based on the previous SyncEvolution testing); now creates its configs and (when using CLIENT_TEST_EVOLUTION_PREFIX=file://) also the Evolution databases automatically SyncEvolution 0.5 -> 0.6pre1, 26.03.2006 ---------------------------------------- * C++ client library: CVS snapshot from 26.03.2006 * added support for synchronizing Evolution notes (aka memos) as iCal 2.0 journal * added --enable-static-cxa = linking C++ runtime statically: binaries produced for 0.6 will have less external dependencies than the 0.5 binaries * added hacks for Maemo/Nokia 770, including a build mode with dynamically loadable modules (--enable-shared, --enable-maemo, --with-patched-dbus) * implemented synccompare as pure Perl script using Algorithm::Diff instead of external diff tool * synccompare did not figure out width of shell window as it should have * better error handling if creating the before/after database dumps fails (SF #1685637) * example configs are now installed in share/doc/syncevolution, enabled message limit and large object support in them * workaround for Funambol 3.0 trailing = parser bug UPGRADING Old config files continue to work, but it is recommended to set the following options to enable message size limits: maxMsgSize = 8192 maxObjSize = 500000 loSupport = 1 SyncEvolution 0.4 -> 0.5, 12.11.2006 ------------------------------------ * C++ client library revision "syncevolution-0-5": - added support for sending changes in smaller chunks ("Large Object Support"): disabled by default, see updated example configuration - time is printed with GMT offset so that a server admin in a different timezone can always figure out how a client log relates to events on the server - special item keys as they might be stored in some calendars after importing non-Evolution events are now properly supported * bug fix: in 0.4 it was necessary to manually configure the verDTD or the Funambol 3.0a server would choke on the invalid SyncML during the second synchronization with SyncEvolution; now this option is set automatically * added support and testing of transmitting just the changes from client to server or vice versa; see "one-way-from-server/client" in example configuration * fixed/updated comments in the example configuration * improved automated testing and fixed the problem that CPPUnit was not found unless it was part of the system * Now works on Maemo/Nokia 770: minor changes were necessary so that the system address book can now be selected under the name "<>. Copying 300 contacts into the Nokia 770 went fine, but any further attempt to synchronize suffered from timeouts inside the embedded Evolution Data Server. SyncEvolution 0.3 -> 0.4, 11.09.2006 ------------------------------------ * C++ client library revision "syncevolution-0-4": - added support for device information, required by some servers - fixed incompatibilities with non-Funambol servers - the user agent string can now be modified in the spds/syncml/config.txt, but it is recommended to not set it explicitly. Then SyncEvolution will automatically insert its current version. - #305795: for tasks the "text/x-todo" type from the configuration was sent to servers instead of the correct "text/calendar" provided by SyncEvolution itself - sync modes "refresh-client/server" can now be specified as "refresh-from-client/server" in the config * updated default syncml/config.txt: - firstTimeSyncMode has never been implemented in the library, removed its documentation, - added documentation for userAgent - use "refresh-from-client/server" * SF issue 1511951: support copying changes back from EGroupware server by not expecting the UID of calendar items to be unmodified * fixed a bug where after a refresh-from-client sync changes would be sent to the server again during a two-way sync although the server already had them * implemented authentication for Evolution databases * synccompare was removing too many parts of vCards with single-value ORG properties * improved error reporting when selected server is not configured * changed vCard parser to make it compatible with servers which send a verbatim semicolon as part of properties where the semicolon has no special meaning * If minor errors occur like not being able to insert an item at the client or server side, then it is reported in the log and output, but the next synchronization will be a normal synchronization, not a forced slow one as in previous versions. The old approach ensured that the problem was noticed and fixed, but required user assistance. With the new approach synchronization continues to work, although without fixing the root cause of the problem. * Workaround for bug in Evolution 2.0.6 (and perhaps other versions): for calendars and task lists not all deleted items were reported at once thus a single synchronization would only tell the server about a subset of the changes. Repeating the synchronization would eventually be told of all changes, so now this repetition is built into the code which queries for changes and a single synchronization is sufficient as it should be. SyncEvolution 0.4 pre2 -> 0.4, 11.09.2006 ----------------------------------------- * adapted to C++ client library from CVS head, tagged as syncevolution-0-4: devinfo.patch patch was merged with several changes to the API * SF issue 1511951: support copying changes back from EGroupware server by not expecting the UID of calendar items to be unmodified SyncEvolution 0.4 pre1 -> pre2, 21.08.2006 ------------------------------------------ * C++ client library revision "syncevolution-0-4-pre2": most patches were merged into CVS head, but .patches/devinfo.patch still needs to be applied manually when checking out from the Funambol CVS instead of using the bundled version * fixed a bug where after a refresh-from-client sync changes would be sent to the server again during a two-way sync although the server already had them * implemented authentication for Evolution databases * synccompare was removing too many parts of vCards with single-value ORG properties * improved error reporting when selected server is not configured * use 7-bit quoted-printable encoding with explicit UTF-8 charset for vCard 2.1 to avoid any potential confusion about the content; not really necessary because SyncML specifies 8-bit UTF-8 as the default * fix for 0.4 pre 1: sending CHARSET is not allowed (and not needed) for vCard 3.0, so it was removed again (did not harm either) * fix for 0.4 pre 1: sending vCard 2.1 to Synthesis server did not work because the new device info always mentioned 3.0 as the preferred format - now the preferred format matches the one that was configured and that thus will be used. SyncEvolution 0.3 -> 0.4 pre 1, 2006-08-06 ------------------------------------------ * C++ client library revision "funambol30ga" plus the patches stored in its ".patches" directory: - the user agent string can now be modified in the spds/syncml/config.txt, but it is recommended to not set it explicitly. Then SyncEvolution will automatically insert its current version. - now compatible with additional servers (fixed some SyncML protocol issues, added support for sending device information) - revised API of the client library - #305795: for tasks the "text/x-todo" type from the configuration was sent to servers instead of the correct "text/calendar" provided by SyncEvolution itself - sync modes "refresh-client/server" can now be specified as "refresh-from-client/server" in the config * updated default syncml/config.txt: - firstTimeSyncMode has never been implemented in the library, removed its documentation, - added documentation for userAgent - use "refresh-from-client/server" * changed vCard parser to make it compatible with servers which send a verbatim semicolon as part of properties where the semicolon has no special meaning * If minor errors occur like not being able to insert an item at the client or server side, then it is reported in the log and output, but the next synchronization will be a normal synchronization, not a forced slow one as in previous versions. The old approach ensured that the problem was noticed and fixed, but required user assistance. With the new approach synchronization continues to work, although without fixing the root cause of the problem. * Workaround for bug in Evolution 2.0.6 (and perhaps other versions): for calendars and task lists not all deleted items were reported at once thus a single synchronization would only tell the server about a subset of the changes. Repeating the synchronization would eventually be told of all changes, so now this repetition is built into the code which queries for changes and a single synchronization is sufficient as it should be. * Made it compile on Maemo 2.0, the Nokia 770 build environment, by adding "--disable-ecal". Not tested yet, though. SyncEvolution 0.3, 2006-06-27 ----------------------------- * added syncing of calendars and tasks as iCalendar 2.0 * added syncing of contacts as vCard 3.0 * tested extensively with sync.scheduleworld.com and added an example configuration for it * uses C++ client library revision "wmplugin_3_0_20" which contains several bug fixes, among them proper support for special characters and memory handling fixes * much nicer listing of changes made during a sync, handled by the improved "synccompare" utility script (formerly known as "normalize_vcard") * improved automated testing SyncEvolution 0.2, 2006-03-19 ----------------------------- * added automatic backup mechanism and log storage, see "Automatic Backups and Logging". * output no longer is the original log data, but rather a human-readable report of errors and synchronization results. * "normalize_vcard" can now also compare two .vcf files directly. * improved unit tests to catch more errors * hide certain differences in vcards coming back from the server: duplication of extended vcard properties, missing TYPE=OTHER * fixed client library problems: see http://forge.objectweb.org/tracker/?group_id=96&atid=100096 #304792, #304829 * added some more problems to the "Known Problems" section SyncEvolution 0.1, 2006-03-13 ----------------------------- * initial release syncevolution_1.4/README-DLT.rst000066400000000000000000000030171230021373600164670ustar00rootroot00000000000000Diagnostic Log and Trace ======================== Diagnostic Log and Trace (DLT) is a logging mechanism defined and implemented by GENIVI: http://projects.genivi.org/diagnostic-log-trace/ SyncEvolution optionally supports DLT as follows: * syncevo-dbus-server, syncevo-dbus-helper and syncevo-local-sync can log to DLT. Operations with "syncevolution --daemon=no" never use DLT. * Each of the three processes uses a different application ID. By default, these IDs are "SYNS", "SYNH", "SYNL". These default can be changed via configure options. All processes use just one context, with the fixed ID "SYNC". * syncevo-dbus-helper and syncevo-local-sync only run occasionally. This makes is hard to adjust their log levels, for example via the dlt-viewer, because the processes and their contexts are only shown (known?) while the processes run. To work around this, the initial log level of these two helpers are inherited from the log level of the "SYNC" context in syncevo-dbus-helper. * That log level defaults to "WARN", which ensures that normal runs produce no output. * To enable DLT support during compilation, use "--enable-dlt" and "--with-dlt-syncevolution=SYNS,SYNH,SYNL" where SYNS/H/L are the actual application IDs. * To enable DLT support at runtime, run syncevo-dbus-server with "--dlt". Logging to syslog should be disabled with "--no-syslog". * The hierarchical log from libsynthesis gets flattened into a flat stream of messages and no syncevolution-log.html files are written. syncevolution_1.4/README.packagers000066400000000000000000000025071230021373600172010ustar00rootroot00000000000000Some of the advanced features of SyncEvolution depend on optional packages. In addition to the more obvious dependencies of the backends there are more subtle dependencies: - for good time zone support, libsynthesis must have access to either libical or libecal - direct sync with phones depends on bluez and openobex - GNOME Bluetooth Panel needs libgnome-bluetooth-dev *AND* --enable-gnome-bluetooth-panel - either glib or libnss should be available, so that SyncEvolution can use SHA-256 hashes instead of a weaker built-in algorithm for hashes in the database dump .ini files - libnotify is needed by the syncevo-dbus-server, although --disable-notify can be used to avoid that. - Localization data is shared between sync-ui and syncevo-dbus-server and thus needs to be packaged with the core SyncEvolution. - to enable CalDAV/CardDAV: - use --enable-dav - needs libneon[-gnutls].pc (use gnutls version, if available, but make sure that it is >= 0.29.5, which has this patch: http://lists.manyfish.co.uk/pipermail/neon/2010-November/001294.html) - ensure that one of the following commands is available at runtime: adnshost, host, nslookup - as of SyncEvolution >= 1.3 rst2html tool should be made as a build requirement, because README.html is optionally generated during build instead of being distributed. syncevolution_1.4/README.rst000066400000000000000000001405321230021373600160520ustar00rootroot00000000000000=============== SyncEvolution =============== ------------------------------------------------ synchronize personal information management data ------------------------------------------------ :Manual section: 1 :Version: 1.0 :Date: Apr 28, 2010 SYNOPSIS ======== List and manipulate databases: syncevolution --print-databases|--create-database|--remove-database [] [ ] Show information about configuration(s): syncevolution --print-servers|--print-configs|--print-peers Show information about a specific configuration: syncevolution --print-config [--quiet] [--] [main| ...] List sessions: syncevolution --print-sessions [--quiet] [--] Show information about SyncEvolution: syncevolution --help|-h|--version Run a synchronization as configured: syncevolution [ ...] Run a synchronization with properties changed just for this run: syncevolution --run [--] [ ...] Restore data from the automatic backups: syncevolution --restore --before|--after [--dry-run] [--] ... Create, update or remove a configuration: syncevolution --configure [--] [ ...] syncevolution --remove|--migrate [--] List items: syncevolution --print-items [--] [ []] Export item(s): syncevolution [--delimiter ] --export ||- [--] [ [ [ ...]]] --luids ... Add item(s): syncevolution [--delimiter |none] --import ||- [--] [ []] --luids ... Update item(s): syncevolution --update [--] syncevolution [--delimiter |none] --update |- [--] ... --luids ... Remove item(s): syncevolution --delete-items [--] ( ... | '*') DESCRIPTION =========== This text explains the usage of the SyncEvolution command line. SyncEvolution synchronizes personal information management (PIM) data such as contacts, appointments, tasks and memos using the Synthesis sync engine, which provides support for the SyncML synchronization protocol. SyncEvolution synchronizes with SyncML servers over HTTP and with SyncML capable phones locally over Bluetooth (new in 1.0). Plugins provide access to the data which is to be synchronized. Binaries are available for Linux desktops (synchronizing data in GNOME Evolution, with KDE supported indirectly already and Akonadi support in development), for MeeGo (formerly Moblin) and for Maemo 5/Nokia N900. The source code can be compiled for Unix-like systems and provides a framework to build custom SyncML clients or servers. TERMINOLOGY =========== peer A peer is the entity that data is synchronized with. This can be another device (like a phone), a server (like Google) or even the host itself (useful for synchronizing two different databases). host The device or computer that SyncEvolution runs on. database Each peer has one or more databases that get synchronized (Google Calendar, Google Contacts). Conceptually a database is a set of items where each item is independent of the others. data source A name for something that provides access to data. Primarily used for the configuration which combines backend and database settings, sometimes also instead of these two terms. local/remote Synchronization always happens between a pair of databases and thus has two sides. One database or side of a sync is remote (the one of the peer) or local (SyncEvolution). For the sake of consistency (and lack of better terms), these terms are used even if the peer is another instance of SyncEvolution and/or all data resides on the same storage. sync config A sync configuration defines how to access a peer: the protocol which is to be used, how to find the peer, credentials, etc. Peers might support more than one protocol, in which case multiple sync configs have to be created. Sync configs can be used to initiate a sync (like contacting a SyncML server) or to handle an incoming sync request (when acting as SyncML server which is contacted by the peer). source config Each data source corresponds to a local database. A source config defines how to access that database, like a sync config does for peers. This information about a local database is independent of the peers that the database might be synchronized with. Sync configs use these shared source configs and add additional, per-peer settings to each of them that define how that local database maps to a remote database in the peer. By default a source config is inactive inside a sync config and thus ignored. It must be activated by setting the unshared `sync` property to something other than `none` (aka `disabled`). In SyncEvolution's predefined configuration templates, the following names for sources are used. Different names can be chosen for sources that are defined manually. * addressbook: a list of contacts * calendar: calendar *events* * memo: plain text notes * todo: task list * calendar+todo: a virtual source combining one local "calendar" and one "todo" source (required for synchronizing with some phones) backend Access to databases is provided by SyncEvolution backends. It does not matter where that data is stored. Some backends provide access to data outside of the host itself (`CalDAV and CardDAV`_, ActiveSync). configuration property Sync and source configs contain configuration properties. Each property is a name/value pair. Sync properties are used in sync configs, source properties in source configs. The names were chosen so that they are unique, i.e., no sync property has the same name as a source property. A property can be *unshared* (has separate values for each peer, therefore sometimes also called *per-peer*; for example the `uri` property which defines the remote database), *shared* (same value for all peers; for example the `database` property for selecting the local database) or *global* (exactly one value). context Sync and source configs are defined inside a configuration context. Typically each context represents a certain set of sources. The values of shared properties are only shared inside their context. That way it is possible to define a second `work` context with a `work calendar` source using one database and use the implicit `default` context for a private `calendar` source with a different database. context config The shared and global properties of a certain context. configuration template Templates define the settings for specific peers. Some templates are packaged together with SyncEvolution, others may be added by packagers or users. Settings from templates are copied once into the sync config when creating it. There is no permanent link back to the template, so updating a template has no effect on configs created from it earlier. A template only contains unshared properties. Therefore it is possible to first set shared properties (for example, choosing which databases to synchronize in the default context), then add sync configs for different peers to that context without reseting the existing settings. local sync Traditionally, a sync config specifies SyncML as the synchronization protocol. The peer must support SyncML for this to work. When the peer acts as SyncML server, conflict resolution happens on the peer, outside of the control of SyncEvolution. In a so called `local sync`_, SyncEvolution connects two of its own backends and runs all of the synchronization logic itself on the host. target config In addition to the normal sync config, a local sync also uses a target config. This target config is a special kind of sync config. It defines sync properties that are necessary to access databases on the other side of the local sync. Sync configs can have arbitrary names while a target config must be named `target-config`. COMMAND LINE CONVENTIONS ======================== The ```` and the ```` strings in the command line synopsis are used to find the sync resp. source configs. Depending on which other parameters are given, different operations are executed. A config name has the format ``[][@]``. When the context is not specified explicitly, SyncEvolution first searches for an existing configuration with the given name. If not found, it uses the ``@default`` context as fallback. Thus the empty config name is an alias for ``@default``. The ```` part identifies a specific sync or target config inside the context. It is optional and does not have to be specified when not needed, for example when configuring the shared settings of sources (``--configure @default addressbook``) or accessing items inside a source (``--print-items @work calendar``). Listing sources on the command line limits the operation to those sources (called *active sources* below). If not given, all sources defined for the config are active. Some operations require the name of exactly one source. Properties are set with key/value assignments and/or the ``--sync/source-property`` keywords. Those keywords are only needed for the hypothetical situation that a sync and source property share the same name (not normally the case). Without them, SyncEvolution automatically identifies which kind of property is meant based on the name. A ```` assignment has the following format:: [/][@|@@]= The optional ```` or ``@`` suffix limits the scope of the value to that particular configuration. This is useful when running a local sync, which involves a sync and a target configuration. For example, the log level can be specified separately for both sides:: --run loglevel@default=1 loglevel@google-calendar=4 google-calendar@default A string without a second @ sign inside is always interpreted as a context name, so in contrast to the ```` string, ``foo`` cannot be used to reference the ``foo@default`` configuration. Use the full name including the context for that. When no config or context is specified explicitly, a value is changed in all active configs, typically the one given with ````. The priority of multiple values for the same config is `more specific definition wins`, so ``@`` overrides ``@``, which overrides `no suffix given`. Specifying some suffix which does not apply to the current operation does not trigger an error, so beware of typos. Source properties can be specified with a ``/`` prefix. This allows limiting the value to the selected source. For example:: --configure "addressbook/database=My Addressbook" \ "calendar/database=My Calendar" \ @default addressbook calendar Another way to achieve the same effect is to run the ``--configure`` operation twice, once for ``addressbook`` and once for ``calendar``:: --configure "database=My Addressbook" @default addressbook --configure "calendar/database=My Calendar" @default calendar If the same property is set both with and without a ``/`` prefix, then the more specific value with that prefix is used for that source, regardless of the order on the command line. The following command enables all sources except for the addressbook:: --configure --source-property addressbook/sync=none \ --source-property sync=two-way \ USAGE ===== :: syncevolution --print-databases [] [ ] If no additional arguments are given, then SyncEvolution will list all available backends and the databases that can be accessed through each backend. This works without existing configurations. However, some backends, like for example the CalDAV backend, need additional information (like credentials or URL of a remote server). This additional information can be provided on the command line with property assignments (``username=...``) or in an existing configuration. When listing all databases of all active sources, the output starts with a heading that lists the values for the ``backend`` property which select the backend, followed by the databases. Each database has a name and a unique ID (in brackets). Typically both can be used as value of the 'database' property. One database might be marked as ``default``. It will be used when ``database`` is not set explicitly. When selecting an existing source configuration or specifying the ``backend`` property on the command line, only the databases for that backend are listed and the initial line shows how that backend was selected (/ resp. backend value). Some backends do not support listing of databases. For example, the file backend synchronizes directories with one file per item and always needs an explicit ``database`` property because it cannot guess which directory it is meant to use. :: syncevolution --create-database [] [ ] Creates a new database for the selected ``backend``, using the information given in the ``database`` property. As with ``--print-databases``, it is possible to give the properties directly without configuring a source first. The interpretation of the ``database`` property depends on the backend. Not all backends support this operation. The EDS backend uses the value of the ``database`` as name of the new database and assigns a unique URI automatically. :: syncevolution --remove-database [] [ ] Looks up the database based on the ``database`` property (depending on the backend, both name and a URI are valid), then deletes the data. Note that source configurations using the database are not removed. :: syncevolution Without the optional list of sources, all sources which are enabled in their configuration file are synchronized. :: syncevolution ... Otherwise only the ones mentioned on the command line are active. It is possible to configure sources without activating their synchronization: if the synchronization mode of a source is set to `disabled`, the source will be ignored. Explicitly listing such a source will synchronize it in `two-way` mode once. Progress and error messages are written into a log file that is preserved for each synchronization run. Details about that is found in the `Automatic Backups and Logging` section below. All errors and warnings are printed directly to the console in addition to writing them into the log file. Before quitting SyncEvolution will print a summary of how the local data was modified. This is done with the `synccompare` utility script described in the `Exchanging Data`_ section. When the ``logdir`` property is enabled (since v0.9 done by default for new configurations), then the same comparison is also done before the synchronization starts. In case of a severe error the synchronization run is aborted prematurely and SyncEvolution will return a non-zero value. Recovery from failed synchronization is done by forcing a full synchronization during the next run, i.e. by sending all items and letting the SyncML server compare against the ones it already knows. This is avoided whenever possible because matching items during a slow synchronization can lead to duplicate entries. After a successful synchronization the server's configuration file is updated so that the next run can be done incrementally. If the configuration file has to be recreated e.g. because it was lost, the next run recovers from that by doing a full synchronization. The risk associated with this is that the server might not recognize items that it already has stored previously which then would lead to duplication of items. :: syncevolution --configure [ ...] Options in the configuration can be modified via the command line. Source properties are changed for all sources unless sources are listed explicitly. Some source properties have to be different for each source, in which case syncevolution must be called multiple times with one source listed in each invocation. :: syncevolution --remove Deletes the configuration. If the refers to a specific peer, only that peer's configuration is removed. If it refers to a context, that context and all peers inside it are removed. Note that there is no confirmation question. Neither local data referenced by the configuration nor the content of log dirs are deleted. :: syncevolution --run [ ...] Options can also be overridden for just the current run, without changing the configuration. In order to prevent accidentally running a sync session when a configuration change was intended, either --configure or --run must be given explicitly if options are specified on the command line. :: syncevolution --status [ ...] Prints what changes were made locally since the last synchronization. Depends on access to database dumps from the last run, so enabling the ``logdir`` property is recommended. :: syncevolution --print-servers|--print-configs|--print-peers syncevolution --print-config [--quiet] [main| ...] syncevolution --print-sessions [--quiet] These commands print information about existing configurations. When printing a configuration a short version without comments can be selected with --quiet. When sources are listed, only their configuration is shown. `Main` instead or in combination with sources lists only the main peer configuration. :: syncevolution --restore --before|--after [--dry-run] ... This restores local data from the backups made before or after a synchronization session. The --print-sessions command can be used to find these backups. The source(s) have to be listed explicitly. There is intentionally no default, because as with --remove there is no confirmation question. With --dry-run, the restore is only simulated. The session directory has to be specified explicitly with its path name (absolute or relative to current directory). It does not have to be one of the currently active log directories, as long as it contains the right database dumps for the selected sources. A restore tries to minimize the number of item changes (see section `Item Changes and Data Changes`_). This means that items that are identical before and after the change will not be transmitted anew to the peer during the next synchronization. If the peer somehow needs to get a clean copy of all local items, then use ``--sync refresh-from-local`` in the next run. :: syncevolution --print-items syncevolution [--delimiter ] --export ||- [ [ [ ...]]] syncevolution [--delimiter |none] --import ||- [ ] syncevolution --update syncevolution [--delimiter |none] --update |- ... syncevolution --delete-items ( ... | *) Restore depends on the specific format of the automatic backups created by SyncEvolution. Arbitrary access to item data is provided with additional options. here is the unique local identifier assigned to each item in the source, transformed so that it contains only alphanumeric characters, dash and underscore. A star * in --delete-items selects all items for deletion. There are two ways of specifying luids: either as additional parameters after the config and source parameters (which may be empty in this case, but must be given) or after the ``--luids`` keyword. and may be given to define the database which is to be used. If not given or not refering to an existing configuration (which is not an error, due to historic reasons), the desired backend must be given via the ``backend`` property, like this:: syncevolution --print-items backend=evolution-contacts syncevolution --export - backend=evolution-contacts \ --luids pas-id-4E33F24300000006 pas-id-4E36DD7B00000007 The desired backend database can be chosen via ``database=``. See ``--print-databases``. OPTIONS ======= Here is a full description of all that can be put in front of the server name. Whenever an option accepts multiple values, a question mark can be used to get the corresponding help text and/or a list of valid values. --sync|-s |? Temporarily synchronize the active sources in that mode. Useful for a `refresh-from-local` or `refresh-from-remote` sync which clears all data at one end and copies all items from the other. **Warning:** `local` is the data accessed via the sync config directly and `remote` is the data on the peer, regardless where the data is actually stored physically. --print-servers|--print-configs|--print-peers Prints the names of all configured peers to stdout. There is no difference between these options, the are just aliases. --print-servers|--print-configs|--print-peers|-p Prints the complete configuration for the selected to stdout, including up-to-date comments for all properties. The format is the normal .ini format with source configurations in different sections introduced with [] lines. Can be combined with --sync-property and --source-property to modify the configuration on-the-fly. When one or more sources are listed after the name on the command line, then only the configs of those sources are printed. `main` selects the main configuration instead of source configurations. Using --quiet suppresses the comments for each property. When setting a --template, then the reference configuration for that peer is printed instead of an existing configuration. \--print-sessions Prints information about previous synchronization sessions for the selected peer or context are printed. This depends on the ``logdir`` property. The information includes the log directory name (useful for --restore) and the synchronization report. In combination with --quiet, only the paths are listed. --configure|-c Modify the configuration files for the selected peer and/or sources. If no such configuration exists, then a new one is created using one of the template configurations (see --template option). Choosing a template sets most of the relevant properties for the peer and the default set of sources (see above for a list of those). Anything specific to the user (like username/password) still has to be set manually. When creating a new configuration and listing sources explicitly on the command line, only those sources will be set to active in the new configuration, i.e. `syncevolution -c memotoo addressbook` followed by `syncevolution memotoo` will only synchronize the address book. The other sources are created in a disabled state. When modifying an existing configuration and sources are specified, then the source properties of only those sources are modified. By default, creating a config requires a template. Source names on the command line must match those in the template. This allows catching typos in the peer and source names. But it also prevents some advanced use cases. Therefore it is possible to disable these checks in two ways:: - use `--template none` or - specify all required sync and source properties that are normally in the templates on the command line (syncURL, backend, ...) --run|-r To prevent accidental sync runs when a configuration change was intended, but the `--configure` option was not used, `--run` must be specified explicitly when sync or source properties are selected on the command line and they are meant to be used during a sync session triggered by the invocation. \--migrate In older SyncEvolution releases a different layout of configuration files was used. Using --migrate will automatically migrate to the new layout and rename the into .old to prevent accidental use of the old configuration. WARNING: old SyncEvolution releases cannot use the new configuration! The switch can also be used to migrate a configuration in the current configuration directory: this preserves all property values, discards obsolete properties and sets all comments exactly as if the configuration had been created from scratch. WARNING: custom comments in the configuration are not preserved. --migrate implies --configure and can be combined with modifying properties. \--print-items Shows all existing items using one line per item using the format "[: ]". Whether the description is available depends on the backend and the kind of data that it stores. \--export Writes all items in the source or all items whose is given into a directory if the --export parameter exists and is a directory. The of each item is used as file name. Otherwise it creates a new file under that name and writes the selected items separated by the chosen delimiter string. stdout can be selected with a dash. The default delimiter (two line breaks) matches a blank line. As a special case, it also matches a blank line with DOS line ending (line break, carriage return, line break). This works for vCard 3.0 and iCalendar 2.0, which never contain blank lines. When exporting, the default delimiter will always insert two line breaks regardless whether the items contain DOS line ends. As a special case, the initial newline of a delimiter is skipped if the item already ends in a newline. \--import Adds all items found in the directory or input file to the source. When reading from a directory, each file is treated as one item. Otherwise the input is split at the chosen delimiter. "none" as delimiter disables splitting of the input. \--update Overwrites the content of existing items. When updating from a directory, the name of each file is taken as its luid. When updating from file or stdin, the number of luids given on the command line must match with the number of items in the input. \--delete-items Removes the specified items from the source. Most backends print some progress information about this, but besides that, no further output is produced. Trying to remove an item which does not exist typically leads to an ERROR message, but is not reflected in a non-zero result of the command line invocation itself because the situation is not reported as an error by backends (removal of non-existent items is not an error in SyncML). Use a star \* instead or in addition to listing individual luids to delete all items. --sync-property|-y =|=?|? Overrides a source-independent configuration property for the current synchronization run or permanently when --configure is used to update the configuration. Can be used multiple times. Specifying an unused property will trigger an error message. --source-property|-z =|=?|? Same as --sync-property, but applies to the configuration of all active sources. `--sync ` is a shortcut for `--source-property sync=`. --template|-l |default|? Can be used to select from one of the built-in default configurations for known SyncML peers. Defaults to the name, so --template only has to be specified when creating multiple different configurations for the same peer, or when using a template that is named differently than the peer. `default` is an alias for `memotoo` and can be used as the starting point for servers which do not have a built-in template. A pseudo-random device ID is generated automatically. Therefore setting the `deviceId` sync property is only necessary when manually recreating a configuration or when a more descriptive name is desired. The available templates for different known SyncML servers are listed when using a single question mark instead of template name. When using the `?` format, a fuzzy search for a template that might be suitable for talking to such a device is done. The matching works best when using ` = `. If you don't know the manufacturer, you can just keep it as empty. The output in this mode gives the template name followed by a short description and a rating how well the template matches the device (100% is best). --status|-t The changes made to local data since the last synchronization are shown without starting a new one. This can be used to see in advance whether the local data needs to be synchronized with the server. --quiet|-q Suppresses most of the normal output during a synchronization. The log file still contains all the information. --keyring[=]|-k A legacy option, now the same as setting the global keyring sync property. When not specifying a value explicitly, "true" for "use some kind of keyring" is implied. See "--sync-property keyring" for details. --daemon[=yes/no] By default, the SyncEvolution command line is executed inside the syncevo-dbus-server process. This ensures that synchronization sessions started by the command line do not conflict with sessions started via some other means (GUI, automatically). For debugging purposes or very special use cases (running a local sync against a server which executes inside the daemon) it is possible to execute the operation without the daemon (--daemon=no). --help|-h Prints usage information. \--version Prints the SyncEvolution version. CONFIGURATION PROPERTIES ======================== This section lists predefined properties. Backends can add their own properties at runtime if none of the predefined properties are suitable for a certain setting. Those additional properties are not listed here. Use ``--sync/source-property ?`` to get an up-to-date list. The predefined properties may also be interpreted slightly differently by each backend and sync protocol. Sometimes this is documented in the comment for each property, sometimes in the documentation of the backend or sync protocol. Properties are listed together with all recognized aliases (in those cases where a property was renamed at some point), its default value, sharing state (unshared/shared/global). Some properties must be defined, which is marked with the word `required`. Sync properties --------------- << see "syncevolution --sync-property ?" >> Source properties ----------------- << see "syncevolution --source-property ?" >> EXAMPLES ======== List the known configuration templates:: syncevolution --template ? Create a new configuration, using the existing Memotoo template:: syncevolution --configure \ username=123456 \ "password=!@#ABcd1234" \ memotoo Note that putting passwords into the command line, even for short-lived processes as the one above, is a security risk in shared environments, because the password is visible to everyone on the machine. To avoid this, remove the password from the command above, then add the password to the right config.ini file with a text editor. This command shows the directory containing the file:: syncevolution --print-configs Review configuration:: syncevolution --print-config memotoo Synchronize all sources:: syncevolution memotoo Deactivate all sources:: syncevolution --configure \ sync=none \ memotoo Activate address book synchronization again, using the --sync shortcut:: syncevolution --configure \ --sync two-way \ memotoo addressbook Change the password for a configuration:: syncevolution --configure \ password=foo \ memotoo Set up another configuration for under a different account, using the same default databases as above:: syncevolution --configure \ username=joe \ password=foo \ --template memotoo \ memotoo_joe Set up another configuration using the same account, but different local databases (can be used to simulate synchronizing between two clients, see `Exchanging Data`_:: syncevolution --configure \ username=123456 \ password=!@#ABcd1234" \ sync=none \ memotoo@other syncevolution --configure \ --source-property database= \ @other addressbook syncevolution --configure \ sync=two-way \ memotoo@other addressbook syncevolution memotoo syncevolution memotoo@other Migrate a configuration from the <= 0.7 format to the current one and/or updates the configuration so that it looks like configurations created anew with the current syncevolution:: syncevolution --migrate memotoo .. _local sync: Synchronization beyond SyncML ============================= In the simple examples above, SyncEvolution exchanges data with servers via the SyncML protocol. Starting with release 1.2, SyncEvolution also supports other protocols like CalDAV and CardDAV. These protocols are implemented in backends which look like data sources. SyncEvolution then synchronizes data between a pair of backends. Because the entire sync logic (matching of items, merging) is done locally by SyncEvolution, this mode of operation is called *local sync*. Some examples of things that can be done with local sync: * synchronize events with a CalDAV server and contacts with a CardDAV server * mirror a local database as items in a directory, with format conversion and one-way or two-way data transfer (export vs. true syncing) Because local sync involves two sides, two configurations are needed. One is called the *target config*. By convention it must be called ``target-config@``, for example ``target-config@google-calendar``. The target config holds properties which apply to all sources inside that context, like user name, password and URL for the server. Once configured, the target config can be used to list/import/export/update items via the SyncEvolution command line. It cannot be used for synchronization because it does not defined what the items are supposed to be synchronized with. For synchronization, a second *sync config* is needed. This config has the same role as the traditional SyncML configs and is typically defined in the same implicit ``@default`` context as those configs. All configs in that context use the same local data. The sync config defines the database pairs and the sync mode (one-way, two-way, ...). The first step is to select a target config with ``syncURL=local://@``. Multiple sync configs can access the same target config. In the second step, the ``uri`` of each source in the sync config must be set to the name of the corresponding source in the target config. The ``sync`` property in the sync config defines the direction of the data flow. It can be set temporarily when starting a synchronzation with the sync config. **Warning:** because the client in the local sync starts the sync, ``preventSlowSync=0`` must be set in the target config to have an effect. CalDAV and CardDAV ================== This section explains how to use local syncing for CalDAV and CardDAV. Both protocols are based on WebDAV and are provided by the same backend. They share ``username/password/syncURL`` properties defined in their target config. The credentials must be provided if the server is password protected. The ``syncURL`` is optional if the ``username`` is an email address and the server supports auto-discovery of its CalDAV and/or CardDAV services (using DNS SRV entries, ``.well-known`` URIs, properties of the current principal, ...). Alternatively, credentials can also be set in the ``databaseUser`` and ``databasePassword`` properties of the source. The downside is that these values have to be set for each source and cannot be shared. The advantage is that, in combination with setting ``database``, such sources can be used as part of a normal SyncML server or client sync config. SyncEvolution then reads and writes data directly from the server and exchanges it via SyncML with the peer that is defined in the sync config. The ``database`` property of each source can be set to the URL of a specific *collection* (= database in WebDAV terminology). If not set, then the WebDAV backend first locates the server based on ``username`` or ``syncURL`` and then scans it for the default event resp. contact collection. This is done once in the initial synchronization. At the end of a successful synchroniation, the automatic choice is made permanent by setting the ``database`` property. **Warning:** the protocols do not uniquely identify this default collection. The backend tries to make an educated guess, but it might pick the wrong one if the server provides more than one address book or calendar. It is safer to scan for collections manually with ``--print-databases`` and then use the URL of the desired collection as value of ``database``. To scan for collections, use:: syncevolution --print-databases \ backend= \ username= \ "password=!@#ABcd1234" \ syncURL= Configuration templates for Google Calendar, Yahoo Calendar and a generic CalDAV/CardDAV server are included in SyncEvolution. The Yahoo template also contains an entry for contact synchronization, but using it is not recommended due to known server-side issues. The following commands set up synchronization with a generic WebDAV server that supports CalDAV, CardDAV and auto-discovery. For Google and Yahoo, replace ``webdav`` with ``google-calendar`` resp. ``yahoo`` and remove the ``addressbook`` source when setting up the sync config. :: # configure target config syncevolution --configure \ --template webdav \ username=123456@example.com \ "password=!@#ABcd1234" \ target-config@webdav # configure sync config syncevolution --configure \ --template SyncEvolution_Client \ syncURL=local://@webdav \ username= \ password= \ webdav \ calendar addressbook # initial slow sync syncevolution --sync slow webdav # incremental sync syncevolution webdav Here are some alternative ways of configuring the target config:: # A) Server has one URL as starting point instead of DNS auto-discovery. syncevolution --configure \ --template webdav \ username=123456 \ "password=!@#ABcd1234" \ syncURL=http://example.com \ target-config@webdav # B) Explicitly specify collections (from server documentation or --print-databases). # The 'calendar' and 'addressbook' names are the ones expected by the sync config # above, additional sources can also be configured and/or the names can be changed. syncevolution --configure \ username=123456 \ "password=!@#ABcd1234" \ addressbook/backend=carddav \ addressbook/database=http://example.com/addressbooks/123456/ \ calendar/backend=caldav \ calendar/database=http://example.com/calendar/123456/ \ target-config@webdav \ calendar addressbook Finally, here is how the ``@webdav`` context needs to be configured so that SyncML clients or servers can be added to it:: # configure sources syncevolution --configure \ databaseUser=123456 \ "databasePassword=!@#ABcd1234" \ addressbook/backend=carddav \ addressbook/database=http://example.com/addressbooks/123456/ \ calendar/backend=caldav \ calendar/database=http://example.com/calendar/123456/ \ @webdav \ calendar addressbook # configure one peer (Memotoo in this example): syncevolution --configure \ username=654321 \ password=^749@2524 \ memotoo@webdav # sync syncevolution --sync slow memotoo@webdav NOTES ===== Exchanging Data --------------- SyncEvolution transmits address book entries as vCard 2.1 or 3.0 depending on the sync format chosen in the configuration. Evolution uses 3.0 internally, so SyncEvolution converts between the two formats as needed. Calendar items and tasks can be sent and received in iCalendar 2.0 as well as vCalendar 1.0, but vCalendar 1.0 should be avoided if possible because it cannot represent all data that Evolution stores. .. note:: The Evolution backends are mentioned as examples; the same applies to other data sources. How the server stores the items depends on its implementation and configuration. To check which data is preserved, one can use this procedure (described for contacts, but works the same way for calendars and tasks): 1. synchronize the address book with the server 2. create a new address book in Evolution and view it in Evolution once (the second step is necessary in at least Evolution 2.0.4 to make the new address book usable in SyncEvolution) 3. add a configuration for that second address book and the same URI on the SyncML server, see EXAMPLES_ above 4. synchronize again, this time using the other data source Now one can either compare the address books in Evolution or do that automatically, described here for contacts: - save the complete address books: mark all entries, save as vCard - invoke `synccompare` with two file names as arguments and it will normalize and compare them automatically Normalizing is necessary because the order of cards and their properties as well as other minor formatting aspects may be different. The output comes from a side-by-side comparison, but is augmented by the script so that the context of each change is always the complete item that was modified. Lines or items following a ">" on the right side were added, those on the left side followed by a "<" were removed, and those with a "|" between text on the left and right side were modified. The automatic unit testing (see HACKING) contains a `testItems` test which verifies the copying of special entries using the same method. Modifying one of the address books or even both at the same time and then synchronizing back and forth can be used to verify that SyncEvolution works as expected. If you do not trust SyncEvolution or the server, then it is prudent to run these checks with a copy of the original address book. Make a backup of the .evolution/addressbook directory. Item Changes and Data Changes ----------------------------- SyncML clients and servers consider each entry in a database as one item. Items can be added, removed or updated. This is the item change information that client and server exchange during a normal, incremental synchronization. If an item is saved, removed locally, and reimported, then this is usually reported to a peer as "one item removed, one added" because the information available to SyncEvolution is not sufficient to determine that this is in fact the same item. One exception are iCalendar 2.0 items with their globally unique ID: the modification above will be reported to the server as "one item updated". That is better, but still not quite correct because the content of the item has not changed, only the meta information about it which is used to detect changes. This cannot be avoided without creating additional overhead for normal synchronizations. SyncEvolution reports *item changes* (the number of added, removed and updated items) as well as *data changes*. These data changes are calculated by comparing database dumps using the `synccompare` tool. Because this data comparison ignores information about which data belongs to which item, it is able to detect that re-adding an item that was removed earlier does not change the data, in contrast to the item changes. On the other hand, removing one item and adding a different one may look like updating just one item. Automatic Backups and Logging ----------------------------- To support recovery from a synchronization which damaged the local data or modified it in an unexpected way, SyncEvolution can create the following files during a synchronization: - a dump of the data in a format which can be restored by SyncEvolution, usually a single file per item containing in a standard text format (VCARD/VCALENDAR) - a full log file with debug information - another dump of the data after the synchronization for automatic comparison of the before/after state with `synccompare` If the sync configuration property ``logdir`` is set, then a new directory will be created for each synchronization in that directory, using the format `---
--[-]` with the various fields filled in with the time when the synchronization started. The sequence suffix will only be used when necessary to make the name unique. By default, SyncEvolution will never delete any data in that log directory unless explicitly asked to keep only a limited number of previous log directories. This is done by setting the ``maxlogdirs`` limit to something different than the empty string and 0. If a limit is set, then SyncEvolution will only keep that many log directories and start removing the "less interesting" ones when it reaches the limit. Less interesting are those where no data changed and no error occurred. To avoid writing any additional log file or database dumps during a synchronization, the ``logdir`` can be set to ``none``. To reduce the verbosity of the log, set ``loglevel``. If not set or 0, then the verbosity is set to 3 = DEBUG when writing to a log file and 2 = INFO when writing to the console directly. To debug issues involving data conversion, level 4 also dumps the content of items into the log. ENVIRONMENT =========== The following environment variables control where SyncEvolution finds files and other aspects of its operations. http_proxy Overrides the proxy settings temporarily. Setting it to an empty value disables the normal proxy settings. HOME/XDG_CACHE_HOME/XDG_CONFIG_HOME SyncEvolution follows the XDG_ desktop standard for its files. By default, `$HOME/.config/syncevolution` is the location for configuration files. `$HOME/.cache/syncevolution` holds session directories with log files and database dumps. .. _XDG: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html SYNCEVOLUTION_DEBUG Setting this to any value disables the filtering of stdout and stderr that SyncEvolution employs to keep noise from system libraries out of the command line output. SYNCEVOLUTION_GNUTLS_DEBUG Enables additional debugging output when using the libsoup HTTP transport library. SYNCEVOLUTION_DATA_DIR Overrides the default path to the bluetooth device lookup table, normally `/usr/lib/syncevolution/`. SYNCEVOLUTION_BACKEND_DIR Overrides the default path to plugins, normally `/usr/lib/syncevolution/backends`. SYNCEVOLUTION_LIBEXEC_DIR Overrides the path where additional helper executables are found, normally `/usr/libexec`. SYNCEVOLUTION_LOCALE_DIR Overrides the path to directories with the different translations, normally `/usr/share/locale`. SYNCEVOLUTION_TEMPLATE_DIR Overrides the default path to template files, normally `/usr/share/syncevolution/templates`. SYNCEVOLUTION_XML_CONFIG_DIR Overrides the default path to the Synthesis XML configuration files, normally `/usr/share/syncevolution/xml`. These files are merged into one configuration each time the Synthesis SyncML engine is started as part of a sync session. Note that in addition to this directory, SyncEvolution also always searches for configuration files inside `$HOME/.config/syncevolution-xml`. Files with the same relative path and name as in `/usr/share/syncevolution/xml` override those files, others extend the final configuration. BUGS ==== See `known issues`_ and the `support`_ web page for more information. .. _known issues: http://syncevolution.org/documentation/known-issues .. _support: http://syncevolution.org/support SEE ALSO ======== http://syncevolution.org AUTHORS ======= :Main developer: Patrick Ohly , http://www.estamos.de :Contributors: http://syncevolution.org/about/contributors :To contact the project publicly (preferred): syncevolution@syncevolution.org :Intel-internal team mailing list (confidential): syncevolution@lists.intel.com syncevolution_1.4/SyncEvolution.plist.in000066400000000000000000000035761230021373600206740ustar00rootroot00000000000000 bundleIdentifier de.estamos.iphone.SyncEvolution name SyncEvolution version __VERSION__ location http://www.estamos.de/iphone/zips/__FILENAME__ size __SIZE__ description A command line tool to synchronize the address book via SyncML. url http://www.estamos.de/projects/SyncML/ scripts install CopyPath usr/bin/syncevolution /usr/bin/syncevolution CopyPath usr/bin/synccompare /usr/bin/synccompare SetStatus Run 'syncevolution <server>' to synchronize. IfNot ExistsPath /var/root/.sync4j CopyPath usr/share/doc/syncevolution/ /var/root/.sync4j/evolution uninstall RemovePath /usr/bin/syncevolution RemovePath /usr/bin/synccompare syncevolution_1.4/autogen.sh000077500000000000000000000021011230021373600163510ustar00rootroot00000000000000#!/bin/sh set -e # wipe out temporary autotools files, necessary # when switching between distros and SyncEvolution releases rm -rf aclocal.m4 m4 autom4te.cache config.guess config.sub config.h.in configure depcomp install-sh ltmain.sh missing configure.in src/Makefile.am # intltoolize fails to copy its macros unless m4 exits mkdir m4 #env GEN_AUTOTOOLS_SET_VERSION=1 sh ./gen-autotools.sh glib-gettextize --force --copy intltoolize --force --copy --automake autoreconf -v -i -f -W all -W no-portability -W no-obsolete #libtoolize -c #glib-gettextize --force --copy #intltoolize --force --copy --automake #aclocal -I m4 -I m4-repo #autoheader #automake -a -c -Wno-portability #autoconf # This hack is required for the autotools on Debian Etch. # Without it, configure expects a po/Makefile where # only po/Makefile.in is available. This patch fixes # configure so that it uses po/Makefile.in, like more # recent macros do. #perl -pi -e 's;test ! -f "po/Makefile";test ! -f "po/Makefile.in";; s;mv "po/Makefile" "po/Makefile.tmp";cp "po/Makefile.in" "po/Makefile.tmp";;' configure syncevolution_1.4/autotroll.am000066400000000000000000000065401230021373600167270ustar00rootroot00000000000000# Makerules. # This file is part of AutoTroll. # Copyright (C) 2006, 2007, 2009, 2010 Benoit Sigoure. # # AutoTroll is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, # USA. # # In addition, as a special exception, the copyright holders of AutoTroll # give you unlimited permission to copy, distribute and modify the configure # scripts that are the output of Autoconf when processing the macros of # AutoTroll. You need not follow the terms of the GNU General Public License # when using or distributing such scripts, even though portions of the text of # AutoTroll appear in them. The GNU General Public License (GPL) does govern # all other use of the material that constitutes AutoTroll. # # This special exception to the GPL applies to versions of AutoTroll # released by the copyright holders of AutoTroll. Note that people who make # modified versions of AutoTroll are not obligated to grant this special # exception for their modified versions; it is their choice whether to do so. # The GNU General Public License gives permission to release a modified version # without this exception; this exception also makes it possible to release a # modified version which carries forward this exception. # ------------- # # DOCUMENTATION # # ------------- # # See autotroll.m4 :) # --- # # MOC # # --- # %.moc.cpp: %.hpp $(AM_V_GEN)$(MOC) $(QT_CPPFLAGS) $(EXTRA_CPPFLAGS) $< -o $@ %.moc.cpp: %.hh $(AM_V_GEN)$(MOC) $(QT_CPPFLAGS) $(EXTRA_CPPFLAGS) $< -o $@ %.moc.cpp: %.h $(AM_V_GEN)$(MOC) $(QT_CPPFLAGS) $(EXTRA_CPPFLAGS) $< -o $@ %moc.cc: %.hpp $(AM_V_GEN)$(MOC) $(QT_CPPFLAGS) $(EXTRA_CPPFLAGS) $< -o $@ %.moc.cc: %.hh $(AM_V_GEN)$(MOC) $(QT_CPPFLAGS) $(EXTRA_CPPFLAGS) $< -o $@ %.moc.cc: %.h $(AM_V_GEN)$(MOC) $(QT_CPPFLAGS) $(EXTRA_CPPFLAGS) $< -o $@ %.moc.cxx: %.hpp $(AM_V_GEN)$(MOC) $(QT_CPPFLAGS) $(EXTRA_CPPFLAGS) $< -o $@ %.moc.cxx: %.hh $(AM_V_GEN)$(MOC) $(QT_CPPFLAGS) $(EXTRA_CPPFLAGS) $< -o $@ %.moc.cxx: %.h $(AM_V_GEN)$(MOC) $(QT_CPPFLAGS) $(EXTRA_CPPFLAGS) $< -o $@ %.moc.C: %.hpp $(AM_V_GEN)$(MOC) $(QT_CPPFLAGS) $(EXTRA_CPPFLAGS) $< -o $@ %.moc.C: %.hh $(AM_V_GEN)$(MOC) $(QT_CPPFLAGS) $(EXTRA_CPPFLAGS) $< -o $@ %.moc.C: %.h $(AM_V_GEN)$(MOC) $(QT_CPPFLAGS) $(EXTRA_CPPFLAGS) $< -o $@ # --- # # UIC # # --- # %.ui.hpp: %.ui $(AM_V_GEN)$(UIC) $< -o $@ %.ui.hh: %.ui $(AM_V_GEN)$(UIC) $< -o $@ %.ui.h: %.ui $(AM_V_GEN)$(UIC) $< -o $@ # --- # # RCC # # --- # %.qrc.cpp: %.qrc $(AM_V_GEN)$(RCC) -name `echo "$<" | sed 's|^.*/\(.*\)\.qrc$$|\1|'` $< -o $@ %.qrc.cc: %.qrc $(AM_V_GEN)$(RCC) -name `echo "$<" | sed 's|^.*/\(.*\)\.qrc$$|\1|'` $< -o $@ %.qrc.cxx: %.qrc $(AM_V_GEN)$(RCC) -name `echo "$<" | sed 's|^.*/\(.*\)\.qrc$$|\1|'` $< -o $@ %.qrc.C: %.qrc $(AM_V_GEN)$(RCC) -name `echo "$<" | sed 's|^.*/\(.*\)\.qrc$$|\1|'` $< -o $@ syncevolution_1.4/build/000077500000000000000000000000001230021373600154555ustar00rootroot00000000000000syncevolution_1.4/build/build.am000066400000000000000000000006161230021373600170760ustar00rootroot00000000000000dist_noinst_SCRIPTS += build/gen-git-version.sh \ build/source2html.py EXTRA_DIST += \ build/export-foreign-git.sh \ build/export-gdbus.sh \ build/export-synthesis-xml.sh \ build/gen-backends-am.sh \ build/gen-backends.sh \ build/gen-changelog.pl \ build/gen-linguas.sh \ build/import-foreign-git.sh \ build/import-gdbus.sh \ build/import-synthesis-xml.sh \ build/update-copyrights.sh syncevolution_1.4/build/export-foreign-git.sh000077500000000000000000000027131230021373600215500ustar00rootroot00000000000000#! /bin/sh # # Exports changes made to files so that the upstream # maintainers can import the changes into their own # git repo. All changes which are not marked as # being from the remote git repo with a "commit ID" # comment are exported. # # Result are numbered .patch files, as with "git format-patch". # # Run this inside the top level of a clean # syncevolution git repository with the following # parameters: # - file system path for foreign git repository # - a remote directory into which the source file(s) are to be placed, # preserving all remaining directories after stripping # - common local directory to be stripped from source file(s) # - one or more source file names, with paths relative to the # local repository set -e set -x FOREIGN="$1" shift TARGET_DIR="$1" shift SOURCE_DIR="$1" shift SOURCE="$@" FOREIGN_NAME=`basename $FOREIGN` # iterate over commits involving the relevant files, # starting with oldest one counter=1 for commit in `(git log --format=format:%H $SOURCE; echo) | perl -e 'print reverse(<>)'`; do if git log -n 1 $commit | grep -q "$FOREIGN_NAME commit ID"; then # nothing to do, is in original git repo true else file=`printf %03d $counter`-`git log -n 1 --format=format:%f $commit`.patch counter=`expr $counter + 1` git log -n 1 -p --stat --format=email $commit $SOURCE | perl -p -e "s;$SOURCE_DIR;$TARGET_DIR;g;" -e "s/^index [0-9a-f]*\.\.[0-9a-f]* [0-9]*\n$//;" >$file fi done syncevolution_1.4/build/export-gdbus.sh000077500000000000000000000010571230021373600204420ustar00rootroot00000000000000#! /bin/sh # # Run this inside the top level of a clean # syncevolution git repository. Pass the path # to a gdbus repository (default: ../libgdbus). # # The script generates .patch files for all changes # made in the current branch to files which are # shared with gdbus. The resulting files can # be imported with "git am". set -e set -x `dirname $0`/export-foreign-git.sh "${1:-../libgdbus}" src src/gdbus \ src/gdbus/debug.c \ src/gdbus/debug.h \ src/gdbus/gdbus.h \ src/gdbus/mainloop.c \ src/gdbus/object.c \ src/gdbus/watch.c syncevolution_1.4/build/export-synthesis-xml.sh000077500000000000000000000013541230021373600221650ustar00rootroot00000000000000#! /bin/sh # # Run this inside the top level of a clean # syncevolution git repository. Pass the path # to a gdbus repository (default: ../libsynthesis). # # The script generates .patch files for all changes # made in the current branch to files which are # shared with gdbus. The resulting files can # be imported with "git am". set -e set -x path="${1:-../libsynthesis}" files="`((cd $path/src/sysync_SDK && find configs \( -name '*.xml' -o -name 'update-samples.pl' -o -name README \) -a \! \( -name 'sync*_sample_config.xml' -o -name sunbird_client.xml \)) && (cd src/syncevo && find configs -name '*.xml' -o name README)) | sort -u | sed -e 's;^;src/syncevo/;'`" `dirname $0`/export-foreign-git.sh "$path" src/sysync_SDK src/syncevo $files syncevolution_1.4/build/gen-backends-am.sh000077500000000000000000000015241230021373600207320ustar00rootroot00000000000000#!/bin/sh amfile='src/backends/backends.am' tmpfile="$amfile.$$" rm -f "$tmpfile" touch "$tmpfile" BACKENDS="`echo src/backends/*/configure-sub.in | sed -e 's%/configure-sub\.in%%g' | sort`" BACKEND_REGISTRIES="`echo src/backends/*/*Register.cpp | sort`" tf() { echo "$1" >>"$tmpfile" } # tf '# This is a stupid workaround for an absolute path in SYNCEVOLUTION_LIBS.' # tf '# See AUTOTOOLS-TODO for details.' # tf '@SYNCEVOLUTION_LIBS@: src/syncevo/libsyncevolution.la ; @true' # tf '' tf "BACKENDS = $BACKENDS" tf '' tf "BACKEND_REGISTRIES = $BACKEND_REGISTRIES" tf '' tf '# backend includes' for backend in $BACKENDS do name=`echo "$backend" | sed -e 's%src/backends/%%'` tf "include \$(top_srcdir)/$backend/$name.am" done if test ! -f "$amfile" || ! cmp -s "$amfile" "$tmpfile" then mv "$tmpfile" "$amfile" else rm -f "$tmpfile" fi syncevolution_1.4/build/gen-backends.sh000077500000000000000000000013341230021373600203360ustar00rootroot00000000000000#! /bin/sh # # This script prints all configure-sub.in in src/backends # directory to standard output. This is meant to be used # from m4_esyscmd inside configure.ac. # # The motivation for this non-standard approach was that # it allows adding new backends without touching core # files, which should have simplified the development of # out-of-tree backends. Now git pretty much removes # the need for such tricks, but it's still around. tmpfile="configure.in.$$" rm -f "$tmpfile" for sub in src/backends/*/configure-sub.in do echo "# vvvvvvvvvvvvvv $sub vvvvvvvvvvvvvv" >>"$tmpfile" cat "$sub" >>"$tmpfile" echo "# ^^^^^^^^^^^^^^ $sub ^^^^^^^^^^^^^^" >>"$tmpfile" echo >>"$tmpfile" done cat "$tmpfile" rm -f "$tmpfile" syncevolution_1.4/build/gen-changelog.pl000077500000000000000000000057761230021373600205320ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Text::Wrap; use Pod::Usage; use Getopt::Long; use POSIX qw( strftime ); $Text::Wrap::columns = 74; # git commands our $GIT_LOG = 'git log --pretty=format:"%at|%an|<%ae>|%h|%s"'; our $GIT_DIFF_TREE = 'git diff-tree --name-only -r'; my $help; my $result = GetOptions( "h|help" => \$help, ); pod2usage(1) if $help; our $revs = $ARGV[0] or undef; my $log_cmd = $GIT_LOG; $log_cmd .= ' ' . $revs if defined $revs; open my $git_log, '-|', $log_cmd or die("Unable to invoke git-log: $!\n"); while (<$git_log>) { my $log_line = $_; chomp($log_line); my ($timestamp, $committer, $email, $commit_hash, $subject) = split /\|/, $log_line, 5; # use a shorter date line my $date = strftime("%Y-%m-%d", localtime($timestamp)); print STDOUT $date, " ", $committer, " ", $email, "\n\n"; # list the file changes if ($commit_hash) { my $diff_cmd = $GIT_DIFF_TREE . " " . $commit_hash; open my $git_diff, '-|', $diff_cmd or die("Unable to invoke git-diff-tree: $!\n"); while (<$git_diff>) { my $diff_line = $_; chomp($diff_line); next if $diff_line =~ /^$commit_hash/; print STDOUT "\t* ", $diff_line, ":\n"; } close($git_diff); } else { print STDOUT "\t* *:\n"; } print STDOUT "\n"; # no need to use the full body, the subject will do if (defined $subject) { $subject =~ s/\t//g; print STDOUT wrap("\t", "\t", $subject), "\n"; } print STDOUT "\n"; } close($git_log); 0; __END__ =pod =head1 NAME gen-changelog - Creates a ChangeLog from a git log =head1 SYNOPSIS gen-changelog =head1 DESCRIPTION B is a small Perl script that reads the output of git log and creates a file using the GNU ChangeLog format. It should be used when creating a tarball of a project, to provide a full log of the changes to the users. =head1 OPTIONS =over 4 =item -h, --help Prints a brief help message =item EsinceE..EuntilE Show only commits between the named two commits. When either EsinceE or EuntilE is omitted, it defaults to `HEAD`, i.e. the tip of the current branch. For a more complete list of ways to spell EsinceE and EuntilE, see "SPECIFYING REVISIONS" section in git rev-parse. =back =head1 CAVEATS B is very simple and should be tweaked to fit your use case. It does fit the author's, but he'll gladly accept patches and requests. =head1 EXAMPLES =over 4 =item Print the full log and redirect it to a file gen-changelog > ChangeLog =item Print the changelog of the local changes gen-changelog origin..HEAD =back =head1 AUTHOR Emmanuele Bassi Eebassi (at) gnome.orgE =head1 COPYRIGHT AND LICENSE Copyright (C) 2009 Emmanuele Bassi This program is free software. It can be distributed and/or modified under the terms of Perl itself. See L for further details. =cut syncevolution_1.4/build/gen-git-version.sh000077500000000000000000000061551230021373600210400ustar00rootroot00000000000000#! /bin/sh # This scripts takes current version as a parameter, generates the version # and prints it to standard output - this way it is usable with m4_esyscmd, # which can be used inside AC_INIT. The script will print the passed version if # the following checks pass: # - SyncEvolution source is clean (git status reports # no "modified" files or "untracked" files, or the source # is not in git at all) # - the source is tagged with the version of SyncEvolution # (git describe --tags HEAD reports something which matches, # for example syncevolution-1-0-beta-2a for 1.0beta2a) # - same for libsynthesis, if the SYNTHESISSRC env variable # is set # # If these tests fail, the version is extended: # ++SE++SYSYNC+ # = date # = [+unclean] # = shortened hash from describe (for example, 1040ffd) # +unclean = source was dirty set -e version="$1" checksource () { dir=$1 force=$2 dirty= if [ ! -d $dir/.git ]; then return fi cur=`pwd` cd $dir if git status | grep -e "modified:" -e "Untracked files:" -q; then dirty='+unclean' fi describe=`git describe --tags` hash=`cat .git/HEAD | sed -e 's/ref: //'` if [ "`echo $hash | sed -e 's/[0-9a-fA-F]//g'`" ] ; then # contains other characters than simple hex, probably a reference: # convert to abbreviated hash hash=`git show-ref --abbrev --hash --verify $hash` else # already a hash, abbreviate hash=`echo $hash | sed -e 's/\(......\).*/\1/'` fi if git show-ref --tags | grep -q $hash; then # there is at least one tag matching HEAD; # pick the most recent one (based on lexical sorting) exact=1 tag=`git show-ref --tags | grep $hash | sort | tail -1 | sed -e 's;.*refs/tags/;;'` else # Detect --g suffix added when tag is older than HEAD. # Remove suffix to get tag (doesn't matter if we do not pick # the most recent one). exact= tag=`echo $describe | sed -e 's/-[0123456789]*-g.*//'` fi simpletag=$tag # Hyphens between numbers in the tag are dots in the version # and all other hyphens can be removed. while true; do tmp=`echo $simpletag | sed -e 's/\([0123456789]\)-\([0123456789]\)/\1.\2/'` if [ $tmp = $simpletag ]; then break else simpletag=$tmp fi done simpletag=`echo $simpletag | sed -e 's/-//g'` if [ "$dirty" ] || [ "$force" ]; then # previous check failed, always print hash echo $hash$dirty elif [ "$exact" ] && echo $simpletag | grep -q "syncevolution${version}\$"; then true else echo $hash$dirty fi cd $cur } versionsuffix='' syncevo=`checksource .` if [ "$SYNTHESISSRC" ]; then sysync=`checksource $SYNTHESISSRC $syncevo` fi # run check again, to get hash when only libsynthesis failed syncevo=`checksource . $sysync` if [ "$syncevo" ]; then versionsuffix="+SE+$syncevo" fi if [ "$sysync" ]; then versionsuffix="$versionsuffix+SYSYNC+$sysync" fi if [ "$versionsuffix" ]; then versionsuffix="+`date +%Y%m%d`$versionsuffix" fi # using printf, because echo -n is not portable. hopefully printf is. printf %s "$version$versionsuffix" syncevolution_1.4/build/gen-linguas.sh000077500000000000000000000004271230021373600202300ustar00rootroot00000000000000#!/bin/sh -e lings_new="LINGUAS.new.$$" lings="LINGUAS" # create LINGUAS file: every .po is included cd po ls -1 *.po | sort -u | sed -e 's/.po$//' >"$lings_new" if test -f "$lings" && cmp -s "$lings" "$lings_new" then rm "$lings_new" else mv "$lings_new" "$lings" fi syncevolution_1.4/build/import-foreign-git.sh000077500000000000000000000054441230021373600215450ustar00rootroot00000000000000#! /bin/sh # # Run this inside the top level of a clean # syncevolution git repository with the following # parameters: # - file system path for foreign git repository # - name of local branch for importing changes # - a local directory into which the source file(s) is to be placed, # preserving all remaining directories after stripping # - number of directory levels to strip from source file(s) # - one or more source file names, with paths relative to the # foreign repository set -e set -x FOREIGN="$1" shift TARGET_BRANCH="$1" shift TARGET_DIR="$1" shift SOURCE_LEVELS="$1" shift SOURCE="$@" FOREIGN_NAME=`basename $FOREIGN` TARGET=`for i in $SOURCE; do echo $i | perl -p -e "s;([^/]*/){$SOURCE_LEVELS};$TARGET_DIR/;"; done` PATCH=`mktemp` MSG=`mktemp` git checkout $TARGET_BRANCH # find lastest imported commit: # import everything unless one of the files already exists, # in which case we assume that all of the others also exist revisions=master for i in $TARGET; do if [ -f $i ]; then revisions="`git log -n 1 -- $TARGET | tail -1`..master" break fi done count=`(cd "$FOREIGN" && git log -p $revisions -- $SOURCE) | grep '^commit' | wc -l` # iterate over all commits from oldest to newest i=1 while [ $i -le $count ]; do # get complete patch (cd "$FOREIGN" && git log -p --max-count=1 --skip=`expr $count - $i` $revisions -- $SOURCE) >$PATCH # get just the commit message (cd "$FOREIGN" && git log --max-count=1 --skip=`expr $count - $i` $revisions -- $SOURCE) >$MSG # apply patch to file: enter directory and skip pathname from patch if ! (cd $TARGET_DIR && patch -p`expr $SOURCE_LEVELS + 1` <$PATCH); then echo "patch failed in $TARGET_DIR: patch -p`expr $SOURCE_LEVELS + 1` <$PATCH" echo "continue? yes/no [no]" read yesno if [ "$yesno" != "yes" ]; then exit 1 fi fi # now commit it (can't use commit because we want to preserve date): # - add to index for t in $TARGET; do [ -f $t ] && git add $t done # - write index id=`git write-tree` # - find information for commit and commit parent=`git show-ref --heads --hash $TARGET_BRANCH` origid=`grep ^commit $MSG | sed -e 's/commit //'` GIT_AUTHOR_NAME="`grep ^Author: $MSG | sed -e 's/Author: \(.*\) <.*/\1/'`" GIT_AUTHOR_EMAIL="`grep ^Author: $MSG | sed -e 's/Author: [^<]*<\([^>]*\)>/\1/'`" GIT_AUTHOR_DATE="`grep ^Date: $MSG | sed -e 's/Date: *//'`" export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE id=`(grep '^ ' $MSG | sed -e 's/^ *//' && echo && echo "$FOREIGN_NAME commit ID:" && echo $origid) | git commit-tree $id -p $parent` # - update branch and check it out git update-ref refs/heads/$TARGET_BRANCH $id git reset --hard $TARGET_BRANCH # next patch i=`expr $i + 1` done syncevolution_1.4/build/import-gdbus.sh000077500000000000000000000012221230021373600204250ustar00rootroot00000000000000#! /bin/sh # # Run this inside the top level of a clean # syncevolution git repository. Pass the path # to a gdbus repository (default: ../libgdbus). # # The script switches to the "gdbus" branch # in the syncevolution repo and then merges all # patches committed to the "master" branch in the # gdbus repo, updating the "gdbus" branch # as it goes along. # # The original commit IDs are recorded # at the end of each commit message. set -e set -x `dirname $0`/import-foreign-git.sh "${1:-../libgdbus}" gdbus src/gdbus 1 \ src/debug.c \ src/debug.h \ src/gdbus.h \ src/mainloop.c \ src/Makefile.am \ src/object.c \ src/watch.c syncevolution_1.4/build/import-synthesis-xml.sh000077500000000000000000000014571230021373600221620ustar00rootroot00000000000000#! /bin/sh # # Run this inside the top level of a clean # syncevolution git repository. Pass the path # to a synthesis repository (default: ../libsynthesis). # # The script switches to the "synthesis-xml-fragments" branch # in the syncevolution repo and then merges all # patches committed to the "master" branch in the # synthesis repo, updating the "synthesis" branch # as it goes along. # # The original commit IDs are recorded # at the end of each commit message. set -e set -x path="${1:-../libsynthesis}" files="`cd $path && find src/sysync_SDK/configs/ \( -name '*.xml' -o -name 'update-samples.pl' -o -name README \) -a \! \( -name 'sync*_sample_config.xml' -o -name sunbird_client.xml \)`" `dirname $0`/import-foreign-git.sh "${1:-../libsynthesis}" synthesis-xml-fragments src/syncevo/configs 3 $files syncevolution_1.4/build/source2html.py000066400000000000000000000036541230021373600203060ustar00rootroot00000000000000#!/usr/bin/python """ Converts source code (first parameter, can be - for stdin) to HTML (stdout), using pygments if installed, otherwise simple text manipulation without syntax highlighting. In both cases the output will have "True-" as anchors for each line. """ import sys filename = sys.argv[1] if filename == '-': code = sys.stdin.read() else: code = open(filename).read() out = sys.stdout try: import pygments import pygments.lexers from pygments.formatters import HtmlFormatter if filename == '-': lexer = pygments.lexers.guess_lexer(code) else: lexer = pygments.lexers.guess_lexer_for_filename(filename, code) formatter = HtmlFormatter(full=True, linenos=True, lineanchors=True) pygments.highlight(code, lexer, formatter, out) except: import cgi print >>sys.stderr, "source2html.py failed with pygments:", sys.exc_info() print >>sys.stderr, "falling back to internal code" out.write('''
''')
    lines = code.split('\n')
    for line in range(1, len(lines)):
        out.write('%4d\n' % line)
    out.write('%4d' % len(lines))
    out.write('
')
    for lineno, line in enumerate(lines):
        out.write('%s\n' % (lineno + 1, cgi.escape(line)))
    out.write('''
''') syncevolution_1.4/build/update-copyrights.sh000077500000000000000000000047771230021373600215060ustar00rootroot00000000000000#! /bin/sh # # Usage: update-copyrights.sh "author" # # 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) version 3. # # 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 Street, Fifth Floor, Boston, MA # 02110-1301 USA currentyear=`date +%Y` oldyear=`expr $currentyear - 1` export currentyear oldyear author addcopyright () { file="$1" author="$2" if grep -w "Copyright.*$currentyear.*$author" $file >/dev/null; then # done true elif grep -w "Copyright.*-$oldyear $author" $file >/dev/null; then # replace end year perl -pi -e 's/-$ENV{oldyear} $ENV{author}/-$ENV{currentyear} $ENV{author}/' $file echo updated: $author: $file elif grep -w "Copyright.*$oldyear $author" $file >/dev/null; then # add consecutive year perl -pi -e 's/$ENV{oldyear} $ENV{author}/$ENV{oldyear}-$ENV{currentyear} $ENV{author}/' $file echo updated: $author: $file elif grep -w "Copyright.*$author" $file >/dev/null; then # add separate year perl -pi -e 's/(Copyright.*) $ENV{author}/$1, $ENV{currentyear} $ENV{author}/' $file echo updated: $author: $file elif grep -w "Copyright" $file >/dev/null; then # add new line after the last copyright line # -i doesn't work with reading all lines? perl -e '$_ = join ("", <>); s/(.*)((^[ *#]*Copyright)[^\n]*)/$1$2\n$3 (C) $ENV{currentyear} $ENV{author}/ms; print;' \ $file >$file.bak && mv $file.bak $file && echo added: $author: $file || rm $file.bak && echo no copyright: $author: $file else echo skipped: $author: $file fi } for file in `git ls-files "$@"`; do git log --since=2009-01-01 --pretty='format:%ai: %an <%ae>' $file | grep ^$currentyear | sed -e 's/.*: //' | sort -u | while read author; do case $author in *intel.com*) author="Intel Corporation" esac addcopyright "$file" "$author" done done syncevolution_1.4/build/xsl-update.sh000077500000000000000000000006421230021373600201040ustar00rootroot00000000000000#! /bin/sh # # Download current version of all our docbook XSL files. # Handles download errors by retrying. Does not handle # new or removed files, that needs to be done manually. set -x cd `dirname $0`/xsl for i in `find * -type f`; do for attempt in `seq 0 10`; do if wget -O $i.tmp http://docbook.sourceforge.net/release/xsl/current/$i && mv $i.tmp $i; then break fi done done syncevolution_1.4/build/xsl/000077500000000000000000000000001230021373600162635ustar00rootroot00000000000000syncevolution_1.4/build/xsl/COPYING000066400000000000000000000037171230021373600173260ustar00rootroot00000000000000Copyright --------- Copyright (C) 1999-2007 Norman Walsh Copyright (C) 2003 Jiří Kosek Copyright (C) 2004-2007 Steve Ball Copyright (C) 2005-2008 The DocBook Project Copyright (C) 2011-2012 O'Reilly Media Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ``Software''), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Except as contained in this notice, the names of individuals credited with contribution to this software shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from the individuals in question. Any stylesheet derived from this Software that is publically distributed will be identified with a different name and the version strings in any derived Software will be changed so that no possibility of confusion between the derived package and this Software will exist. Warranty -------- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL NORMAN WALSH OR ANY OTHER CONTRIBUTOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Contacting the Author --------------------- The DocBook XSL stylesheets are maintained by Norman Walsh, , and members of the DocBook Project, syncevolution_1.4/build/xsl/README000066400000000000000000000163121230021373600171460ustar00rootroot00000000000000---------------------------------------------------------------------- README file for the DocBook XSL Stylesheets ---------------------------------------------------------------------- $Id: README 9397 2012-06-02 22:35:07Z bobstayton $ These are XSL stylesheets for transforming DocBook XML document instances into various output formats. This README file provides only very minimal documentation on using the stylesheets. For more complete information, see Bob Stayton's book "DocBook XSL: The Complete Guide", available online at: http://www.sagehill.net/docbookxsl/ ---------------------------------------------------------------------- Installation ---------------------------------------------------------------------- See the INSTALL file for information about installing this release. ---------------------------------------------------------------------- How to use the stylesheets ---------------------------------------------------------------------- The base canonical URI for these stylesheets is: http://docbook.sourceforge.net/release/xsl/current/ You call any of the stylesheets in this distribution by doing one of the following: - Use the base canonical URI in combination with one of the pathnames below. For example, for "chunked" HTML, output: http://docbook.sourceforge.net/release/xsl/current/html/chunk.xsl If your system has a working XML Catalog or SGML Catalog setup (most Linux systems do), then that URI will automatically be resolved and replaced with a local pathname on your system. - Use a "real" local system base path in combination with one of the pathnames below. For example, for "chunked" HTML, output: /usr/share/xml/docbook/stylesheet/nwalsh/html/chunk.xsl To transform documents created with the standard DocBook schema/DTD, use one of the following stylesheets: fo/docbook.xsl - for XSL-FO html/docbook.xsl - for HTML (as a single file) html/chunk.xsl - for HTML (chunked into multiple files) html/onechunk.xsl - for HTML (chunked output in single file) xhtml/*.xsl - for XHTML versions of the above xhtml-1_1/*.xsl - for XHTML 1.1 versions of the above xhtml5/*.xsl - for XHTML5 versions of the above epub/docbook.xsl - for .epub version 2 and earlier epub3/docbook.xsl - for .epub version 3 and later htmlhelp/htmlhelp.xsl - for HTML Help javahelp/javahelp.xsl - for JavaHelp eclipse/eclipse.xsl - for Eclipse Help manpages/docbook.xsl - for groff/nroff man pages */profile-* - single-pass-profiling versions of all above roundtrip/*.xsl - for DocBook to WordML, etc., to DocBook assembly/assemble.xsl - converts an assembly into a DocBook document assembly/topic-maker-chunk.xsl - converts a DocBook document into an assembly with topic files. To transform documents created with the DocBook Slides schema/DTD, use one of the following stylesheets: slides/html/*.xsl - for HTML slides of various kinds slides/xhtml/*.xsl - for XHTML slides of various kinds slides/fo/plain.xsl - for XSL-FO slides slides/htmlhelp/... - for HTML Help slides To transform documents created with the DocBook Website schema/DTD, use one of the following stylesheets: website/website.xsl - for non-tabular, non-chunked output website/tabular.xsl - for tabular, non-chunked output website/chunk-* - for chunked output To generate a titlepage customization layer from a titlepage spec: template/titlepage.xsl For fo titlepage customizations, set the stylesheet parameter named 'ns' to 'http://www.w3.org/1999/XSL/Format' when using this stylesheet. For xhtml titlepage customizations, set the stylesheet parameter named 'ns' to 'http://www.w3.org/1999/xhtml' when using this stylesheet. For details about creating titlepage spec files and generating and using titlepage customization layers, see "DocBook XSL: The Complete Guide" ---------------------------------------------------------------------- Manifest ---------------------------------------------------------------------- AUTHORS contact information BUGS about known problems COPYING copyright information INSTALL installation instructions README this file RELEASE.* per-release cumulative summaries of user-visible changes TODO about planned features not yet implemented VERSION release metadata, including the current version number (note that the VERSION file is an XSL stylesheet) NEWS changes since the last public release (for a cumulative list of changes, see the ChangeHistory.xml file) assembly/ for making and processing DocBook assemblies. common/ code used among several output formats (HTML, FO, manpages,...) docsrc/ documentation sources eclipse/ for producing Eclipse Help epub/ for producing .epub version 2. epub3/ for producing .epub version 3 and beyond. extensions/ DocBook XSL Java extensions fo/ for producing XSL-FO highlighting files used for adding source-code syntax highlighting in output html/ for producing HTML htmlhelp/ for producing HTML Help images/ images used in callouts and graphical admonitions javahelp/ for producing Java Help lib/ utility stylesheets with schema-independent functions manpages/ for producing groff/troff man pages profiling/ for profiling (omitting/including conditional text) roundtrip/ for "round trip" conversion among DocBook and various word-processor formats (WordML, etc.) slides/ for producing slides output (from Slides source) template/ templates for building stylesheet customization layers tools/ assorted supplementary tools website/ for producing website output (from Website source) xhtml/ for producing XHTML xhtml-1_1/ for producing (stricter) XHTML 1.1 xhtml5/ for producing XHTML5 ---------------------------------------------------------------------- Changes ---------------------------------------------------------------------- See the NEWS file for changes made since the previous release. See the RELEASE-NOTES.html or RELEASE-NOTES.txt or RELEASE-NOTES.pdf files for per-release cumulative summaries of significant user-visible changes. For online access to a hyperlinked view of all changes made over the entire history of the codebase, see the following: http://docbook.svn.sourceforge.net/viewvc/docbook/trunk/xsl/?view=log WARNING: That above change history is a very long list and may take a long time to load/download. You can also create an XML-formatted "ChangeHistory.xml" copy of the complete change history for the codebase by running the following commands: svn checkout https://docbook.svn.sf.net/svnroot/docbook/trunk/xsl svn log --xml --verbose xsl > ChangeHistory.xml ---------------------------------------------------------------------- Copyright information ---------------------------------------------------------------------- See the accompanying file named COPYING. syncevolution_1.4/build/xsl/VERSION.xsl000066400000000000000000000106571230021373600201510ustar00rootroot00000000000000 docbook-xsl 1.77.0 9371 $Revision: 9399 $ $URL: https://docbook.svn.sourceforge.net/svnroot/docbook/trunk/xsl/VERSION $ DocBook XSL Stylesheets 1.77.1 * Major feature enhancements http://sourceforge.net/projects/docbook/ http://prdownloads.sourceforge.net/docbook/{DISTRONAME-VERSION}.tar.gz?download http://prdownloads.sourceforge.net/docbook/{DISTRONAME-VERSION}.zip?download http://prdownloads.sourceforge.net/docbook/{DISTRONAME-VERSION}.bz2?download http://sourceforge.net/project/shownotes.php?release_id={SFRELID} http://docbook.svn.sourceforge.net/viewvc/docbook/ http://lists.oasis-open.org/archives/docbook-apps/ This is a release with bugfixes and some enhancements. You must specify the sf-relid as a parameter. : : : syncevolution_1.4/build/xsl/common/000077500000000000000000000000001230021373600175535ustar00rootroot00000000000000syncevolution_1.4/build/xsl/common/common.xsl000066400000000000000000002271111230021373600215770ustar00rootroot00000000000000 ]> Common » Base Template Reference $Id: common.xsl 9347 2012-05-11 03:49:49Z bobstayton $ Introduction This is technical reference documentation for the “base” set of common templates in the DocBook XSL Stylesheets. This is not intended to be user documentation. It is provided for developers writing customization layers for the stylesheets. Tests if a given node is a component-level element This template returns '1' if the specified node is a component (Chapter, Appendix, etc.), and '0' otherwise. node The node which is to be tested. This template returns '1' if the specified node is a component (Chapter, Appendix, etc.), and '0' otherwise. 1 0 Tests if a given node is a section-level element This template returns '1' if the specified node is a section (Section, Sect1, Sect2, etc.), and '0' otherwise. node The node which is to be tested. This template returns '1' if the specified node is a section (Section, Sect1, Sect2, etc.), and '0' otherwise. 1 0 Returns the hierarchical level of a section This template calculates the hierarchical level of a section. The element sect1 is at level 1, sect2 is at level 2, etc. Recursive sections are calculated down to the fifth level. node The section node for which the level should be calculated. Defaults to the context node. The section level, 1, 2, etc. 1 2 3 4 5 6 5 4 3 2 1 2 3 4 5 5 5 4 3 2 1 1 Returns the hierarchical level of a QandASet This template calculates the hierarchical level of a QandASet. The level, 1, 2, etc. 1 1 1 2 3 5 4 3 2 1 1 question answer qandadiv qandaset id- [FAMILY Given] , , [ ] { } [ ] ... | 4pi Selects and processes an appropriate media object from a list This template takes a list of media objects (usually the children of a mediaobject or inlinemediaobject) and processes the "right" object. This template relies on a template named "select.mediaobject.index" to determine which object in the list is appropriate. If no acceptable object is located, nothing happens. olist The node list of potential objects to examine. Calls <xsl:apply-templates> on the selected object. Selects the position of the appropriate media object from a list This template takes a list of media objects (usually the children of a mediaobject or inlinemediaobject) and determines the "right" object. It returns the position of that object to be used by the calling template. If the parameter use.role.for.mediaobject is nonzero, then it first checks for an object with a role attribute of the appropriate value. It takes the first of those. Otherwise, it takes the first acceptable object through a recursive pass through the list. This template relies on a template named "is.acceptable.mediaobject" to determine if a given object is an acceptable graphic. The semantics of media objects is that the first acceptable graphic should be used. If no acceptable object is located, no index is returned. olist The node list of potential objects to examine. count The position in the list currently being considered by the recursive process. Returns the position in the original list of the selected object. 1 1 1 0 1 0 0 1 0 1 Returns '1' if the specified media object is recognized This template examines a media object and returns '1' if the object is recognized as a graphic. object The media object to consider. 0 or 1 0 1 1 1 0 . . Warn users about references to non-unique IDs If passed an ID in linkend, check.id.unique prints a warning message to the user if either the ID does not exist or the ID is not unique. Error: no ID for constraint linkend: . Warning: multiple "IDs" for constraint linkend: . Warn users about incorrectly typed references If passed an ID in linkend, check.idref.targets makes sure that the element pointed to by the link is one of the elements listed in element-list and warns the user otherwise. Error: linkend ( ) points to " " not (one of): Unexpected context in procedure.step.numeration: 1 2 loweralpha lowerroman upperalpha upperroman arabic arabic 1. a. i. A. I. Unexpected numeration: circle square disc Print a set of years with collapsed ranges This template prints a list of year elements with consecutive years printed as a range. In other words: 1992 1993 1994]]> is printed 1992-1994, whereas: 1992 1994]]> is printed 1992, 1994. This template assumes that all the year elements contain only decimal year numbers, that the elements are sorted in increasing numerical order, that there are no duplicates, and that all the years are expressed in full century+year (1999 not 99) notation. years The initial set of year elements. print.ranges If non-zero, multi-year ranges are collapsed. If zero, all years are printed discretely. single.year.ranges If non-zero, two consecutive years will be printed as a range, otherwise, they will be printed discretely. In other words, a single year range is 1991-1992 but discretely it's 1991, 1992. This template returns the formatted list of years. , , - , , , , - , Search in a table for the "best" match for the node This template searches in a table for the value that most-closely (in the typical best-match sense of XSLT) matches the current (element) node location. / Converts a string to all uppercase letters Given a string, this template does a language-aware conversion of that string to all uppercase letters, based on the values of the lowercase.alpha and uppercase.alpha gentext keys for the current locale. It affects only those characters found in the values of lowercase.alpha and uppercase.alpha. All other characters are left unchanged. string The string to convert to uppercase. Converts a string to all lowercase letters Given a string, this template does a language-aware conversion of that string to all lowercase letters, based on the values of the uppercase.alpha and lowercase.alpha gentext keys for the current locale. It affects only those characters found in the values of uppercase.alpha and lowercase.alpha. All other characters are left unchanged. string The string to convert to lowercase. Returns localized choice separator This template enables auto-generation of an appropriate localized "choice" separator (for example, "and" or "or") before the final item in an inline list (though it could also be useful for generating choice separators for non-inline lists). It currently works by evaluating a processing instruction (PI) of the form <?dbchoice choice="foo"?> : if the value of the choice pseudo-attribute is "and" or "or", returns a localized "and" or "or" otherwise returns the literal value of the choice pseudo-attribute The latter is provided only as a temporary workaround because the locale files do not currently have translations for the word or. So if you want to generate a a logical "or" separator in French (for example), you currently need to do this: <?dbchoice choice="ou"?> The dbchoice processing instruction is an unfortunate hack; support for it may disappear in the future (particularly if and when a more appropriate means for marking up "choice" lists becomes available in DocBook). Evaluates an info profile This template evaluates an "info profile" matching the XPath expression given by the profile parameter. It relies on the XSLT evaluate() extension function. The value of the profile parameter can include the literal string $info. If found in the value of the profile parameter, the literal string $info string is replaced with the value of the info parameter, which should be a set of *info nodes; the expression is then evaluated using the XSLT evaluate() extension function. profile A string representing an XPath expression info A set of *info nodes Returns a node (the result of evaluating the profile parameter) Error: The "info profiling" mechanism currently requires an XSLT engine that supports the evaluate() XSLT extension function. Your XSLT engine does not support it. Returns mimetype for media format This takes as input a 'format' param and returns a mimetype string. It uses an xsl:choose after first converting the input to all uppercase. application/postscript application/pdf image/png image/svg+xml image/jpeg image/jpeg image/gif image/gif image/gif audio/acc audio/mpeg audio/mpeg audio/mpeg audio/mpeg audio/mp4 audio/mpeg audio/wav video/mp4 video/mp4 video/ogg video/ogg video/webm syncevolution_1.4/build/xsl/common/en.xml000066400000000000000000001251611230021373600207050ustar00rootroot00000000000000 Symbols A a À à Á á Â â Ã ã Ä ä Å å Ā ā Ă ă Ą ą Ǎ ǎ Ǟ ǟ Ǡ ǡ Ǻ ǻ Ȁ ȁ Ȃ ȃ Ȧ ȧ B b ƀ Ɓ ɓ Ƃ ƃ C c Ç ç Ć ć Ĉ ĉ Ċ ċ Č č Ƈ ƈ ɕ D d Ď ď Đ đ Ɗ ɗ Ƌ ƌ Dž Dz ȡ ɖ E e È è É é Ê ê Ë ë Ē ē Ĕ ĕ Ė ė Ę ę Ě ě Ȅ ȅ Ȇ ȇ Ȩ ȩ ế F f Ƒ ƒ G g Ĝ ĝ Ğ ğ Ġ ġ Ģ ģ Ɠ ɠ Ǥ ǥ Ǧ ǧ Ǵ ǵ H h Ĥ ĥ Ħ ħ Ȟ ȟ ɦ I i Ì ì Í í Î î Ï ï Ĩ ĩ Ī ī Ĭ ĭ Į į İ Ɨ ɨ Ǐ ǐ Ȉ ȉ Ȋ ȋ J j Ĵ ĵ ǰ ʝ K k Ķ ķ Ƙ ƙ Ǩ ǩ L l Ĺ ĺ Ļ ļ Ľ ľ Ŀ ŀ Ł ł ƚ Lj ȴ ɫ ɬ ɭ M m ɱ ḿ N n Ñ ñ Ń ń Ņ ņ Ň ň Ɲ ɲ ƞ Ƞ Nj Ǹ ǹ ȵ ɳ O o Ò ò Ó ó Ô ô Õ õ Ö ö Ø ø Ō ō Ŏ ŏ Ő ő Ɵ Ơ ơ Ǒ ǒ Ǫ ǫ Ǭ ǭ Ǿ ǿ Ȍ ȍ Ȏ ȏ Ȫ ȫ Ȭ ȭ Ȯ ȯ Ȱ ȱ P p Ƥ ƥ Q q ʠ R r Ŕ ŕ Ŗ ŗ Ř ř Ȑ ȑ Ȓ ȓ ɼ ɽ ɾ S s Ś ś Ŝ ŝ Ş ş Š š Ș ș ʂ T t Ţ ţ Ť ť Ŧ ŧ ƫ Ƭ ƭ Ʈ ʈ Ț ț ȶ U u Ù ù Ú ú Û û Ü ü Ũ ũ Ū ū Ŭ ŭ Ů ů Ű ű Ų ų Ư ư Ǔ ǔ Ǖ ǖ Ǘ ǘ Ǚ ǚ Ǜ ǜ Ȕ ȕ Ȗ ȗ V v Ʋ ʋ ṿ W w Ŵ ŵ X x Y y Ý ý ÿ Ÿ Ŷ ŷ Ƴ ƴ Ȳ ȳ Z z Ź ź Ż ż Ž ž Ƶ ƶ Ȥ ȥ ʐ ʑ syncevolution_1.4/build/xsl/common/entities.ent000066400000000000000000000203601230021373600221100ustar00rootroot00000000000000 normalize.sort.input normalize.sort.output '> syncevolution_1.4/build/xsl/common/gentext.xsl000066400000000000000000000733501230021373600217710ustar00rootroot00000000000000 .formal object.xref.markup: empty xref template for linkend=" " and @xrefstyle=" " Xref is only supported to listitems in an orderedlist: ??? %n 1 Attempt to use %d in gentext with no referrer! % % labelnumber labelname label quotedtitle title nopage pagenumber pageabbrev Page page nodocname docnamelong docname %n %t %t %p syncevolution_1.4/build/xsl/common/l10n.dtd000066400000000000000000000024501230021373600210230ustar00rootroot00000000000000 syncevolution_1.4/build/xsl/common/l10n.xml000066400000000000000000000056011230021373600210510ustar00rootroot00000000000000 syncevolution_1.4/build/xsl/common/l10n.xsl000066400000000000000000000504741230021373600210670ustar00rootroot00000000000000 No localization exists for " " or " ". Using default " ". No " " localization of " " exists . ; using "en". bullet No " " localization of dingbat exists; using "en". startquote endquote nestedstartquote nestedendquote No " " localization exists. No context named " " exists in the " " localization. No template for " " (or any of its leaves) exists in the context named " " in the " " localization. No " " localization exists. No context named " " exists in the " " localization. No template for " " (or any of its leaves) exists in the context named " " in the " " localization. 1 0 syncevolution_1.4/build/xsl/common/labels.xsl000066400000000000000000000755721230021373600215650ustar00rootroot00000000000000 Provides access to element labels Processing an element in the label.markup mode produces the element label. Trailing punctuation is not added to the label. . Request for label of unexpected element: label.markup: this can't happen!   1 a i A I Unexpected numeration: 0 Returns true if $section should be labelled Returns true if the specified section should be labelled. By default, this template returns zero unless the section level is less than or equal to the value of the $section.autolabel.max.depth parameter, in which case it returns $section.autolabel. Custom stylesheets may override it to get more selective behavior. 1 1 Unexpected .autolabel value: ; using default. Returns format for autolabel parameters Returns format passed as parameter if non zero. Supported format are 'arabic' or '1', 'loweralpha' or 'a', 'lowerroman' or 'i', 'upperlapha' or 'A', 'upperroman' or 'I', 'arabicindic' or '١'. If its not one of these then returns the default format. syncevolution_1.4/build/xsl/common/olink.xsl000066400000000000000000001411421230021373600214220ustar00rootroot00000000000000 Olinks not processed: must specify a $target.database.document parameter when using olinks with targetdoc and targetptr attributes. Olink error: the targetset element and children in ' ' should not be in any namespace. Olink error: could not open target database ' '. Olink debug: cases for targetdoc=' ' and targetptr=' ' in language ' '. Olink debug: CaseA matched. Olink debug: CaseA NOT matched Olink debug: CaseB matched. Olink debug: CaseB NOT matched Olink debug: CaseC matched. Olink debug: CaseC NOT matched. Olink debug: CaseD matched. Olink debug: CaseD NOT matched Olink debug: CaseE matched. Olink debug: CaseE NOT matched. Olink debug: CaseF matched. Olink debug: CaseF NOT matched. Olink debug: CaseB key is the final selection: Olink debug: CaseA key is the final selection: Olink debug: CaseC key is the final selection: Olink debug: CaseD key is the final selection: Olink debug: CaseF key is the final selection: Olink debug: CaseE key is the final selection: Olink debug: No case matched for lang ' '. 1 0 Olink error: cannot compute relative sitemap path because $current.docid ' ' not found in target database. Olink warning: cannot compute relative sitemap path without $current.docid parameter xrefstyle is ' '. Olink error: no gentext template exists for xrefstyle ' ' for element ' ' in language ' ' in context 'xref-number-and-title '. Using template without @style. Olink error: no gentext template exists for xrefstyle ' ' for element ' ' in language ' ' in context 'xref-number '. Using template without @style. Olink error: no gentext template exists for xrefstyle ' ' for element ' ' in language ' ' in context 'xref '. Using template without @style. Olink error: no gentext template exists for xrefstyle ' ' for element ' ' in language ' '. Trying '%t'. Olink debug: xrefstyle template is ' '. 1 0 Olink error: no generated text for targetdoc/targetptr/lang = ' '. ???? Olink error: no generated text for targetdoc/targetptr/lang = ' '. ???? Olink error: cannot locate targetdoc in sitemap / ../ syncevolution_1.4/build/xsl/common/pi.xsl000066400000000000000000000331061230021373600207160ustar00rootroot00000000000000 Common Processing Instruction Reference $Id: pi.xsl 8782 2010-07-27 21:15:17Z mzjn $ Introduction This is generated reference documentation for all user-specifiable processing instructions (PIs) in the “common” part of the DocBook XSL stylesheets. You add these PIs at particular points in a document to cause specific “exceptions” to formatting/output behavior. To make global changes in formatting/output behavior across an entire document, it’s better to do it by setting an appropriate stylesheet parameter (if there is one). Generates a localized choice separator Use the dbchoice choice PI to generate an appropriate localized “choice” separator (for example, and or or) before the final item in an inline simplelist This PI is a less-than-ideal hack; support for it may disappear in the future (particularly if and when a more appropriate means for marking up "choice" lists becomes available in DocBook). dbchoice choice="and"|"or"|string" choice="and" generates a localized and separator choice="or" generates a localized or separator choice="string" generates a literal string separator choice Inserts a date timestamp Use the dbtimestamp PI at any point in a source document to cause a date timestamp (a formatted string representing the current date and time) to be inserted in output of the document. dbtimestamp format="formatstring" [padding="0"|"1"] format="formatstring" Specifies format in which the date and time are output For details of the content of the format string, see Date and time. padding="0"|"1" Specifies padding behavior; if non-zero, padding is is added format padding 1 Timestamp processing requires XSLT processor with EXSLT date support. Generates delimiters around embedded TeX equations in output Use the dbtex delims PI as a child of a textobject containing embedded TeX markup, to cause that markup to be surrounded by $ delimiter characters in output. This feature is useful for print/PDF output only if you use the obsolete and now unsupported PassiveTeX XSL-FO engine. dbtex delims="no"|"yes" dbtex delims="no"|"yes" Specifies whether delimiters are output tex.math.delims 0 0 0 0 0 Timestamp processing requires an XSLT processor with support for the EXSLT node-set() function. syncevolution_1.4/build/xsl/common/stripns.xsl000066400000000000000000000320601230021373600220060ustar00rootroot00000000000000 info objectinfo blockinfo WARNING: cannot add @xml:base to node set root element. Relative paths may not work. / 1 0 Stripping namespace from DocBook 5 document. It is suggested to use namespaced version of the stylesheets available in distribution file 'docbook-xsl-ns' at //http://sourceforge.net/projects/docbook/files/ which does not require namespace stripping step. Processing stripped document. syncevolution_1.4/build/xsl/common/subtitles.xsl000066400000000000000000000162361230021373600223310ustar00rootroot00000000000000 Provides access to element subtitles Processing an element in the subtitle.markup mode produces the subtitle of the element. Request for subtitle of unexpected element: ???SUBTITLE??? syncevolution_1.4/build/xsl/common/table.xsl000066400000000000000000000435221230021373600214000ustar00rootroot00000000000000 0: 0: : 0 Determine the column number in which a given entry occurs If an entry has a colname or namest attribute, this template will determine the number of the column in which the entry should occur. For other entrys, nothing is returned. entry The entry-element which is to be tested. This template returns the column number if it can be determined, or 0 (the empty string) 1 1 1 1 : syncevolution_1.4/build/xsl/common/targets.xsl000066400000000000000000000251111230021373600217540ustar00rootroot00000000000000 Collects information for potential cross reference targets Processing the root element in the collect.targets mode produces a set of target database elements that can be used by the olink mechanism to resolve external cross references. The collection process is controlled by the collect.xref.targets parameter, which can be yes to collect targets and process the document for output, only to only collect the targets, and no (default) to not collect the targets and only process the document. A targets.filename parameter must be specified to receive the output if collect.xref.targets is set to yes so as to redirect the target data to a file separate from the document output. Must specify a $targets.filename parameter when $collect.xref.targets is set to 'yes'. The xref targets were not collected.
Warning: processing automatic glossary without a glossary.collection file. Warning: processing automatic glossary but unable to open glossary.collection file ' '
syncevolution_1.4/build/xsl/common/titles.xsl000066400000000000000000000701351230021373600216150ustar00rootroot00000000000000 Provides access to element titles Processing an element in the title.markup mode produces the title of the element. This does not include the label. Request for title of element with no title: (id=" ") (xml:id=" ") ???TITLE??? REFENTRY WITHOUT TITLE??? ERROR: glossdiv missing its required title Note Important Caution Warning Tip Question Answer Question Endterm points to nonexistent ID: ??? XRef to nonexistent id: ??? Endterm points to nonexistent ID: ??? syncevolution_1.4/build/xsl/common/utility.xsl000066400000000000000000000277771230021373600220320ustar00rootroot00000000000000 Common » Utility Template Reference $Id: utility.xsl 7101 2007-07-20 15:32:12Z xmldoc $ Introduction This is technical reference documentation for the miscellaneous utility templates in the DocBook XSL Stylesheets. These templates are defined in a separate file from the set of “common” templates because some of the common templates reference DocBook XSL stylesheet parameters, requiring the entire set of parameters to be imported/included in any stylesheet that imports/includes the common templates. The utility templates don’t import or include any DocBook XSL stylesheet parameters, so the utility templates can be used without importing the whole set of parameters. This is not intended to be user documentation. It is provided for developers writing customization layers for the stylesheets. Logs/emits formatted notes and warnings The log.message template is a utility template for logging/emitting formatted messages – that is, notes and warnings, along with a given log “level” and an identifier for the “source” that the message relates to. level Text to log/emit in the message-level field to indicate the message level (Note or Warning) source Text to log/emit in the source field to identify the “source” to which the notification/warning relates. This can be any arbitrary string, but because the message lacks line and column numbers to identify the exact part of the source document to which it relates, the intention is that the value you pass into the source parameter should give the user some way to identify the portion of their source document on which to take potentially take action in response to the log message (for example, to edit, change, or add content). So the source value should be, for example, an ID, book/chapter/article title, title of some formal object, or even a string giving an XPath expression. context-desc Text to log/emit in the context-description field to describe the context for the message. context-desc-field-length Specifies length of the context-description field (in characters); default is 12 If the text specified by the context-desc parameter is longer than the number of characters specified in context-desc-field-length, it is truncated to context-desc-field-length (12 characters by default). If the specified text is shorter than context-desc-field-length, it is right-padded out to context-desc-field-length (12 by default). If no value has been specified for the context-desc parameter, the field is left empty and the text of the log message begins with the value of the message parameter. message Text to log/emit in the actual message field message-field-length Specifies length of the message field (in characters); default is 45 Outputs a message (generally, to standard error). 12 right right : : Gets a title from the current document The get.doc.title template is a utility template for returning the first title found in the current document. Returns a string containing some identifying title for the current document . Right-pads or left-pads a string out to a certain length This function takes string padVar and pads it out in the direction rightLeft to the string-length length, using string padChar (a space character by default) as the padding string (note that padChar can be a string; it is not limited to just being a single character). This function began as a copy of Nate Austin's prepend-pad function in the Padding Content section of Dave Pawson's XSLT FAQ. Returns a (padded) string. left syncevolution_1.4/build/xsl/html/000077500000000000000000000000001230021373600172275ustar00rootroot00000000000000syncevolution_1.4/build/xsl/html/admon.xsl000066400000000000000000000112231230021373600210540ustar00rootroot00000000000000 25 note warning caution tip important note Note Warning Caution Tip Important Note
:
[{$alt}]

syncevolution_1.4/build/xsl/html/annotations.xsl000066400000000000000000000130311230021373600223120ustar00rootroot00000000000000 Note namesp. cut stripped namespace before processing Note namesp. cut processing stripped document Unable to strip the namespace from DB5 document, cannot proceed. ID ' ' not found in document. 0 syncevolution_1.4/build/xsl/html/ebnf.xsl000066400000000000000000000242621230021373600206770ustar00rootroot00000000000000 $Id: ebnf.xsl 9358 2012-05-12 23:37:10Z bobstayton $ Walsh Norman 19992000 Norman Walsh HTML EBNF Reference
Introduction This is technical reference documentation for the DocBook XSL Stylesheets; it documents (some of) the parameters, templates, and other elements of the stylesheets. This reference describes the templates and parameters relevant to formatting EBNF markup. This is not intended to be user documentation. It is provided for developers writing customization layers for the stylesheets, and for anyone who's interested in how it works. Although I am trying to be thorough, this documentation is known to be incomplete. Don't forget to read the source, too :-)
1 EBNF for
EBNF productions
[ ]   Error: no ID for productionrecap linkend: . Warning: multiple "IDs" for productionrecap linkend: . |
production Non-terminals with no content must point to production elements in the current document. Invalid xpointer for empty nt: ??? /*   */
constraintdef : :  ]

syncevolution_1.4/build/xsl/html/footnote.xsl000066400000000000000000000300341230021373600216140ustar00rootroot00000000000000 #ftn. [ ] ERROR: A footnoteref element has a linkend that points to an element that is not a footnote. Typically this happens when an id attribute is accidentally applied to the child of a footnote element. target element: linkend/id: #ftn. [ ] # [ ] ftn. # [ ]


footnote-hr 100

The following annotations are from this essay. You are seeing them here because your browser doesn’t support the user-interface techniques used to make them appear as ‘popups’ on modern browsers.

ftn.
Warning: footnote number may not be generated correctly; unexpected as first child of footnote.
syncevolution_1.4/build/xsl/html/formal.xsl000066400000000000000000000377701230021373600212550ustar00rootroot00000000000000 1


-float

-float
before before


-float
Broken table: tr descendent of CALS Table. before Broken table: row descendent of HTML table. before before
float: ;
syncevolution_1.4/build/xsl/html/glossary.xsl000066400000000000000000000432111230021373600216230ustar00rootroot00000000000000 %common.entities; ]> &setup-language-variable;
&setup-language-variable;
&setup-language-variable;

0 1 0 1 ( )
0 1 0 1 ( )
0 1 0 1
, , ,

Warning: glosssee @otherterm reference not found:

Warning: glossseealso @otherterm reference not found: &setup-language-variable; Warning: processing automatic glossary without a glossary.collection file. Warning: processing automatic glossary but unable to open glossary.collection file ' '
&setup-language-variable;
syncevolution_1.4/build/xsl/html/graphics.xsl000066400000000000000000001627441230021373600215750ustar00rootroot00000000000000 1 1
0 0 0 1 0 1.0 1.0 1.0 px px px px 0 0 middle Warning: imagemaps not supported on scaled images 0 middle manufactured viewport for HTML img cellpadding: 0; cellspacing: 0; height: px
background-color:
calspair , , , Warning: only calspair or otherunits='imagemap' supported in imageobjectco
middle
No insertfile extension available. Cannot insert . Check use.extensions and textinsert.extension parameters.
No insertfile extension available. No insertfile extension available. Cannot insert . Check use.extensions and textinsert.extension parameters.
syncevolution_1.4/build/xsl/html/html-rtf.xsl000066400000000000000000000263611230021373600215240ustar00rootroot00000000000000

syncevolution_1.4/build/xsl/html/html.xsl000066400000000000000000000566531230021373600207420ustar00rootroot00000000000000 left right left right left right ltr rtl ltr div 0 # # bullet bullet © ® (SM)   ID recommended on : ... ERROR: no root element for CSS source file' '. ERROR: missing CSS input filename. syncevolution_1.4/build/xsl/html/htmltbl.xsl000066400000000000000000000110451230021373600214260ustar00rootroot00000000000000 float: left right none none ; syncevolution_1.4/build/xsl/html/index.xsl000066400000000000000000000236141230021373600210740ustar00rootroot00000000000000

( )
( )
syncevolution_1.4/build/xsl/html/info.xsl000066400000000000000000000031571230021373600207200ustar00rootroot00000000000000 syncevolution_1.4/build/xsl/html/inline.xsl000066400000000000000000001362011230021373600212400ustar00rootroot00000000000000 %common.entities; ]> _blank _top 1 0 1 0 XLink to nonexistent id: span ( ) , , abbr acronym http://example.com/cgi-bin/man.cgi? ( ) SM Warning: glossary.collection specified, but there are automatic glossaries There's no entry for in Error: no glossentry for glossterm: . element </ > & ; &# ; % ; <? > <? ?> < > < /> <!-- --> < mailto: > + - - - + ( ) [ ] [ ] [ ] [ ]

syncevolution_1.4/build/xsl/html/keywords.xsl000066400000000000000000000023151230021373600216270ustar00rootroot00000000000000 , syncevolution_1.4/build/xsl/html/lists.xsl000066400000000000000000001217471230021373600211310ustar00rootroot00000000000000 compact list-style-type: ;
circle disc square
  • list-style-type:
  • compact 1 a i A I Unexpected numeration:
  • compact


    Simple list 1
    , Simple list 1
    Simple list 1
    1 1 1 1 1   1 1 1 1 1 1   before
    0 1 0 1

    :
    Callout list

    ??? # ???
    syncevolution_1.4/build/xsl/html/math.xsl000066400000000000000000000217261230021373600207200ustar00rootroot00000000000000 Unsupported TeX math notation: \nopagenumbers \bye \special{dvi2bitmap outputfile } $ $ \vfill\eject \special{dvi2bitmap outputfile } $$ $$ \vfill\eject \documentclass{article} \pagestyle{empty} \begin{document} \end{document} \special{dvi2bitmap outputfile } $ $ \newpage \special{dvi2bitmap outputfile } $$ $$ \newpage 0 0 1 syncevolution_1.4/build/xsl/html/param.xsl000066400000000000000000000474101230021373600210650ustar00rootroot00000000000000 .png images/ /* ====================================================================== Annotations */ div.annotation-list { visibility: hidden; } div.annotation-nocss { position: absolute; visibility: hidden; } div.annotation-popup { position: absolute; z-index: 4; visibility: hidden; padding: 0px; margin: 2px; border-style: solid; border-width: 1px; width: 200px; background-color: white; } div.annotation-title { padding: 1px; font-weight: bold; border-bottom-style: solid; border-bottom-width: 1px; color: white; background-color: black; } div.annotation-body { padding: 2px; } div.annotation-body p { margin-top: 0px; padding-top: 0px; } div.annotation-close { position: absolute; top: 2px; right: 2px; } http://docbook.sourceforge.net/release/images/annot-close.png http://docbook.sourceforge.net/release/images/annot-open.png http://docbook.sourceforge.net/release/script/AnchorPosition.js http://docbook.sourceforge.net/release/script/PopupWindow.js A . . http://docbook.sourceforge.net/release/bibliography/bibliography.xml normal 60 .png 15 images/callouts/ 10 10102 no 1 left before all docbook.css.xml no images/draft.png ::= #F5DCB3 com.example.help DocBook Online Help Sample Example provider 1 1 0 1 figure before example before equation before table before procedure before task before kr appendix toc,title article/appendix nop article toc,title book toc,title,figure,table,example,equation chapter toc,title part toc,title preface toc,title qandadiv toc qandaset toc reference toc,title sect1 toc sect2 toc sect3 toc sect4 toc sect5 toc section toc set toc,title no .html copyright text/javascript text/css alias.h User1 User2 htmlhelp.chm iso-8859-1 toc.hhc 5 index.hhk htmlhelp.hhp Main context.h basic no no iso-8859-1 en 5 3 HTML.manifest + .gif images/ 1 6in no fragid= .olink replace pubid /cgi-bin/olink sysid 0 I 90 10 ; . number I index . .!?: 8 0 #E0E0E0 0 solid 0.5pt a solid 0.5pt olinkdb.xml target.db tex-math-equations.tex dl 8 2 _top 0 , 0 docs ../common/ index.html true en index.html writing-mode : syncevolution_1.4/build/xsl/html/pi.xsl000066400000000000000000001377641230021373600204110ustar00rootroot00000000000000 HTML Processing Instruction Reference $Id: pi.xsl 9022 2011-07-14 19:21:36Z bobstayton $ Introduction This is generated reference documentation for all user-specifiable processing instructions (PIs) in the DocBook XSL stylesheets for HTML output. You add these PIs at particular points in a document to cause specific “exceptions” to formatting/output behavior. To make global changes in formatting/output behavior across an entire document, it’s better to do it by setting an appropriate stylesheet parameter (if there is one). Sets background color for an image Use the dbhtml background-color PI before or after an image (graphic, inlinegraphic, imagedata, or videodata element) as a sibling to the element, to set a background color for the image. dbhtml background-color="color" background-color="color" An HTML color value Background color Sets background color on a CALS table row or table cell Use the dbhtml bgcolor PI as child of a CALS table row or cell to set a background color for that table row or cell. dbhtml bgcolor="color" bgcolor="color" An HTML color value Cell background color Specifies cellpadding in CALS table or qandaset output Use the dbhtml cellpadding PI as a child of a CALS table or qandaset to specify the value for the HTML cellpadding attribute in the output HTML table. dbhtml cellpadding="number" cellpadding="number" Specifies the cellpadding html.cellpadding Cell spacing and cell padding, Q and A formatting Specifies cellspacing in CALS table or qandaset output Use the dbhtml cellspacing PI as a child of a CALS table or qandaset to specify the value for the HTML cellspacing attribute in the output HTML table. dbhtml cellspacing="number" cellspacing="number" Specifies the cellspacing html.cellspacing Cell spacing and cell padding, Q and A formatting Set value of the class attribute for a CALS table row Use the dbhtml class PI as a child of a row to specify a class attribute and value in the HTML output for that row. dbhtml class="name" class="name" Specifies the class name Table styles in HTML output Specifies a directory name in which to write files When chunking output, use the dbhtml dir PI as a child of a chunk source to cause the output of that chunk to be written to the specified directory; also, use it as a child of a mediaobject to specify a directory into which any long-description files for that mediaobject will be written. The output directory specification is inherited by all chunks of the descendants of the element. If descendants need to go to a different directory, then add another dbhtml dir processing instruction as a child of the source element for that chunk, and specify the path relative to the ancestor path. For example, to put most chunk files into shared but one chapter into exception at the same level, use: ... ]]> dbhtml dir="path" dir="path" Specifies the pathname for the directory base.dir dbhtml dir processing instruction Specifies a filename for a chunk When chunking output, use the dbhtml filename PI as a child of a chunk source to specify a filename for the output file for that chunk. Include the filename suffix. You cannot include a directory path in the filename value, or your links may not work. Add a dbhtml dir processing instruction to specify the output directory. You can also combine the two specifications in one processing instruction: dbhtml dir="mydir" filename="myfile.html". dbhtml filename="filename" filename="path" Specifies the filename for the file use.id.as.filename dbhtml filenames Specifies presentation style for a funcsynopsis Use the dbhtml funcsynopsis-style PI as a child of a funcsynopsis or anywhere within a funcsynopsis to control the presentation style for output of all funcprototype instances within that funcsynopsis. dbhtml funcsynopsis-style="kr"|"ansi" funcsynopsis-style="kr" Displays funcprototype output in K&R style funcsynopsis-style="ansi" Displays funcprototype output in ANSI style funcsynopsis.style Specifies a path to the location of an image file Use the dbhtml img.src.path PI before or after an image (graphic, inlinegraphic, imagedata, or videodata element) as a sibling to the element, to specify a path to the location of the image; in HTML output, the value specified for the img.src.path attribute is prepended to the filename. dbhtml img.src.path="path" img.src.path="path" Specifies the pathname to prepend to the name of the image file img.src.path Using fileref Specifies the label width for a qandaset Use the dbhtml label-width PI as a child of a qandaset to specify the width of labels. dbhtml label-width="width" label-width="width" Specifies the label width (including units) Q and A formatting Specifies interval for line numbers in verbatims Use the dbhtml linenumbering.everyNth PI as a child of a “verbatim” element – programlisting, screen, synopsis — to specify the interval at which lines are numbered. dbhtml linenumbering.everyNth="N" linenumbering.everyNth="N" Specifies numbering interval; a number is output before every Nth line linenumbering.everyNth Line numbering Specifies separator text for line numbers in verbatims Use the dbhtml linenumbering.separator PI as a child of a “verbatim” element – programlisting, screen, synopsis — to specify the separator text output between the line numbers and content. dbhtml linenumbering.separator="text" linenumbering.separator="text" Specifies the text (zero or more characters) linenumbering.separator Line numbering Specifies width for line numbers in verbatims Use the dbhtml linenumbering.width PI as a child of a “verbatim” element – programlisting, screen, synopsis — to specify the width set aside for line numbers. dbhtml linenumbering.width="width" linenumbering.width="width" Specifies the width (inluding units) linenumbering.width Line numbering Specifies presentation style for a variablelist or segmentedlist Use the dbhtml list-presentation PI as a child of a variablelist or segmentedlist to control the presentation style for the list (to cause it, for example, to be displayed as a table). dbhtml list-presentation="list"|"table" list-presentation="list" Displays the list as a list list-presentation="table" Displays the list as a table variablelist.as.table segmentedlist.as.table Variable list formatting in HTML Specifies the width of a variablelist or simplelist Use the dbhtml list-width PI as a child of a variablelist or a simplelist presented as a table, to specify the output width. dbhtml list-width="width" list-width="width" Specifies the output width (including units) Variable list formatting in HTML Specifies the height for a CALS table row Use the dbhtml row-height PI as a child of a row to specify the height of the row. dbhtml row-height="height" row-height="height" Specifies the row height (including units) Row height (obsolete) Sets the starting number on an ordered list This PI is obsolete. The intent of this PI was to provide a means for setting a specific starting number for an ordered list. Instead of this PI, set a value for the override attribute on the first listitem in the list; that will have the same effect as what this PI was intended for. dbhtml start="character" start="character" Specifies the character to use as the starting number; use 0-9, a-z, A-Z, or lowercase or uppercase Roman numerals List starting number Do not chunk any descendants of this element. When generating chunked HTML output, adding this PI as the child of an element that contains elements that would normally be generated on separate pages if generating chunked output causes chunking to stop at this point. No descendants of the current element will be split into new HTML pages: Configuring pencil ... ]]> dbhtml stop-chunking Chunking into multiple HTML files Specifies summary for CALS table, variablelist, segmentedlist, or qandaset output Use the dbhtml table-summary PI as a child of a CALS table, variablelist, segmentedlist, or qandaset to specify the text for the HTML summary attribute in the output HTML table. dbhtml table-summary="text" table-summary="text" Specifies the summary text (zero or more characters) Variable list formatting in HTML, Table summary text Specifies the width for a CALS table Use the dbhtml table-width PI as a child of a CALS table to specify the width of the table in output. dbhtml table-width="width" table-width="width" Specifies the table width (including units or as a percentage) default.table.width Table width Sets character formatting for terms in a variablelist Use the dbhtml term-presentation PI as a child of a variablelist to set character formatting for the term output of the list. dbhtml term-presentation="bold"|"italic"|"bold-italic" term-presentation="bold" Specifies that terms are displayed in bold term-presentation="italic" Specifies that terms are displayed in italic term-presentation="bold-italic" Specifies that terms are displayed in bold-italic Variable list formatting in HTML Specifies separator text among terms in a varlistentry Use the dbhtml term-separator PI as a child of a variablelist to specify the separator text among term instances. dbhtml term-separator="text" term-separator="text" Specifies the text (zero or more characters) variablelist.term.separator Variable list formatting in HTML Specifies the term width for a variablelist Use the dbhtml term-width PI as a child of a variablelist to specify the width for term output. dbhtml term-width="width" term-width="width" Specifies the term width (including units) Variable list formatting in HTML Specifies whether a TOC should be generated for a qandaset Use the dbhtml toc PI as a child of a qandaset to specify whether a table of contents (TOC) is generated for the qandaset. dbhtml toc="0"|"1" toc="0" If zero, no TOC is generated toc="1" If 1 (or any non-zero value), a TOC is generated Q and A list of questions, Q and A formatting Generates a hyperlinked list of commands Use the dbcmdlist PI as the child of any element (for example, refsynopsisdiv) containing multiple cmdsynopsis instances; a hyperlinked navigational “command list” will be generated at the top of output for that element, enabling users to quickly jump to each command synopsis. dbcmdlist [No parameters] No cmdsynopsis elements matched dbcmdlist PI, perhaps it's nested too deep?
    Generates a hyperlinked list of functions Use the dbfunclist PI as the child of any element (for example, refsynopsisdiv) containing multiple funcsynopsis instances; a hyperlinked navigational “function list” will be generated at the top of output for that element, enabling users to quickly jump to to each function synopsis. dbfunclist [No parameters] No funcsynopsis elements matched dbfunclist PI, perhaps it's nested too deep?
    Copies an external well-formed HTML/XML file into current doc Use the dbhtml-include href PI anywhere in a document to cause the contents of the file referenced by the href pseudo-attribute to be copied/inserted “as is” into your HTML output at the point in document order where the PI occurs in the source. The referenced file may contain plain text (as long as it is “wrapped” in an html element — see the note below) or markup in any arbitrary vocabulary, including HTML — but it must conform to XML well-formedness constraints (because the feature in XSLT 1.0 for opening external files, the document() function, can only handle files that meet XML well-formedness constraints). Among other things, XML well-formedness constraints require a document to have a single root element. So if the content you want to include is plain text or is markup that does not have a single root element, wrap the content in an html element. The stylesheets will strip out that surrounding html “wrapper” when they find it, leaving just the content you want to insert. dbhtml-include href="URI" href="URI" Specifies the URI for the file to include; the URI can be, for example, a remote http: URI, or a local filesystem file: URI textinsert.extension Inserting external HTML code, External code files href ERROR: dbhtml-include processing instruction href has no content. ERROR: dbhtml-include processing instruction has missing or empty href value. Sets topic name and topic id for context-sensitive HTML Help Use the dbhh PI as a child of components that should be used as targets for context-sensitive help requests. dbhh topicname="name" topicid="id" topicname="name" Specifies a unique string constant that identifies a help topic topicid="id" Specifies a unique integer value for the topicname string Context-sensitive help filename
    #
    #
    / /
    syncevolution_1.4/build/xsl/html/qandaset.xsl000066400000000000000000000364361230021373600215730ustar00rootroot00000000000000

    width: 100%; 1%
    syncevolution_1.4/build/xsl/html/refentry.xsl000066400000000000000000000224441230021373600216230ustar00rootroot00000000000000


    ( )

    , em-dash :

    0 1 6

    syncevolution_1.4/build/xsl/html/sections.xsl000066400000000000000000000555031230021373600216160ustar00rootroot00000000000000 1 2 3 4 5 6 clear: both 1 1 2 3 4 5 6 syncevolution_1.4/build/xsl/html/synop.xsl000066400000000000000000001445661230021373600211470ustar00rootroot00000000000000 ]>



    ( )   ( )
        
        
        
      


    ( ) ; ... ) ; , ) ;
    ;
    ( ) Function synopsis cellspacing: 0; cellpadding: 0;
     
     
    ( ) ;   ... ) ;   , ) ;       ; ( ) ;

    ( void) ; ... ) ; , ) ; ( ) Function synopsis cellspacing: 0; cellpadding: 0;
     
     
    ( void) ;   ... ) ;   , ) ; ( ) java Unrecognized language on :
        
        
        
        
           extends
          
          
            
        
    implements
        
    throws  {
    }
    ,   , , ,    ;     void  0 ,
     
       ( )
        throws 
    ;
        
        
        
        
          : 
          
          
            
        
    implements
        
    throws  {
    }
    ,   , , ,    ;     void  ,    ( )
        throws 
    ;
        
        
        interface 
        
        
          : 
          
          
            
        
    implements
        
    throws  {
    }
    ,   , , ,    ;     void  ,    ( )
        raises( )
    ;
        
        
        package 
        
        ;
        
    @ISA = ( );
    ,   , , ,    ;     void  , sub { ... };
    syncevolution_1.4/build/xsl/html/table.xsl000066400000000000000000001253411230021373600210540ustar00rootroot00000000000000 0   border- : ; border- -width: ; border- -style: ; border- -color: ; Error: CALS tables must specify the number of columns. 100% border-collapse: collapse; border-collapse: collapse; border-collapse: collapse; border-collapse: collapse; border-collapse: collapse; border: none; border-collapse: collapse; 0 1 100% No convertLength function available. No adjustColumnWidths function available.
    Warning: overlapped row contains content! This row intentionally left blank 1 th th th td 1 0   1 : 0: 0 : 1 1 1 1
    syncevolution_1.4/build/xsl/html/task.xsl000066400000000000000000000046111230021373600207230ustar00rootroot00000000000000 before
    syncevolution_1.4/build/xsl/html/titlepage.templates.xsl000066400000000000000000005476021230021373600237500ustar00rootroot00000000000000
    1
    1

    1
    1

    1
    1
    1
    1
    1
    1

    1
    1
    1
    1
    1
    1
    1
    1
    1
    1
    1
    1
    1
    1
    1
    1

    1
    1

    1
    1

    1
    1

    1
    1

    1
    1

    1
    1

    1
    1
    1
    1
    1
    1
    1
    1
    1
    1
    1
    1
    syncevolution_1.4/build/xsl/html/titlepage.xsl000066400000000000000000001121561230021373600217430ustar00rootroot00000000000000

    Authors








     

    Copyright

    copyright

    ,















    : ,





    3 2 RevHistory
    border-style:solid; width:100%; revhistory
    ,  



    syncevolution_1.4/build/xsl/html/toc.xsl000066400000000000000000000305571230021373600205560ustar00rootroot00000000000000
  • Warning: don't know what to generate for lot that has no children.
    syncevolution_1.4/build/xsl/html/verbatim.xsl000066400000000000000000000335661230021373600216050ustar00rootroot00000000000000 pre The shade.verbatim parameter is deprecated. Use CSS instead, for example: pre. { background-color: #E0E0E0; } The shade.verbatim parameter is deprecated. Use CSS instead, for example: pre. { background-color: #E0E0E0; }
                
                
                
                  
                
              

                
                
                
              

    Unexpected verbatim environment: 1 No numberLines function available.
    syncevolution_1.4/build/xsl/html/xref.xsl000066400000000000000000001401231230021373600207240ustar00rootroot00000000000000 http://docbook.org/xlink/role/olink Endterm points to nonexistent ID: ??? ERROR: xref linking to has no generated link text. ??? XRef to nonexistent id: ??? Endterm points to nonexistent ID: ??? suppress anchor removing removing Don't know what gentext to create for xref to: " ", (" ") ??? [ ] No bibliography entry: found in [ ] Endterm points to nonexistent ID: ??? Link element has no content and no Endterm. Nothing to show in the link to ??? Olink debug: root element of target.database ' ' is ' '. Error: unresolved olink: targetdoc/targetptr = ' / '. Warning: olink linkmode pointer is wrong. # ? & syncevolution_1.4/build/xsl/lib/000077500000000000000000000000001230021373600170315ustar00rootroot00000000000000syncevolution_1.4/build/xsl/lib/lib.xsl000066400000000000000000000474771230021373600203520ustar00rootroot00000000000000 http://... Unrecognized unit of measure: . Unrecognized unit of measure: . filename 1 / / / syncevolution_1.4/configure.ac000066400000000000000000001462541230021373600166600ustar00rootroot00000000000000dnl -*- mode: Autoconf; -*- dnl Invoke autogen.sh to produce a configure script. # The STABLE_VERSION is used when building # Debian packages. For prereleases (beta, alpha), # set it to something like "0.9.2+" and the AC_INIT # VERSION to 1.0beta1 to produce 0.9.2+1.0beta1. # # Starting with the 1.1 release cycle, the rpm-style # .99 pseudo-version number is used to mark a pre-release. AC_INIT([syncevolution], [m4_esyscmd([build/gen-git-version.sh 1.4])]) # STABLE_VERSION=1.0.1+ AC_SUBST(STABLE_VERSION) # Generate some files. SE_GENERATE_AM_FILES SE_GENERATE_LINGUAS # Default value for --enable/disable-release-mode. # Determined by gen-autotools.sh based on versioning. # Official, stable releases enable it, pre-releases # disable it. # SE_CHECK_FOR_STABLE_RELEASE # Minimum version of libsynthesis as defined in its # configure script and thus .pc files: define([SYNTHESIS_MIN_VERSION], [3.4.0.47.1]) # Line above is patched by gen-autotools.sh. Handle # both "yes" and "no". m4_define([STABLE_RELEASE_HELP], m4_if(STABLE_RELEASE,[yes],[--disable-release-mode],[--enable-release-mode])) AC_ARG_ENABLE(release-mode, AS_HELP_STRING([STABLE_RELEASE_HELP], [Controls whether resulting binary is for end-users or testers/developers. For example, stable releases automatically migrate on-disk files without asking, whereas other releases ask before making downgrades impossible (or difficult). Default in this source code is "stable release: STABLE_RELEASE"]), enable_release_mode="$enableval", enable_release_mode="STABLE_RELEASE") if test "$enable_release_mode" = "yes"; then AC_DEFINE(SYNCEVOLUTION_STABLE_RELEASE, 1, [binary is meant for end-users]) fi AM_INIT_AUTOMAKE([1.11.1 tar-ustar silent-rules subdir-objects -Wno-portability]) AM_PROG_CC_C_O AM_MAINTAINER_MODE([enable]) # needed for nightly builds where reconfiguration fails under certain chroots AC_CONFIG_MACRO_DIR([m4]) define([SYNTHESISSRC_REPO], []) dnl Specify git revisions/branches without prefix, i.e., without 'origin'. dnl We'll sort that out below. define([SYNTHESISSRC_REVISION], [syncevolution-0.9]) AC_CONFIG_HEADERS(config.h) AC_LIBTOOL_DLOPEN AC_PROG_LIBTOOL dnl check for programs. AC_PROG_CXX AC_PROG_MAKE_SET # Boost headers: boost/foreach.hpp is needed (1.33/Debian Etch # doesn't have it, 1.34/Ubuntu 8.10 Hardy does). 1.35 is available # as Debian Etch backport. AX_BOOST_BASE(1.34) # TODO: Fix code to pass with -pedantic -Wextra. # -Wno-unknown-pragmas needed because icalstrdup.h # currently uses the "#pragma }" trick. Should remove that. # Fix code to work without deprecated methods: G GDK GDK_PIXBUF CAIRO PANGO GTK DK_ARG_ENABLE_WARNINGS([SYNCEVO_WFLAGS], [-Wall -Wno-unknown-pragmas -Wno-deprecated-declarations], [-Wall -Wno-unknown-pragmas -Wno-deprecated-declarations], []) dnl default device type (see AC_DEFINE below) DEVICE_TYPE=workstation AC_ARG_WITH(synthesis-src, AS_HELP_STRING([--with-synthesis-src=], [Specifies location of the Synthesis root directory. Use this when the Synthesis library is to be compiled as part of the SyncEvolution compilation. In release versions of SyncEvolution, a copy of the Synthesis code is bundled under 'src/synthesis' and compiled unless something else is specified. --with-synthesis-src can be given a path to sources checked out already, a Subversion repository URL or a git repository URL. When given a repository URL, then the configure script will checkout the sources into 'src/synthesis-workdir' or update that working copy if the directory already exists. Default: bundled source in src/synthesis (in released SyncEvolution sources), SYNTHESISSRC_REPO otherwise.]), [SYNTHESISSRC="$withval" test "$SYNTHESISSRC" != "yes" || AC_MSG_ERROR([--with-synthesis-src requires a parameter (base directory, svn URL or git URL)])], [SYNTHESISSRC="$SYNTHESISSRC_DEF"; REVISION="SYNTHESISSRC_REVISION"]) AC_ARG_WITH(syncml-engines, AS_HELP_STRING([--with-syncml-engines=client|server|both], [Determines which kind of support for SyncML is compiled and linked into SyncEvolution. Default is both. Currently has no effect.]), [SYNCML_ENGINES="$withval"], SYNCML_ENGINES=both) case $SYNCML_ENGINES in both|client) AC_DEFINE(ENABLE_SYNCML_CLIENT, 1, [SyncML client support available]);; esac case $SYNCML_ENGINES in both|server) AC_DEFINE(ENABLE_SYNCML_SERVER, 1, [SyncML server support available]);; esac case $SYNCML_ENGINES in both|server|client) true;; *) AC_MSG_ERROR([Invalid value for --with-syncml-engines: $SYNCML_ENGINES]);; esac AC_ARG_WITH(synthesis-username, AS_HELP_STRING([--with-synthesis-username=], [username to use when checking out --with-synthesis-src sources from Subversion, default 'guest']), [USERNAME="$withval"], [USERNAME="guest"]) AC_ARG_WITH(synthesis-revision, AS_HELP_STRING([--with-synthesis-revision=], [Identifies which source revision to use from --with-synthesis-src repository, empty string stands for latest. Default for default --synthesis-src: SYNTHESISSRC_REVISION]), [REVISION="$withval"]) AC_ARG_ENABLE(shared, AS_HELP_STRING([--enable-shared], [build backends as dynamically loadable modules]), enable_shared="$enableval", enable_shared="no") AC_ARG_ENABLE(static, AS_HELP_STRING([--enable-static], [build backends also as static libraries]), enable_static="$enableval", enable_static="no") AC_ARG_ENABLE(unit-tests, AS_HELP_STRING([--enable-unit-tests], [enables tests embedded in the source code of the library (changes content of executable)]), enable_unit_tests="$enableval", enable_unit_tests="no") AC_ARG_ENABLE(integration-tests, AS_HELP_STRING([--enable-integration-tests], [enables tests outside of the library (can be used together with normal builds of the library)]), enable_integration_tests="$enableval", enable_integration_tests="no") AC_ARG_ENABLE(static-cxx, AS_HELP_STRING([--enable-static-cxx], [build executables which contain libstdc++ instead of requiring suitable libstdc++.so to run]), enable_static_cxx="$enableval", enable_static_cxx="no") AC_ARG_ENABLE(evolution-compatibility, AS_HELP_STRING([--enable-evolution-compatibility], [build executables which only call Evolution via dlopen/dlsym: this avoids all hard dependencies on EDS shared objects, but might lead to crashes when their ABI changes; use --enable-evolution-compatibility=ical to enable a weaker mode where linking is done normally and only libical.so.0/1 enum differences are worked around (allows patching resulting executables to use either of these two)]), enable_evolution_compatibility="$enableval", enable_evolution_compatibility="no") AC_ARG_ENABLE(developer-mode, AS_HELP_STRING([--enable-developer-mode], [The dynamic loadble backend libraries is loaded from current build directory instead of the standard library path]), enable_developer_mode="$enableval", enable_developer_mode="no") # Maemo hacks: # - wrap e_book_from_string() to fix invalid parameter # - don't use UTF-8 encoding in Perl script AC_ARG_ENABLE(maemo, AS_HELP_STRING([--enable-maemo], [enables some hacks which work around problems with the Maemo 2.0 until at least 3.0 EDS-Dbus]), [AC_DEFINE(ENABLE_MAEMO, 1, [enable Maemo hacks]) DEVICE_TYPE=Maemo MODIFY_SYNCCOMPARE='-e "s/use encoding/#use encoding/;" -e "s/:utf8//;"']) AC_SUBST(MODIFY_SYNCCOMPARE) AC_CHECK_HEADERS(signal.h dlfcn.h) # cppunit-config is used even when both unit tests and integration tests are disabled. AC_PATH_PROG([CPPUNIT_CONFIG], [cppunit-config], [no]) # cppunit needed? if test "x$enable_unit_tests" = 'xyes' || test "x$enable_integration_tests" = 'xyes'; then test "x$CPPUNIT_CONFIG" != 'xno' || AC_MSG_ERROR("cppunit-config not found.") fi if test "x$CPPUNIT_CONFIG" != 'xno'; then # Export the flags if available, even if not enabled. This allows # "make src/client-test" in cases where "make all" would not build # client-test. CPPUNIT_CXXFLAGS=`$CPPUNIT_CONFIG --cflags` CPPUNIT_LDFLAGS=`$CPPUNIT_CONFIG --libs` fi AC_SUBST(CPPUNIT_CXXFLAGS) AC_SUBST(CPPUNIT_LDFLAGS) if test "x$enable_unit_tests" = 'xyes'; then AC_DEFINE(ENABLE_UNIT_TESTS, 1, [enable unit tests inside the library's source code]) fi if test "x$enable_integration_tests" = 'xyes'; then AC_DEFINE(ENABLE_INTEGRATION_TESTS, 1, [enable integration tests inside the final library]) fi AM_CONDITIONAL([ENABLE_UNIT_TESTS], [test "$enable_unit_tests" = "yes"]) AM_CONDITIONAL([ENABLE_TESTING], [test "$enable_unit_tests" = "yes" || test "$enable_integration_tests" = "yes" ]) if test $enable_static_cxx = "yes"; then LIBS="$LIBS -L." CORE_LDADD_DEP=libstdc++.a fi AC_SUBST(CORE_LDADD_DEP) # Check for transport layer. # Both curl and libsoup can be enabled and disabled explicitly. # The default is to use libsoup if available, otherwise curl. AC_MSG_CHECKING([for libcurl]) if LIBCURL_LIBS=`sh -c 'curl-config --libs' 2>&AS_MESSAGE_LOG_FD` && \ LIBCURL_CFLAGS=`sh -c 'curl-config --cflags' 2>&AS_MESSAGE_LOG_FD`; then AC_MSG_RESULT([yes]) have_libcurl="yes" else AC_MSG_RESULT([no]) have_libcurl="no" fi PKG_CHECK_MODULES(LIBSOUP, libsoup-gnome-2.4, [have_libsoup="yes" AC_DEFINE(HAVE_LIBSOUP_SOUP_GNOME_FEATURES_H, 1, [enable GNOME specific libsoup])], [PKG_CHECK_MODULES(LIBSOUP, libsoup-2.4, have_libsoup="yes", have_libsoup="no")]) PKG_CHECK_MODULES(LIBOPENOBEX, openobex, have_obex="yes", have_obex="no") have_bluetooth="no" if test $have_obex = "yes"; then PKG_CHECK_MODULES(BLUEZ, bluez, have_bluez="yes", have_bluez="no") if test $have_bluez = "yes"; then have_bluetooth="yes" fi fi AC_SUBST(LIBOPENOBEX_CFLAGS) AC_SUBST(LIBOPENOBEX_LIBS) AC_SUBST(BLUEZ_CFLAGS) AC_SUBST(BLUEZ_LIBS) TRANSPORT= TRANSPORT_LIBS= TRANSPORT_CFLAGS= AC_ARG_WITH(ca-certificates, AS_HELP_STRING([--with-ca-certificates=], [Specifies location of one or more CA certificate files. This sets the default value for the SSLServerCertificates option. Default: empty when using libcurl (because it has its own default), a list of paths known to work for Debian and Red Hat otherwise.]), [CA_CERTIFICATES="$withval"]) # choose default http transport (mirrors code in EvolutionSyncClient::createTransportAgent()) if test "$have_libsoup" = "yes"; then default_http_transport="libsoup" elif test "$have_libcurl" = "yes"; then default_http_transport="libcurl" fi AC_ARG_ENABLE(libcurl, AS_HELP_STRING([--enable-libcurl], [enable libcurl as transport layer]), [ if test "$enableval" = "yes"; then test "$have_libcurl" = "yes" || AC_MSG_ERROR([libcurl not found]) TRANSPORT="$TRANSPORT libcurl" TRANSPORT_LIBS="$TRANSPORT_LIBS $LIBCURL_LIBS" TRANSPORT_CFLAGS="$TRANSPORT_CFLAGS $LIBCURL_CFLAGS" AC_DEFINE(ENABLE_LIBCURL, 1, [enable libcurl transport]) else libcurl_disabled="yes" fi ], [ if test "$have_libcurl" = "yes" && test "$default_http_transport" = "libcurl" ; then TRANSPORT="$TRANSPORT libcurl" TRANSPORT_LIBS="$TRANSPORT_LIBS $LIBCURL_LIBS" TRANSPORT_CFLAGS="$TRANSPORT_CFLAGS $LIBCURL_CFLAGS" AC_DEFINE(ENABLE_LIBCURL, 1, [enable libcurl transport]) fi ]) AC_ARG_ENABLE(libsoup, AS_HELP_STRING([--enable-libsoup], [enable libsoup as transport layer]), [ if test "$enableval" = "yes"; then test "$have_libsoup" = "yes" || AC_MSG_ERROR([libsoup not found]) TRANSPORT="$TRANSPORT libsoup" TRANSPORT_LIBS="$TRANSPORT_LIBS $LIBSOUP_LIBS" TRANSPORT_CFLAGS="$TRANSPORT_CFLAGS $LIBSOUP_CFLAGS" AC_DEFINE(ENABLE_LIBSOUP, 1, [enable libsoup transport]) else libsoup_disabled="yes" fi ], [ if test "$have_libsoup" = "yes" && test "$default_http_transport" = "libsoup"; then TRANSPORT="$TRANSPORT libsoup" TRANSPORT_LIBS="$TRANSPORT_LIBS $LIBSOUP_LIBS" TRANSPORT_CFLAGS="$TRANSPORT_CFLAGS $LIBSOUP_CFLAGS" AC_DEFINE(ENABLE_LIBSOUP, 1, [enable libsoup transport]) fi ]) # SoupTransportAgent depends on glib case "$TRANSPORT" in *libsoup*) need_glib=yes;; esac AC_ARG_ENABLE(dlt, AS_HELP_STRING([--enable-dlt], [enable logging via GENIVI Diagnostic Log and Trace (DLT)]), [enable_dlt=$enableval test $enable_dlt = "yes" || test $enable_dlt = "no" || AC_ERROR([invalid value of --enable-dlt: $enableval])], [enable_dlt="no"]) if test "$enable_dlt" = "yes"; then PKG_CHECK_MODULES(DLT, automotive-dlt, [USE_DLT=1], [AC_ERROR([dlt not found, required for --enable-dlt])]) AC_DEFINE(USE_DLT, 1, "optionally use GENIVI Diagnostic Log and Trace for logging") AC_ARG_WITH([dlt-syncevolution], AS_HELP_STRING([--with-dlt-syncevolution=SYNS,SYNH,SYNL], [controls the application IDs used by syncevo-dbus-server, syncevo-dbus-helper and syncevo-local-sync]), [with_dlt_ids="$withval"], [with_dlt_ids="SYNS,SYNH,SYNL"]) syns=`echo $with_dlt_ids | cut -d , -f 1` synh=`echo $with_dlt_ids | cut -d , -f 2` synl=`echo $with_dlt_ids | cut -d , -f 3` AC_DEFINE_UNQUOTED(DLT_SYNCEVO_DBUS_SERVER_ID, "$syns", "DLT app ID for syncevo-dbus-server") AC_DEFINE_UNQUOTED(DLT_SYNCEVO_DBUS_HELPER_ID, "$synh", "DLT app ID for syncevo-dbus-helper") AC_DEFINE_UNQUOTED(DLT_SYNCEVO_LOCAL_HELPER_ID, "$synl", "DLT app ID for syncevo-local-helper") fi bluetooth_disabled=no AC_ARG_ENABLE(bluetooth, AS_HELP_STRING([--enable-bluetooth], [enable bluetooth transport support]), [ enable_bluetooth="$enableval" if test "$enableval" = "no"; then bluetooth_disabled=yes fi ], [ enable_bluetooth="$have_bluetooth" ]) if test "$enable_bluetooth" = "yes"; then # currently we need Bluetooth and OBEX support test "$have_bluetooth" = "yes" || AC_MSG_ERROR([openobex or bluez not found]) AC_LANG(C) CFLAGS_old="$CFLAGS" CFLAGS="$CPPFLAGS $BLUEZ_CFLAGS" # test in this order: # - recent libbluetooth (no _safe variant, base function has bufsize) # - intermediate with _safe # - else assume old-style (no bufsize, no _safe) # # The source code checks the signature both by via pointer assignment and calling # it (better safe than sorry). One these should fail if the signature is not right. AC_COMPILE_IFELSE([AC_LANG_SOURCE([ #include #include sdp_record_t *(*extract_pdu)(const uint8_t *pdata, int bufsize, int *scanned) = sdp_extract_pdu; void foo(void) { uint8_t *pdata = NULL; int scanned; sdp_extract_pdu(pdata, 100, &scanned); } ])], AC_DEFINE(HAVE_BLUEZ_BUFSIZE, 1, [base libbluetooth functions accept bufsize parameter]), AC_COMPILE_IFELSE([AC_LANG_SOURCE([ #include #include sdp_record_t *(*extract_pdu)(const uint8_t *pdata, int bufsize, int *scanned) = sdp_extract_pdu_safe; void foo(void) { uint8_t *pdata = NULL; int scanned; sdp_extract_pdu_safe(pdata, 100, &scanned); } ])], AC_DEFINE(HAVE_BLUEZ_SAFE, 1, [libbluetooth has _safe variants]))) CFLAGS="$CFLAGS_old" if test "$have_obex" = "yes"; then AC_DEFINE(ENABLE_OBEX, 1, [define if openobex library is available]) fi if test "$have_bluez" = "yes"; then AC_DEFINE(ENABLE_BLUETOOTH, 1, [define if bluez library is available]) fi fi AM_CONDITIONAL([ENABLE_OBEX], [test "$have_obex" = "yes" && test "$enable_bluetooth" = "yes"]) AM_CONDITIONAL([ENABLE_BLUETOOTH], [test "$have_bluetooth" = "yes" && test "$enable_bluetooth" = "yes"]) if test ! "$TRANSPORT" && test "$libsoup_disabled" != "yes" && test "$libcurl_disabled" != "yes" && test "$bluetooth_disabled" != "yes" && test "$have_bluetooth" != "yes" ; then AC_MSG_ERROR([no transport library found, configure with --disable-libcurl --disable-libsoup --disable-bluetooth to continue anyway (only useful if users of libsyncevolution provide transport implementation)]) fi # for libsoup we must specify the SSL certificate file outself if test "$libsoup_disabled" != "yes" && test -z "$CA_CERTIFICATES"; then # Debian and Red Hat paths CA_CERTIFICATES="/etc/ssl/certs/ca-certificates.crt:/etc/pki/tls/certs/ca-bundle.crt:/usr/share/ssl/certs/ca-bundle.crt" fi AC_DEFINE_UNQUOTED(SYNCEVOLUTION_SSL_SERVER_CERTIFICATES, "$CA_CERTIFICATES", [default value for SSLServerCertificates option]) AC_SUBST(TRANSPORT_LIBS) AC_SUBST(TRANSPORT_CFLAGS) AC_ARG_ENABLE(ssl-certificate-check, AS_HELP_STRING([--disable-ssl-certificate-check], [Disable SSL certificate checking in all server *templates*. Users can still choose to enable or disable it in their configuration. This is necessary on platforms where the transport library has problems verifying the server's certificate (libsoup + Google, http://bugzilla.moblin.org/show_bug.cgi?id=4551)]), enable_ssl_certificate_check="$enableval", enable_ssl_certificate_check="yes") if test "$enable_ssl_certificate_check" = "yes"; then AC_DEFINE(ENABLE_SSL_CERTIFICATE_CHECK, 1, [enable SSL certificate check in server templates]) fi # for dbus interface file mangling AC_PATH_PROG(XSLT, xsltproc) # Changes in GTK3 mean that supporting both GTK3 and GTK2 in the same codebase # is difficult. We want to support GTK2 for the time being so the code is forked. AC_ARG_ENABLE(gtk, AS_HELP_STRING([--enable-gtk=major version], [Selects the gtk+ version ("2" or "3") to use for the UI. If this option is used, --enable-gui should be used as well. "3" is the default option if available, otherwise "2".]), [ if test "$enableval" = "3" ; then gtk_version=gtk+-3.0 elif test "$enableval" = "2" ; then gtk_version=gtk+-2.0 else AC_MSG_ERROR([Unknown gtk version: '$enableval']) fi ], [ PKG_CHECK_EXISTS([gtk+-3.0], [gtk_version=gtk+-3.0], [gtk_version=gtk+-2.0]) ]) AM_CONDITIONAL([COND_GTK2], [test "$gtk_version" = "gtk+-2.0"]) AC_ARG_ENABLE(gui, AS_HELP_STRING([--enable-gui[=gui type]], [enables building the GTK+ UI that uses the SyncEvolution DBus API. Options: gtk, moblin, all (builds sync-ui-gtk and sync-ui-moblin) "gtk" is the default for --enable-gui without type. No GUI is built when --enable-gui is not used.]), [ if test "$enableval" = "gtk" ; then enable_gui=gtk elif test "$enableval" = "yes" ; then enable_gui=gtk elif test "$enableval" = "moblin" ; then enable_gui=moblin elif test "$enableval" = "no" ; then enable_gui=no elif test "$enableval" = "all" ; then enable_gui=all else AC_MSG_ERROR([Unknown gui type: '$enableval']) fi ], [ enable_gui=no ]) AM_CONDITIONAL([COND_GUI], [test "$enable_gui" != "no"]) AC_ARG_ENABLE(core, AS_HELP_STRING([--enable-core], [enables building the core SyncEvolution (library, backends)]), enable_core="$enableval", enable_core="yes") AM_CONDITIONAL([COND_CORE], [test "$enable_core" = "yes"]) AC_ARG_ENABLE(dbus-service, AS_HELP_STRING([--enable-dbus-service=args], [Enables building the dbus service executable and all related features (the DBus wrapper library, command line usage of server, etc). The optional arguments are syncevo-dbus-server command line arguments that are used when auto-starting via D-Bus or .desktop file. By default, the daemon logs to syslog. This can be changed via command line arguments. ]), enable_dbus_service="$enableval", [if test $enable_gui = "no"; then enable_dbus_service="no" else enable_dbus_service="yes" fi]) AM_CONDITIONAL([COND_DBUS], [test "$enable_dbus_service" != "no"]) if test "$enable_dbus_service" != "no" && test "$enable_dbus_service" != "yes"; then SYNCEVO_DBUS_SERVER_ARGS="$enable_dbus_service" fi AC_SUBST(SYNCEVO_DBUS_SERVER_ARGS) AC_ARG_WITH([gio-gdbus], AS_HELP_STRING([--with-gio-gdbus], [enables use of GIO's GDBus instead of the in-tree, Bluez gdbus.]), with_gio_gdbus="$withval", PKG_CHECK_EXISTS([gio-2.0 >= 2.30], [with_gio_gdbus="yes"], [with_gio_gdbus="no"])) AM_CONDITIONAL([COND_GIO_GDBUS], [test "x$with_gio_gdbus" = "xyes"]) # We only need to check for dbus-1 if gio-gdbus is not used. # # Local sync depends on D-Bus communication between parent # and child process (works without a D-Bus daemon), and local # sync is not an optional feature. Could be made one if # someone is interested enough. # # Therefore, at the moment, either libdbus or gio-gdbus are needed # unconditionally. glib is needed in all cases now. need_glib=yes AS_IF([test "x$with_gio_gdbus" = "xyes"], [PKG_CHECK_MODULES([DBUS], [gio-2.0 >= 2.26]) AC_DEFINE([WITH_GIO_GDBUS],[],[Set if using GIO GDBus])], [PKG_CHECK_MODULES(DBUS, dbus-1, dummy=yes, AC_MSG_ERROR(libdbus-1 is required)) AC_CHECK_LIB(dbus-1, dbus_watch_get_unix_fd, dummy=yes, AC_DEFINE(NEED_DBUS_WATCH_GET_UNIX_FD, 1, [Define to 1 if you need the dbus_watch_get_unix_fd() function.]))]) if test "$enable_dbus_service" != "no"; then if test -z "$XSLT"; then AC_MSG_ERROR([xsltproc not found, is required for D-Bus service]) fi # Recent libnotify releases work with gtk+-2.0 and gtk+-3.0. AC_ARG_ENABLE([notify], AS_HELP_STRING([--enable-notify], [send notifications for automatic sync events, using libnotify]), enable_notify="$enableval", PKG_CHECK_EXISTS([libnotify $gtk_version], [enable_notify="yes"], [enable_notify="no"])) AS_IF([test "x$enable_notify" = "xyes"], [PKG_CHECK_MODULES([LIBNOTIFY], [libnotify $gtk_version])] [AC_DEFINE(HAS_NOTIFY, 1, [define if libnotify could be used in dbus service])]) AC_ARG_ENABLE(notify-compatibility, AS_HELP_STRING([--enable-notify-compatibility], [increase compatibility with binary libnotify installations by loading libnotify.so.1..4 dynamically instead of linking against it]), [enable_notify_compat="$enableval"], [enable_notify_compat="no"] ) if test "$enable_notify_compat" = "yes"; then AC_DEFINE(NOTIFY_COMPATIBILITY, 1, [dynamically open libnotify]) LIBNOTIFY_LIBS="`echo $LIBNOTIFY_LIBS | sed -e 's/\(-lnotify\|[^ ]*libnotify.la\)/-ldl/'`" fi # Here we're using QtGui too because mlite fails to depend on it, # despite using QAction. PKG_CHECK_MODULES(MLITE, [mlite QtGui], HAVE_MLITE=yes, HAVE_MLITE=no) AC_ARG_ENABLE(mlite, AS_HELP_STRING([--enable-mlite], [send notifications for automatic sync events, using mlite (off by default)]), [ enable_mlite="$enableval" test "$enableval" = "no" || test $HAVE_MLITE = "yes" || AC_MSG_ERROR([required mlite package not found]) ], [ enable_mlite="no" ]) if test $enable_mlite = "yes"; then AC_DEFINE(HAS_MLITE, 1, [define if mlite could be used in dbus service]) else # don't use mlite, even if found MLITE_CFLAGS= MLITE_LIBS= fi AC_DEFINE(DBUS_SERVICE, 1, [define if dbus service is enabled]) AC_ARG_ENABLE(dbus-service-pim, AS_HELP_STRING([--enable-dbus-service-pim[=]], [Enable implementation of org._01.pim D-Bus APIs (depends on libfolks), using src/dbus/server/pim/locale-factory-.cpp to implement sorting and searching. The default is =boost, which uses boost::locale.]), [ enable_dbus_pim="$enableval" ], [ enable_dbus_pim="no" ]) case "$enable_dbus_pim" in no) ;; *) if test "$enable_dbus_pim" = "yes"; then enable_dbus_pim=boost fi if ! test -r "$srcdir/src/dbus/server/pim/locale-factory-$enable_dbus_pim.cpp"; then AC_MSG_ERROR([invalid value '$enable_dbus_pim' for --enable-dbus-service-pim, $srcdir/src/dbus/server/pim/locale-factory-$enable_dbus_pim.cpp does not exist or is not readable]) fi PKG_CHECK_MODULES(FOLKS, [folks folks-eds]) AC_DEFINE(ENABLE_DBUS_PIM, 1, [org._01.pim D-Bus API enabled]) DBUS_PIM_PLUGIN=$enable_dbus_pim AC_SUBST(DBUS_PIM_PLUGIN) case "$enable_dbus_pim" in boost) AX_BOOST_LOCALE # AX_BOOST_LOCALE incorrectly puts -L/... into LDFLAGS. # That's broken because it then overrides the search path # for *all* libraries in a link, not just for boost. Fix # this by putting the LDFLAGS before the lib and leaving # DBUS_PIM_PLUGIN_LDFLAGS empty (for now - might have to # be revised if there ever are any boost flags which need # to go to the start of the link line). DBUS_PIM_PLUGIN_LIBS='$(BOOST_LDFLAGS) $(BOOST_LOCALE_LIB)' DBUS_PIM_PLUGIN_LDFLAGS= # We need to call ICU directly for the Han->Latin transformation. PKG_CHECK_MODULES(ICU, [icu-uc]) ;; esac AC_SUBST(DBUS_PIM_PLUGIN_CFLAGS) AC_SUBST(DBUS_PIM_PLUGIN_LIBS) AC_SUBST(DBUS_PIM_PLUGIN_LDFLAGS) # http://code.google.com/p/libphonenumber/ AC_LANG(C++) SAVED_CPPFLAGS=$CPPFLAGS CPPFLAGS="$CPPFLAGS ${PHONENUMBERS_CFLAGS}" AC_CHECK_HEADERS([phonenumbers/phonenumberutil.h]) SAVED_LIBS=$LIBS if test ! "$PHONENUMBERS_LIBS"; then PHONENUMBERS_LIBS=-lphonenumber fi LIBS="$LIBS $PHONENUMBERS_LIBS" AC_LINK_IFELSE( [AC_LANG_PROGRAM([#include ], [i18n::phonenumbers::PhoneNumberUtil::GetInstance()])], [true], [AC_ERROR([libphonebook not found, set PHONENUMBERS_CFLAGS and PHONENUMBERS_LIBS.])]) AC_SUBST(PHONENUMBERS_CFLAGS) AC_SUBST(PHONENUMBERS_LIBS) LIBS=$SAVED_LIBS CPPFLAGS=$SAVED_CPPFLAGS ;; esac fi AM_CONDITIONAL([NOTIFY_COMPATIBILITY], [test "$enable_notify_compat" = "yes"]) AM_CONDITIONAL([COND_DBUS_PIM], [test "$enable_dbus_pim" != "no"]) AC_SUBST(DBUS_CFLAGS) AC_SUBST(DBUS_LIBS) AC_SUBST(DBUS_GLIB_CFLAGS) AC_SUBST(DBUS_GLIB_LIBS) AC_SUBST(LIBNOTIFY_CFLAGS) AC_SUBST(LIBNOTIFY_LIBS) AC_SUBST(LIBEXECDIR) DBUS_SERVICES_DIR="${datadir}/dbus-1/services" AC_SUBST(DBUS_SERVICES_DIR) AC_DEFINE_UNQUOTED(DBUS_SERVICES_DIR, "$DBUS_SERVICES_DIR", [Location of D-Bus services directory]) if test "$enable_gui" != "no" || test "$enable_dbus_service" != "no"; then IT_PROG_INTLTOOL([0.37.1]) GETTEXT_PACKAGE=syncevolution AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE, "$GETTEXT_PACKAGE", [The gettext package name]) AM_GLIB_GNU_GETTEXT SYNCEVOLUTION_LOCALEDIR=[${datadir}/locale] fi # decide which sync-ui(s) we are building: # sync-ui (in either GTK or Moblin mode) or both (in separate binaries) if test $gtk_version = "gtk+-3.0"; then gtk_dir=src/gtk3-ui else gtk_dir=src/gtk-ui fi case $enable_gui in all) GUI_PROGRAMS=${gtk_dir}'/sync-ui-gtk${EXEEXT} '${gtk_dir}'/sync-ui-moblin${EXEEXT}'; GUI_DESKTOP_FILES="${gtk_dir}/sync-gtk.desktop ${gtk_dir}/sync-moblin.desktop";; gtk|moblin) GUI_PROGRAMS=${gtk_dir}'/sync-ui${EXEEXT}'; GUI_DESKTOP_FILES="${gtk_dir}/sync.desktop";; no) GUI_PROGRAMS=; GUI_DESKTOP_FILES=;; *) AC_MSG_ERROR([Unknown enable_gui type: '$enable_gui']) esac if test $enable_gui != "no"; then PKG_CHECK_MODULES(DBUS_GLIB, dbus-glib-1 glib-2.0) AC_PATH_PROG(DBUS_BINDING_TOOL, dbus-binding-tool) AC_PATH_PROG(GLIB_GENMARSHAL, glib-genmarshal) gui_modules="$gtk_version glib-2.0 dbus-glib-1 >= 0.60 gio-2.0 gio-unix-2.0" if test $enable_gui = "moblin"; then AC_DEFINE(USE_MOBLIN_UX, 1, [Use Moblin UI widgets]) fi # gtk3 provides a switch widget, otherwise we need mx-gtk if test $gtk_version != "gtk+-3.0" && test $enable_gui = "moblin" -o $enable_gui = "all"; then gui_modules="$gui_modules mx-gtk-1.0" PKG_CHECK_MODULES(MX_GTK_0_99_1, mx-gtk-1.0 >= 0.99.1, have_mx_gtk_0_99_1="yes", have_mx_gtk_0_99_1="no") if test $have_mx_gtk_0_99_1 = "yes"; then AC_DEFINE(MX_GTK_0_99_1, 1, [we have Mx-Gtk 0.99.1 or better]) fi fi PKG_CHECK_MODULES(UNIQUE, unique-1.0, have_unique="yes", have_unique="no") if test $have_unique = "yes"; then gui_modules="$gui_modules unique-1.0" AC_DEFINE(ENABLE_UNIQUE, 1, [enable single-app-instance functionality]) fi PKG_CHECK_MODULES(GTK_2_18, gtk+-2.0 >= 2.18, have_gtk_2_18="yes", have_gtk_2_18="no") if test $have_gtk_2_18 = "yes"; then AC_DEFINE(GTK_2_18, 1, [we have GTK+ 2.18 or better]) fi PKG_CHECK_MODULES(GUI, $gui_modules) elif test "$enable_dbus_service" != "no"; then # syncevo-dbus-server needs localization : else INTLTOOL_UPDATE="true" USE_NLS="no" fi AC_SUBST(SYNCEVOLUTION_LOCALEDIR) AC_SUBST(GETTEXT_PACKAGE) AC_SUBST(GUI_CFLAGS) AC_SUBST(GUI_LIBS) AC_SUBST(GUI_PROGRAMS) AC_SUBST(GUI_DESKTOP_FILES) # C++ regular expression support is required often enough to make it # mandatory. PKG_CHECK_MODULES(PCRECPP, libpcrecpp,, AC_CHECK_LIB(pcrecpp,main, AC_SUBST(PCRECPP_LIBS,-lpcrecpp), AC_MSG_ERROR([pcrecpp not found]) )) # need rst2man for man pages AC_ARG_WITH(rst2man, AS_HELP_STRING([--with-rst2man=], [Specifies an explicit path to the utility if not found in PATH. An explicit --without-rst2man or not having it installed turn off building of man pages.]), [RST2MAN=$withval if test "$RST2MAN" = "yes"; then AC_PATH_PROG(RST2MAN, rst2man, "no") fi test "$RST2MAN" = "no" || test -x "$RST2MAN" || AC_MSG_ERROR([--with-rst2man=$RST2MAN: tool not found])], [AC_PATH_PROG(RST2MAN, rst2man, "no")]) AM_CONDITIONAL([COND_MAN_PAGES], [test "$RST2MAN" != "no"]) # need rst2html for HTML version of README AC_ARG_WITH(rst2html, AS_HELP_STRING([--with-rst2html=], [Specifies an explicit path to the utility if not found in PATH. An explicit --without-rst2html or not having it installed turn off building of README in HTML format.]), [RST2HTML=$withval if test "$RST2HTML" = "yes"; then AC_PATH_PROG(RST2HTML, rst2html, "no") fi test "$RST2HTML" = "no" || test -x "$RST2HTML" || AC_MSG_ERROR([--with-rst2html=$RST2HTML: tool not found])], [AC_PATH_PROG(RST2HTML, rst2html, "no")]) AM_CONDITIONAL([COND_HTML_README], [test "$RST2HTML" != "no"]) # absolute patch to source of Synthesis client library SYNTHESIS_SRC=no-synthesis-source AC_SUBST(SYNTHESIS_SRC) if test "$SYNTHESISSRC" && test "$SYNTHESISSRC" != "none"; then # default: checkout a copy of the sources, remove it during maintainer-clean and distclean CLEAN_CLIENT_SRC=synthesis-workdir SYNTHESIS_SRC=$PWD/src/synthesis-workdir AC_MSG_NOTICE( [updating the content of $SYNTHESIS_SRC from $SYNTHESISSRC] ) case "$SYNTHESISSRC" in *.git) protocol=git;; *://*) protocol="`echo $SYNTHESISSRC | sed -e 's;://.*;;'`";; *) protocol="file";; esac mkdir -p src case $protocol in file) # use existing copy of the sources CLEAN_CLIENT_SRC= case "$SYNTHESISSRC" in /*) SYNTHESIS_SRC="$SYNTHESISSRC";; *) SYNTHESIS_SRC="$PWD/$SYNTHESISSRC";; esac ;; *svn*|*http*) SYNTHESISSRCREV="$SYNTHESISSRC" if test "$REVISION"; then revarg="-r $REVISION " if `echo $SYNTHESISSRC | grep '@[0123456789]*'` >/dev/null; then : else SYNTHESISSRCREV="$SYNTHESISSRC@$REVISION" fi fi if test -d $SYNTHESIS_SRC ; then ( set -x; cd $SYNTHESIS_SRC && svn --username=$USERNAME switch $revarg "$SYNTHESISSRC" ) || AC_MSG_ERROR([updating from $SYNTHESISSRC failed]) else (set -x; svn --username=$USERNAME checkout $revarg "$SYNTHESISSRCREV" $SYNTHESIS_SRC ) || AC_MSG_ERROR([checking out $SYNTHESISSRC failed]) fi ;; *) if test -d $SYNTHESIS_SRC ; then ( set -x; cd $SYNTHESIS_SRC && git fetch "$SYNTHESISSRC" ) || AC_MSG_ERROR([updating from $SYNTHESISSRC failed]) else ( set -x; git clone "$SYNTHESISSRC" $SYNTHESIS_SRC ) || AC_MSG_ERROR([cloning $SYNTHESISSRC failed]) fi if test "$REVISION"; then # git 1.6 finds tags and branches without explicit prefix, 1.4.4.4 doesn't ( set -x; cd $SYNTHESIS_SRC && (git checkout "$REVISION" || git checkout "tags/$REVISION" || git checkout "origin/$REVISION") ) || AC_MSG_ERROR([checking out $SYNTHESISSRC failed]) fi ;; esac elif test "$SYNTHESISSRC" != "none" && test -d $srcdir/src/synthesis; then # use existing copy of the sources; beware of # out-of-tree compilation case $srcdir in /*) SYNTHESIS_SRC="$srcdir/src/synthesis";; *) SYNTHESIS_SRC="$PWD/$srcdir/src/synthesis";; esac elif test "$enable_shared" = "no"; then # link against engine PKG_CHECK_MODULES([SYNTHESIS], [synthesis >= 3.4]) SYNTHESIS_ENGINE="$SYNTHESIS_LIBS -lsynthesis -lsmltk" else # link against SDK alone, except in client-test #PKG_CHECK_MODULES(SYNTHESIS, "synthesis-sdk") #SYNTHESIS_ENGINE="`echo $SYNTHESIS_LIBS | sed -e 's/-lsynthesisstubs/-lsynthesis/'`" # can't use the SDK alone because of sysync::SySyncDebugPuts() PKG_CHECK_MODULES([SYNTHESIS], [synthesis >= SYNTHESIS_MIN_VERSION]) SYNTHESIS_ENGINE="$SYNTHESIS_LIBS" fi if test $SYNTHESIS_SRC != "no-synthesis-source"; then ( cd $SYNTHESIS_SRC && ( test -f configure || sh autogen.sh ) ) || AC_MSG_ERROR([--with-synthesis-src=$SYNTHESIS_SRC: no Synthesis configure script found in that directory]) SYNTHESIS_CONFIGURE="$SYNTHESIS_SRC/configure" chmod u+x $SYNTHESIS_SRC/configure $SYNTHESIS_SRC/config.sub $SYNTHESIS_SRC/config.guess # use local copy of the sources, with dependencies # to trigger building the synthesis library SYNTHESIS_SUBDIR=$PWD/src/build-synthesis SYNTHESIS_CFLAGS="-I$SYNTHESIS_SUBDIR/src" SYNTHESIS_LIBS="$SYNTHESIS_SUBDIR/src/libsynthesissdk.la $SYNTHESIS_SUBDIR/src/libsmltk.la" if test "x$enable_core" = "xno" && test "x$enable_gui" != "xno"; then # SYNTHESIS_SUBDIR is ignored, at least build headers for GUI SYNTHESIS_SUBDIR_INCLUDES=src/synthesis-includes fi if test "$enable_shared" = "no"; then # link against the engines that were enabled case $SYNCML_ENGINES in both|client|server) SYNTHESIS_LIBS="$SYNTHESIS_LIBS $SYNTHESIS_SUBDIR/src/libsynthesis.la $SYNTHESIS_SUBDIR/src/libsmltk.la";; esac AC_DEFINE(ENABLE_SYNCML_LINKED, 1, [SyncML engines are linked directly]) else # It would be nice if we could avoid linking against libsynthesis.la here. # This doesn't work at the moment because sysync::SySyncDebugPuts() # is called directly by the libsynthesissdk instead of going through # the normal C function pointer lookup. SYNTHESIS_LIBS="$SYNTHESIS_LIBS $SYNTHESIS_SUBDIR/src/libsynthesis.la $SYNTHESIS_SUBDIR/src/libsmltk.la" fi SYNTHESIS_DEP=$SYNTHESIS_LIBS # for linking client-test SYNTHESIS_ENGINE="$SYNTHESIS_SUBDIR/src/libsynthesis.la" AC_MSG_NOTICE( [configuring the Synthesis library] ) if (set -x; mkdir -p $SYNTHESIS_SUBDIR && cd $SYNTHESIS_SUBDIR && eval "\$SHELL \"\$SYNTHESIS_CONFIGURE\" $ac_configure_args \"--srcdir=\$SYNTHESIS_SRC\" " ); then true; else AC_MSG_ERROR( [configuring Synthesis library failed] ) fi # do the version check with the .pc files prepared by the configure step above export PKG_CONFIG_PATH=$SYNTHESIS_SUBDIR:$PKG_CONFIG_PATH PKG_CHECK_MODULES([WITH_SYNTHESIS_SRC], [synthesis >= SYNTHESIS_MIN_VERSION], [], [AC_MSG_ERROR([need at least libsynthesis >= SYNTHESIS_MIN_VERSION; the latest libsynthesis for SyncEvolution is the one from http://cgit.freedesktop.org/SyncEvolution/])]) fi AC_SUBST(SYNTHESIS_CFLAGS) AC_SUBST(SYNTHESIS_LIBS) AC_SUBST(SYNTHESIS) AC_SUBST(SYNTHESIS_SUBDIR) AC_SUBST(SYNTHESIS_SUBDIR_INCLUDES) AC_SUBST(SYNTHESIS_DEP) AC_SUBST(SYNTHESIS_ENGINE) AC_SUBST(SYNTHESIS_LIB) AC_SUBST(SYNTHESISSRC) BACKEND_CPPFLAGS="$SYNTHESIS_CFLAGS $EPACKAGE_CFLAGS $EBOOK_CFLAGS $ECAL_CFLAGS $GLIB_CFLAGS $BOOST_CPPFLAGS" AC_SUBST(BACKEND_CPPFLAGS) # GNOME Bluetooth Panel plugin PKG_CHECK_MODULES(GNOMEBLUETOOTH, [gnome-bluetooth-1.0 >= 2.27.6], [have_gbt="yes" GNOMEBLUETOOTH_DIR=`$PKG_CONFIG --variable=libdir gnome-bluetooth-1.0 2>/dev/null`/gnome-bluetooth], have_gbt="no") AC_SUBST(GNOMEBLUETOOTH_CFLAGS) AC_SUBST(GNOMEBLUETOOTH_DIR) AC_ARG_ENABLE(gnome-bluetooth-panel-plugin, AS_HELP_STRING([--enable-gnome-bluetooth-panel-plugin], [GNOME Bluetooth panel plugin adding a "sync" button for paired devices (off by default)]), [enable_gnome_bluetooth_panel="$enableval"], [enable_gnome_bluetooth_panel="no"] ) if test "$enable_gnome_bluetooth_panel" = "yes"; then test "$have_gbt" = "yes" || AC_MSG_ERROR([--enable-gnome-bluetooth-panel requires pkg-config information for gnome-bluetooth-1.0 >= 2.27.6 which was not found]) fi AM_CONDITIONAL([ENABLE_GNOME_BLUETOOTH_PANEL], [test "$have_gbt" = "yes" && test "$enable_gnome_bluetooth_panel" = "yes"]) AC_ARG_ENABLE(doc, AS_HELP_STRING([--enable-doc], [generate backend and DBus API documentation]), enable_doc="$enableval", enable_doc="no") AM_CONDITIONAL([COND_DOC], [test "$enable_doc" != "no"]) dnl add backends stuff. SE_ADD_BACKENDS dnl -*- mode: Autoconf; -*- dnl Invoke autogen.sh to produce a configure script. AC_ARG_ENABLE(qt-dbus, AS_HELP_STRING([--enable-qt-dbus], [build Qt bindings for D-Bus]), enable_qt_dbus="$enableval", enable_qt_dbus="no") if test "$enable_qt_dbus" = "yes"; then AC_DEFINE(ENABLE_QT_DBUS, 1, [Qt D-Bus bindings available]) need_qt_modules="$need_qt_modules +dbus" AC_PATH_PROG([QDBUSXML_TO_CPP], [qdbusxml2cpp], [no]) test "x$QDBUSXML_TO_CPP" != 'xno' || AC_MSG_ERROR([--enable-qt-dbus requires qdbusxml2cpp, which was not found]) fi AM_CONDITIONAL([ENABLE_QT_DBUS], [test "$enable_qt_dbus" = "yes"]) AC_SUBST(QT_DBUS_LIBS) AC_CONFIG_FILES([src/dbus/qt/syncevolution-qt-dbus.pc]) dnl -*- mode: Autoconf; -*- dnl Invoke autogen.sh to produce a configure script. dnl configure-pre.in and src/backends/*/configure-sub.in and configure-post.in come before this part AC_SUBST(BACKEND_CPPFLAGS) enable_any="no" backend_is_enabled () { eval echo \${enable_${1}} } for backend in $BACKENDS; do if test `backend_is_enabled $backend` = "yes"; then enable_any="yes" SYNCEVOLUTION_MODULES="$SYNCEVOLUTION_MODULES src/backends/sync${backend}.la" fi done if test "$enable_any" = "no"; then AC_MSG_ERROR([no backend enabled - refusing to continue: $anymissing]) fi dnl glib initialization is done only if requested by some configure-sub.in, dnl for not needed otherwise even if found if test "$need_glib" = "yes"; then # HAVE_GLIB (aka GLIBFOUND) are a catch-all for these # three GNOME libs. Assume we have all three unless one of # the checks fails. GLIBFOUND=yes dnl check for glib - calling g_type_init() is expected on Maemo PKG_CHECK_MODULES(GLIB, "glib-2.0", , GLIBFOUND=no) # This check here is broken on Ubuntu 8.04: it calls glib-config, # which isn't found, but the error is not detected by configure. #if test "x${GLIBFOUND}" = "xno"; then # PKG_CHECK_MODULES(GLIB, "glib", GLIBFOUND=yes, GLIBFOUND=no) #fi PKG_CHECK_MODULES(GTHREAD, "gthread-2.0", , GLIBFOUND=no) PKG_CHECK_MODULES(GOBJECT, "gobject-2.0", , GLIBFOUND=no) PKG_CHECK_MODULES(GIO, "gio-2.0", , GLIBFOUND=no) if test "x${GLIBFOUND}" = "xyes"; then AC_DEFINE(HAVE_GLIB, 1, [glib found]) else AC_MSG_ERROR([not all GNOME libraries found]) fi BACKEND_CPPFLAGS="$BACKEND_CPPFLAGS $GLIB_CFLAGS $GTHREAD_CFLAGS $GOBJECT_CFLAGS" fi dnl use libical if and only if required by some backend if test "$need_ical" = "yes"; then PKG_CHECK_MODULES(LIBICAL, libical, [true], [PKG_CHECK_MODULES(LIBICAL, libecal-1.2)]) AC_DEFINE(ENABLE_ICAL, 1, [libical in use]) fi AM_CONDITIONAL([ENABLE_ICAL], [test "$need_ical" = "yes"]) # Check for Qt if some backend needs it. if test "$need_qt_modules"; then AT_WITH_QT([-gui $need_qt_modules], [$qt_config], [$qt_misc]) fi # determine from where we can get a SHA-256 implementation have_sha="no" if test "$GLIBFOUND" = "yes"; then # only use glib if we need it anyway, also has to be at lease 2.16 PKG_CHECK_MODULES(GLIB216, [glib-2.0 >= 2.16], [AC_DEFINE(USE_SHA256, 1, [choose implementation of SHA-256]) have_sha="glib"], [true]) fi if test "$have_sha" = "no"; then # Fallback is Mozilla NSS. In contrast to libgcrypt it has a .pc # file and a simple API. PKG_CHECK_MODULES(NSS, "nss", [AC_DEFINE(USE_SHA256, 2, [choose implementation of SHA-256]) have_sha="nss"], [true]) fi dnl figure out whether we link all code statically or as modules AM_CONDITIONAL([ENABLE_MODULES], [test "$enable_shared" = "yes"]) if test "$enable_shared" = "yes"; then AC_DEFINE(ENABLE_MODULES, 1, [enable dynamically opening sync source backends]) fi AC_SUBST(SYNCEVOLUTION_LDADD) dnl CXXFLAGS gets applied to SyncEvolution and the client library. dnl For e.g. "-Wall -Werror" this might not be such a good idea; dnl SYNCEVOLUTION_CXXFLAGS can be used instead. It applies only dnl to the sources in the SyncEvolution repository. AC_SUBST(SYNCEVOLUTION_CXXFLAGS) dnl a quick-and-dirty way to detect compilation for the iPhone if test "$host" = "arm-apple-darwin"; then AC_DEFINE(IPHONE, 1, [compiling for iPhone]) DEVICE_TYPE=iPhone fi dnl --enable-evolution-compatibility if test "$enable_evolution_compatibility" = "yes"; then AC_DEFINE(EVOLUTION_COMPATIBILITY, 1, [avoid hard dependency on Evolution shared objects]) # don't link against libs wrapped by eds_abi_wrapper (no longer limited to EDS alone...) ECAL_LIBS= EBOOK_LIBS= EPACKAGE_LIBS= BLUEZ_LIBS= fi if test "$enable_evolution_compatibility" = "ical" || test "$enable_evolution_compatibility" = "yes"; then AC_DEFINE(EVOLUTION_ICAL_COMPATIBILITY, 1, [work with both libical.so.0 and libical.so.1]) fi AM_CONDITIONAL([ENABLE_EVOLUTION_COMPATIBILITY], [test "$enable_evolution_compatibility" = "yes"]) PKG_CHECK_MODULES(LIBICAL_AVAILABLE, libical >= 0.43, AC_DEFINE(HAVE_LIBICAL_R, 1, [have recent enough libical with _r variants]), pass) dnl --enable-developer-mode if test "$enable_developer_mode" = "yes"; then BACKENDS_SEARCH_DIRECTORY="`pwd`/src/backends/" else BACKENDS_SEARCH_DIRECTORY='$(libdir)/syncevolution/backends/' fi BACKENDS_DIRECTORY='$(libdir)/syncevolution/backends' AC_SUBST(BACKENDS_DIRECTORY) AC_SUBST(BACKENDS_SEARCH_DIRECTORY) # for ActiveSync backend syncevo_backenddir='$(libdir)'/syncevolution/backends AC_SUBST(syncevo_backenddir) dnl This string is sent as part of the SyncML DevInf (device dnl information) structure to the server. All SyncEvolution platforms dnl use "SyncEvolution" as HTTP user agent and "Mod" (model), so the dnl device type is the only way how different kinds of clients can be dnl distinguished. AC_DEFINE_UNQUOTED(DEVICE_TYPE, "$DEVICE_TYPE", "SyncML DevInf DevType") AC_CHECK_HEADERS(stdarg.h valgrind/valgrind.h execinfo.h) AC_DEFINE(SYNTHESIS, 1, "using Synthesis engine") # fallback for lack of --with-docdir support in older automake if test ! "$docdir"; then docdir = ${datadir}/doc/syncevolution AC_SUBST(docdir) fi AC_ARG_ENABLE(dbus-timeout-hack, AS_HELP_STRING([--enable-dbus-timeout-hack], [Enables code which overrides the default timeout in dbus_connection_send_with_reply() so that the method call never times out. Needed for libecal/ebook >= 2.30, so enabled by default if either of these is enabled.]), [enable_dbus_timeout_hack=$enableval], [if test $enable_ebook = "yes" || test $enable_ecal = "yes"; then enable_dbus_timeout_hack="yes" fi]) if test "$enable_dbus_timeout_hack" = "yes"; then AC_DEFINE(ENABLE_DBUS_TIMEOUT_HACK, 1, [overrides the default D-Bus timeout so that synchronous calls never time out]) fi # Avoid hard-coding paths in backends. These names are chosen so # that a backend can alternatively use its own top-level configure # with PKG_CHECK_MODULES(SYNCEVOLUTION, "syncevolution") to set them. # need absolute path, use pwd instead of relative $srcdir # # When adding something here, remember to also update syncevolution.pc.in. # -lrt is for clock_gettime() in the Timespec.h inline functions. SYNCEVOLUTION_CFLAGS=-I`cd $srcdir && pwd`/src # Linker flags including libsyncevolution.la and some libs. SYNCEVOLUTION_LIBS="src/syncevo/libsyncevolution.la -lrt" AC_SUBST(SYNCEVOLUTION_CFLAGS) AC_SUBST(SYNCEVOLUTION_LIBS) # invoking syncevolution binary is allowed to fail when cross-compiling AM_CONDITIONAL([COND_CROSS_COMPILING], [test "$cross_compiling" = "yes"]) # Set by any of the backends providing a keyring, determines the # default for the "keyring" option. if test "$have_keyring" = "yes"; then AC_DEFINE(HAVE_KEYRING, 1, [some kind of secure credential store is available]) fi AC_CONFIG_FILES([ Makefile src/syncevo/syncevolution.pc src/synthesis-includes/Makefile po/Makefile.in src/dbus/glib/syncevo-dbus.pc ]) AC_OUTPUT echo echo CONFIGURATION SUMMARY echo "Core SyncEvolution: $enable_core" for backend in $BACKENDS; do eval echo $backend: \${enable_${backend}} done echo "DBus service: $enable_dbus_service" echo "org._01.pim support in DBus service: $enable_dbus_pim" echo "Notifications: $enable_notify" echo "GIO GDBus: $with_gio_gdbus" echo "GNOME keyring: $enable_gnome_keyring" if test "$enable_gui" = "no"; then echo "UI (DBus client): no" else echo "UI (DBus client): $enable_gui (using $gtk_version)" fi echo "Bluetooth transport: $have_bluetooth" echo "GNOME Bluetooth panel plugin: $enable_gnome_bluetooth_panel" echo "SHA-256: $have_sha" echo "API documentation: $enable_doc" echo "D-Bus Timeout Hack: $enable_dbus_timeout_hack" echo syncevolution_1.4/description000066400000000000000000000005671230021373600166340ustar00rootroot00000000000000 SyncEvolution synchronizes personal information management (PIM) data via various protocols (SyncML, CalDAV/CardDAV, ActiveSync). It syncs contacts, appointments, tasks and memos. It syncs to web services or to SyncML-capable phones via Bluetooth. Binaries are available for Linux desktops (using GNOME Evolution, or KDE's Akonadi), for MeeGo and for Maemo 5 (Nokia N900). syncevolution_1.4/docs/000077500000000000000000000000001230021373600153065ustar00rootroot00000000000000syncevolution_1.4/docs/Sync4jContribution.pdf000066400000000000000000002311021230021373600215520ustar00rootroot00000000000000%PDF-1.4 %äüöß 2 0 obj << /Length 3 0 R /Filter /FlateDecode >> stream x]ۊH}o:lPU>P?؇,٥y2)@Ry~n)Ќӟc?~//o_4mzh?u_Oo_a%_iiw;KKrZ_Zm ~k852sV^]/å|FJ]K,piZ]Ϸ i.#2<b;/LX,ݫND}3a[̏!lxlCx+8cHj7!+ qvpq[-\?h+;k.3BOx'K=3* 3pmc穥> Nx?wfӎ@~w9<1ń.Dͮoqv>^a. Cg, Bw<\ߥ#HΚӁU ʩYa]F¹+! xXpmA zZ<5JD5N B3"џo˯<;3v"M}n`M(kR c%4#00Lo5gre!ϸfrK pb6@CJ3 ˭#K" 3(0-ic}󉫋TxqG>Oݓu~ џ9Y954AcoaT]w8xhhsv4!Sp. %»(v8~dvF`̜2o,!ptҊ%qT`^~1.4|U7<嘛8aЏq1keُ؟ 4 <1ɁF#݌ ;T}¸b%Imjq>}d] h4_ a>dݷ;wR0QIf$ustyd/@KgAv֦YκTzuQe|jz.b9NJ%cpEVtqfga2Q2\z`Npt~J XC]Uq7<#w7z`e%;/&bWv8,$!wT\_Lgzm$;k!Ђ`U"#yޮ%W1&Qi=Œ&eҵpt t茧zX 0qk8HΥ+q+1eى6u%oB #!X7Y쥩 (*,ȠxzYGPW෭ P #֛]Qo@l*=.334&><Ys<;y@&! Ȉ^:4Y3eٗfDF -ո,{hyv`G= ew%o 2FAn-`_a@ ´fNa>#|r~bl4aJ71+ߺޔ~Ai[$ɐ  #d00C"33:xX W4h0L뚓M/W=dV£#hBMwTx7\U!ݮ N  |8m+ۓTrwsr|Z. "Y&ɴsA I} Pԡ"]])9y:$IO;DLt\YѷJ#g(:a, HpiDΪr[dFj6x!bv0aPz` <Ծ1qг/3rSQBaP }2*Qe䳕G!N`_JJPBzVVu! ։6P SR|Ktz BZ2lQ" oZyvlOb&o>i|Na Cصr|0̨BCTupÀV|1| v.d$zajO)3j1ir{:!Hz1t 9:>9CjpR%ذUѦ9o ΑP~1? 8JYa5\E6:_2̍0n udR֢4_}H ?ld knqaA L5G" ZݢV#2/m  4 ^Y t~RSk)POGC)aܥ1S2cbgG2&#j (VVq8>*3ii] 1SK;[>~]ޟ5MT+K/Jay0'Ec3 oJvf0KQ悰Sڥ&u'pX^ ¹"Aʪ;WPATۏ>Or;y%k37 Gny͒-}c`L!_M!7c6|Җ>r7 UЋqQ ]\}Us]\L5((5c6WEP¬&TLJ8-h 7IӋծ$x{uJTJM;Nq)W FWᚺbK~*RmVd.+s>h`s(SE3~#mȔ``Bl,ٻy|殏p+i/4d/q~`"ZU> )`QkE+mJ BOBK( BGtid!i nnAKEДq۶W5DBD|"# .r`LXp8 Ȩ <>gTGmQpС]z~dS8èXe4ҡUuܪ-ԉ~aKȪDx[;kwJ2](J̍ >$KwUfC IE|,iS+m.u@' 4^4ɶV=̣!}eU{Mܖ 3asamPֳ<c0z e 7 tv'|:+Gg ֻ/'GWd}lilO1w5Өˤ^feT`z5s}(ciD+ZAns}t]Pwg 4oA 5R1Xl *G%Q,<+T>"+r,CJ\fYz>K ՄQK%Ro~j }= MiEڔ_!n!.*5hCeYYqW~"T3~@\S:-ﮊ?(L3^5O?fh8iB8eO_2s1 _/|Rpb+9n0ÑCHC_y[C"f^[yE/2BQ+a%ӳ/j[ooq;,䯻Oendstream endobj 3 0 obj 4759 endobj 5 0 obj << /Length 6 0 R /Filter /FlateDecode >> stream x]ێ }`*#L, ?%萬SۻipEe.n&6BP<@BZ3KPBPC>O3^*2:$'iŏ8027q'yB讦㔄Rcc,mlS:xl8[s~5 %D Z1jWhۊ=WEgfS?֋XGku_ 7y[L<03{&,2la)4Ptֽy?z\;L1t\\ѸR HWAT ༌Hea}C!`.``-PIciƦ^%qu{ Bkě$ 7z~8T^!tH!X)[]pGWwG7=JNĎ[Ĩ4 -cU+ S0c_UQ`OKTn,ehYjcFTʩ U*bf RZX}+d')0CIP.kl]XM_Mhm:X `yJPc _ͬ@@Ě+] µ 7Uq]#ȸy`K RDpkE LPv4z $#gA]Ƌ0z7 yV6fd;+(?C4iZQl؍Bٙc8 /i+b}YאGRY~ԑ (Yw)y{RP Di!GMlkL5LP6 s!9M,V}&zvȡƸzk9ո38S+yVS-fh,j-;0ߙ"׮{B _tZ*yE}1J3=jĨOY(qH] j^!])]ɾWVP3LgZ 4Ofrv2%ojM{<4)md 2f/u =^8]x(ÍNd#Tv.\[Pn1KL?bC\_ePdPQQ4uəߔY>S^S:@>zU d#g<`FTOY[Ygv3")E鯌job;x4 {;l ZnUƆ#@'T bHƽ ] &j.8+eݰC1R@UckXװYsҜr\o S\**PW9xKͰ=2ڰDd TPKC Pe)Wϐbdp(A&+%Dy !0ϱ3+6m,%52}D}%La{|5RYSsUU3\W|ߴCkUYIޏ81(cdLj κɃQ+&-ұOk7'6s)θ.[p8#Cat\`b37c2xfÍJ=EUCc=yUHj JNO R1G pܛ͓R nXEYM Y8F-2Sq w`G /7`QP[ "|80e2ni`b$]A&(}{&b*eLlYP#iWeZ,#{ <)5_2o6&9BU@< T#ucl'n7U뤄.:Tr_V8>д,a tb̶ܸ{_}'&> stream x 0 `]V ;]; e?ksbB_mp89CH]znpke*\:g#EL, Ghq$YD_%a騈Q4`Ursĉr^QE/1pါ*BȄgBB$oS1 `<[:Mۙ_-r=8sHck ?endstream endobj 9 0 obj 234 endobj 11 0 obj << /Length 12 0 R /Filter /FlateDecode /Length1 50252 >> stream xԼy|T?~]kfkf&d&Ʉ@.dA DEQ)HPI`]m kZEmJhZڇ>Nاrs9w9|ޟk.AT@4_v[amWOi)W/xŝO ^]ӷ钵n[glypW_s^翀g/ߺ~o]BkGE\rRo (XB訙^ȥl F4UsfX+UjV FbN`(de*Qg6̚(͙ҊBM߃\~i?*`@){jG(C#(* Gm)4u{<6:th;~1(3(BȆY zAv[l素~dEplGU*ԄNR|*}qdCҏKqt͇܇Ȁ"h'*2ЏJ_@Khz߈?F>ԇ2YfDG;rh;(zx.'݌nBTnb#,؄h)-=OP 5v$ԋDAoNb5`)#]u轧ѷq\(e޲ZuГ$nxL?f%sRC Z8^w@s5;c& t[8lB,,=S=G^TUh+چE߀Q}?p3oh.}1\F]J+"|ވ~O)(5@=JIaR=<Ɋ<Z6|zng؂ø]3j& o]> 'KC*p zzO mU?ZG tset7};}:fy]>ǭ-^Yit+"\B튠$ʢZK@aD7 n=z} #ڼ~P.|low3Q~آT H5QFjlP'wh7I`{>F aJ {٧orQn>닉do,.W|ҊvhUt7A'a{(z x/SPCF `K`[J xN\7[N|=$&>l_( bjQ*MKyb"6R[am0BOSԻѕZz~  $4`620o3?ec>gl }}ERd)PP|Sp:F\!|/m|kf>yax9ZF_NEE+{aۀzt(sb@AOx5@Vǘ$Z<w(h)/BqhYW7 tmv4vH5ɃܭuF{asurN/:ׯ(aT\/BKMupIZ__7F7xlU6 P.Gِ`~dT"$,sDb4'5Bg1j4/@N۵i| 1 ’ֹ!)Hte9)Lל/t|DF,|O/XM-GR}I}i}ɪ.eooۗ}\_wn*LzjAHU]~l5вo>L5h㨩vQG9drҥ!bB 7q<\Qo~حo+!wۦi>_:R4C44 SV T jh5  +D!0t[tbknM:6Ќ9q/PQ/B,3F}T+ؗB4!%ނ/FYd"lCdj2>H!s@ @A !8VUl[-[<|[{ pu >=}cԸdR>%tF=<!ƨozҸ##xz===~XC8sCj}6N;HD:tK ,!Q=H86kuŗs 01jw㲗\ɗ3+{6., , ~~v7 eAF3]&8^ ߠ֠uRѵ*b@UUEs2f7 ̨I zNeaX#<VUWXg/c hF1!F4QX_̮hw &mn*sY ' ?vwU[iU̜kuUř*S++n,n_u\J_cbE ۀ=9ߩ1ՀfJ%}ޛ k{Ua TyG+z [ [SO<^IccOFho։V=|8_$,(U`{_QOkSZoB+IT=:b)Yv L ]J1S$8N#,WQJH&44R!(z"D^22cL-z% 3J!TZh׼V J4 >95JFq$ (%FS㩓)3 ˏƖXtv`C& ȫT568my ݺ=! .PxԽq> t $Q ?S2ɫIbˉ!rАDŽ'aC-!l8Yp&+a=Af# [Y^(ܦ}a|EWuL1vEp:րE׉NK܀/i6j4 J֩Al1٪:; jo =wdxP>K߃f˥|.qo2eS 5RyT&ΰ2h :É:\c̹7)76;.uwmJ^oWmw\u]rj~}O$ f %gxQeüp:3q.H&A%oV&pKܩdT|RJш1RDdx  }fs:ƀ3*؟TjGrWI+wcX'U$ՋX/7$tTg#, gtɳ=@uZ.i1y:Q&?Bn8ɍP)9o t bL `E_-x ?g}c8ڕޚKf.Qg^pNy!ت{BHKqXԨTkC!5~qۛj~vu؛ h i8"=]R`O[YPZRa WXWZv;{84#x@bxk7 x!8[W2F  |;>fFFJI3.h NFv.YuPYp,E4^WW]gbg|,|D7Y4!˟\a TVa,—:ElUBAΤD`QD`PdHb p uzY_{nwnrP-Cn Cj1 0q4@_ٲo_q[Ks[ܼ+}/ OWqGo|?{GR$H_?2aZo8;_di {Bs8,C0-Z$ BINV>PX~3Xegh4 FM42 &2㦓&/i| !|<C1Z33\vt3g 3⨚SljJ같T43IqEtgrޤ gyлǑPMRTcŃ/Y^9>rp#x/,jzFcGbw8iLf~L[2vInLg( +49>W[߶|`\bvJ'E Tb@3 4wp o-sS3o>t@]'O1T@v#h@5>Zm LiF( T}:m ͩWkŘ?oNXt&u0-ٳEfTA5IcчҬCt[%wK@Ԗe@NGYxu:W oذ{ծJG<ɟ=n +8,Wμw |+X'szC4H*Z)ŔIG2S:vsfUa TQUPWaQ MݫWC |?!aI1ٓrGrG *TKiXOhFV%_ݴlt[+1WA僪ůTh-utTD*z$/N>Ћ1 q:T:^J hՌ>"'*Ps~1NiOȨMߟ|o6K~=~ HӑOD0-F2*r1ۧecY' |@j4С,!g yi(@Ѫ` ԪZ)YMӳ g'y9+w4|nn7⢬t@ؕ s33QO #xGi-aװAJ F@:7p$MP!*:ʨ#%UUs'!p*+clԉ*L 9qef('HI,cYz#0擢sF2}-yɨhygclņ<@ڦk\PMp61X#j԰/|7_f>M]ײrm񷕕PQm@]VYƎ=*'w]K-W<`Dwi3Ӈo>3 _~U2DPش+YmZ,e*q].7PwgYe-Wؗ{}>6{xi0l ?A/r?t|̟vxTqqwX p$t@dd| >}}qI)wi0h ) H"UWa ($ԇ0ER ={'#N sT3 BTdU4S_-P&zIH4NL 34:LlZ@너tA2VP@L<6T )qk ṲEzG>T%gz @`YK۳nQm9}=N-ۼu=ySj;ľJFB?88ad566z7p=oh/ʔ< ;NN2M!j.,;Uv7r7g^'bF ܙɺ [W23\ st3;u>B/q6UV__ogӳ飏~ofT.3Ѭb+kM3M^h?` *Զ+RLc[k cojߗ}K˾k18{vF'WZ}f)Zƻ}oit!+]ks!G"dV63kI*ƑЁK!:4t]G||/STNJ8܆$(m#N36aS<؄*IhSQpP!RT α/I&ߣ30Ee!oGI;+l(^폎GOF5ji~vn5պ}*ъ~q`#nН4IcD 4E4Î,IUOO| $(;&O AԧAk'Ⱦ&?$N,pL~eGzABu!rNX.knWַs Ќl^Nen =!QDF7pDlou呕nbeE .wD Dޖ&D+0 n(čƖ䖧HKxp-›XzdE*El9H܅g j)U SSKvmjEdw l],oXq疭[^IhKֽdחqdV]jC&Ҷ9۹p]KuLb]r*z]ω Ru_ {yN)jzi,@BEzĊ !\5nO{{ݙY .rv|Wk#'Է/^gsTbbw.]ݾ+YJѓ?}qiXvso bR!-jh%=-q\-V hZhBՎadx3

    *Ij^X ےxfqf{ru2eɫ['(]˳4y:3ĘqD-xP3eLDi&%nGqNfTyUĕYPPѪODJ(v2B|"IY!a}}Җme A <;mQuBɽ-seÚp&f8CFUꔌhSF$rU[-e6V[/"c6o̗-kv[ޅ4yi2+&Rپ̍SHH9)]HS(m[}7bV5OBt|OIPD1)Kqxxrzqrǹ0oh=fKE:hmj&iA!ILzi}fY VkROb >ʒy9ESO#J?Zbk<7S.y\GBa>&(qMQW#"(<ahs畔7 S æB}AYq&PY]♔a}e7?:Y?]po7_ʱ6N ƥ.j *:'c֒3;΀үDc.:Dg279G_lV[ G_w>=?g\*ٟwpcLsgeH_VNU+@Ar@ r*IwV AQeWYU=8N$%N?nJC;ʬT\NQ^ACmWUP5:UPE "?J5N("%Wo*9jeF$krH9tLタpIMYq;8@?3mj:mXula~<\y!S`4==RmhCQF?O||\g;|-(qxS)hpX. ER./¾@"U(-VaTtD”GoFLL"Ჱa~ T1|TR!ShjzY20&xtgHјO 5"ar:-q[b콕4yėM֋>Ǐmb-HKߴ*1[!mb  (zl9g9Cv-T)ڤ16]K$ZNZc:^v%03g%3-W/p~lN'LT\a>$@I8JZ}.~jԽnn]v*pDT4#3ԪHRs}&|n)Pab>?8_@uY|T!&!gGV s9g)9sAJf9Rj4Eȁt*zݣ1|adjtD}AVʏ]rB3![2y;h8f#7"x5 @}Lg"EXM2 ăӱf<|aȴ՞a;ܑ|zEtT?_bo]VIPMBuޫIOS+tn*2{ W(8CF*l(Jq=fz,zz5!.cw#3z؏ť3JuU+JR#8}^oZ eLՅScgM&n\¿p ̲VIoKUjj:jnٍׯP4Z]WY@<ߐ\;x%3:-NdZAxR x '`Y aTC*;hEfJLh 6BSTK=R|o7t7]⏊P'xz!hA!0[y睜wjc[,e>rY+x]}~R& J_ ugRUTvF?_D)7>y-!3ۗ=k-`/(3rgZþr"/ij/Ag$ dN"3hd&YvLUU/L#Z2 %uSYل9D_/@>m2y_4{$?h1ghEToj?qCjL9]ެ/mp8bTp2/=m{tWjh!󂭑hGv  wn+!^G_Jz=~ɔ (όM"ʨ"5 MN"n>vǞÅm60Uvkk hńB*́.( A <"%=Hy^U01퍕vH=)+`,jŢ0>HWy^i*dVB@ml;b7h*?у?=z9*zAzEQ0e(xJ`0_cqtI,@-+Eێ@׿;xz^ԇ²PɆ^ # I c)\S1H?-]JDQ\!4~ndkYg Db"xdU 91yDF EN(ji'~OLMgy qM+'ʖ~y/ ৕`’ĿRo)į4,5$0MMD;.H$9sȖ"ڈ(<>O]֞^A6#=fHVƊ?PWu E379g 6 ѠK-׈& i7 `yZP⹲~@jKH7m{J+m}Z0z^};Ev+Zz=dAxDyUQ#A^RSp1-Ct'lpڧ^sp`73E̔[?H^r![:%F59ˋBrm&f%Vhpl)K#B1}tnx(99Nn3fOz,ªva(bd[ӫnÌ*J&7.NX=h%A4W!N4ާq1F5VA`z'=d:}[ RCb>Mi^0]h Ga^cClM1ꃣĨ1z}-Rx7·h N)юv tL,z>#Dg`L9aĴC,'KBΰ:l )\JwXeVo*T+8Dw7˞ښZ^&G۽ϼPƊx|9śx &WXm  b;=+WF*鬤$ODW2gK,$[w:=UVasokFa3ۙ3>#|MR󺘇L\vK16 ;)t.:LØ|p`P/YޖÛSvU]i789(vO)A`d MpY"{F`]VWOYl-l䯡ثۊ3Luf)6?ؿf|~i w($͏qH4N||K>: 3 -z(>EoW(H СPNte4Cr.;=b\|"')իy}UXY֯<7 <ۄ[7-6}XaqqEqϛ Wk{'<]G?z凟]PHo|Y/aC!&Kb- ՠ^'[3nz|R45l.{]F@9@|0ow|?%Qt/?Οi~+NI)EԡEH?^il(+>Dh2Ӻu?)+JLK29w'>RIȋJ&DVT[VOsThQC^rLF},f_B!r#>/(ctDr}D*CIT?ubE Sk4]e;py`!{Pzp{ʆ g;_\_TԒ9/W/%{}AvNmC!J7DŽ79H#?<yВߑoFV(A/¬P4:/:^22a~=wNPA|z*&AֆC'1FgS,gp԰HYU Bs>:I=^bg*}Do((ݬ|?qHA_\oecCA0c7y}a;b18b< ^2(.))?gx6rJʌI2 8#v{Kx{z/-.&dstp, X)! u+!rAUxvuȯ4J$BK͝W kq9n:ZUAwӏo u[o|kݍ2q^k{n^n i{֟}gbd}~G$z;}VHMO0T쬸Q9rzҷŽM 9܏(NOXmYK4 1>H3rӜat(L'?6IDfS-p9 ƿJ*)@_ X? ~HJQ 9C]AQI1I lfrz ոW?`1h,ӝ 7ucûV޺oMt{5{w1.|XǶ+Djp'_ީ(=6灾' bY IZE0%çL@50ZxsII2gޓL2`N Q0I&M2af­om>@! hE{W{ookXVm9}?^k>g=(FtI7+䕹(Wb228Lwrzo[%1!, hb ; 6dWSb 7DM!1: XU؜3iׯm}/4ĶVg XQ͒+̾q_~>voi"c?LgbMS8/+b-EI0(.HƫN=H 3RYO׈7koF7F<_RZt-a#;#^~L>xiO3=l9|US0rl}wW׏=߻~Wl:;Ǫ*4{so9=ZWwnxzӱljvZzpˍopeZ?)ԃ0'™Rb%R]lr[_b^ï1g0 c5FZ^2Rd L.2V+UIѲ*o=|SXmHO1o#y AB.8 Ja~4*$_RV|@lJ ՒWt;_ j؃]O{56R ]볒[d,BInSǬQ8BH^+_.?,5Mkl)2ƌo4Uս$}lvXla[V؜*j j_fmmv5v3RHSW*ɹ r~4o?ag]рb"or6$sFs$9SXԐ1% gei1nf(9ͼ¼Q0vUgk(JGں[KΊGvc ]tołBHS:܁UYMl7z1cg^q+p[yX:/֓?ޮo>7PQ3x/;hE) ]{`㑜\{,503gV =g>/S%ٻ"Eg{9l"$OmC.f{;̝4=Gt/2b]n7˰׍r>.G>=i-a=J$R]833XAT薼EE+3?(땣yOzOT ߿O\Qd0h@SW8Q.R2 ^*0F&5X3B8)poف2 o5'&9@(}sS|&hV&f(!t) (Uz.K"t~ 4{HR:^Kh*e 4S[F@ mU5c+q\92CX -z-ްαM=Y}ŝ{%_y㖇%+v{mӦ{U1~K iLq~AP[FV@ Q ϴMhSnt{ȃy;};Bh{`$P僑"x-L1oM껪kȫ|wohwc/V 8_y_Kا4f#/Ѿ)pcUon9P U6# !a M۽ys5|MsM;nnmo yi@aƛl"q[X?^ޢ;J@-"^JJQU˺FrwJi) Z{LY݂x{9}tk?](Ӛ1 pEMyMR+|l&t ӡsy,İ3>\ޣ ȋ f9nh~t  3zx0ѷ*z4qC5_f ż*~ь55'QHh1-SS5"|ygl3@TRtPBW EpE!8x\%9f%jٻw+m0֚ +Msab'X@WʰwpslE+3gMWE8m!߿Z9~GmO+Alg0)6j{7ρS}i; C0}>֒ jd+e#z Tz* ( 0z_Zt]b6鰹 KNm2dα>xN96Ol*#yu\22V 2TSB xib"9!A`$@ 4z_.5]]}뿬I>bo>tSˇijʛ:Ԋ,r[=h>/TxvnqWgY_Q*ګIտה7UKu:|]0 ,W7K W|zz_s͠|T=j5ZF=/IQ_48"U5T9NFhJDl*\nG*i%d*Vk7/0=c?"UJ*rʗ[UXpDh@mJ5C|ߦb5Qy>;Py_LF#9Q.)ee*Xp5OB7uۉhi U}~~.!\iEwMTbEpiyC5̪I׏aV>_Z\Z5,k%zx!b;C`]L { +l}bIL'Arrft~<+2-u0x.5H(<.5kMSx:,X`trV8ddɧ>O4(Ɍ1+xtTJA1hecx ދW Oq4&!η`Ywa?a gүBǰt :w3 ˜|h3,`5~ڔ>=g=. юy\ڦޡّͱ37UrCwW|Mm='xoǔ{q8GBIS'? Xi܊P"][)-L:$ yBѭd+WCdžUaYެؤf%rb|K8Y!|Rw* ;PN^7CEIct~G V100P I>7s X#/?PRkvC@~@pCpWcE) b&ĨU*B\Ov6c|UŤ[FY8[6UlR1@TPTxKyqs'O~Cyn ̮5I!y'akP}~-&i`]yA2Grb6XsP:ːl86]^  XkPrJFt*9Ff dQ=;w<_1!\9`)` pL sJe V!; H?y+ۧߝ~{__<~uWq-^~fq1_:r;~ncg1CEx 2;tCᑪէU>MrcGu5I0cn~/ ?}Rw*rji^51cd+hW>tRFDMK5NepHI( Ǩ^øCAsB5USg]|:fXpt-~ݣ(QY!nCNG宭_訕J$z,,8`:mNG((h!꣒-fNrn@wZr\n؏j, kjG6U&nTdL00y m2cZ<)Bzpߒgp-}qdG3]6yOGYX0Kl)aMs9ײgmO/2>>Mn+Yk(CТ~:U6*.WXbAGoObgu٫МD}@aNJlyHXm0C>Htv|w5RjlT~9FHCN[ .Q\,{8snZfG+ӍsۄS|t< Z=j'7M&|yFPfoa(wၜ/9^*Aa3r Yʻ *dNrN屺 cZ,H_dG7IM=.=1`=.2眙. 6ɾ.e__P)0:cG}3`m_ůZ8l\!DK#08>]6sҾy4K>?1NORlv@!?ym ªk"}$,7- C-$FlB?n{kjmXej[ux6٤a-AEXɭUʦn Fhat(|reK*iޱJܺ4 Xׇnbgߴ o9GpS #έjF8m6: 5pnͳۭR@(k`7&EkL 5N* ɮ6XhRX1̘ Mq91/f690ٌ1C5f%NuӀdxm\svfXѓ}V=|>Y.c9dӡЦ`AɿKwDWL#$yd*㫺 ?>SXo G1٘|bRu: \nQ(\:SP] 9=: !cȹ+KKKs QBv|BrIʇʏ8B7(o8Xǚ檁Ul2}}3'OA}t7xLU8FޘЃ 睳|"V3q.jL|E+h3d᣸8LW!8wOޥgk>`TR۞t=r+vN%*zca7zY==(kU=d 6-⽶q۸]]RR;%KTK4m+dDg(x!/x 6YJ\,tͦ&Foj21D.$&ޠmcg˫:.:KWd34\ V{}zqDUp nRdeXӕFAb0^A.!*#/KLjne2 !k6O{ڑgzߟw_h rgvOΉ{b$9?'n큫?ܽ~G}}+j>ޟ'=lEfO8h~XyJ/s2& [a "lyJ03/ Er9{zt޸ch3rGd叙H5=d;Ɉj%d`mv%d=r55/ZRՌֲkdR7/EuRHTAk IaCSh:᜺7Z2N!-f fLF4rJ,azN`EĊo-eEby;lq93o1 u÷ٲ~fη}.37lmKZ+^xfy>X Eyډ*(ܦ~L?nik٭TJ+}UEߴ$`/)r܂PMT&" ؔH}>eOd*5f|do4uȌ<_Ԁ7ŬL7։mx/OCH)hf+%S̫|Ha XTZJ 1$AW+Z] 0Ҝ>*`^-kZF#se@[(=8~#4 %i4>.Cǀ/eBY嬅|wK<ZH~t=ZA6X P uC_L5<;?&Ұ3M 7+&a/x?fS!u˗SL))Uݤ7tkM's|2O;mSc|Y ~)]=;=mXaE~T||^ ⻑/GޫJV͟w~bՌS-Ci/r~(`*a]M؅SDBDpp(E\ E\lːkW ?+mE\%]nq5J)Y5SɋVЗ fdrpB1DZ%"΢Q%"n!qQq\(ǔ/JqsĔq5 D\""e7w~ -BuHq)ሽ2^Fq9 Q\AW[DxAāGy"<ʻYāGy8ȑ##GDxqQ~"#w@āGEx IHW;(BtGqKbn*4M;ʹ$-4|v+w4B hwR8Ž4/+/\!] u^RQO8Z4Wu8jC֢nBISqh1<'n{h Bz!9`KhxL8bzL4-__X% _ y j(/ BCv@=qh54MP>IS3=/rsAi陞CA(4@'R_J% \_w)O: :#PB5C[N3IK]-䠝$?2R*pNWB/iۻ/&AP/iTvIaڪz{đQFe1C !#"RWRYOgh8VRJ>P.u\]T6Z27ӺqIڑh_Hl:fK>g5Wv<꧔LCqZnS0mk; Ӳ:hdvIti0(&СҮ ک%hEsD 6B٤>:>2b2iIG729:Ch["׺(4oEOӺ(:(6Ht|Yem4ˉ0>˿h/&hA8Q%6:)h=8h$ мY R4D̘ Nuf8t݌]::}cL"*9\'g/ȥ@~Q_LqA:(J}le3W4sE,ڲ^#M{+J]7c\7%;һ4Eㇴl4pT*~~S];^Q֍=V(itҒOЙ"o @ 0"h>iy*PP `;MC$e .1&y5x%ǩNO)od䢔B)J,_<5~ ȿr5JduB4}7C5iyJʹla 153G[0DV'ֲFZKچMJFR=Hx~VеHKW͙w>;dû#3egy#rY򚭓xhΪ`ͶGO}s8 di+tN$yXCoڻGzekaq%cg兑)JE(Hfg5I&rfyy |1Rٚ{1t>Jy"A&_'Αœ2^|xM(h8mlEs,fxלz蠔caO'ΤN͑uSjKtly2MovUy)VXARg#ktZR0DզhV>ӾsٹPX٥Fi)W/AzϖLkq.!a c2!Χϋ>GZB2\~qXZwZ;K.ħ-J3al dTJvQ4T("EޚCb0V@LTbU`GAߛ3c6.t fõ#x{;̭Np qd*Isܒx&_$ ¸5ACX,\Ne9W]՝IsW'҉PcmO_"ͭN sW'W'{l5Dsb|D*M*,_Wdgs(kV$T#O]%;?\*Փ$R@ukxskWqM\Kݐ|$P+=]$o0[&F t[ӞITG?dTIC[Hm .egO*F[Bǡ+Уpz >HrPpwO{7דi#Ots !tb wkK'\?A#Nz;\_loOӝi\{*AiHCk]=^C}bq}n# hPO*N6r)M3 -B Jvu_ĶL?3N&WH%ISDC!‰N]G(2@$)h m9ځ܃H4&9Df0Ne7ޟ>dᒽ\:3mJA?)1H?ý m==xp dd$ڒVoH#ӂ%ץi]=  <$ H ]$$Mѓ荏CT; DB脺3}L/Koց<Ȉ,'gD/DeuK/_i5״[|q5 \V5^UiUt wS)@/3diz@-"~#A=9DU Saq]Dklq`V- .j d0pD@AI@v3ɮ 3 T Gd3sCARt"37w9F$l/O&!sD{g{wѼ2aPJST\ҨޞQi:2.A'ɣaPЃm=nR%D1)tqE;g;GF!(4M{"/ %&Nw'aC=0lI:dƩ8I>B YVw^X قx$X&"n^|bjV*5B`"w^d7*VӪƨHBb8rn3ɶlGtᒍ˾^ܱﱧNأ_|XVŇ/>|a+_|XVŇ/>|a+4~}IEE>YR(4JIV)!G}7d*E<Ҧ]{st__O64TSS"XTI#&+9 ޘ8h̯&-2Y\VF} WBfo@z`0g_+f_b_l/MhP٧ 9q1X]} 9 JQ=OH\'@G#΃Ĕ@`eɵ}!= WػMx&~aICblѿĿW|bi`NC}!sa{Hwa05>rpy(vNZ;;r;r;Ϧ^HS^i4C*a6 Qp  tO*4OCz~ {'vX^;tuMʺN 5vN *>)UD׉MĤRCByױhrDH oy]9GQvT2*`i5+-CȹۢP)Yr]DX+$;aYrDnf&BNF-Dϩ_UKe^I_slm d{edʽrfz@=f jNVf)oeQp {$@α_ 2m/lԛ_~lFRxC:=CB%1[XLL6I Vp `_Gʧ( O9D=|ae_'jv>?tjMx:\B$# CD$!C-VD4~C6!TJk,=QMgGxDmވ!iۑ!-Y)x% 2!sD)ɵT0Ox* V>=VN -"yhG}k ύPIYk&Gb>oG?9I5|t N8E^?|tJK|y <i|Diq=X`2ÞM}<ἱ =^-W8Wq.Makɽ*g'A)|;E2pC~mʺy*$ǃ|<#o_%_ 䜼@/UNQ L!Q0 ȥs2dJ(n`@``簍LExԈ.W8%pqE5axgnhFPtcDn8t]_]--qvƍjӸԳȆ,Cu:%qmFh+q Z+ rq|KO2[dÒZ6ۙ Wp}IL2f %I6$rIl%MbnXr}/'@|Ou ey (x)D^Z)$@s 5O I$96 #Мp 4A! HQ@?K,o$Ɠ_v7$< [< -_궍qܱ_noik&~<1KObxg w,~v,9n<'Lx'eƋ=S6J [L:xF}Hj$uѺ^7SE-$Zaղb. SӖe\Y4 Qee$ F'A^ݰ8 l,B%гd:,73ha5Kڴqva߲do k_e{j>Q+l`i+nf;uqw?ᖑ/m> endobj 14 0 obj << /Length 533 /Filter /FlateDecode >> stream x]͎0H| )B$)>'EjaFU7烝lCی&1t1ܴn}9KƑS7!W]>zhϝYȘpy)|C^ӯK{ Ў&0u8=LC=e4y*'#\p* e{ qN¬"B[V:0g);M+f! RXG)^!2I!r|´=Ċa$xeD Yؔ df> endobj 16 0 obj << /Length 17 0 R /Filter /FlateDecode /Length1 27708 >> stream x x\ŕ0z[io-ݷfZVddɲ-2(VKjYe--l Ğ!2̄vE!h lę@gƐTݫ@bf}{VU:uYvK&38?^bMMHTDп4§4{.ʶ#C7 `*H}z8yllOc1 &בyΡ|Uy.|>< Z+.;^5f'& ?oQ_C<$7p܍ʲ OO("Mp?ո>$kEl*qoG\?WP_U-a< 8%ђLTɮ'7\6vc;σmZ n$$WzE>O繓U5] QqQ+--tNB{a$ؐFr9REȧIY o俸tK p'>/ůQ/d/=t6{G__dZB&=o#(sS}ΡBThMVI\$";>rI$'!gp \w9w w#>]|e'{ieU><:Ѽ^_]KUhyIs>hD<CG-1 F~ ~7Q,$|KfZHu+'7W}/2 )9E~G3W5pቺ^|kox3o|.?+<Ebx6&.vʯ+2SjNZPwշ>:E{@ׄ;ՊssxrGܗȣEC g?r7(Fb/w%O)7.x%}K'-0L#_Nqj1jc\A+,ʴ{<}-Ux;wQJnq^ Ox Nr(իҹ)U j8ˏ 7xmJIJ;ωKLx# E̢PFz>ة8:/R2DPi,rocW0V8j008Q?U 497.@!4w Ƨ,CJ{! RЌ3m`EοH_[٣¿$蕎RK]ZD_6r;. %:5|\M]Q!#Ms#iu\ZK14d*jnZ]͔(vmҜpFIӀ? ƨPmU5El!HO 1g>o k͎({Xos4m.Olٵ@}Vݺc==Hr9}Zq;P܍gG܂[ $T:wպ]ÇCd¶iG,#C<? -¡.G>o: MZEzLQ1E1I k:ڷHP\ €pᙪhCU`Tw\C2hŷ.#;@Aj'+pPQ7N://*slP|ЅԔDNtf/@f oO33)WЙ噕}.3MJTY1SZk$oL;w-dٶw_ԓVd(Դd,5+ȴ#!395jk1UY]&^pQ"ȰõwVJ%+ x_ivk7ʫMCj|fVeIJn ؊=$dh3+:2nM=pmY==Ӗ,6|#텴eTiJmȚI%̛RJ<})TIOԕFK;vrل̑dAǷ,Fټ-Ub!q$'exNsQkuj=fo-t{8/E0}2MӴNRmrߞoֹk%JOρ[L!&ī4F:ͣj٢b.>/ˬ D¡X{9k.zVoVty}.碠Rt\D{"(.艞.@EҴ}2[Hct/*ՊJԠSU'Z)j/*"JRy*Wr/ȩL.dThv5STVn^-Np9unr15JLq^!&QgR u5@йOWiJgmAJJhO7;d2,\Kʓ+*]OrEޣ'' f3>/v&f/gl4l2VMN./,>S6>w۳6sm3l \n{>bKWfmhY!_e֙zA4 #i5#Oz7ӸSN5>k3֊u|N2$4;9 RF_=wqь)k^Zjh^" Cl)ɒirKI$5\5{hjj(.T )Q'T-7z5:O)nWK} U?JU9ʻ+5 *unLo.! ^Sm3z!n1'Tin{"m0Y'8:/Z|TD%IR%ZtZ*,YiE>?!d8CAzӨDki3+|MJcXRTꋦi(E[lGmQVo;M9D,o*p^$Eh,KW!4Zw|iI]gK.nTQziǺJ98'TnEr3A.*[5ani2]yͣC&Y`vH̗l*e }] \iߕ?4??7d[9k%Q4Rh2ϛJ5s fQoLXKC%R]bƠej^&6z_FP0(sܞ̌ N99 g.N`v<$`Cc`){\QkDž4)yJQs;8;`?sn&z{sk-ҭG݃<6uG#}_iTNλܬmް~Ӻ+/\?߻"֤L.ƒK}\6WjhGm'qpVw&d&$lJPT'ܓ,Y) >LJH+ISjW+].۞rv˥T)uրVӃӉZT4ū00ͫhW ]Ez*OIEI*Stsӛ3[n9û OX4rhD?&vw͕ݜ;ٞBR LFr7y(3 Fhuɉ;?.D|E?3Mn)]ZxGcӅ#GjCRz$Iԡڡ^|:8ڥ>B~Dl-r]"k@n[iUە99f[b?Oc 6ŭ9f0ݙ6d9;:zmZATjrs*{-j*zI/t35jwr]$v-NL=:ubJt_SE/'$dpYb/[<٦6Mx2 m-f. SԔ դDMsJ/lqVFbj7rm34A31f<+dIq>b}Otxt?;yBѕ3.  }UmzEl͋3B 0z#M k/xk-%ޒEt+jVR do8i`DkI]_YԒK**N3ީ^p@6̮KJ{rUhce?daag˧n6|utm*&S_>S^*6 ޹]!]\qcݺJ7YPˍo{nrFm(˿Y_dR[g^HC؞ܛ<<HNVϽhy{jiH&r%?ď97hx#E[VY#hxMRHҚ:G$s6צR=QK }Ǯ 3-b2J]γNSȗLYhجh+&$} fb:b>C?z'xc=)G v1Yl9i4NhAFr,6 I/ đ*}*l՝y^))|vgʹynzaϕorR\>#ۛJуBQlPMvZN~GV1%A-ړVs+;<dg^rJY[%N=w{)T \n{˥vU8Da-R؆t:|hUnMNҵ(fW؜>h;moSeM&3dX1[iZYP\SEl٢EXtS,hl` N7pG | V |9n`oygYe_n{k;[:A蕯üh^C51ӟէojwɺlA*dy)mDgHDUf# 6IY?/=-&VKҮuШ {Zk;Ʃ zMv~MYmQmIxOf".mhLгz\?g6Q_0?Z 8oK}0P-r?CP}S;nܹug?\[](5,eK2%8g?Zʝ]=ݵtpħL?gK^ĵnѝbo_ ԫ.ȁ}QV{2վjwuV$>ʿK֩K-p _FU5R[+y.w2B{7h@~Y1Jrb?Aehhϊ}п$9|<N,G%0*%. ӾdݓLIvvi.FRM\³Yӌ"N:"[0ӣ_7$;z0EC#YzFV4<"~,#iē,JaFm RuH ӲƓ~v̫$!*X3j)kcu)D䙱b ɋLbjL~L'a2 lZ}kQ2b5ZduJ^^uEa-yC+9xe;cXtt׷bi;(V=x lǚނ.Ìނ6 бw>aLJĂqY+eU]e-K.IOM2}rƥ\Fyɓdk(y \I} ʑ$<r%b˚Y;?:_,))|*>sPQ%g[K7rrt!; q&٠E>51$-c)[ X^>- h1Kqvd!*٪ (' Z(^g幒Ho%vO[Bp@4 #Qa|dXhG@*Ąc#t$,lu몫K*+FFmÑ-'lm\^9N\veUeB^Gp 4Z\6lk{۩C^h067*H BQ.t#G  A!0FJ(!ڡ k"a{?:FBhpep 26Cv}Yp0P#!ϡ`(#E-=tp7c+YE%W.›xictbe:RJ]jƢLk.o*EKQ5kv^+M EBTlT+/Eᯬb~,IXTrd\ ]cRYK,[ޅ3XWwros #^ ~L[X^~ed=๻eKd3kl* -(*;ՠS)@A0FUye>Dߟ5'nYSR` p<wef? eQ X(٤TJ1;+I9!5"p-6l_6?@|pd._1o ۇ 2fe,A? =`,`WgN_`G92I,M@]/:xod8$.v ljcXƲ,uaѻf2 ~'[)f\ooã1iRfzoG#w.U5e'NvΘ% ǒ hi%iYv ٰW;`69̎XÎw,XfpJl֜&>ȗ1ˠQ,Q, Td"41x cQp a,b9EFp8^;a} KoZ҇eQ,XaQI5T (S<ۡ1(Y pA`b 9@q@y|s kiUL<*-_]ZެGvU5ǎ:##jdC&SXb$1cό=YdݩuɎSg;zɢSEgx#r#aRO.~?vG[P3z.=o z>/QռjAuZuNRU3#*]]W*Ź&5QX8lf?}Xu\XR i f>Ѿ RK,un97YpvsQo^e\\ʸ|W~"Ņܾ^AW+GzA"] ra]J!`E),<`]eLX۱pܽX=e3s=1ClRc,Z3v5{=HF$t^|;Lqm1]RvǰpTu=c8~ ʺ] xv,=!GzRS1'Z4sӱ`}{ʆ\3Ǒbu9^&+рMfc66I; ZD1 mbqZ/5XQ sd)V/llދ ٰy76| qH[lkH(Ypb;G짤PGrp8"c# y(VRXcC|-6t'6:=Jnctֆc8=c%،:ʱ16Xt79Fк0N%5P(_"iD E>H3i7G{d})m==:U^XgnmO[W7뽨}#=2 ij{ڣxFk[s{hӳ8FZq2܃hq{ :hh5PGp5h7c f C)!5Eo'M !}Mڰ E r ؆Nюy> endobj 19 0 obj << /Length 290 /Filter /FlateDecode >> stream x]n0EH,E3I#!Ģ=Y&ufuYk xhA8*{Q R wCD 3Zwd`tvI|hh8L|cA{yּ͗=B@m-rv1gs q4@+^9dUj?%t⫵:Za& 1"H RCJc ,;8Ȳ  NiDĞA^n5m&C˼ha4uBJ}f0=~5ar endstream endobj 20 0 obj << /Type /Font /Subtype /TrueType /BaseFont /BAAAAA+TimesNewRomanPS-BoldMT /FirstChar 0 /LastChar 13 /Widths [ 777 722 500 556 333 443 277 556 556 250 722 500 443 833 ] /FontDescriptor 18 0 R /ToUnicode 19 0 R >> endobj 21 0 obj << /F1 20 0 R /F2 15 0 R >> endobj 22 0 obj << /Font 21 0 R /ProcSet [ /PDF /Text ] >> endobj 1 0 obj << /Type /Page /Parent 10 0 R /Resources 22 0 R /MediaBox [ 0 0 612 792 ] /Group << /S /Transparency /CS /DeviceRGB /I true >> /Contents 2 0 R >> endobj 4 0 obj << /Type /Page /Parent 10 0 R /Resources 22 0 R /MediaBox [ 0 0 612 792 ] /Group << /S /Transparency /CS /DeviceRGB /I true >> /Contents 5 0 R >> endobj 7 0 obj << /Type /Page /Parent 10 0 R /Resources 22 0 R /MediaBox [ 0 0 612 792 ] /Group << /S /Transparency /CS /DeviceRGB /I true >> /Contents 8 0 R >> endobj 23 0 obj << /Count 8 /First 24 0 R /Last 31 0 R >> endobj 24 0 obj << /Title /Dest [1 0 R /XYZ 108 451.9 0] /Parent 23 0 R /Next 25 0 R >> endobj 25 0 obj << /Title /Dest [1 0 R /XYZ 108 386.7 0] /Parent 23 0 R /Prev 24 0 R /Next 26 0 R >> endobj 26 0 obj << /Title /Dest [1 0 R /XYZ 108 161.9 0] /Parent 23 0 R /Prev 25 0 R /Next 27 0 R >> endobj 27 0 obj << /Title /Dest [4 0 R /XYZ 108 720 0] /Parent 23 0 R /Prev 26 0 R /Next 28 0 R >> endobj 28 0 obj << /Title /Dest [4 0 R /XYZ 108 614.9 0] /Parent 23 0 R /Prev 27 0 R /Next 29 0 R >> endobj 29 0 obj << /Title /Dest [4 0 R /XYZ 108 523.1 0] /Parent 23 0 R /Prev 28 0 R /Next 30 0 R >> endobj 30 0 obj << /Title /Dest [4 0 R /XYZ 108 457.9 0] /Parent 23 0 R /Prev 29 0 R /Next 31 0 R >> endobj 31 0 obj << /Title /Dest [4 0 R /XYZ 108 406 0] /Parent 23 0 R /Prev 30 0 R >> endobj 10 0 obj << /Type /Pages /Resources 22 0 R /MediaBox [ 0 0 595 842 ] /Kids [ 1 0 R 4 0 R 7 0 R ] /Count 3 >> endobj 32 0 obj << /Type /Catalog /Pages 10 0 R /Outlines 23 0 R >> endobj 33 0 obj << /Title /Author /Subject /Keywords /Creator /Producer /CreationDate (D:20050907233344+02'00') >> endobj xref 0 34 0000000000 65535 f 0000057597 00000 n 0000000021 00000 n 0000004865 00000 n 0000057784 00000 n 0000004892 00000 n 0000008720 00000 n 0000057971 00000 n 0000008747 00000 n 0000009066 00000 n 0000076980 00000 n 0000009092 00000 n 0000041179 00000 n 0000041206 00000 n 0000041456 00000 n 0000042074 00000 n 0000042604 00000 n 0000056540 00000 n 0000056567 00000 n 0000056822 00000 n 0000057197 00000 n 0000057466 00000 n 0000057524 00000 n 0000058158 00000 n 0000058234 00000 n 0000059599 00000 n 0000065305 00000 n 0000067139 00000 n 0000069639 00000 n 0000071273 00000 n 0000072379 00000 n 0000073025 00000 n 0000077154 00000 n 0000077236 00000 n trailer << /Size 34 /Root 32 0 R /Info 33 0 R /ID [ ] >> startxref 77533 %%EOF syncevolution_1.4/m4-repo/000077500000000000000000000000001230021373600156415ustar00rootroot00000000000000syncevolution_1.4/m4-repo/README000066400000000000000000000006261230021373600165250ustar00rootroot00000000000000This directory contains m4 macros which we store in our own git repository because they are not necessarily available in a typical Linux distro. The m4 directory is created automatically during autogen.sh and contains temporary copies of system macros. It is wiped out at the beginning of our autogen.sh because these copies do not always work when moving between distros: libtool had problems with that. syncevolution_1.4/m4-repo/autotroll.m4000066400000000000000000000501301230021373600201270ustar00rootroot00000000000000# Build Qt apps with the autotools (Autoconf/Automake). # M4 macros. # This file is part of AutoTroll. # Copyright (C) 2006 Benoit Sigoure # # AutoTroll is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, # USA. # # In addition, as a special exception, the copyright holders of AutoTroll # give you unlimited permission to copy, distribute and modify the configure # scripts that are the output of Autoconf when processing the macros of # AutoTroll. You need not follow the terms of the GNU General Public License # when using or distributing such scripts, even though portions of the text of # AutoTroll appear in them. The GNU General Public License (GPL) does govern # all other use of the material that constitutes AutoTroll. # # This special exception to the GPL applies to versions of AutoTroll # released by the copyright holders of AutoTroll. Note that people who make # modified versions of AutoTroll are not obligated to grant this special # exception for their modified versions; it is their choice whether to do so. # The GNU General Public License gives permission to release a modified version # without this exception; this exception also makes it possible to release a # modified version which carries forward this exception. # ------------- # # DOCUMENTATION # # ------------- # # Disclaimer: Never tested with anything else than Qt 4.2! Feedback welcome. # Simply invoke AT_WITH_QT in your configure.ac. AT_WITH_QT can take # arguments which are documented in depth below. The default arguments are # equivalent to the default .pro file generated by qmake. # # Invoking AT_WITH_QT will do the following: # - Add a --with-qt option to your configure # - Find qmake, moc and uic and save them in the make variables $(QMAKE), # $(MOC), $(UIC). # - Save the path to Qt in $(QT_PATH) # - Find the flags to use Qt, that is: # * $(QT_DEFINES): -D's defined by qmake. # * $(QT_CFLAGS): CFLAGS as defined by qmake (C?!) # * $(QT_CXXFLAGS): CXXFLAGS as defined by qmake. # * $(QT_INCPATH): -I's defined by qmake. # * $(QT_CPPFLAGS): Same as $(QT_DEFINES) + $(QT_INCPATH) # * $(QT_LFLAGS): LFLAGS defined by qmake. # * $(QT_LDFLAGS): Same thing as $(QT_LFLAGS). # * $(QT_LIBS): LIBS defined by qmake. # # You *MUST* invoke $(MOC) and/or $(UIC) where necessary. AutoTroll provides # you with Makerules to ease this, here is a sample Makefile.am to use with # AutoTroll which builds the code given in the chapter 7 of the Qt Tutorial: # http://doc.trolltech.com/4.2/tutorial-t7.html # # ------------------------------------------------------------------------- # include $(top_srcdir)/build-aux/autotroll.mk # # ACLOCAL_AMFLAGS = -I build-aux # # bin_PROGRAMS = lcdrange # lcdrange_SOURCES = $(BUILT_SOURCES) lcdrange.cpp lcdrange.h main.cpp # lcdrange_CXXFLAGS = $(QT_CXXFLAGS) $(AM_CXXFLAGS) # lcdrange_CPPFLAGS = $(QT_CPPFLAGS) $(AM_CPPFLAGS) # lcdrange_LDFLAGS = $(QT_LDFLAGS) $(LDFLAGS) # lcdrange_LDADD = $(QT_LIBS) $(LDADD) # # BUILT_SOURCES = lcdrange.moc.cpp # ------------------------------------------------------------------------- # # Note that your MOC, UIC and QRC files *MUST* be listed manually in # BUILT_SOURCES. If you name them properly (eg: .moc.cc, .qrc.cc, .ui.cc -- of # course you can use .cpp or .cxx or .C rather than .cc) AutoTroll will build # them automagically for you (using implicit rules defined in autotroll.mk). m4_define([_AUTOTROLL_SERIAL], [m4_translit([ # serial 5 ], [# ], [])]) m4_ifdef([AX_INSTEAD_IF], [], [AC_DEFUN([AX_INSTEAD_IF], [m4_ifval([$1], [AC_MSG_WARN([$2]); [$1]], [AC_MSG_ERROR([$2])])])]) m4_pattern_forbid([^AT_])dnl m4_pattern_forbid([^_AT_])dnl # AT_WITH_QT([QT_modules], [QT_config], [QT_misc], [RUN-IF-FAILED], [RUN-IF-OK]) # ------------------------------------------------------------------------------ # Enable Qt support and add an option --with-qt to the configure script. # # The QT_modules argument is optional and defines extra modules to enable or # disable (it's equivalent to the QT variable in .pro files). Modules can be # specified as follows: # # AT_WITH_QT => No argument -> No QT value. # Qmake sets it to "core gui" by default. # AT_WITH_QT([xml]) => QT += xml # AT_WITH_QT([+xml]) => QT += xml # AT_WITH_QT([-gui]) => QT -= gui # AT_WITH_QT([xml -gui +sql svg]) => QT += xml sql svg # QT -= gui # # The QT_config argument is also optional and follows the same convention as # QT_modules. Instead of changing the QT variable, it changes the CONFIG # variable, which is used to tweak configuration and compiler options. # # The last argument, QT_misc (also optional) will be copied as-is the .pro # file used to guess how to compile Qt apps. You may use it to further tweak # the build process of Qt apps if tweaking the QT or CONFIG variables isn't # enough for you. # # RUN-IF-FAILED is arbitrary code to execute if Qt cannot be found or if any # problem happens. If this argument is omitted, then AC_MSG_ERROR will be # called. RUN-IF-OK is arbitrary code to execute if Qt was successfully found. AC_DEFUN([AT_WITH_QT], [AC_REQUIRE([AC_CANONICAL_HOST])dnl AC_REQUIRE([AC_CANONICAL_BUILD])dnl AC_REQUIRE([AC_PROG_CXX])dnl echo "$as_me: this is autotroll.m4[]_AUTOTROLL_SERIAL" >&AS_MESSAGE_LOG_FD test x"$TROLL" != x && echo 'ViM rox emacs.' dnl Memo: AC_ARG_WITH(package, help-string, [if-given], [if-not-given]) AC_ARG_WITH([qt], [AS_HELP_STRING([--with-qt], [Path to Qt @<:@Look in PATH and /usr/local/Trolltech@:>@])], [QT_PATH=$withval]) # this is a hack to get decent flow control with 'break' for _qt_ignored in once; do # Find Qt. AC_ARG_VAR([QT_PATH], [Path to the Qt installation]) if test -d /usr/local/Trolltech; then # Try to find the latest version. tmp_qt_paths=`echo /usr/local/Trolltech/*/bin | tr ' ' '\n' | sort -nr \ | xargs | sed 's/ */:/g'` fi # Path to which recent MacPorts (~v1.7) install Qt4. test -d /opt/local/libexec/qt4-mac/bin \ && tmp_qt_paths="$tmp_qt_paths:/opt/local/libexec/qt4-mac/bin" # Find qmake. AC_ARG_VAR([QMAKE], [Qt Makefile generator command]) AC_PATH_PROGS([QMAKE], [qmake qmake-qt4 qmake-qt3], [missing], [$QT_DIR:$QT_PATH:$PATH:$tmp_qt_paths]) if test x"$QMAKE" = xmissing; then AX_INSTEAD_IF([$4], [Cannot find qmake in your PATH. Try using --with-qt.]) break fi # Find moc (Meta Object Compiler). AC_ARG_VAR([MOC], [Qt Meta Object Compiler command]) AC_PATH_PROGS([MOC], [moc moc-qt4 moc-qt3], [missing], [$QT_PATH:$PATH:$tmp_qt_paths]) if test x"$MOC" = xmissing; then AX_INSTEAD_IF([$4], [Cannot find moc (Meta Object Compiler) in your PATH. Try using --with-qt.]) break fi # Find uic (User Interface Compiler). AC_ARG_VAR([UIC], [Qt User Interface Compiler command]) AC_PATH_PROGS([UIC], [uic uic-qt4 uic-qt3 uic3], [missing], [$QT_PATH:$PATH:$tmp_qt_paths]) if test x"$UIC" = xmissing; then AX_INSTEAD_IF([$4], [Cannot find uic (User Interface Compiler) in your PATH. Try using --with-qt.]) break fi # Find rcc (Qt Resource Compiler). AC_ARG_VAR([RCC], [Qt Resource Compiler command]) AC_PATH_PROGS([RCC], [rcc], [false], [$QT_PATH:$PATH:$tmp_qt_paths]) if test x"$UIC" = xfalse; then AC_MSG_WARN([Cannot find rcc (Qt Resource Compiler) in your PATH.\ Try using --with-qt.]) fi AC_MSG_CHECKING([whether host operating system is Darwin]) at_darwin=no at_qmake_args= case $host_os in darwin*) at_darwin=yes at_qmake_args='-spec macx-g++' ;; esac AC_MSG_RESULT([$at_darwin]) # If we don't know the path to Qt, guess it from the path to qmake. if test x"$QT_PATH" = x; then QT_PATH=`dirname "$QMAKE"` fi if test x"$QT_PATH" = x; then AX_INSTEAD_IF([$4], [Cannot find the path to your Qt install. Use --with-qt.]) break fi AC_SUBST([QT_PATH]) # Get ready to build a test-app with Qt. if mkdir conftest.dir && cd conftest.dir; then :; else AX_INSTEAD_IF([$4], [Cannot mkdir conftest.dir or cd to that directory.]) break fi cat >conftest.h <<_ASEOF #include class Foo: public QObject { Q_OBJECT; public: Foo(); ~Foo() {} public Q_SLOTS: void setValue(int value); Q_SIGNALS: void valueChanged(int newValue); private: int value_; }; _ASEOF cat >conftest.cpp <<_ASEOF #include "conftest.h" Foo::Foo() : value_ (42) { connect(this, SIGNAL(valueChanged(int)), this, SLOT(setValue(int))); } void Foo::setValue(int value) { value_ = value; } int main() { Foo f; } _ASEOF if $QMAKE -project; then :; else AX_INSTEAD_IF([$4], [Calling $QMAKE -project failed.]) break fi # Find the .pro file generated by qmake. pro_file='conftest.dir.pro' test -f $pro_file || pro_file=`echo *.pro` if test -f "$pro_file"; then :; else AX_INSTEAD_IF([$4], [Can't find the .pro file generated by Qmake.]) break fi dnl Tweak the value of QT in the .pro if have been the 1st arg. m4_ifval([$1], [_AT_TWEAK_PRO_FILE([QT], [$1])]) dnl Tweak the value of CONFIG in the .pro if have been given a 2nd arg. m4_ifval([$2], [_AT_TWEAK_PRO_FILE([CONFIG], [$2])]) m4_ifval([$3], [ # Add the extra-settings the user wants to set in the .pro echo "$3" >>"$pro_file" ]) echo "$as_me:$LINENO: Invoking $QMAKE on $pro_file" >&AS_MESSAGE_LOG_FD sed 's/^/| /' "$pro_file" >&AS_MESSAGE_LOG_FD if $QMAKE $at_qmake_args; then :; else AX_INSTEAD_IF([$4], [Calling $QMAKE $at_qmake_args failed.]) break fi # QMake has a very annoying misfeature: sometimes it generates Makefiles # where all the references to the files from the Qt installation are # relative. We can't use them as-is because if we take, say, a # -I../../usr/include/Qt from that Makefile, the flag is invalid as soon # as we use it in another (sub) directory. So what this perl pass does is # that it rewrite all relative paths to absolute paths. Another problem # when building on Cygwin is that QMake mixes paths with blackslashes and # forward slashes and paths must be handled with extra care because of the # stupid Windows drive letters. echo "$as_me:$LINENO: fixing the Makefiles:" Makefile* >&AS_MESSAGE_LOG_FD cat >fixmk.pl <<\EOF [use strict; use Cwd qw(cwd abs_path); # This variable is useful on Cygwin for the following reason: Say that you are # in `/' (that is, in fact you are in C:/cygwin, or something like that) if you # `cd ..' then obviously you remain in `/' (that is in C:/cygwin). QMake # generates paths that are relative to C:/ (or another driver letter, whatever) # so the trick to get the `..' resolved properly is to prepend the absolute # path of the current working directory in a Windows-style. C:/cygwin/../ will # properly become C:/. my $d = ""; my $r2a = 0; my $b2f = 0; my $cygwin = 0; if ($^O eq "cygwin") { $cygwin = 1; $d = cwd(); $d = `cygpath --mixed '$d'`; chomp($d); $d .= "/"; } sub rel2abs($) { my $p = $d . shift; # print "r2a p=$p"; -e $p || return $p; if ($cygwin) { $p = `cygpath --mixed '$p'`; chomp($p); } else { # Do not use abs_path on Cygwin: it incorrectly resolves the paths that are # relative to C:/ rather than `/'. $p = abs_path($p); } # print " -> $p\n"; ++$r2a; return $p; } # Only useful on Cygwin. sub back2forward($) { my $p = shift; # print "b2f p=$p"; -e $p || return $p; $p = `cygpath --mixed '$p'`; chomp($p); # print " -> $p\n"; ++$b2f; return $p; } foreach my $mk (@ARGV) { next if $mk =~ /~$/; open(MK, $mk) or die("open $mk: $!"); # print "mk=$mk\n"; my $file = join("", ); close(MK) or die("close $mk: $!"); rename $mk, $mk . "~" or die("rename $mk: $!"); $file =~ s{(?:\.\.[\\/])+(?:[^"'\s:]+)}{rel2abs($&)}gse; $file =~ s{(?:[a-zA-Z]:[\\/])?(?:[^"\s]+\\[^"\s:]+)+} {back2forward($&)}gse if $cygwin; open(MK, ">", $mk) or die("open >$mk: $!"); print MK $file; close(MK) or die("close >$mk: $!"); print "$mk: updated $r2a relative paths and $b2f backslash-style paths\n"; $r2a = 0; $b2f = 0; }] EOF perl >&AS_MESSAGE_LOG_FD -w fixmk.pl Makefile* || AC_MSG_WARN([failed to fix the Makefiles generated by $QMAKE]) rm -f fixmk.pl # Try to compile a simple Qt app. AC_CACHE_CHECK([whether we can build a simple Qt app], [at_cv_qt_build], [at_cv_qt_build=ko : ${MAKE=make} if $MAKE >&AS_MESSAGE_LOG_FD 2>&1; then at_cv_qt_build='ok, looks like Qt 4' else echo "$as_me:$LINENO: Build failed, trying to #include \ instead" >&AS_MESSAGE_LOG_FD sed 's///' conftest.h > tmp.h && mv tmp.h conftest.h if $MAKE >&AS_MESSAGE_LOG_FD 2>&1; then at_cv_qt_build='ok, looks like Qt 3' else # Sometimes (such as on Debian) build will fail because Qt hasn't been # installed in debug mode and qmake tries (by default) to build apps in # debug mode => Try again in release mode. echo "$as_me:$LINENO: Build failed, trying to enforce release mode" \ >&AS_MESSAGE_LOG_FD _AT_TWEAK_PRO_FILE([CONFIG], [+release]) sed 's///' conftest.h > tmp.h && mv tmp.h conftest.h if $MAKE >&AS_MESSAGE_LOG_FD 2>&1; then at_cv_qt_build='ok, looks like Qt 4, release mode forced' else echo "$as_me:$LINENO: Build failed, trying to #include \ instead" >&AS_MESSAGE_LOG_FD sed 's///' conftest.h >tmp.h && mv tmp.h conftest.h if $MAKE >&AS_MESSAGE_LOG_FD 2>&1; then at_cv_qt_build='ok, looks like Qt 3, release mode forced' else at_cv_qt_build=ko echo "$as_me:$LINENO: failed program was:" >&AS_MESSAGE_LOG_FD sed 's/^/| /' conftest.h >&AS_MESSAGE_LOG_FD echo "$as_me:$LINENO: failed program was:" >&AS_MESSAGE_LOG_FD sed 's/^/| /' conftest.cpp >&AS_MESSAGE_LOG_FD fi # if make with Qt3-style #include and release mode forced. fi # if make with Qt4-style #include and release mode forced. fi # if make with Qt3-style #include. fi # if make with Qt4-style #include. ])dnl end: AC_CACHE_CHECK(at_cv_qt_build) if test x"$at_cv_qt_build" = xko; then AX_INSTEAD_IF([$4], [Cannot build a test Qt program]) cd .. break fi QT_VERSION_MAJOR=`echo "$at_cv_qt_build" | sed 's/[[^0-9]]*//g'` AC_SUBST([QT_VERSION_MAJOR]) # This sed filter is applied after an expression of the form: /^FOO.*=/!d; # It starts by removing the beginning of the line, removing references to # SUBLIBS, removing unnecessary whitespaces at the beginning, and prefixes # all variable uses by QT_. qt_sed_filter='s///; s/$(SUBLIBS)//g; s/^ *//; s/\$(\(@<:@A-Z_@:>@@<:@A-Z_@:>@*\))/$(QT_\1)/g' # Find the Makefile (qmake happens to generate a fake Makefile which invokes # a Makefile.Debug or Makefile.Release). We we have both, we'll pick the # Makefile.Release. The reason is that the main difference is that release # uses -Os and debug -g. We can override -Os by passing another -O but we # usually don't override -g. if test -f Makefile.Release; then at_mfile='Makefile.Release' else at_mfile='Makefile' fi if test -f $at_mfile; then :; else AX_INSTEAD_IF([$4], [Cannot find the Makefile generated by qmake.]) cd .. break fi # Find the DEFINES of Qt (should have been named CPPFLAGS). AC_CACHE_CHECK([for the DEFINES to use with Qt], [at_cv_env_QT_DEFINES], [at_cv_env_QT_DEFINES=`sed "/^DEFINES@<:@^A-Z=@:>@*=/!d;$qt_sed_filter" $at_mfile`]) AC_SUBST([QT_DEFINES], [$at_cv_env_QT_DEFINES]) # Find the CFLAGS of Qt (We can use Qt in C?!) AC_CACHE_CHECK([for the CFLAGS to use with Qt], [at_cv_env_QT_CFLAGS], [at_cv_env_QT_CFLAGS=`sed "/^CFLAGS@<:@^A-Z=@:>@*=/!d;$qt_sed_filter" $at_mfile`]) AC_SUBST([QT_CFLAGS], [$at_cv_env_QT_CFLAGS]) # Find the CXXFLAGS of Qt. AC_CACHE_CHECK([for the CXXFLAGS to use with Qt], [at_cv_env_QT_CXXFLAGS], [at_cv_env_QT_CXXFLAGS=`sed "/^CXXFLAGS@<:@^A-Z=@:>@*=/!d;$qt_sed_filter" $at_mfile`]) AC_SUBST([QT_CXXFLAGS], [$at_cv_env_QT_CXXFLAGS]) # Find the INCPATH of Qt. AC_CACHE_CHECK([for the INCPATH to use with Qt], [at_cv_env_QT_INCPATH], [at_cv_env_QT_INCPATH=`sed "/^INCPATH@<:@^A-Z=@:>@*=/!d;$qt_sed_filter" $at_mfile`]) AC_SUBST([QT_INCPATH], [$at_cv_env_QT_INCPATH]) AC_SUBST([QT_CPPFLAGS], ["$at_cv_env_QT_DEFINES $at_cv_env_QT_INCPATH"]) # Find the LFLAGS of Qt (Should have been named LDFLAGS) AC_CACHE_CHECK([for the LDFLAGS to use with Qt], [at_cv_env_QT_LDFLAGS], [at_cv_env_QT_LDFLAGS=`sed "/^LFLAGS@<:@^A-Z=@:>@*=/!d;$qt_sed_filter" $at_mfile`]) AC_SUBST([QT_LFLAGS], [$at_cv_env_QT_LDFLAGS]) AC_SUBST([QT_LDFLAGS], [$at_cv_env_QT_LDFLAGS]) # Find the LIBS of Qt. AC_CACHE_CHECK([for the LIBS to use with Qt], [at_cv_env_QT_LIBS], [at_cv_env_QT_LIBS=`sed "/^LIBS@<:@^A-Z@:>@*=/!d;$qt_sed_filter" $at_mfile` if test x$at_darwin = xyes; then # Fix QT_LIBS: as of today Libtool (GNU Libtool 1.5.23a) doesn't handle # -F properly. The "bug" has been fixed on 22 October 2006 # by Peter O'Gorman but we provide backward compatibility here. at_cv_env_QT_LIBS=`echo "$at_cv_env_QT_LIBS" \ | sed 's/^-F/-Wl,-F/;s/ -F/ -Wl,-F/g'` fi ]) AC_SUBST([QT_LIBS], [$at_cv_env_QT_LIBS]) cd .. && rm -rf conftest.dir # Run the user code $5 done # end hack (useless for to be able to use break) ]) # AT_REQUIRE_QT_VERSION(QT_version, RUN-IF-FAILED, RUN-IF-OK) # ----------------------------------------------------------- # Check (using qmake) that Qt's version "matches" QT_version. # Must be run AFTER AT_WITH_QT. Requires autoconf 2.60. # # RUN-IF-FAILED is arbitrary code to execute if Qt cannot be found or if any # problem happens. If this argument is omitted, then AC_MSG_ERROR will be # called. RUN-IF-OK is arbitrary code to execute if Qt was successfully found. AC_DEFUN([AT_REQUIRE_QT_VERSION], [ AC_PREREQ([2.60]) # this is a hack to get decent flow control with 'break' for _qt_ignored in once; do if test x"$QMAKE" = x; then AX_INSTEAD_IF([$2], [\$QMAKE is empty.\ Did you invoke AT@&t@_WITH_QT before AT@&t@_REQUIRE_QT_VERSION?]) break fi AC_CACHE_CHECK([for Qt's version], [at_cv_QT_VERSION], [echo "$as_me:$LINENO: Running $QMAKE --version:" >&AS_MESSAGE_LOG_FD $QMAKE --version >&AS_MESSAGE_LOG_FD 2>&1 qmake_version_sed=['/^.*\([0-9]\.[0-9]\.[0-9]\).*$/!d;s//\1/'] at_cv_QT_VERSION=`$QMAKE --version 2>&1 | sed "$qmake_version_sed"`]) if test x"$at_cv_QT_VERSION" = x; then AX_INSTEAD_IF([$2], [Cannot detect Qt's version.]) break fi AC_SUBST([QT_VERSION], [$at_cv_QT_VERSION]) AS_VERSION_COMPARE([$QT_VERSION], [$1], [AX_INSTEAD_IF([$2; break;], [This package requires Qt $1 or above.])]) # Run the user code $3 done # end hack (useless for to be able to use break) ]) # _AT_TWEAK_PRO_FILE(QT_VAR, VALUE) # --------------------------- # @internal. Tweak the variable QT_VAR in the .pro. # VALUE is an IFS-separated list of value and each value is rewritten # as follows: # +value => QT_VAR += value # -value => QT_VAR -= value # value => QT_VAR += value AC_DEFUN([_AT_TWEAK_PRO_FILE], [ # Tweak the value of $1 in the .pro file for $2. qt_conf='' for at_mod in $2; do at_mod=`echo "$at_mod" | sed 's/^-//; tough s/^+//; beef :ough s/^/$1 -= /;n :eef s/^/$1 += /'` qt_conf="$qt_conf $at_mod" done echo "$qt_conf" | sed 1d >>"$pro_file" ]) syncevolution_1.4/m4-repo/ax_boost_base.m4000066400000000000000000000241531230021373600207200ustar00rootroot00000000000000# =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_boost_base.html # =========================================================================== # # SYNOPSIS # # AX_BOOST_BASE([MINIMUM-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) # # DESCRIPTION # # Test for the Boost C++ libraries of a particular version (or newer) # # If no path to the installed boost library is given the macro searchs # under /usr, /usr/local, /opt and /opt/local and evaluates the # $BOOST_ROOT environment variable. Further documentation is available at # . # # This macro calls: # # AC_SUBST(BOOST_CPPFLAGS) / AC_SUBST(BOOST_LDFLAGS) # # And sets: # # HAVE_BOOST # # LICENSE # # Copyright (c) 2008 Thomas Porschberg # Copyright (c) 2009 Peter Adolphs # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 20 AC_DEFUN([AX_BOOST_BASE], [ AC_ARG_WITH([boost], [AS_HELP_STRING([--with-boost@<:@=ARG@:>@], [use Boost library from a standard location (ARG=yes), from the specified location (ARG=), or disable it (ARG=no) @<:@ARG=yes@:>@ ])], [ if test "$withval" = "no"; then want_boost="no" elif test "$withval" = "yes"; then want_boost="yes" ac_boost_path="" else want_boost="yes" ac_boost_path="$withval" fi ], [want_boost="yes"]) AC_ARG_WITH([boost-libdir], AS_HELP_STRING([--with-boost-libdir=LIB_DIR], [Force given directory for boost libraries. Note that this will override library path detection, so use this parameter only if default library detection fails and you know exactly where your boost libraries are located.]), [ if test -d "$withval" then ac_boost_lib_path="$withval" else AC_MSG_ERROR(--with-boost-libdir expected directory name) fi ], [ac_boost_lib_path=""] ) if test "x$want_boost" = "xyes"; then boost_lib_version_req=ifelse([$1], ,1.20.0,$1) boost_lib_version_req_shorten=`expr $boost_lib_version_req : '\([[0-9]]*\.[[0-9]]*\)'` boost_lib_version_req_major=`expr $boost_lib_version_req : '\([[0-9]]*\)'` boost_lib_version_req_minor=`expr $boost_lib_version_req : '[[0-9]]*\.\([[0-9]]*\)'` boost_lib_version_req_sub_minor=`expr $boost_lib_version_req : '[[0-9]]*\.[[0-9]]*\.\([[0-9]]*\)'` if test "x$boost_lib_version_req_sub_minor" = "x" ; then boost_lib_version_req_sub_minor="0" fi WANT_BOOST_VERSION=`expr $boost_lib_version_req_major \* 100000 \+ $boost_lib_version_req_minor \* 100 \+ $boost_lib_version_req_sub_minor` AC_MSG_CHECKING(for boostlib >= $boost_lib_version_req) succeeded=no dnl On 64-bit systems check for system libraries in both lib64 and lib. dnl The former is specified by FHS, but e.g. Debian does not adhere to dnl this (as it rises problems for generic multi-arch support). dnl The last entry in the list is chosen by default when no libraries dnl are found, e.g. when only header-only libraries are installed! libsubdirs="lib" ax_arch=`uname -m` if test $ax_arch = x86_64 -o $ax_arch = ppc64 -o $ax_arch = s390x -o $ax_arch = sparc64; then libsubdirs="lib64 lib lib64" fi dnl first we check the system location for boost libraries dnl this location ist chosen if boost libraries are installed with the --layout=system option dnl or if you install boost with RPM if test "$ac_boost_path" != ""; then BOOST_CPPFLAGS="-I$ac_boost_path/include" for ac_boost_path_tmp in $libsubdirs; do if test -d "$ac_boost_path"/"$ac_boost_path_tmp" ; then BOOST_LDFLAGS="-L$ac_boost_path/$ac_boost_path_tmp" break fi done elif test "$cross_compiling" != yes; then for ac_boost_path_tmp in /usr /usr/local /opt /opt/local ; do if test -d "$ac_boost_path_tmp/include/boost" && test -r "$ac_boost_path_tmp/include/boost"; then for libsubdir in $libsubdirs ; do if ls "$ac_boost_path_tmp/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi done BOOST_LDFLAGS="-L$ac_boost_path_tmp/$libsubdir" BOOST_CPPFLAGS="-I$ac_boost_path_tmp/include" break; fi done fi dnl overwrite ld flags if we have required special directory with dnl --with-boost-libdir parameter if test "$ac_boost_lib_path" != ""; then BOOST_LDFLAGS="-L$ac_boost_lib_path" fi CPPFLAGS_SAVED="$CPPFLAGS" CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" export CPPFLAGS LDFLAGS_SAVED="$LDFLAGS" LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" export LDFLAGS AC_REQUIRE([AC_PROG_CXX]) AC_LANG_PUSH(C++) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ @%:@include ]], [[ #if BOOST_VERSION >= $WANT_BOOST_VERSION // Everything is okay #else # error Boost version is too old #endif ]])],[ AC_MSG_RESULT(yes) succeeded=yes found_system=yes ],[ ]) AC_LANG_POP([C++]) dnl if we found no boost with system layout we search for boost libraries dnl built and installed without the --layout=system option or for a staged(not installed) version if test "x$succeeded" != "xyes"; then _version=0 if test "$ac_boost_path" != ""; then if test -d "$ac_boost_path" && test -r "$ac_boost_path"; then for i in `ls -d $ac_boost_path/include/boost-* 2>/dev/null`; do _version_tmp=`echo $i | sed "s#$ac_boost_path##" | sed 's/\/include\/boost-//' | sed 's/_/./'` V_CHECK=`expr $_version_tmp \> $_version` if test "$V_CHECK" = "1" ; then _version=$_version_tmp fi VERSION_UNDERSCORE=`echo $_version | sed 's/\./_/'` BOOST_CPPFLAGS="-I$ac_boost_path/include/boost-$VERSION_UNDERSCORE" done fi else if test "$cross_compiling" != yes; then for ac_boost_path in /usr /usr/local /opt /opt/local ; do if test -d "$ac_boost_path" && test -r "$ac_boost_path"; then for i in `ls -d $ac_boost_path/include/boost-* 2>/dev/null`; do _version_tmp=`echo $i | sed "s#$ac_boost_path##" | sed 's/\/include\/boost-//' | sed 's/_/./'` V_CHECK=`expr $_version_tmp \> $_version` if test "$V_CHECK" = "1" ; then _version=$_version_tmp best_path=$ac_boost_path fi done fi done VERSION_UNDERSCORE=`echo $_version | sed 's/\./_/'` BOOST_CPPFLAGS="-I$best_path/include/boost-$VERSION_UNDERSCORE" if test "$ac_boost_lib_path" = ""; then for libsubdir in $libsubdirs ; do if ls "$best_path/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi done BOOST_LDFLAGS="-L$best_path/$libsubdir" fi fi if test "x$BOOST_ROOT" != "x"; then for libsubdir in $libsubdirs ; do if ls "$BOOST_ROOT/stage/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi done if test -d "$BOOST_ROOT" && test -r "$BOOST_ROOT" && test -d "$BOOST_ROOT/stage/$libsubdir" && test -r "$BOOST_ROOT/stage/$libsubdir"; then version_dir=`expr //$BOOST_ROOT : '.*/\(.*\)'` stage_version=`echo $version_dir | sed 's/boost_//' | sed 's/_/./g'` stage_version_shorten=`expr $stage_version : '\([[0-9]]*\.[[0-9]]*\)'` V_CHECK=`expr $stage_version_shorten \>\= $_version` if test "$V_CHECK" = "1" -a "$ac_boost_lib_path" = "" ; then AC_MSG_NOTICE(We will use a staged boost library from $BOOST_ROOT) BOOST_CPPFLAGS="-I$BOOST_ROOT" BOOST_LDFLAGS="-L$BOOST_ROOT/stage/$libsubdir" fi fi fi fi CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" export CPPFLAGS LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" export LDFLAGS AC_LANG_PUSH(C++) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ @%:@include ]], [[ #if BOOST_VERSION >= $WANT_BOOST_VERSION // Everything is okay #else # error Boost version is too old #endif ]])],[ AC_MSG_RESULT(yes) succeeded=yes found_system=yes ],[ ]) AC_LANG_POP([C++]) fi if test "$succeeded" != "yes" ; then if test "$_version" = "0" ; then AC_MSG_NOTICE([[We could not detect the boost libraries (version $boost_lib_version_req_shorten or higher). If you have a staged boost library (still not installed) please specify \$BOOST_ROOT in your environment and do not give a PATH to --with-boost option. If you are sure you have boost installed, then check your version number looking in . See http://randspringer.de/boost for more documentation.]]) else AC_MSG_NOTICE([Your boost libraries seems to old (version $_version).]) fi # execute ACTION-IF-NOT-FOUND (if present): ifelse([$3], , :, [$3]) else AC_SUBST(BOOST_CPPFLAGS) AC_SUBST(BOOST_LDFLAGS) AC_DEFINE(HAVE_BOOST,,[define if the Boost library is available]) # execute ACTION-IF-FOUND (if present): ifelse([$2], , :, [$2]) fi CPPFLAGS="$CPPFLAGS_SAVED" LDFLAGS="$LDFLAGS_SAVED" fi ]) syncevolution_1.4/m4-repo/ax_boost_locale.m4000066400000000000000000000104761230021373600212500ustar00rootroot00000000000000# =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_boost_locale.html # =========================================================================== # # SYNOPSIS # # AX_BOOST_LOCALE # # DESCRIPTION # # Test for System library from the Boost C++ libraries. The macro requires # a preceding call to AX_BOOST_BASE. Further documentation is available at # . # # This macro calls: # # AC_SUBST(BOOST_LOCALE_LIB) # # And sets: # # HAVE_BOOST_LOCALE # # LICENSE # # Copyright (c) 2012 Xiyue Deng # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 1 AC_DEFUN([AX_BOOST_LOCALE], [ AC_ARG_WITH([boost-locale], AS_HELP_STRING([--with-boost-locale@<:@=special-lib@:>@], [use the Locale library from boost - it is possible to specify a certain library for the linker e.g. --with-boost-locale=boost_locale-gcc-mt ]), [ if test "$withval" = "no"; then want_boost="no" elif test "$withval" = "yes"; then want_boost="yes" ax_boost_user_locale_lib="" else want_boost="yes" ax_boost_user_locale_lib="$withval" fi ], [want_boost="yes"] ) if test "x$want_boost" = "xyes"; then AC_REQUIRE([AC_PROG_CC]) AC_REQUIRE([AC_CANONICAL_BUILD]) CPPFLAGS_SAVED="$CPPFLAGS" CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" export CPPFLAGS LDFLAGS_SAVED="$LDFLAGS" LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" export LDFLAGS AC_CACHE_CHECK(whether the Boost::Locale library is available, ax_cv_boost_locale, [AC_LANG_PUSH([C++]) CXXFLAGS_SAVE=$CXXFLAGS AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@include ]], [[boost::locale::generator gen; std::locale::global(gen(""));]])], ax_cv_boost_locale=yes, ax_cv_boost_locale=no) CXXFLAGS=$CXXFLAGS_SAVE AC_LANG_POP([C++]) ]) if test "x$ax_cv_boost_locale" = "xyes"; then AC_SUBST(BOOST_CPPFLAGS) AC_DEFINE(HAVE_BOOST_LOCALE,,[define if the Boost::Locale library is available]) BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/@<:@^\/@:>@*//'` LDFLAGS_SAVE=$LDFLAGS if test "x$ax_boost_user_locale_lib" = "x"; then for libextension in `ls $BOOSTLIBDIR/libboost_locale*.so* $BOOSTLIBDIR/libboost_locale*.dylib* $BOOSTLIBDIR/libboost_locale*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_locale.*\)\.so.*$;\1;' -e 's;^lib\(boost_locale.*\)\.dylib.*$;\1;' -e 's;^lib\(boost_locale.*\)\.a.*$;\1;'` ; do ax_lib=${libextension} AC_CHECK_LIB($ax_lib, exit, [BOOST_LOCALE_LIB="-l$ax_lib"; AC_SUBST(BOOST_LOCALE_LIB) link_locale="yes"; break], [link_locale="no"]) done if test "x$link_locale" != "xyes"; then for libextension in `ls $BOOSTLIBDIR/boost_locale*.dll* $BOOSTLIBDIR/boost_locale*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^\(boost_locale.*\)\.dll.*$;\1;' -e 's;^\(boost_locale.*\)\.a.*$;\1;'` boost_locale; do ax_lib=${libextension} AC_CHECK_LIB($ax_lib, exit, [BOOST_LOCALE_LIB="-l$ax_lib"; AC_SUBST(BOOST_LOCALE_LIB) link_locale="yes"; break], [link_locale="no"]) done fi else for ax_lib in $ax_boost_user_locale_lib boost_locale-$ax_boost_user_locale_lib; do AC_CHECK_LIB($ax_lib, exit, [BOOST_LOCALE_LIB="-l$ax_lib"; AC_SUBST(BOOST_LOCALE_LIB) link_locale="yes"; break], [link_locale="no"]) done fi if test "x$ax_lib" = "x"; then AC_MSG_ERROR(Could not find a version of the library!) fi if test "x$link_locale" = "xno"; then AC_MSG_ERROR(Could not link against $ax_lib !) fi fi CPPFLAGS="$CPPFLAGS_SAVED" LDFLAGS="$LDFLAGS_SAVED" fi ]) syncevolution_1.4/m4-repo/ax_check_gnu_make.m4000066400000000000000000000054111230021373600215170ustar00rootroot00000000000000# =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_check_gnu_make.html # =========================================================================== # # SYNOPSIS # # AX_CHECK_GNU_MAKE() # # DESCRIPTION # # This macro searches for a GNU version of make. If a match is found, the # makefile variable `ifGNUmake' is set to the empty string, otherwise it # is set to "#". This is useful for including a special features in a # Makefile, which cannot be handled by other versions of make. The # variable _cv_gnu_make_command is set to the command to invoke GNU make # if it exists, the empty string otherwise. # # Here is an example of its use: # # Makefile.in might contain: # # # A failsafe way of putting a dependency rule into a makefile # $(DEPEND): # $(CC) -MM $(srcdir)/*.c > $(DEPEND) # # @ifGNUmake@ ifeq ($(DEPEND),$(wildcard $(DEPEND))) # @ifGNUmake@ include $(DEPEND) # @ifGNUmake@ endif # # Then configure.in would normally contain: # # AX_CHECK_GNU_MAKE() # AC_OUTPUT(Makefile) # # Then perhaps to cause gnu make to override any other make, we could do # something like this (note that GNU make always looks for GNUmakefile # first): # # if ! test x$_cv_gnu_make_command = x ; then # mv Makefile GNUmakefile # echo .DEFAULT: > Makefile ; # echo \ $_cv_gnu_make_command \$@ >> Makefile; # fi # # Then, if any (well almost any) other make is called, and GNU make also # exists, then the other make wraps the GNU make. # # LICENSE # # Copyright (c) 2008 John Darrington # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 7 AC_DEFUN([AX_CHECK_GNU_MAKE], [ AC_CACHE_CHECK( for GNU make,_cv_gnu_make_command, _cv_gnu_make_command='' ; dnl Search all the common names for GNU make for a in "$MAKE" make gmake gnumake ; do if test -z "$a" ; then continue ; fi ; if ( sh -c "$a --version" 2> /dev/null | grep GNU 2>&1 > /dev/null ) ; then _cv_gnu_make_command=$a ; break; fi done ; ) ; dnl If there was a GNU version, then set @ifGNUmake@ to the empty string, '#' otherwise if test "x$_cv_gnu_make_command" != "x" ; then ifGNUmake='' ; else ifGNUmake='#' ; AC_MSG_RESULT("Not found"); fi AC_SUBST(ifGNUmake) ] ) syncevolution_1.4/m4-repo/dk-warn.m4000066400000000000000000000071251230021373600174530ustar00rootroot00000000000000## Copyright (c) 2004-2007 Daniel Elstner ## ## This file is part of danielk's Autostuff. ## ## danielk's Autostuff is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as published ## by the Free Software Foundation; either version 2 of the License, or (at ## your option) any later version. ## ## danielk's Autostuff is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY ## or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License ## for more details. ## ## You should have received a copy of the GNU General Public License along ## with danielk's Autostuff; if not, write to the Free Software Foundation, ## Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #serial 20070116 ## DK_ARG_ENABLE_WARNINGS(variable, min-flags, max-flags, [deprecation-prefixes]) ## ## Provide the --enable-warnings configure argument, set to "min" by default. ## and should be space-separated lists of compiler ## warning flags to use with --enable-warnings=min or --enable-warnings=max, ## respectively. Warning level "fatal" is the same as "max" but in addition ## enables -Werror mode. ## ## If not empty, should be a list of module prefixes ## which is expanded to -D_DISABLE_DEPRECATED flags if fatal warnings ## are enabled, too. ## AC_DEFUN([DK_ARG_ENABLE_WARNINGS], [dnl m4_if([$3],, [AC_FATAL([3 arguments expected])])[]dnl dnl AC_ARG_ENABLE([warnings], [AS_HELP_STRING( [--enable-warnings=@<:@min|max|fatal|no@:>@], [control compiler pickyness @<:@min@:>@])], [dk_enable_warnings=$enableval], [dk_enable_warnings=min])[]dnl dk_lang= case $ac_compile in *'$CXXFLAGS '*) dk_lang='C++' dk_cc=$CXX dk_conftest=conftest.${ac_ext-cc} ;; *'$CFLAGS '*) dk_lang=C dk_cc=$CC dk_conftest=conftest.${ac_ext-c} ;; esac AS_IF([test "x$dk_lang" != x], [ AC_MSG_CHECKING([which $dk_lang compiler warning flags to use]) case $dk_enable_warnings in no) dk_warning_flags=;; max) dk_warning_flags="$3";; fatal) dk_warning_flags="$3 -Werror";; *) dk_warning_flags="$2";; esac dk_deprecation_flags= m4_if([$4],,, [ AS_IF([test "x$dk_enable_warnings" = xfatal], [ dk_deprecation_prefixes="$4" for dk_prefix in $dk_deprecation_prefixes do dk_deprecation_flags="${dk_deprecation_flags}-D${dk_prefix}_DISABLE_DEPRECATED " done ]) ])[]dnl dk_tested_flags= AS_IF([test "x$dk_warning_flags" != x], [ # Keep in mind that the dummy source must be devoid of any # problems that might cause diagnostics. AC_LANG_CONFTEST([AC_LANG_SOURCE( [[int main(int argc, char** argv) { return (argv != 0) ? argc : 0; }]])]) for dk_flag in $dk_warning_flags do # Test whether the compiler accepts the flag. GCC doesn't bail # out when given an unsupported flag but prints a warning, so # check the compiler output instead. dk_cc_out=`$dk_cc $dk_tested_flags $dk_flag -c "$dk_conftest" 2>&1 || echo failed` rm -f "conftest.${OBJEXT-o}" AS_IF([test "x$dk_cc_out" = x], [ AS_IF([test "x$dk_tested_flags" = x], [dk_tested_flags=$dk_flag], [dk_tested_flags="$dk_tested_flags $dk_flag"]) ], [ echo "$dk_cc_out" >&AS_MESSAGE_LOG_FD ]) done rm -f "$dk_conftest" ]) dk_all_flags=$dk_deprecation_flags$dk_tested_flags AC_SUBST([$1], [$dk_all_flags]) test "x$dk_all_flags" != x || dk_all_flags=none AC_MSG_RESULT([$dk_all_flags]) ]) ]) syncevolution_1.4/m4-repo/se_macros.m4000066400000000000000000000143621230021373600200640ustar00rootroot00000000000000#serial 20110803 ## _SE_VERSIONS(MAJOR_VERSION, MINOR_VERSION, MICRO_VERSION, NANO_VERSION) ## ## exports MICRO_VERSION and NANO_VERSION to se_test_micro_version and ## se_test_nano_version defines respectively. If any of passed parameters are ## empty, those defines are defined as '0'. MAJOR_VERSION and MINOR_VERSION are ## ignored. ## m4_define([_SE_VERSIONS], [dnl m4_ifval([$3], [dnl nonempty part m4_define([se_test_micro_version], [$3]) ], [dnl empty part m4_define([se_test_micro_version], [0]) ] )[]dnl m4_ifval([$4], [dnl nonempty part m4_define([se_test_nano_version], [$4]) ], [dnl empty part m4_define([se_test_nano_version], [0]) ] )[]dnl ] )[]dnl ## SE_CHECK_FOR_STABLE_RELEASE ## ## No-op if STABLE_RELEASE is already defined. ## ## Defines STABLE_RELEASE to 'no' if the nano version is 99 (1.1.99.5 is ## an unstable, pre-release version) or the current version is dirty ## (something is appended by gen-git-version.sh). ## ## If above fails, then STABLE_RELEASE is defined to 'yes'. ## AC_DEFUN([SE_CHECK_FOR_STABLE_RELEASE], [m4_ifndef([STABLE_RELEASE], [dnl ifndef part m4_define([se_test_plus_index], m4_index(AC_PACKAGE_VERSION, [+]))[]dnl m4_if(se_test_plus_index, [-1], [dnl if part m4_define([se_test_version], AC_PACKAGE_VERSION) ], [dnl else part m4_define([se_test_version], m4_substr(AC_PACKAGE_VERSION, [0], se_test_plus_index)) m4_define([se_test_dirty_version], [yes]) ] )[]dnl _SE_VERSIONS(m4_bpatsubst(se_test_version, [[^0-9A-Za-z]+], [,]))[]dnl m4_if(se_test_micro_version, [99], [dnl if part m4_define([se_test_nano_number], m4_translit(se_test_nano_version, [A-Za-z])) dnl m4_if(m4_eval(se_test_nano_number [>= 5]), [1], dnl [dnl if part dnl m4_define([STABLE_RELEASE], [yes]) dnl ] dnl ) ], [dnl else part m4_ifndef([se_test_dirty_version], [dnl ifndef part m4_define([STABLE_RELEASE], [yes]) ] ) ] )[]dnl m4_ifndef([STABLE_RELEASE], [dnl ifndef part m4_define([STABLE_RELEASE], [no]) ] ) dnl macros cleanup m4_ifdef([se_test_plus_index], [dnl ifdef part m4_undefine([se_test_plus_index]) ] )[]dnl m4_ifdef([se_test_version], [dnl ifdef part m4_undefine([se_test_version]) ] )[]dnl m4_ifdef([se_test_dirty_version], [dnl ifdef part m4_undefine([se_test_dirty_version]) ] )[]dnl m4_ifdef([se_test_micro_version], [dnl ifdef part m4_undefine([se_test_micro_version]) ] )[]dnl m4_ifdef([se_test_nano_version], [dnl ifdef part m4_undefine([se_test_nano_version]) ] )[]dnl m4_ifdef([se_test_nano_number], [dnl ifdef part m4_undefine([se_test_nano_number]) ] )[]dnl ] )[]dnl ] ) ## SE_ENABLE_BACKENDS_PRE ## ## Marks BACKEND_DEFINES and SYNCSOURCES as variables to be substituted. ## For internal use only. ## AC_DEFUN([SE_ENABLE_BACKENDS_PRE], [AC_SUBST(SYNCSOURCES) AC_SUBST(BACKEND_DEFINES) BACKENDS='' BACKEND_DEFINES='' SYNCSOURCES='' ]) ## SE_ARG_ENABLE_BACKEND(BACKEND, DIR, HELP-STRING, [ACTION-IF-GIVEN], ## [ACTION-IF-NOT-GIVEN]) ## ## Same as AC_ARG_ENABLE(), but also tells configure that the ## backend exists. ## ## BACKEND = name of modules built in that dir as .la files without the ## obligatory sync prefix, e.g. "ebook" ## DIR = name of the directory inside src/backends, e.g., "evolution" ## AC_DEFUN([SE_ARG_ENABLE_BACKEND], [AC_REQUIRE([SE_ENABLE_BACKENDS_PRE]) AC_ARG_ENABLE($1, $3, $4, $5) BACKENDS="$BACKENDS $1" BACKEND_DEFINES="$BACKEND_DEFINES ENABLE_[]m4_translit($1, [a-z], [A-Z])" for source in $2 do SYNCSOURCES="$SYNCSOURCES src/backends/$2/sync$1.la" done ] ) ## SE_ADD_BACKENDS ## ## Adds backends available under src/backends. See build/gen-backends.sh script. ## AC_DEFUN([SE_ADD_BACKENDS], [m4_esyscmd(build/gen-backends.sh) ] ) ## SE_GENERATE_AM_FILES ## ## Generates some .am files needed by automake. ## AC_DEFUN([SE_GENERATE_AM_FILES], [m4_syscmd(build/gen-backends-am.sh) ] ) ## SE_GENERATE_LINGUAS ## ## Generates LINGUAS file. ## AC_DEFUN([SE_GENERATE_LINGUAS], [m4_syscmd(build/gen-linguas.sh) ] ) syncevolution_1.4/po/000077500000000000000000000000001230021373600147745ustar00rootroot00000000000000syncevolution_1.4/po/ChangeLog000066400000000000000000000000001230021373600165340ustar00rootroot00000000000000syncevolution_1.4/po/LINGUAS.README000066400000000000000000000002451230021373600167560ustar00rootroot00000000000000For translators: LINGUAS file is generated, so putting .po file here is enough. To test translation please run ./autogen.sh in top level directory of the project. syncevolution_1.4/po/Makevars000066400000000000000000000034311230021373600164710ustar00rootroot00000000000000# Makefile variables for PO directory in any package using GNU gettext. # Usually the message domain is the same as the package name. DOMAIN = $(PACKAGE) # These two variables depend on the location of this directory. subdir = po top_builddir = .. # These options get passed to xgettext. XGETTEXT_OPTIONS = --keyword=_ --keyword=N_ # This is the copyright holder that gets inserted into the header of the # $(DOMAIN).pot file. Set this to the copyright holder of the surrounding # package. (Note that the msgstr strings, extracted from the package's # sources, belong to the copyright holder of the package.) Translators are # expected to transfer the copyright for their translations to this person # or entity, or to disclaim their copyright. The empty string stands for # the public domain; in this case the translators are expected to disclaim # their copyright. COPYRIGHT_HOLDER = # This is the email address or URL to which the translators shall report # bugs in the untranslated strings: # - Strings which are not entire sentences, see the maintainer guidelines # in the GNU gettext documentation, section 'Preparing Strings'. # - Strings which use unclear terms or require additional context to be # understood. # - Strings which make invalid assumptions about notation of date, time or # money. # - Pluralisation problems. # - Incorrect English spelling. # - Incorrect formatting. # It can be your email address, or a mailing list address where translators # can write to without being subscribed, or the URL of a web page through # which the translators can contact you. MSGID_BUGS_ADDRESS = http://moblin.org/projects/syncevolution # This is the list of locale categories, beyond LC_MESSAGES, for which the # message catalogs shall be used. It is usually empty. EXTRA_LOCALE_CATEGORIES = syncevolution_1.4/po/POTFILES.in000066400000000000000000000007421230021373600165540ustar00rootroot00000000000000src/gtk-ui/main.c src/gtk-ui/sync-ui.c src/gtk-ui/sync-ui-config.c src/gtk-ui/ui.xml src/gtk-ui/sync.desktop.in src/gtk-ui/sync-gtk.desktop.in src/gtk-ui/sync-config-widget.c src/gtk3-ui/main.c src/gtk3-ui/sync-ui.c src/gtk3-ui/sync-ui-config.c src/gtk3-ui/ui.xml src/gtk3-ui/sync.desktop.in src/gtk3-ui/sync-gtk.desktop.in src/gtk3-ui/sync-config-widget.c src/gnome-bluetooth/syncevolution.c src/dbus/server/notification-backend-libnotify.cpp src/dbus/server/auto-sync-manager.cpp syncevolution_1.4/po/POTFILES.skip000066400000000000000000000001201230021373600171020ustar00rootroot00000000000000src/gtk-ui/ui.xml src/gtk-ui/gtkinfobar.c src/synthesis/src/pcre/pcre_compile.c syncevolution_1.4/po/README000066400000000000000000000047611230021373600156640ustar00rootroot00000000000000Translation README Syncevolution Moblin (GTK+) UI uses standard gettext translations (.po files) and Transifex for translator web access. What follows is a very short technical guide to translating. For more extensive documentation, please refer to the GNOME localisation guide* or even the gettext manual** (the latter is unfortunately fairly programmer oriented). Notes to translators ==================== 1. Get the strings that need translating The easiest way to get the po-file is via http://translate.moblin.org/projects/syncevolution/ . If the file for your language does not exist yet, take the template file (.pot) and rename it to xx.po, where xx is the language code). Alternatively if you have configured SyncEvolution sources, you can get an up-to-date template (.pot) and .po files by running "make update-po" in /po source directory. 2. Translate Simply fill in the empty msgstr strings in the file. Graphical translation tools such as gtranslator are available in most linux distributions. Two features a translator must know are c-format strings and plural form handling. For help with c-format strings, see GNOME Localisation Guide*. If there are untranslatable strings or you do not understand the context, please file a bug on moblin bugzilla (product "SyncEvolution", component "GTK UI") or ask on the mailing list. 3. Upload Upload the .po file using the webservice: For existing translations click "Send a translation for this language". For new languages use "Add new translation". Alternatively you can open a bug on moblin bugzilla and attach the po-file there. Notes to developers =================== 1. Testing to-be-translated strings: The translation webservice produces the translation files but "make update-po" in /po source directory can still be useful for testing: it will update the translation template and all translations with new strings. When a translation is listed in po/LINGUAS, 'make' will build the message catalog (.gmo) and "make install" will install it. 2. Getting strings translated: The translation webservice watches 'moblin-transifex' branch. When you want your new strings to be translated, rebase/merge from master. Depending on the case, you may want to mention about string changes on the mailing list. 3. Getting translated strings into a release merge moblin-transifex to master once in a while to get translation updates. * http://live.gnome.org/TranslationProject/LocalisationGuide ** http://www.gnu.org/software/gettext/manual/gettext.html syncevolution_1.4/po/ar.po000066400000000000000000000430411230021373600157400ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: syncevolution\n" "Report-Msgid-Bugs-To: http://moblin.org/projects/syncevolution\n" "POT-Creation-Date: 2009-10-16 11:24+0000\n" "PO-Revision-Date: 2009-11-16 16:28+0200\n" "Last-Translator: Yousef Abu Al Naser \n" "Language-Team: ITSOFTEX \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=6; plural= n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" "X-Poedit-Language: Arabic\n" #. TRANSLATORS: this is the application name that may be used by e.g. #. the windowmanager #: ../src/gtk-ui/main.c:31 #: ../src/gtk-ui/ui.glade.h:28 #: ../src/gtk-ui/sync.desktop.in.h:1 msgid "Sync" msgstr "تزامن" #: ../src/gtk-ui/sync-ui.c:259 msgid "Addressbook" msgstr "دفتر العناوين" #: ../src/gtk-ui/sync-ui.c:261 msgid "Calendar" msgstr "التقويم" #: ../src/gtk-ui/sync-ui.c:263 msgid "Todo" msgstr "للعمل" #: ../src/gtk-ui/sync-ui.c:265 msgid "Memo" msgstr "ملاحظة" #: ../src/gtk-ui/sync-ui.c:320 msgid "Failed to save current service in GConf configuration system" msgstr "فشل في حفظ الخدمة الحالية في نظام مكونات GConf" #: ../src/gtk-ui/sync-ui.c:331 msgid "Failed to save service configuration to SyncEvolution" msgstr "فشل في حفظ مكونات الخدمة لسينك افولوشن" #: ../src/gtk-ui/sync-ui.c:416 msgid "Failed to get service configuration from SyncEvolution" msgstr "فشل في الحصول على مكونات الخدمة من سينك افولوشن" #: ../src/gtk-ui/sync-ui.c:480 msgid "Failed to remove service configuration from SyncEvolution" msgstr "فشل في ازالة مكونات الخدمة من سينك افولوشن" #: ../src/gtk-ui/sync-ui.c:600 msgid "Service must have a name and server URL" msgstr "يتوجب على الخدمة ان يكون لها اسم وعنوان خادم" #. sync is no longer in progress for some reason #: ../src/gtk-ui/sync-ui.c:676 msgid "Failed to cancel: sync was no longer in progress" msgstr "فشل في الالغاء. لم يكن التزامن في التقدم." #: ../src/gtk-ui/sync-ui.c:680 msgid "Failed to cancel sync" msgstr "فشل في إلغاء التزامن" #: ../src/gtk-ui/sync-ui.c:684 msgid "Canceling sync" msgstr "جاري إلغاء التزامن" #: ../src/gtk-ui/sync-ui.c:698 msgid "Trying to cancel sync" msgstr "محاولة إلغاء التزامن" #: ../src/gtk-ui/sync-ui.c:705 #, c-format msgid "Do you want to delete all local data and replace it with data from %s? This is not usually advised." msgstr "هل ترغب بمحي كل البيانات المحلية واستبدالها ببيانات من %s؟ بشكل عام هذا شيء غير محبذ. " #: ../src/gtk-ui/sync-ui.c:710 #, c-format msgid "Do you want to delete all data in %s and replace it with your local data? This is not usually advised." msgstr "هل ترغب بمحي كل البيانات في %s وتبديلها ببياناتك المحلية؟ بشكل عام لا ننصح بذلك." #: ../src/gtk-ui/sync-ui.c:727 msgid "No, cancel sync" msgstr "كلا, إلغاء التزامن" #: ../src/gtk-ui/sync-ui.c:728 msgid "Yes, delete and replace" msgstr "نعم، امح واستبدل" #: ../src/gtk-ui/sync-ui.c:750 msgid "No sources are enabled, not syncing" msgstr "لا يوجد مصادر مفعّلة، لم يجر التزامن" #: ../src/gtk-ui/sync-ui.c:767 msgid "A sync is already in progress" msgstr "عملية تزامن أخرى جارية" #: ../src/gtk-ui/sync-ui.c:769 msgid "Failed to start sync" msgstr "فشل في بدء التزامن" #: ../src/gtk-ui/sync-ui.c:774 msgid "Starting sync" msgstr "بدء التزامن" #: ../src/gtk-ui/sync-ui.c:799 msgid "Last synced just seconds ago" msgstr "آخر تزامن منذ بضعة ثواني" #: ../src/gtk-ui/sync-ui.c:802 msgid "Last synced a minute ago" msgstr "آخر تزامن منذ دقيقة" #: ../src/gtk-ui/sync-ui.c:805 #, c-format msgid "Last synced %ld minutes ago" msgstr "آخر تزامن منذ %ld دقائق" #: ../src/gtk-ui/sync-ui.c:808 msgid "Last synced an hour ago" msgstr "آخر تزامن منذ ساعة" #: ../src/gtk-ui/sync-ui.c:811 #, c-format msgid "Last synced %ld hours ago" msgstr "آخر تزامن منذ %ld ساعات" #: ../src/gtk-ui/sync-ui.c:814 msgid "Last synced a day ago" msgstr "آخر تزامن منذ يوم" #: ../src/gtk-ui/sync-ui.c:817 #, c-format msgid "Last synced %ld days ago" msgstr "آخر تزامن منذ %ld أيام" #: ../src/gtk-ui/sync-ui.c:902 msgid "Sync again" msgstr "تزامن مرة أخرى" #: ../src/gtk-ui/sync-ui.c:904 #: ../src/gtk-ui/ui.glade.h:29 msgid "Sync now" msgstr "تزامن الآن" #: ../src/gtk-ui/sync-ui.c:913 msgid "Syncing" msgstr "جاري التزامن" #: ../src/gtk-ui/sync-ui.c:919 msgid "Cancel sync" msgstr "إلغاء التزامن" #. TRANSLATORS: placeholder is a source name, shown with checkboxes in main window #: ../src/gtk-ui/sync-ui.c:1266 #, c-format msgid "%s (not supported by this service)" msgstr "%s (غير معتمد في هذه الخدمة)" #: ../src/gtk-ui/sync-ui.c:1299 #, c-format msgid "There was one remote rejection." msgid_plural "There were %d remote rejections." msgstr[0] "كان هنالك %d عمليات رفض عن بعد" msgstr[1] "كان هنالك عملية رفض واحدة" msgstr[2] "كان هنالك عمليتا رفض عن بعد" msgstr[3] "كان هنالك %d عمليات رفض عن بعد" msgstr[4] "كان هنالك %d عملية رفض عن بعد" msgstr[5] "كان هنالك %d عملية رفض عن بعد" #: ../src/gtk-ui/sync-ui.c:1304 #, c-format msgid "There was one local rejection." msgid_plural "There were %d local rejections." msgstr[0] "كان هنالك %d عمليات رفض محلية" msgstr[1] "كان هنالك عملية رفض محلية واحدة" msgstr[2] "كان هنالك عمليتا رفض محليتان" msgstr[3] "كان هنالك %d عمليات رفض محلية" msgstr[4] "كان هنالك %d عملية رفض محلية" msgstr[5] "كان هنالك %d عملية رفض محلية" #: ../src/gtk-ui/sync-ui.c:1309 #, c-format msgid "There were %d local rejections and %d remote rejections." msgstr "كان هنالك %d عمليات رفض محلية و %d عمليات رفض عن بعد" #: ../src/gtk-ui/sync-ui.c:1314 #, c-format msgid "Last time: No changes." msgstr "آخر مرة: بدون تغيير" #: ../src/gtk-ui/sync-ui.c:1316 #, c-format msgid "Last time: Sent one change." msgid_plural "Last time: Sent %d changes." msgstr[0] "آخر مرة: ارسلت %d تغييرات" msgstr[1] "آخر مرة ارسلت تغيير واحد" msgstr[2] "آخر مرة ارسلت تغييران" msgstr[3] "آخر مرة: ارسلت %d تغييرات" msgstr[4] "آخر مرة ارسلت %d تغيير" msgstr[5] "آخر مرة ارسلت %d تغيير" #. This is about changes made to the local data. Not all of these #. changes were requested by the remote server, so "applied" #. is a better word than "received" (bug #5185). #: ../src/gtk-ui/sync-ui.c:1324 #, c-format msgid "Last time: Applied one change." msgid_plural "Last time: Applied %d changes." msgstr[0] "آخر مرة: طبقت %d تغييرات" msgstr[1] "آخر مرة: طبقت تغيير واحد" msgstr[2] "آخر مرة: طبقت تغييران" msgstr[3] "آخر مرة: طبقت %d تغييرات" msgstr[4] "آخر مرة: طبقت %d تغيير" msgstr[5] "آخر مرة: طبقت %d تغيير" #: ../src/gtk-ui/sync-ui.c:1329 #, c-format msgid "Last time: Applied %d changes and sent %d changes." msgstr "آخر مرة: طبقت %d تغييرات وارسلت %d تغييرات" #: ../src/gtk-ui/sync-ui.c:1421 msgid "Failed to get server configuration from SyncEvolution" msgstr "فشل في الحصول على مكونات الخادم من سينك افولوشن" #: ../src/gtk-ui/sync-ui.c:1473 msgid "ScheduleWorld enables you to keep your contacts, events, tasks, and notes in sync." msgstr "ScheduleWorld يمكّنك من ابقاء جهات الاتصال، احداث، مهام والملاحظات في تزامن" #: ../src/gtk-ui/sync-ui.c:1476 msgid "Google Sync can back up and synchronize your Address Book with your Gmail contacts." msgstr "بامكان جوجل سينك ان يقوم بنسخ احتياطية وتزامن لدفتر العناوين مع حسابك في Gmail" #. TRANSLATORS: Please include the word "demo" (or the equivalent in #. your language): Funambol is going to be a 90 day demo service #. in the future #: ../src/gtk-ui/sync-ui.c:1482 msgid "Back up your contacts and calendar. Sync with a singleclick, anytime, anywhere (DEMO)." msgstr "قم باعداد نسخ احتياطية لجهات الاتصال والتقويم الخاص بك. تزامن بنقرة واحدة, في اي وقت واي مكان." #: ../src/gtk-ui/sync-ui.c:1510 msgid "New service" msgstr "خدمة جديدة" #: ../src/gtk-ui/sync-ui.c:1557 msgid "Server URL" msgstr "عنوان الخادم" #. TRANSLATORS: placeholder is a source name in settings window #: ../src/gtk-ui/sync-ui.c:1579 #, c-format msgid "%s URI" msgstr "عنوان %s " #: ../src/gtk-ui/sync-ui.c:1716 #: ../src/gtk-ui/ui.glade.h:17 msgid "Launch website" msgstr "فتح الموقع" #: ../src/gtk-ui/sync-ui.c:1720 msgid "Setup and use" msgstr "اضبط واستخدم" #: ../src/gtk-ui/sync-ui.c:1766 msgid "Failed to get list of manually setup services from SyncEvolution" msgstr "فشل في الحصول على قائمة الخدمات المضبوطة يدويا من سينك افولوشن" #: ../src/gtk-ui/sync-ui.c:1807 msgid "Failed to get list of supported services from SyncEvolution" msgstr "فشل في الحصول على قائمة الخدمات المدعومة من سينك افولوشن" #. TODO: this is a hack... SyncEnd should be a signal of it's own, #. not just hacked on top of the syncevolution error codes #: ../src/gtk-ui/sync-ui.c:1968 msgid "Service configuration not found" msgstr "تكوين الخدمة غير موجود" #: ../src/gtk-ui/sync-ui.c:1974 msgid "Not authorized" msgstr "غير مخوّل" #: ../src/gtk-ui/sync-ui.c:1976 msgid "Forbidden" msgstr "محظور" #: ../src/gtk-ui/sync-ui.c:1978 msgid "Not found" msgstr "غير موجود" #: ../src/gtk-ui/sync-ui.c:1980 msgid "Fatal database error" msgstr "خطأ فادح في قاعدة البيانات" #: ../src/gtk-ui/sync-ui.c:1982 msgid "Database error" msgstr "مشكلة في قاعدة البيانات" #: ../src/gtk-ui/sync-ui.c:1984 msgid "No space left" msgstr "لم يبق مكان" #. TODO identify problem item somehow ? #: ../src/gtk-ui/sync-ui.c:1987 msgid "Failed to process SyncML" msgstr "فشل في تحليل SyncML" #: ../src/gtk-ui/sync-ui.c:1989 msgid "Server authorization failed" msgstr "فشل في تخويل الخادم" #: ../src/gtk-ui/sync-ui.c:1991 msgid "Failed to parse configuration file" msgstr "فشل في تحليل ملف المكونات" #: ../src/gtk-ui/sync-ui.c:1993 msgid "Failed to read configuration file" msgstr "فشل في قراءة ملف المكونات" #: ../src/gtk-ui/sync-ui.c:1995 msgid "No configuration found" msgstr "لا يوجد مكوّنات" #: ../src/gtk-ui/sync-ui.c:1997 msgid "No configuration file found" msgstr "ملف التكوين غير موجود" #: ../src/gtk-ui/sync-ui.c:1999 msgid "Server sent bad content" msgstr "ارسل الخادم مضمون غير صالح" #: ../src/gtk-ui/sync-ui.c:2001 msgid "Transport failure (no connection?)" msgstr "فشل في النقل (لا يوجد اتصال؟)" #: ../src/gtk-ui/sync-ui.c:2003 msgid "Connection timed out" msgstr "انقضاء مهلة الاتصال" #: ../src/gtk-ui/sync-ui.c:2005 msgid "Connection certificate has expired" msgstr "انتهت صلاحية رخصة الاتصال" #: ../src/gtk-ui/sync-ui.c:2007 msgid "Connection certificate is invalid" msgstr "رخصة الاتصال غير صالحة" #: ../src/gtk-ui/sync-ui.c:2010 msgid "Connection failed" msgstr "فشل في الاتصال" #: ../src/gtk-ui/sync-ui.c:2012 msgid "URL is bad" msgstr "العنوان خاطئ" #: ../src/gtk-ui/sync-ui.c:2014 msgid "Server not found" msgstr "لم يتم ايجاد الخادم" #: ../src/gtk-ui/sync-ui.c:2016 #, c-format msgid "Error %d" msgstr "خطأ %d" #: ../src/gtk-ui/sync-ui.c:2026 msgid "Sync D-Bus service exited unexpectedly" msgstr "تزامن مع خدمة D-Bus انتهى بطريقة غير متوقعة" #: ../src/gtk-ui/sync-ui.c:2029 #: ../src/gtk-ui/sync-ui.c:2080 msgid "Sync Failed" msgstr "فشل في التزامن" #: ../src/gtk-ui/sync-ui.c:2072 msgid "Sync complete" msgstr "اكتمل التزامن" #: ../src/gtk-ui/sync-ui.c:2077 msgid "Sync canceled" msgstr "تم إلغاء التزامن" #. NOTE extra1 can be error here #: ../src/gtk-ui/sync-ui.c:2095 msgid "Ending sync" msgstr "إنهاء التزامن" #. TRANSLATORS: placeholder is a source name (e.g. 'Calendar') in a progress text #: ../src/gtk-ui/sync-ui.c:2119 #, c-format msgid "Preparing '%s'" msgstr "تحضير '%s'" #. TRANSLATORS: placeholder is a source name in a progress text #: ../src/gtk-ui/sync-ui.c:2131 #, c-format msgid "Sending '%s'" msgstr "ارسال '%s'" #. TRANSLATORS: placeholder is a source name in a progress text #: ../src/gtk-ui/sync-ui.c:2143 #, c-format msgid "Receiving '%s'" msgstr "جاري الحصول '%s'" #: ../src/gtk-ui/ui.glade.h:1 msgid "Data" msgstr "بيانات" #: ../src/gtk-ui/ui.glade.h:2 msgid "No sync service in use" msgstr "لا يوجد خدمة تزامن في الاستخدام" #: ../src/gtk-ui/ui.glade.h:3 msgid "Sync failure" msgstr "فشل في التزامن" #: ../src/gtk-ui/ui.glade.h:4 msgid "Type of Sync" msgstr "فئة التزامن" #: ../src/gtk-ui/ui.glade.h:5 msgid "Manual setup" msgstr "إعداد يدوي" #: ../src/gtk-ui/ui.glade.h:6 msgid "Supported services" msgstr "الخدمات المعتمدة" #: ../src/gtk-ui/ui.glade.h:7 msgid "Add new service" msgstr "اضافة خدمة جديدة" #: ../src/gtk-ui/ui.glade.h:8 msgid "Back to sync" msgstr "عودة للتزامن" #: ../src/gtk-ui/ui.glade.h:9 msgid "" "Change sync\n" "service" msgstr "" "تغيير خدمة\n" "التزامن" #: ../src/gtk-ui/ui.glade.h:11 msgid "Delete all local data and replace it with remote data" msgstr "محي كل البيانات المحلية وتبديلها ببيانات عن بعد" #: ../src/gtk-ui/ui.glade.h:12 msgid "Delete all remote data and replace it with local data" msgstr "محي كل البيانات عن بعد واستبدالها ببيانات محلية" #: ../src/gtk-ui/ui.glade.h:13 msgid "Delete this service" msgstr "حذف هذه الخدمة" #: ../src/gtk-ui/ui.glade.h:14 msgid "Edit service settings" msgstr "تحرير إعدادات الخدمة" #: ../src/gtk-ui/ui.glade.h:15 msgid "" "If you don't see your service above but know that your sync provider uses SyncML\n" "you can setup a service manually." msgstr "" "ان كنت لا ترى اسم خدمتك في الأعلى لكنك تعرف ان مزود التزامن يستخدم SyncML\n" "بامكانك ضبط الخدمة بشكل يدوي." #: ../src/gtk-ui/ui.glade.h:18 msgid "Merge local and remote data (recommended)" msgstr "دمج البيانات المحلية والبعيدة (مفضل)" #: ../src/gtk-ui/ui.glade.h:19 msgid "Password" msgstr "كلمة السر" #: ../src/gtk-ui/ui.glade.h:20 msgid "Reset original server settings" msgstr "اعادة إعدادات الخادم الأصلية" #: ../src/gtk-ui/ui.glade.h:21 msgid "Save and use this service" msgstr "احفظ واستخدم هذه الخدمة" #: ../src/gtk-ui/ui.glade.h:22 msgid "Select sync service" msgstr "أختر خدمة تزامن" #: ../src/gtk-ui/ui.glade.h:23 msgid "Server settings" msgstr "اعدادات الخادم" #: ../src/gtk-ui/ui.glade.h:24 msgid "Service name" msgstr "اسم الخدمة" #: ../src/gtk-ui/ui.glade.h:25 msgid "" "Sorry, you need an internet\n" "connection to sync." msgstr "" "نأسف, للتزامن تحتاج\n" "لاتصال انترنت" #: ../src/gtk-ui/ui.glade.h:27 msgid "Stop using this service" msgstr "توقف عن استخدام هذه الخدمة" #: ../src/gtk-ui/ui.glade.h:30 msgid "Synchronization is not available (D-Bus service does not answer), sorry." msgstr "نأسف, التزامن غير متوفر (خدمة D-Bus لم تتجاوب)" #: ../src/gtk-ui/ui.glade.h:31 msgid "" "To sync you'll need a network connection and an account with a sync service.\n" "We support the following services: " msgstr "" "للتزامن تحتاج الى اتصال شبكة وحساب في خدمة تزامن\n" "نحن ندعم الخدمات التالية:" #: ../src/gtk-ui/ui.glade.h:33 msgid "Username" msgstr "اسم المستخدم" #: ../src/gtk-ui/ui.glade.h:34 msgid "" "You haven't selected a sync service yet. Sync services let you \n" "synchronize your data between your netbook and a web service." msgstr "" "لم يتم اختيار خدمة تزامن بعد. خدمات التزامن تمنحك القدرة\n" "على تزامن بيانات من حاسبوبك الشبكي وخدمة شبكية." #: ../src/gtk-ui/sync.desktop.in.h:2 #: ../src/gtk-ui/sync-gtk.desktop.in.h:2 msgid "Up to date" msgstr "محتلن" #: ../src/gtk-ui/sync-gtk.desktop.in.h:1 msgid "Sync (GTK)" msgstr "تزامن (جي تي كي)" syncevolution_1.4/po/ast.po000066400000000000000000000762701230021373600161370ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: syncevolution.master\n" "Report-Msgid-Bugs-To: http://moblin.org/projects/syncevolution\n" "POT-Creation-Date: 2010-05-22 09:19+0000\n" "PO-Revision-Date: \n" "Last-Translator: astur \n" "Language-Team: Softastur \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-Language: Asturian\n" "X-Poedit-SourceCharset: utf-8\n" "Plural-Forms: nplurals=2; plural=n!=1;\n" #. TRANSLATORS: this is the application name that may be used by e.g. #. the windowmanager #: ../src/gtk-ui/main.c:40 #: ../src/gtk-ui/ui.glade.h:38 #: ../src/gtk-ui/sync.desktop.in.h:1 #: ../src/gnome-bluetooth/syncevolution.c:112 msgid "Sync" msgstr "Sincronizador" #: ../src/gtk-ui/sync-ui.c:266 msgid "Contacts" msgstr "Contautos" #: ../src/gtk-ui/sync-ui.c:268 msgid "Appointments" msgstr "Cites" #: ../src/gtk-ui/sync-ui.c:270 #: ../src/gtk-ui/ui.glade.h:40 msgid "Tasks" msgstr "Xeres" #: ../src/gtk-ui/sync-ui.c:272 msgid "Notes" msgstr "Notes" #. TRANSLATORS: This is a "combination source" for syncing with devices #. * that combine appointments and tasks. the name should match the ones #. * used for calendar and todo above #: ../src/gtk-ui/sync-ui.c:277 msgid "Appointments & Tasks" msgstr "Cites y xeres" #: ../src/gtk-ui/sync-ui.c:349 msgid "Starting sync" msgstr "Aniciando..." #. TRANSLATORS: slow sync confirmation dialog message. Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:387 #, c-format msgid "Do you want to slow sync with %s?" msgstr "Quies una sincronización lenta con %s?" #: ../src/gtk-ui/sync-ui.c:391 msgid "Yes, do slow sync" msgstr "Sí, facer sincronización lenta" #: ../src/gtk-ui/sync-ui.c:391 msgid "No, cancel sync" msgstr "Non, encaboxar la sincronización" #. TRANSLATORS: confirmation dialog for refresh-from-server. Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:424 #, c-format msgid "Do you want to delete all local data and replace it with data from %s? This is not usually advised." msgstr "¿Quies desaniciar tolos datos llocales y sustituyilos polos datos de %s? Esto nun ye mui recomendable." #: ../src/gtk-ui/sync-ui.c:429 #: ../src/gtk-ui/sync-ui.c:460 msgid "Yes, delete and replace" msgstr "Sí, desaniciar y camudar" #: ../src/gtk-ui/sync-ui.c:429 #: ../src/gtk-ui/sync-ui.c:460 #: ../src/gtk-ui/sync-ui.c:1605 msgid "No" msgstr "Non" #. TRANSLATORS: confirmation dialog for refresh-from-client. Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:455 #, c-format msgid "Do you want to delete all data in %s and replace it with your local data? This is not usually advised." msgstr "¿Quies desaniciar tolos datos de %s y sustituyilos polos datos llocales? Esto nun ye mui recomendable." # We are trying to cancel the synchronization #: ../src/gtk-ui/sync-ui.c:487 msgid "Trying to cancel sync" msgstr "Tamos intentando encaboxar la sincronización" # There is no space left #: ../src/gtk-ui/sync-ui.c:529 msgid "No service or device selected" msgstr "Nun s'esbilló dengún serviciu o preséu" # Last synchronization happened a few seconds ago #. TRANSLATORS: This is the title on main view. Placeholder is #. * the service name. Example: "Google - synced just now" #: ../src/gtk-ui/sync-ui.c:537 #, c-format msgid "%s - synced just now" msgstr "%s - sincronizóse fai unos segundos" # Last synchronization happened one minute ago #: ../src/gtk-ui/sync-ui.c:541 #, c-format msgid "%s - synced a minute ago" msgstr "%s - sincronizóse fai un minutu" # Last synchronization happened %ld minutes ago #: ../src/gtk-ui/sync-ui.c:545 #, c-format msgid "%s - synced %ld minutes ago" msgstr "%s - sincronizóse fai %ld minutos" # Last synchronization happened one hour ago #: ../src/gtk-ui/sync-ui.c:550 #, c-format msgid "%s - synced an hour ago" msgstr "%s - sincronizóse fai una hora" # Last synchronization happened %ld hours ago #: ../src/gtk-ui/sync-ui.c:554 #, c-format msgid "%s - synced %ld hours ago" msgstr "%s - sincronizóse fai %ld hores" # Last synchronization happened one day ago #: ../src/gtk-ui/sync-ui.c:559 #, c-format msgid "%s - synced a day ago" msgstr "%s - sincronizóse fai un día" # Last synchronization happened %ld days ago #: ../src/gtk-ui/sync-ui.c:563 #, c-format msgid "%s - synced %ld days ago" msgstr "%s - sincronizóse fai %ld díes" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "You've just restored a backup. The changes have not been " #. * "synced with %s yet" #: ../src/gtk-ui/sync-ui.c:612 #: ../src/gtk-ui/sync-ui.c:726 msgid "Sync now" msgstr "Sincronizar" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "A normal sync is not possible at this time..." message. #. * "Other options" will open Emergency view #: ../src/gtk-ui/sync-ui.c:618 #: ../src/gtk-ui/ui.glade.h:37 msgid "Slow sync" msgstr "Sincronización lenta" #: ../src/gtk-ui/sync-ui.c:619 msgid "Other options..." msgstr "Otres opciones..." # "Select a service". Omitted 'syn' because there's no room in the button for proper display. The context is clear and this will not impair understanding by the user. #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * when no service is selected. Will open configuration view #: ../src/gtk-ui/sync-ui.c:624 msgid "Select sync service" msgstr "Esbillar un serviciu" # Edit the configuration of the service #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * login to service fails. Will open configuration view for this service #: ../src/gtk-ui/sync-ui.c:629 msgid "Edit service settings" msgstr "Editar la configuración del serviciu" # There's no sync service yet between the netbook and a web service. #: ../src/gtk-ui/sync-ui.c:700 msgid "You haven't selected a sync service or device yet. Sync services let you synchronize your data between your netbook and a web service. You can also sync directly with some devices." msgstr "Entá nun escoyisti dengún serviciu o preséu de sincronización. Estos servicios permítente sincronizar los datos ente'l miniportátil y un serviciu web. Tamién puede facese direutamente con dellos preseos." #: ../src/gtk-ui/sync-ui.c:722 msgid "Sync again" msgstr "¡Sincronízate!" #: ../src/gtk-ui/sync-ui.c:743 msgid "Restoring" msgstr "Restaurando" # Synchronization is taking place #: ../src/gtk-ui/sync-ui.c:745 msgid "Syncing" msgstr "Sincronizando" #. TRANSLATORS: This is for the button in main view, right side. #. Keep line length below ~20 characters, use two lines if needed #: ../src/gtk-ui/sync-ui.c:757 #: ../src/gtk-ui/sync-ui.c:3399 msgid "Cancel sync" msgstr "Encaboxar la sincronización" #: ../src/gtk-ui/sync-ui.c:922 msgid "Back to sync" msgstr "Volver a la sincronización" #. TRANSLATORS: label for checkbutton/toggle in main view. #. * Please stick to similar length strings or break the line with #. * "\n" if absolutely needed #: ../src/gtk-ui/sync-ui.c:1224 msgid "Automatic sync" msgstr "" "Sincronización\n" "automática" #. This is the expander label in emergency view. It summarizes the #. * currently selected data sources. First placeholder is service/device #. * name, second a comma separeted list of sources. #. * E.g. "Affected data: Google Contacts, Appointments" #: ../src/gtk-ui/sync-ui.c:1519 #, c-format msgid "Affected data: %s %s" msgstr "Datos afeutaos: %s %s" #: ../src/gtk-ui/sync-ui.c:1524 #, c-format msgid "Affected data: none" msgstr "Datos afeutaos: dengún" #. TRANSLATORS: confirmation for restoring a backup. placeholder is the #. * backup time string defined below #: ../src/gtk-ui/sync-ui.c:1602 #, c-format msgid "Do you want to restore the backup from %s? All changes you have made since then will be lost." msgstr "¿Quies restaurar la copia de seguridá de %s? Tolos cambeos que tengas fechos dende entós van perdese." #: ../src/gtk-ui/sync-ui.c:1605 msgid "Yes, restore" msgstr "Sí, restaurar" #. TRANSLATORS: date/time for strftime(), used in emergency view backup #. * label. Any time format that shows date and time is good. #: ../src/gtk-ui/sync-ui.c:1637 #, c-format msgid "%x %X" msgstr "%x %X" #. TRANSLATORS: label for a backup in emergency view. Placeholder is #. * service or device name #: ../src/gtk-ui/sync-ui.c:1656 #, c-format msgid "Backed up before syncing with %s" msgstr "Copia de seguridá fecha enantes de sincronizar con %s" #: ../src/gtk-ui/sync-ui.c:1673 msgid "Restore" msgstr "Restaurar" #. TRANSLATORS: this is an explanation in Emergency view. #. * Placeholder is a service/device name #: ../src/gtk-ui/sync-ui.c:1780 #, c-format msgid "A normal sync with %s is not possible at this time. You can do a slow two-way sync or start from scratch. You can also restore a backup, but a slow sync or starting from scratch will still be required before normal sync is possible." msgstr "Una sincronización normal con %s nun ye dable nesti intre. Puedes facer una sincronización lenta de dos víes o entamar de cero. Tamién puedes restaurar una copia de seguridá, pero igual se fai necesaria una sincronización lenta o entamar de cero enantes de que seya dable una sincronización normal." #: ../src/gtk-ui/sync-ui.c:1790 #, c-format msgid "If something has gone horribly wrong, you can try a slow sync, start from scratch or restore from backup." msgstr "Si asocede daqué realmente malo, puedes intentar una sincronización lenta, entamar de cero o restaurar la copia de seguridá." #. TRANSLATORS: These are a buttons in Emergency view. Placeholder is a #. * service/device name. Please don't use too long lines, but feel free to #. * use several lines. #: ../src/gtk-ui/sync-ui.c:1799 #, c-format msgid "" "Delete all your local\n" "data and replace with\n" "data from %s" msgstr "" "Desaniciar tolos datos\n" "llocales y trocalos con\n" "datos de %s" #: ../src/gtk-ui/sync-ui.c:1805 #, c-format msgid "" "Delete all data on\n" "%s and replace\n" "with your local data" msgstr "" "Desaniciar tolos datos en\n" "%s y trocalos\n" "colos tos datos llocales" #: ../src/gtk-ui/sync-ui.c:2267 msgid "Failed to get list of supported services from SyncEvolution" msgstr "Error al obtener la llista de servicios compatibles de SyncEvolution" #: ../src/gtk-ui/sync-ui.c:2321 msgid "There was a problem communicating with the sync process. Please try again later." msgstr "Hebo un problema de comunicación nel procesu de sincronización. Inténtalo más sero." #: ../src/gtk-ui/sync-ui.c:2380 msgid "Restore failed" msgstr "Falló al Restaurar" #: ../src/gtk-ui/sync-ui.c:2383 #: ../src/gtk-ui/sync-ui.c:3268 msgid "Sync failed" msgstr "Falló al Sincronizar" #: ../src/gtk-ui/sync-ui.c:2389 msgid "Restore complete" msgstr "Restauráu completu" #: ../src/gtk-ui/sync-ui.c:2392 msgid "Sync complete" msgstr "¡Sincronizáu!" #: ../src/gtk-ui/sync-ui.c:2484 #, c-format msgid "Preparing '%s'" msgstr "Tresnando '%s'" #: ../src/gtk-ui/sync-ui.c:2487 #, c-format msgid "Receiving '%s'" msgstr "Recibiendo '%s'" #: ../src/gtk-ui/sync-ui.c:2490 #, c-format msgid "Sending '%s'" msgstr "Unviando '%s'" #: ../src/gtk-ui/sync-ui.c:2611 #, c-format msgid "There was one remote rejection." msgid_plural "There were %ld remote rejections." msgstr[0] "Hebo un rechazu remotu." msgstr[1] "Hebo %ld rechazos remotos." #: ../src/gtk-ui/sync-ui.c:2616 #, c-format msgid "There was one local rejection." msgid_plural "There were %ld local rejections." msgstr[0] "Hebo un rechazu llocal." msgstr[1] "Hebo %ld rechazos llocales." #: ../src/gtk-ui/sync-ui.c:2621 #, c-format msgid "There were %ld local rejections and %ld remote rejections." msgstr "Hebo %ld rechazos llocales y %ld rechazos remotos." #: ../src/gtk-ui/sync-ui.c:2626 #, c-format msgid "Last time: No changes." msgstr "La cabera vegada: nun hebo cambeos." #: ../src/gtk-ui/sync-ui.c:2628 #, c-format msgid "Last time: Sent one change." msgid_plural "Last time: Sent %ld changes." msgstr[0] "La cabera vegada: unvióse un cambéu." msgstr[1] "La cabera vegada: unviáronse %ld cambeos." #. This is about changes made to the local data. Not all of these #. changes were requested by the remote server, so "applied" #. is a better word than "received" (bug #5185). #: ../src/gtk-ui/sync-ui.c:2636 #, c-format msgid "Last time: Applied one change." msgid_plural "Last time: Applied %ld changes." msgstr[0] "La cabera vegada: aplicóse un cambéu." msgstr[1] "La cabera vegada: aplicáronse %ld cambeos." #: ../src/gtk-ui/sync-ui.c:2641 #, c-format msgid "Last time: Applied %ld changes and sent %ld changes." msgstr "La cabera vegada: aplicáronse %ld cambeos y unviáronse %ld cambeos." #. TRANSLATORS: the placeholder is a error message (hopefully) #. * explaining the problem #: ../src/gtk-ui/sync-ui.c:2848 #, c-format msgid "" "There was a problem with last sync:\n" "%s" msgstr "" "Hebo un problema na cabera sincronización:\n" "%s" #: ../src/gtk-ui/sync-ui.c:2858 #, c-format msgid "You've just restored a backup. The changes have not been synced with %s yet" msgstr "Restauraste una copia de resguardu. Los cambeos entá nun se sincronicen con %s" #: ../src/gtk-ui/sync-ui.c:3146 msgid "Waiting for current operation to finish..." msgstr "Esperando a que la operación actual fine..." #. TRANSLATORS: next strings are error messages. #: ../src/gtk-ui/sync-ui.c:3180 msgid "A normal sync is not possible at this time. The server suggests a slow sync, but this might not always be what you want if both ends already have data." msgstr "Nun ye dable una sincronización normal nesti intre. El sirvidor suxer una sincronización lenta, pero puede qu'esto nun seya lo que quies si dambes partes yá tienen datos." # the syncronization service D-Bus closed unexpectedly #: ../src/gtk-ui/sync-ui.c:3184 msgid "The sync process died unexpectedly." msgstr "El serviciu de sincronización zarróse d'esmenu." #: ../src/gtk-ui/sync-ui.c:3189 msgid "Password request was not answered. You can save the password in the settings to prevent the request." msgstr "Nun se retrucó a la solicitú de contraseña. Pa evitar qu'apaeza otra vegada, puede atroxase la contraseña nos axustes." #. TODO use the service device name here, this is a remote problem #: ../src/gtk-ui/sync-ui.c:3193 msgid "There was a problem processing sync request. Trying again may help." msgstr "Hebo un problema al procesar la solicitú de sincronización. Intentalo otra vegada puede ayudar." #: ../src/gtk-ui/sync-ui.c:3199 msgid "Failed to login. Could there be a problem with your username or password?" msgstr "Accesu fallíu. ¿Podría haber dalgún problema col to usuariu o contraseña?" #: ../src/gtk-ui/sync-ui.c:3202 msgid "Forbidden" msgstr "Prohibío" #. TRANSLATORS: data source means e.g. calendar or addressbook #: ../src/gtk-ui/sync-ui.c:3208 msgid "A data source could not be found. Could there be a problem with the settings?" msgstr "Nun pudo atopase la fonte de datos. ¿Podría haber dalgún problema cola configuración?" # Fatal error in the database #: ../src/gtk-ui/sync-ui.c:3212 msgid "Remote database error" msgstr "Fallu na base de datos remota" #. This can happen when EDS is borked, restart it may help... #: ../src/gtk-ui/sync-ui.c:3215 msgid "There is a problem with the local database. Syncing again or rebooting may help." msgstr "Hai un problema cola base de datos llocal. Sincronizar otra vegada o reaniciar puede aidar." # There is no space left #: ../src/gtk-ui/sync-ui.c:3218 msgid "No space on disk" msgstr "Nun hai espaciu en discu" #: ../src/gtk-ui/sync-ui.c:3220 msgid "Failed to process SyncML" msgstr "Fallu al procesar SyncML" # the server didn't accept the authorization #: ../src/gtk-ui/sync-ui.c:3222 msgid "Server authorization failed" msgstr "El sirvidor nun aceutó l'autorización" # Error while analysing the syntax of the configuration file #: ../src/gtk-ui/sync-ui.c:3224 msgid "Failed to parse configuration file" msgstr "Fallu al analizar el ficheru de configuración" # Error while reading the configuration file #: ../src/gtk-ui/sync-ui.c:3226 msgid "Failed to read configuration file" msgstr "Fallu al lleer del ficheru de configuración" # The configuration has not been found #: ../src/gtk-ui/sync-ui.c:3228 msgid "No configuration found" msgstr "Nun s'alcontró la configuración" # The configuration file has not been found #: ../src/gtk-ui/sync-ui.c:3230 msgid "No configuration file found" msgstr "Nun s'alcontró'l ficheru de configuración" # The server sent an an invalid content #: ../src/gtk-ui/sync-ui.c:3232 msgid "Server sent bad content" msgstr "El sirvidor unvió un conteníu non válidu" #: ../src/gtk-ui/sync-ui.c:3234 msgid "Connection certificate has expired" msgstr "El certificáu de conexón venció" # The connection certificate is not valid #: ../src/gtk-ui/sync-ui.c:3236 msgid "Connection certificate is invalid" msgstr "El certificáu de conexón nun ye válidu" #: ../src/gtk-ui/sync-ui.c:3244 msgid "We were unable to connect to the server. The problem could be temporary or there could be something wrong with the settings." msgstr "Nun pudimos coneutanos col sirvidor. El problema podría ser temporal o podría haber dalgún problema cola configuración." # The URL is incorrect #: ../src/gtk-ui/sync-ui.c:3251 msgid "The server URL is bad" msgstr "La URL del sirvidor ye incorreuta" #: ../src/gtk-ui/sync-ui.c:3256 msgid "The server was not found" msgstr "Nun s'alcontró'l sirvidor" #: ../src/gtk-ui/sync-ui.c:3258 #, c-format msgid "Error %d" msgstr "Fallu: %d" #. TRANSLATORS: password request dialog contents: title, cancel button #. * and ok button #: ../src/gtk-ui/sync-ui.c:3396 msgid "Password is required for sync" msgstr "Requierse contraseña pa la sincronización" #: ../src/gtk-ui/sync-ui.c:3400 msgid "Sync with password" msgstr "Sincronización con contraseña" #. TRANSLATORS: password request dialog message, placeholder is service name #: ../src/gtk-ui/sync-ui.c:3410 #, c-format msgid "Please enter password for syncing with %s:" msgstr "Por favor introduz la contraseña pa sincronizar con %s:" #. title for the buttons on the right side of main view #: ../src/gtk-ui/ui.glade.h:2 msgid "Actions" msgstr "Aiciones" #. text between the two "start from scratch" buttons in emergency view #: ../src/gtk-ui/ui.glade.h:4 msgid "or" msgstr "o" # compatible services #: ../src/gtk-ui/ui.glade.h:5 msgid "Direct sync" msgstr "Sincronización direuta" #: ../src/gtk-ui/ui.glade.h:6 msgid "Network sync" msgstr "Sincronización de rede" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:8 msgid "Restore from backup" msgstr "Restaurar de la copia de seguridá" # compatible services #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:10 msgid "Slow sync" msgstr "Sincronización lenta" # compatible services #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:12 msgid "Start from scratch" msgstr "Entamar dende cero" #: ../src/gtk-ui/ui.glade.h:13 msgid "" "A slow sync compares items from both sides and tries to merge them. \n" "This may fail in some cases, leading to duplicates or lost information." msgstr "" "Una sincronización lenta compara elementos de dambos llaos ya intenta combinalos. \n" "Esto puede fallar en dellos casos, criando duplicaos o perda d'información." # I had to shorten the string to fit the button #: ../src/gtk-ui/ui.glade.h:15 msgid "Add new device" msgstr "Amestar preséu" #: ../src/gtk-ui/ui.glade.h:16 msgid "Add new service" msgstr "Amestar serviciu" #. explanation of "Restore backup" function #: ../src/gtk-ui/ui.glade.h:18 msgid "Backups are made before every time we Sync. Choose a backup to restore. Any changes you have made since then will be lost." msgstr "Les copies de seguridá faense enantes cada vegada que sincronizamos. Escueyi una copia de seguridá pa restaurar. Cualesquier cambéu que tengas fechu dende entós, va perdese." #: ../src/gtk-ui/ui.glade.h:19 msgid "Calendar" msgstr "Calendariu" #. Button in main view, right side. Keep to below 20 chars per line, feel free to use two lines #: ../src/gtk-ui/ui.glade.h:21 msgid "" "Change or edit\n" "sync service" msgstr "" "Camudar o editar\n" "serviciu sincronizador" #. close button for settings window #: ../src/gtk-ui/ui.glade.h:24 msgid "Close" msgstr "Zarrar" #: ../src/gtk-ui/ui.glade.h:25 msgid "" "Delete all data on Zyb \n" "and replace with your\n" "local information" msgstr "" "Desaniciar tolos datos en Zyb \n" "y sustituyilos cola to\n" "información llocal" #: ../src/gtk-ui/ui.glade.h:28 msgid "" "Delete all your local\n" "information and replace\n" "with data from Zyb" msgstr "" "Desaniciar tolos datos\n" "llocales y trocalos con\n" "datos de %s" #. button in main view, right side. Keep length to 20 characters or so, use two lines if needed #: ../src/gtk-ui/ui.glade.h:32 msgid "" "Fix a sync\n" "emergency" msgstr "" "Iguar urxencia\n" "en sincronización" #: ../src/gtk-ui/ui.glade.h:34 msgid "" "If you don't see your service above but know that your sync provider uses SyncML\n" "you can setup a service manually." msgstr "" "Si nun puedes ver el to serviciu de sincronización arriba, pero sabes que'l to fornidor emplega SyncML\n" "puedes configurar el serviciu manualmente." #: ../src/gtk-ui/ui.glade.h:36 msgid "Settings" msgstr "Configuración" #: ../src/gtk-ui/ui.glade.h:39 msgid "Sync Emergency" msgstr "Sincronización d'emerxencia" #: ../src/gtk-ui/ui.glade.h:41 msgid "" "To sync you'll need a network connection and an account with a sync service.\n" "We support the following services: " msgstr "" "Pa executar la sincronización faen falta una conexón de rede y una cuenta nun serviciu de sincronización. \n" "Son compatibles los siguientes servicios:" #: ../src/gtk-ui/ui.glade.h:43 msgid "Use Bluetooth to Sync your data from one device to another." msgstr "Usa Bluetooth pa sincronizar los tos datos dende un preséu a otru. " #: ../src/gtk-ui/ui.glade.h:44 msgid "You will need to add Bluetooth devices before they can be synced." msgstr "Vas necesitar amestar preseos Bluetooth enantes de que puedan sincronizase." #: ../src/gtk-ui/sync.desktop.in.h:2 #: ../src/gtk-ui/sync-gtk.desktop.in.h:2 msgid "Up to date" msgstr "Al día" #: ../src/gtk-ui/sync-gtk.desktop.in.h:1 msgid "Sync (GTK)" msgstr "Sync (GTK)" #: ../src/gtk-ui/sync-config-widget.c:78 msgid "ScheduleWorld enables you to keep your contacts, events, tasks, and notes in sync." msgstr "ScheduleWorld permítete sincronizar los contautos, eventos, xeres y notes." #: ../src/gtk-ui/sync-config-widget.c:81 msgid "Google Sync can back up and synchronize your contacts with your Gmail contacts." msgstr "Google Sync puede facer copia de resguardu y sincroniza los tos contautos colos que tengas en Gmail." #. TRANSLATORS: Please include the word "demo" (or the equivalent in #. your language): Funambol is going to be a 90 day demo service #. in the future #: ../src/gtk-ui/sync-config-widget.c:87 msgid "Back up your contacts and calendar. Sync with a single click, anytime, anywhere (DEMO)." msgstr "Resguarda los tos contautos y el calendariu. Sincronízalos con un clic, en cualesquier intre y dende au seya (DEMO)." #: ../src/gtk-ui/sync-config-widget.c:90 msgid "Mobical Backup and Restore service allows you to securely back up your personal mobile data for free." msgstr "El serviciu Mobical Backup and Restore permítete facer copies de resguardu segures de la información del to móvil de baldre." #: ../src/gtk-ui/sync-config-widget.c:93 msgid "ZYB is a simple way for people to store and share mobile information online." msgstr "ZYB ye un mou cenciellu pa que la xente atroxe y comparta la información móvil en llinia." #: ../src/gtk-ui/sync-config-widget.c:96 msgid "Memotoo lets you access your personal data from any computer connected to the Internet." msgstr "Memotoo permítete acceder a los tos datos personales dende cualesquier computadora coneutada con Internet." # Error while analysing the syntax of the configuration file #: ../src/gtk-ui/sync-config-widget.c:192 msgid "Sorry, failed to save the configuration" msgstr "Sentímoslo, nun pudo atroxase la configuración" #: ../src/gtk-ui/sync-config-widget.c:381 msgid "Service must have a name and server URL" msgstr "El serviciu tien de tener un nome y una URL" #: ../src/gtk-ui/sync-config-widget.c:422 #, c-format msgid "Do you want to reset the settings for %s? This will not remove any synced information on either end." msgstr "¿Quies reaxustar la configuración pa %s? Esto nun desaniciará denguna información sincronizada nes partes." #. TRANSLATORS: buttons in reset-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:426 msgid "Yes, reset" msgstr "Sí, reaniciar" #: ../src/gtk-ui/sync-config-widget.c:427 #: ../src/gtk-ui/sync-config-widget.c:438 msgid "No, keep settings" msgstr "Non, caltener configuración" #: ../src/gtk-ui/sync-config-widget.c:432 #, c-format msgid "Do you want to delete the settings for %s? This will not remove any synced information on either end but it will remove these settings." msgstr "¿Quies desaniciar la configuración de %s? Esto nun desaniciará denguna información sincronizada en dengún de los dos llaos, pero desaniciará esta configuración. " #. TRANSLATORS: buttons in delete-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:437 msgid "Yes, delete" msgstr "Sí, desaniciar" #: ../src/gtk-ui/sync-config-widget.c:467 msgid "Reset settings" msgstr "Devolver la configuración a los valores orixinales" #: ../src/gtk-ui/sync-config-widget.c:470 msgid "Delete settings" msgstr "Desaniciar la configuración" # Configuring and using #: ../src/gtk-ui/sync-config-widget.c:480 msgid "Save and use" msgstr "Guardar y usar" #: ../src/gtk-ui/sync-config-widget.c:483 msgid "" "Save and replace\n" "current service" msgstr "" "Guardar y trocar\n" "serviciu actual" #: ../src/gtk-ui/sync-config-widget.c:493 msgid "Stop using device" msgstr "Dexar d'usar esti preséu" #: ../src/gtk-ui/sync-config-widget.c:496 msgid "Stop using service" msgstr "Dexar d'usar esti serviciu" # URI of %s #. TRANSLATORS: label for an entry in service configuration form. #. * Placeholder is a source name. #. * Example: "Appointments URI" #: ../src/gtk-ui/sync-config-widget.c:679 #, c-format msgid "%s URI" msgstr "URI de %s" #. TRANSLATORS: toggles in service configuration form, placeholder is service #. * or device name #: ../src/gtk-ui/sync-config-widget.c:854 #, c-format msgid "Send changes to %s" msgstr "Unviar cambeos a %s" #: ../src/gtk-ui/sync-config-widget.c:859 #, c-format msgid "Receive changes from %s" msgstr "Recibir cambéu de %s" #: ../src/gtk-ui/sync-config-widget.c:875 msgid "Sync" msgstr "Sincronizar" #. TRANSLATORS: label of a entry in service configuration #: ../src/gtk-ui/sync-config-widget.c:891 msgid "Server address" msgstr "Direición del sirvidor" # I had to use colon to avoid the problem of selecting a masculine or feminine gender in the article, which would create problems down the road. #. TRANSLATORS: explanation before a device template combobox. #. * Placeholder is a device name like 'Nokia N85' or 'Syncevolution #. * Client' #: ../src/gtk-ui/sync-config-widget.c:967 #, c-format msgid "This device looks like it might be a '%s'. If this is not correct, please take a look at the list of supported devices and pick yours if it is listed" msgstr "Paez qu'esti preséu ye: %s'. Si nun ye cierto, mira la llista de los preseos compatibles y seleiciona'l correutu, si apaez." #: ../src/gtk-ui/sync-config-widget.c:973 msgid "We don't know what this device is exactly. Please take a look at the list of supported devices and pick yours if it is listed" msgstr "Nun sabemos qué preséu ye ésti. Mira la llista de los preseos compatibles y seleiciona'l correutu, si apaez." #: ../src/gtk-ui/sync-config-widget.c:1126 #, c-format msgid "%s - Bluetooth device" msgstr "%s - Preseos Bluetooth" #. TRANSLATORS: service title for services that are not based on a #. * template in service list, the placeholder is the name of the service #: ../src/gtk-ui/sync-config-widget.c:1132 #, c-format msgid "%s - manually setup" msgstr "%s - configurar manualmente" # Open the website #. TRANSLATORS: link button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1807 msgid "Launch website" msgstr "Abrir el sitiu web" # Configuring and using #. TRANSLATORS: button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1816 msgid "Set up now" msgstr "Configurar agora" #. TRANSLATORS: labels of entries in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1912 msgid "Username" msgstr "Usuariu" #: ../src/gtk-ui/sync-config-widget.c:1927 msgid "Password" msgstr "Contraseña" #. TRANSLATORS: warning in service configuration form for people #. who have modified the configuration via other means. #: ../src/gtk-ui/sync-config-widget.c:1950 msgid "Current configuration is more complex than what can be shown here. Changes to sync mode or synced data types will overwrite that configuration." msgstr "La configuración ye más complexa de lo que podemos amosar equí. Cambeos en el mou de sincronización o les tribes de datos sincronizaos van sobroscribir esa configuración." #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1969 msgid "Hide server settings" msgstr "Anubrir la configuración del sirvidor" #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1989 msgid "Show server settings" msgstr "Amosar la configuración del sirvidor" #: ../src/gnome-bluetooth/syncevolution.c:110 msgid "Sync in the Sync application" msgstr "Sincronizar n'aplicación de sincronización" # Synchronization is taking place #: ../src/syncevo-dbus-server.cpp:5667 #, c-format msgid "%s is syncing" msgstr "%s ta sincronizándose" #: ../src/syncevo-dbus-server.cpp:5668 #, c-format msgid "We have just started to sync your computer with the %s sync service." msgstr "Anicióse a sincronizar esta máquina col serviciu de sincronización %s." #. if sync is successfully started and done #: ../src/syncevo-dbus-server.cpp:5682 #, c-format msgid "%s sync complete" msgstr "Completóse la sincronización de %s" #: ../src/syncevo-dbus-server.cpp:5683 #, c-format msgid "We have just finished syncing your computer with the %s sync service." msgstr "Finóse de sincronizar esta máquina col serviciu de sincronización %s." #. if sync is successfully started and has errors, or not started successful with a fatal problem #: ../src/syncevo-dbus-server.cpp:5688 msgid "Sync problem." msgstr "Problemes na sincronización." #: ../src/syncevo-dbus-server.cpp:5689 msgid "Sorry, there's a problem with your sync that you need to attend to." msgstr "Hebo un fallu mentantu la sincronización, y tienes qu'igualu." #: ../src/syncevo-dbus-server.cpp:5762 msgid "View" msgstr "Ver" #. Use "default" as ID because that is what mutter-moblin #. recognizes: it then skips the action instead of adding it #. in addition to its own "Dismiss" button (always added). #: ../src/syncevo-dbus-server.cpp:5766 msgid "Dismiss" msgstr "Zarrar" #~ msgid "" #~ "Do you want to replace %s with %s? This will not remove any synced " #~ "information on either end but you will no longer be able to sync with %s." #~ msgstr "" #~ "¿Quies camudar %s por %s? Esto nun va desaniciar denguna información " #~ "sincronizada nes partes pero nun podrás volver a sincronizar con %s." #~ msgid "Yes, use %s" #~ msgstr "Sí, usa %s" #~ msgid "No, use %s" #~ msgstr "Non, usa %s" #~ msgid "Database error" #~ msgstr "Error na base de datos" syncevolution_1.4/po/ca.po000066400000000000000000000400561230021373600157240ustar00rootroot00000000000000# Syncevolution Catalan translation. # Copyright (C) 2009 Free Software Foundation, Inc. # This file is distributed under the same license as the Syncevolution package. # Gil Forcada , 2009. # #: ../src/gtk-ui/sync-ui.c:765 msgid "" msgstr "" "Project-Id-Version: Syncevolution 2.x\n" "Report-Msgid-Bugs-To: http://moblin.org/projects/syncevolution\n" "POT-Creation-Date: 2009-08-15 09:21+0000\n" "PO-Revision-Date: 2010-01-02 14:18+0100\n" "Last-Translator: Gil Forcada \n" "Language-Team: Catalan \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" #. TRANSLATORS: this is the application name that may be used by e.g. #. the windowmanager #: ../src/gtk-ui/main.c:31 ../src/gtk-ui/ui.glade.h:28 #: ../src/gtk-ui/sync.desktop.in.h:1 msgid "Sync" msgstr "Sincronització" #: ../src/gtk-ui/sync-ui.c:259 msgid "Addressbook" msgstr "Llibreta d'adreces" #: ../src/gtk-ui/sync-ui.c:261 msgid "Calendar" msgstr "Calendari" #: ../src/gtk-ui/sync-ui.c:263 msgid "Todo" msgstr "Tasques" #: ../src/gtk-ui/sync-ui.c:265 msgid "Memo" msgstr "Anotacions" #: ../src/gtk-ui/sync-ui.c:320 msgid "Failed to save current service in GConf configuration system" msgstr "" "No s'ha pogut desar el servei actual en el sistema de configuració GConf" #: ../src/gtk-ui/sync-ui.c:331 msgid "Failed to save service configuration to SyncEvolution" msgstr "No s'ha pogut desar la configuració del servei al SyncEvolution" #: ../src/gtk-ui/sync-ui.c:416 msgid "Failed to get service configuration from SyncEvolution" msgstr "No s'ha pogut obtenir la configuració del servei del SyncEvolution" #: ../src/gtk-ui/sync-ui.c:480 msgid "Failed to remove service configuration from SyncEvolution" msgstr "No s'ha pogut suprimir la configuració del servei del SyncEvolution" #: ../src/gtk-ui/sync-ui.c:600 msgid "Service must have a name and server URL" msgstr "El servei ha de tenir un nom i l'URL de servidor" #. sync is no longer in progress for some reason #: ../src/gtk-ui/sync-ui.c:676 msgid "Failed to cancel: sync was no longer in progress" msgstr "No s'ha pogut cancel·lar: ja no s'estava sincronitzant" #: ../src/gtk-ui/sync-ui.c:680 msgid "Failed to cancel sync" msgstr "No s'ha pogut cancel·lar la sincronització" #: ../src/gtk-ui/sync-ui.c:684 msgid "Canceling sync" msgstr "S'està cancel·lant la sincronització" #: ../src/gtk-ui/sync-ui.c:698 msgid "Trying to cancel sync" msgstr "S'està intentant cancel·lar la sincronització" #: ../src/gtk-ui/sync-ui.c:705 #, c-format msgid "" "Do you want to delete all local data and replace it with data from %s? This " "is not usually advised." msgstr "" "Voleu suprimir totes les dades locals i reemplaçar-les amb les dades de %s? " "Normalment no és recomanable fer-ho." #: ../src/gtk-ui/sync-ui.c:710 #, c-format msgid "" "Do you want to delete all data in %s and replace it with your local data? " "This is not usually advised." msgstr "" "Voleu suprimir totes les dades de %s i reemplaçar-les amb les vostres dades " "locals? Normalment no és recomanable fer-ho." #: ../src/gtk-ui/sync-ui.c:727 msgid "No, cancel sync" msgstr "No, cancel·la la sincronització" #: ../src/gtk-ui/sync-ui.c:728 msgid "Yes, delete and replace" msgstr "Sí, suprimeix-ho i reemplaça-ho" #: ../src/gtk-ui/sync-ui.c:750 msgid "No sources are enabled, not syncing" msgstr "No hi ha cap font habilitada, no se sincronitzarà" #: ../src/gtk-ui/sync-ui.c:767 msgid "A sync is already in progress" msgstr "Ja s'està sincronitzant" #: ../src/gtk-ui/sync-ui.c:769 msgid "Failed to start sync" msgstr "No s'ha pogut iniciar la sincronització" #: ../src/gtk-ui/sync-ui.c:774 msgid "Starting sync" msgstr "S'està iniciant la sincronització" #: ../src/gtk-ui/sync-ui.c:799 msgid "Last synced just seconds ago" msgstr "Acabeu de sincronitzar" #: ../src/gtk-ui/sync-ui.c:802 msgid "Last synced a minute ago" msgstr "Fa alguns minuts que s'ha realitzat l'última sincronització" #: ../src/gtk-ui/sync-ui.c:805 #, c-format msgid "Last synced %ld minutes ago" msgstr "Fa %ld minuts que s'ha realitzat l'última sincronització" #: ../src/gtk-ui/sync-ui.c:808 msgid "Last synced an hour ago" msgstr "Fa una hora que s'ha realitzat l'última sincronització" #: ../src/gtk-ui/sync-ui.c:811 #, c-format msgid "Last synced %ld hours ago" msgstr "Fa %ld hores que s'ha realitzat l'última sincronització" #: ../src/gtk-ui/sync-ui.c:814 msgid "Last synced a day ago" msgstr "Ahir es va realitzar l'última sincronització" #: ../src/gtk-ui/sync-ui.c:817 #, c-format msgid "Last synced %ld days ago" msgstr "Fa %ld dies que es va realitzar l'última sincronització" #: ../src/gtk-ui/sync-ui.c:902 msgid "Sync again" msgstr "Sincronitza una altra vegada" #: ../src/gtk-ui/sync-ui.c:904 ../src/gtk-ui/ui.glade.h:29 msgid "Sync now" msgstr "Sincronitza ara" #: ../src/gtk-ui/sync-ui.c:913 msgid "Syncing" msgstr "S'està sincronitzant" #: ../src/gtk-ui/sync-ui.c:919 msgid "Cancel sync" msgstr "Cancel·la la sincronització" #. TRANSLATORS: placeholder is a source name, shown with checkboxes in main window #: ../src/gtk-ui/sync-ui.c:1266 #, c-format msgid "%s (not supported by this service)" msgstr "%s (aquest servei no permet fer-ho)" #: ../src/gtk-ui/sync-ui.c:1299 #, c-format msgid "There was one remote rejection." msgid_plural "There were %d remote rejections." msgstr[0] "Hi ha hagut un rebuig remot." msgstr[1] "Hi ha hagut %d rebuigs remots." #: ../src/gtk-ui/sync-ui.c:1304 #, c-format msgid "There was one local rejection." msgid_plural "There were %d local rejections." msgstr[0] "Hi ha hagut un rebuig local." msgstr[1] "Hi ha hagut %d rebuigs locals." #: ../src/gtk-ui/sync-ui.c:1309 #, c-format msgid "There were %d local rejections and %d remote rejections." msgstr "Hi han hagut %d rebuigs locals i %d rebuigs remots." #: ../src/gtk-ui/sync-ui.c:1314 #, c-format msgid "Last time: No changes." msgstr "L'última vegada: cap canvi." #: ../src/gtk-ui/sync-ui.c:1316 #, c-format msgid "Last time: Sent one change." msgid_plural "Last time: Sent %d changes." msgstr[0] "L'última vegada: es va enviar un canvi." msgstr[1] "L'última vegada: es van enviar %d canvis." #. This is about changes made to the local data. Not all of these #. changes were requested by the remote server, so "applied" #. is a better word than "received" (bug #5185). #: ../src/gtk-ui/sync-ui.c:1324 #, c-format msgid "Last time: Applied one change." msgid_plural "Last time: Applied %d changes." msgstr[0] "L'última vegada: es va aplicar un canvi." msgstr[1] "L'última vegada: es van aplicar %d canvis." #: ../src/gtk-ui/sync-ui.c:1329 #, c-format msgid "Last time: Applied %d changes and sent %d changes." msgstr "L'última vegada: es van aplicar %d canvis i se'n van enviar %d." #: ../src/gtk-ui/sync-ui.c:1421 msgid "Failed to get server configuration from SyncEvolution" msgstr "No s'ha pogut obtenir la configuració del servidor del SyncEvolution" #: ../src/gtk-ui/sync-ui.c:1473 msgid "" "ScheduleWorld enables you to keep your contacts, events, tasks, and notes in " "sync." msgstr "" "El ScheduleWorld us permet mantenir sincronitzats els contactes, els " "esdeveniments, les tasques i les anotacions." #: ../src/gtk-ui/sync-ui.c:1476 msgid "" "Google Sync can back up and synchronize your Address Book with your Gmail " "contacts." msgstr "" "El Google Sync pot fer còpies de seguretat i sincronitzar la llibreta " "d'adreces amb els contactes del GMail." #. TRANSLATORS: Please include the word "demo" (or the equivalent in #. your language): Funambol is going to be a 90 day demo service #. in the future #: ../src/gtk-ui/sync-ui.c:1482 msgid "" "Back up your contacts and calendar. Sync with a singleclick, anytime, " "anywhere (DEMO)." msgstr "" "Feu una còpia de seguretat dels vostres contactes i calendaris. " "Sincronitzeu-los amb un sol clic, a qualsevol hora a qualsevol lloc " "(DEMOSTRACIÓ)." #: ../src/gtk-ui/sync-ui.c:1510 msgid "New service" msgstr "Servei nou" #: ../src/gtk-ui/sync-ui.c:1557 msgid "Server URL" msgstr "URL del servidor" #. TRANSLATORS: placeholder is a source name in settings window #: ../src/gtk-ui/sync-ui.c:1579 #, c-format msgid "%s URI" msgstr "URI de %s" #: ../src/gtk-ui/sync-ui.c:1716 ../src/gtk-ui/ui.glade.h:17 msgid "Launch website" msgstr "Obre el lloc web" #: ../src/gtk-ui/sync-ui.c:1720 msgid "Setup and use" msgstr "Configureu i utilitzeu" #: ../src/gtk-ui/sync-ui.c:1766 msgid "Failed to get list of manually setup services from SyncEvolution" msgstr "" "No s'ha pogut obtenir la llista dels serveis configurats manualment del " "SyncEvolution" #: ../src/gtk-ui/sync-ui.c:1807 msgid "Failed to get list of supported services from SyncEvolution" msgstr "No s'ha pogut obtenir la llista dels serveis admesos pel SyncEvolution" #. TODO: this is a hack... SyncEnd should be a signal of it's own, #. not just hacked on top of the syncevolution error codes #: ../src/gtk-ui/sync-ui.c:1968 msgid "Service configuration not found" msgstr "No s'ha trobat la configuració del servei" #: ../src/gtk-ui/sync-ui.c:1974 msgid "Not authorized" msgstr "No autoritzat" #: ../src/gtk-ui/sync-ui.c:1976 msgid "Forbidden" msgstr "Prohibit" #: ../src/gtk-ui/sync-ui.c:1978 msgid "Not found" msgstr "No s'ha trobat" #: ../src/gtk-ui/sync-ui.c:1980 msgid "Fatal database error" msgstr "Error greu a la base de dades" #: ../src/gtk-ui/sync-ui.c:1982 msgid "Database error" msgstr "Error en la base de dades" #: ../src/gtk-ui/sync-ui.c:1984 msgid "No space left" msgstr "No queda espai lliure" #. TODO identify problem item somehow ? #: ../src/gtk-ui/sync-ui.c:1987 msgid "Failed to process SyncML" msgstr "No s'ha pogut processar el SyncML" #: ../src/gtk-ui/sync-ui.c:1989 msgid "Server authorization failed" msgstr "Ha fallat l'autorització del servidor" #: ../src/gtk-ui/sync-ui.c:1991 msgid "Failed to parse configuration file" msgstr "No s'ha pogut analitzar el fitxer de configuració" #: ../src/gtk-ui/sync-ui.c:1993 msgid "Failed to read configuration file" msgstr "No s'ha pogut llegir el fitxer de configuració" #: ../src/gtk-ui/sync-ui.c:1995 msgid "No configuration found" msgstr "No s'ha trobat la configuració" #: ../src/gtk-ui/sync-ui.c:1997 msgid "No configuration file found" msgstr "No s'ha trobat el fitxer de configuració" #: ../src/gtk-ui/sync-ui.c:1999 msgid "Server sent bad content" msgstr "El servidor ha enviat continguts erronis" #: ../src/gtk-ui/sync-ui.c:2001 msgid "Transport failure (no connection?)" msgstr "Error en la transmissió (no hi ha connexió?)" #: ../src/gtk-ui/sync-ui.c:2003 msgid "Connection timed out" msgstr "La connexió ha expirat" #: ../src/gtk-ui/sync-ui.c:2005 msgid "Connection certificate has expired" msgstr "El certificat de la connexió ha expirat" #: ../src/gtk-ui/sync-ui.c:2007 msgid "Connection certificate is invalid" msgstr "El certificat de la connexió no és vàlid" #: ../src/gtk-ui/sync-ui.c:2010 msgid "Connection failed" msgstr "Ha fallat la connexió" #: ../src/gtk-ui/sync-ui.c:2012 msgid "URL is bad" msgstr "L'URL és errònia" #: ../src/gtk-ui/sync-ui.c:2014 msgid "Server not found" msgstr "No s'ha trobat el servidor" #: ../src/gtk-ui/sync-ui.c:2016 #, c-format msgid "Error %d" msgstr "Error %d" #: ../src/gtk-ui/sync-ui.c:2026 msgid "Sync D-Bus service exited unexpectedly" msgstr "El servei de D-Bus de sincronització s'ha tancat inesperadament" #: ../src/gtk-ui/sync-ui.c:2029 ../src/gtk-ui/sync-ui.c:2080 msgid "Sync Failed" msgstr "Ha fallat la sincronització" #: ../src/gtk-ui/sync-ui.c:2072 msgid "Sync complete" msgstr "S'ha completat la sincronització" #: ../src/gtk-ui/sync-ui.c:2077 msgid "Sync canceled" msgstr "S'ha cancel·lat la sincronització" #. NOTE extra1 can be error here #: ../src/gtk-ui/sync-ui.c:2095 msgid "Ending sync" msgstr "S'està acabat la sincronització" #. TRANSLATORS: placeholder is a source name (e.g. 'Calendar') in a progress text #: ../src/gtk-ui/sync-ui.c:2119 #, c-format msgid "Preparing '%s'" msgstr "S'està preparant «%s»" #. TRANSLATORS: placeholder is a source name in a progress text #: ../src/gtk-ui/sync-ui.c:2131 #, c-format msgid "Sending '%s'" msgstr "S'està enviant «%s»" #. TRANSLATORS: placeholder is a source name in a progress text #: ../src/gtk-ui/sync-ui.c:2143 #, c-format msgid "Receiving '%s'" msgstr "S'està rebent «%s»" #: ../src/gtk-ui/ui.glade.h:1 msgid "Data" msgstr "Dades" #: ../src/gtk-ui/ui.glade.h:2 msgid "No sync service in use" msgstr "No s'està utilitzant cap servei de sincronització" #: ../src/gtk-ui/ui.glade.h:3 msgid "Sync failure" msgstr "S'ha produït un error en la sincronització" #: ../src/gtk-ui/ui.glade.h:4 msgid "Type of Sync" msgstr "Tipus de sincronització" #: ../src/gtk-ui/ui.glade.h:5 msgid "Manual setup" msgstr "Configuració manual" #: ../src/gtk-ui/ui.glade.h:6 msgid "Supported services" msgstr "Serveis coneguts" #: ../src/gtk-ui/ui.glade.h:7 msgid "Add new service" msgstr "Afegeix un servei nou" #: ../src/gtk-ui/ui.glade.h:8 msgid "Back to sync" msgstr "Torna a la sincronització" #: ../src/gtk-ui/ui.glade.h:9 msgid "" "Change sync\n" "service" msgstr "" "Canvia el servei\n" "de sincronització" #: ../src/gtk-ui/ui.glade.h:11 msgid "Delete all local data and replace it with remote data" msgstr "Suprimeix totes les dades locals i reemplaça-les per les remotes" #: ../src/gtk-ui/ui.glade.h:12 msgid "Delete all remote data and replace it with local data" msgstr "Suprimeix totes les dades remotes i reemplaça-les per les locals" #: ../src/gtk-ui/ui.glade.h:13 msgid "Delete this service" msgstr "Suprimeix aquest servei" #: ../src/gtk-ui/ui.glade.h:14 msgid "Edit service settings" msgstr "Edita els paràmetres del servei" #: ../src/gtk-ui/ui.glade.h:15 msgid "" "If you don't see your service above but know that your sync provider uses " "SyncML\n" "you can setup a service manually." msgstr "" "Si no veieu el vostre servei aquí sobre però sabeu que el vostre proveïdor " "de sincronització permet utilitzar SyncML\n" "podeu configurar un servei manualment." #: ../src/gtk-ui/ui.glade.h:18 msgid "Merge local and remote data (recommended)" msgstr "Unifica les dades locals i remotes (recomanat)" #: ../src/gtk-ui/ui.glade.h:19 msgid "Password" msgstr "Contrasenya" #: ../src/gtk-ui/ui.glade.h:20 msgid "Reset original server settings" msgstr "Reinicia els paràmetres originals del servidor" #: ../src/gtk-ui/ui.glade.h:21 msgid "Save and use this service" msgstr "Desa i utilitza aquest servei" #: ../src/gtk-ui/ui.glade.h:22 msgid "Select sync service" msgstr "Seleccioneu el servei de sincronització" #: ../src/gtk-ui/ui.glade.h:23 msgid "Server settings" msgstr "Paràmetres del servidor" #: ../src/gtk-ui/ui.glade.h:24 msgid "Service name" msgstr "Nom del servei" #: ../src/gtk-ui/ui.glade.h:25 msgid "" "Sorry, you need an internet\n" "connection to sync." msgstr "" "Heu d'estar connectat a Internet\n" "per a poder fer una sincronització." #: ../src/gtk-ui/ui.glade.h:27 msgid "Stop using this service" msgstr "No facis servir més aquest servei" #: ../src/gtk-ui/ui.glade.h:30 msgid "" "Synchronization is not available (D-Bus service does not answer), sorry." msgstr "" "No està disponible la sincronització (el servei de D-Bus no respon)." #: ../src/gtk-ui/ui.glade.h:31 msgid "" "To sync you'll need a network connection and an account with a sync " "service.\n" "We support the following services: " msgstr "" "Per a poder sincronitzar necessiteu una connexió a Internet i un compte " "a un servei de sincronització.\n" "Ja hi ha configuracions per als següents serveis:" #: ../src/gtk-ui/ui.glade.h:33 msgid "Username" msgstr "Nom d'usuari" #: ../src/gtk-ui/ui.glade.h:34 msgid "" "You haven't selected a sync service yet. Sync services let you \n" "synchronize your data between your netbook and a web service." msgstr "" "Encara no heu seleccionat cap servei de sincronització. Aquests us \n" "permeten sincronitzar dades entre el vostre ordinador i serveis webs." #: ../src/gtk-ui/sync.desktop.in.h:2 ../src/gtk-ui/sync-gtk.desktop.in.h:2 msgid "Up to date" msgstr "Actualitzat" #: ../src/gtk-ui/sync-gtk.desktop.in.h:1 msgid "Sync (GTK)" msgstr "Sincronització (GTK)" syncevolution_1.4/po/da.po000066400000000000000000000365201230021373600157260ustar00rootroot00000000000000# Danish translation of syncevolution # Copyright (C) 2009 # This file is distributed under the same license as the syncevolution package. # Kris Thomsen , 2009. # #: ../src/gtk-ui/sync-ui.c:765 msgid "" msgstr "" "Project-Id-Version: syncevolution\n" "Report-Msgid-Bugs-To: http://moblin.org/projects/syncevolution\n" "POT-Creation-Date: 2009-11-19 18:24+0000\n" "PO-Revision-Date: 2009-11-24 22:25+0100\n" "Last-Translator: Kris Thomsen \n" "Language-Team: Danish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #. TRANSLATORS: this is the application name that may be used by e.g. #. the windowmanager #: ../src/gtk-ui/main.c:31 ../src/gtk-ui/ui.glade.h:28 #: ../src/gtk-ui/sync.desktop.in.h:1 msgid "Sync" msgstr "Synkronisering" #: ../src/gtk-ui/sync-ui.c:259 msgid "Addressbook" msgstr "Adressebog" #: ../src/gtk-ui/sync-ui.c:261 msgid "Calendar" msgstr "Kalender" #: ../src/gtk-ui/sync-ui.c:263 msgid "Todo" msgstr "Gøremålsliste" #: ../src/gtk-ui/sync-ui.c:265 msgid "Memo" msgstr "Huskeliste" #: ../src/gtk-ui/sync-ui.c:320 msgid "Failed to save current service in GConf configuration system" msgstr "Kunne ikke gemme aktuel tjeneste i GConf-konfigurationssystem" #: ../src/gtk-ui/sync-ui.c:331 msgid "Failed to save service configuration to SyncEvolution" msgstr "Kunne ikke gemme tjenestekonfigurationen til SyncEvolution" #: ../src/gtk-ui/sync-ui.c:416 msgid "Failed to get service configuration from SyncEvolution" msgstr "Kunne ikke hente tjenestekonfigurationen fra SyncEvolution" #: ../src/gtk-ui/sync-ui.c:480 msgid "Failed to remove service configuration from SyncEvolution" msgstr "Kunne ikke fjerne tjenestekonfigurationen fra SyncEvolution" #: ../src/gtk-ui/sync-ui.c:600 msgid "Service must have a name and server URL" msgstr "Tjeneste skal have et navn og en server-URL" #. sync is no longer in progress for some reason #: ../src/gtk-ui/sync-ui.c:676 msgid "Failed to cancel: sync was no longer in progress" msgstr "Kunne ikke annullere: synkronisering var ikke længere kørende" #: ../src/gtk-ui/sync-ui.c:680 msgid "Failed to cancel sync" msgstr "Kunne ikke annullere synkronisering" #: ../src/gtk-ui/sync-ui.c:684 msgid "Canceling sync" msgstr "Annullerer synkronisering" #: ../src/gtk-ui/sync-ui.c:698 msgid "Trying to cancel sync" msgstr "Prøver at annullere synkronisering" #: ../src/gtk-ui/sync-ui.c:705 #, c-format msgid "" "Do you want to delete all local data and replace it with data from %s? This " "is not usually advised." msgstr "" "Vil du slette alle lokale data og erstatte dem med data fra %s? Dette er " "normalvis ikke anbefalet." #: ../src/gtk-ui/sync-ui.c:710 #, c-format msgid "" "Do you want to delete all data in %s and replace it with your local data? " "This is not usually advised." msgstr "" "Vil du slette alle data i %s og erstatte dem med dine lokale data? " "Dette er normalvis ikke anbefalet." #: ../src/gtk-ui/sync-ui.c:727 msgid "No, cancel sync" msgstr "Nej, annullér synkronisering" #: ../src/gtk-ui/sync-ui.c:728 msgid "Yes, delete and replace" msgstr "Ja, slet og erstat" #: ../src/gtk-ui/sync-ui.c:750 msgid "No sources are enabled, not syncing" msgstr "Ingen kilder er aktiveret, synkronisér ikke" #: ../src/gtk-ui/sync-ui.c:767 msgid "A sync is already in progress" msgstr "En synkronisering er allerede i gang" #: ../src/gtk-ui/sync-ui.c:769 msgid "Failed to start sync" msgstr "Kunne ikke starte synkronisering" #: ../src/gtk-ui/sync-ui.c:774 msgid "Starting sync" msgstr "Starter synkronisering" #: ../src/gtk-ui/sync-ui.c:799 msgid "Last synced just seconds ago" msgstr "Sidst synkroniseret for kun få sekunder siden" #: ../src/gtk-ui/sync-ui.c:802 msgid "Last synced a minute ago" msgstr "Sidst synkroniseret for et minut siden" #: ../src/gtk-ui/sync-ui.c:805 #, c-format msgid "Last synced %ld minutes ago" msgstr "Sidst synkroniseret for %ld minutter siden" #: ../src/gtk-ui/sync-ui.c:808 msgid "Last synced an hour ago" msgstr "Sidst synkroniseret for en time siden" #: ../src/gtk-ui/sync-ui.c:811 #, c-format msgid "Last synced %ld hours ago" msgstr "Sidst synkroniseret for %ld timer siden" #: ../src/gtk-ui/sync-ui.c:814 msgid "Last synced a day ago" msgstr "Sidst synkroniseret for en dag siden" #: ../src/gtk-ui/sync-ui.c:817 #, c-format msgid "Last synced %ld days ago" msgstr "Sidst synkroniseret for %ld dage siden" #: ../src/gtk-ui/sync-ui.c:902 msgid "Sync again" msgstr "Synkronisér igen" #: ../src/gtk-ui/sync-ui.c:904 ../src/gtk-ui/ui.glade.h:29 msgid "Sync now" msgstr "Synkronisér nu" #: ../src/gtk-ui/sync-ui.c:913 msgid "Syncing" msgstr "Synkroniserer" #: ../src/gtk-ui/sync-ui.c:919 msgid "Cancel sync" msgstr "Annullér synkronisering" #. TRANSLATORS: placeholder is a source name, shown with checkboxes in main window #: ../src/gtk-ui/sync-ui.c:1266 #, c-format msgid "%s (not supported by this service)" msgstr "%s (ikke understøttet af denne tjeneste)" #: ../src/gtk-ui/sync-ui.c:1299 #, c-format msgid "There was one remote rejection." msgid_plural "There were %d remote rejections." msgstr[0] "Der var én fjernafvisning." msgstr[1] "Der var %d fjernafvisninger." #: ../src/gtk-ui/sync-ui.c:1304 #, c-format msgid "There was one local rejection." msgid_plural "There were %d local rejections." msgstr[0] "Der var én lokal afvisning." msgstr[1] "Der var %d lokale afvisninger." #: ../src/gtk-ui/sync-ui.c:1309 #, c-format msgid "There were %d local rejections and %d remote rejections." msgstr "Der var %d lokale afvisninger og %d fjernafvisninger." #: ../src/gtk-ui/sync-ui.c:1314 #, c-format msgid "Last time: No changes." msgstr "Sidste gang: Ingen ændringer." #: ../src/gtk-ui/sync-ui.c:1316 #, c-format msgid "Last time: Sent one change." msgid_plural "Last time: Sent %d changes." msgstr[0] "Sidste gang: Èn ændring indsendt." msgstr[1] "Sidste gang: %d ændringer indsendt." #. This is about changes made to the local data. Not all of these #. changes were requested by the remote server, so "applied" #. is a better word than "received" (bug #5185). #: ../src/gtk-ui/sync-ui.c:1324 #, c-format msgid "Last time: Applied one change." msgid_plural "Last time: Applied %d changes." msgstr[0] "Sidste gang: Èn ændring tilføjet." msgstr[1] "Sidste gang: %d ændringer tilføjet." #: ../src/gtk-ui/sync-ui.c:1329 #, c-format msgid "Last time: Applied %d changes and sent %d changes." msgstr "Sidste gang: %d ændringer tilføjet og %d ændringer indsendt." #: ../src/gtk-ui/sync-ui.c:1421 msgid "Failed to get server configuration from SyncEvolution" msgstr "Kunne ikke hente serverkonfiguration fra SyncEvolution" #: ../src/gtk-ui/sync-ui.c:1473 msgid "" "ScheduleWorld enables you to keep your contacts, events, tasks, and notes in " "sync." msgstr "" "ScheduleWorld giver dig mulighed for at holde dine kontakter, aktiviteter, gøremål " "og noter synkroniseret." #: ../src/gtk-ui/sync-ui.c:1476 msgid "" "Google Sync can back up and synchronize your Address Book with your Gmail " "contacts." msgstr "" "Google Sync kan tage en sikkerhedskopi og synkronisere din adressebog med " "dine Gmail-kontakter." #. TRANSLATORS: Please include the word "demo" (or the equivalent in #. your language): Funambol is going to be a 90 day demo service #. in the future #: ../src/gtk-ui/sync-ui.c:1482 msgid "" "Back up your contacts and calendar. Sync with a singleclick, anytime, " "anywhere (DEMO)." msgstr "" "Lav en sikkerhedskopi af dine kontakter og kalender. Synkroniser med " "enkeltklik, når som helst og hvor som helst (DEMO)." #: ../src/gtk-ui/sync-ui.c:1510 msgid "New service" msgstr "Ny tjeneste" #: ../src/gtk-ui/sync-ui.c:1557 msgid "Server URL" msgstr "Server URL" #. TRANSLATORS: placeholder is a source name in settings window #: ../src/gtk-ui/sync-ui.c:1579 #, c-format msgid "%s URI" msgstr "%s URI" #: ../src/gtk-ui/sync-ui.c:1716 ../src/gtk-ui/ui.glade.h:17 msgid "Launch website" msgstr "Åben websted" #: ../src/gtk-ui/sync-ui.c:1720 msgid "Setup and use" msgstr "Sæt op og brug" #: ../src/gtk-ui/sync-ui.c:1766 msgid "Failed to get list of manually setup services from SyncEvolution" msgstr "Kunne ikke hente liste over manuelt opsatte tjenester fra SyncEvolution" #: ../src/gtk-ui/sync-ui.c:1807 msgid "Failed to get list of supported services from SyncEvolution" msgstr "Kunne ikke hente liste over understøttede tjenester fra SyncEvolution" #. TODO: this is a hack... SyncEnd should be a signal of it's own, #. not just hacked on top of the syncevolution error codes #: ../src/gtk-ui/sync-ui.c:1968 msgid "Service configuration not found" msgstr "Tjenestekonfiguration ikke fundet" #: ../src/gtk-ui/sync-ui.c:1974 msgid "Not authorized" msgstr "Ikke godkendt" #: ../src/gtk-ui/sync-ui.c:1976 msgid "Forbidden" msgstr "Forbudt" #: ../src/gtk-ui/sync-ui.c:1978 msgid "Not found" msgstr "Ikke fundet" #: ../src/gtk-ui/sync-ui.c:1980 msgid "Fatal database error" msgstr "Fatal databasefejl" #: ../src/gtk-ui/sync-ui.c:1982 msgid "Database error" msgstr "Databasefejl" #: ../src/gtk-ui/sync-ui.c:1984 msgid "No space left" msgstr "Ingen plads tilbage" #. TODO identify problem item somehow ? #: ../src/gtk-ui/sync-ui.c:1987 msgid "Failed to process SyncML" msgstr "Kunne ikke behandle SyncML" #: ../src/gtk-ui/sync-ui.c:1989 msgid "Server authorization failed" msgstr "Godkendelse af server mislykkedes" #: ../src/gtk-ui/sync-ui.c:1991 msgid "Failed to parse configuration file" msgstr "Kunne ikke tolke konfigurationsfil" #: ../src/gtk-ui/sync-ui.c:1993 msgid "Failed to read configuration file" msgstr "Kunne ikke læse konfigurationsfil" #: ../src/gtk-ui/sync-ui.c:1995 msgid "No configuration found" msgstr "Ingen konfiguration fundet" #: ../src/gtk-ui/sync-ui.c:1997 msgid "No configuration file found" msgstr "Ingen konfigurationsfil fundet" #: ../src/gtk-ui/sync-ui.c:1999 msgid "Server sent bad content" msgstr "Serveren sendte ugyldigt indhold" #: ../src/gtk-ui/sync-ui.c:2001 msgid "Transport failure (no connection?)" msgstr "Transportfejl (ingen forbindelse?)" #: ../src/gtk-ui/sync-ui.c:2003 msgid "Connection timed out" msgstr "Tiden for forbindelsen er løbet ud" #: ../src/gtk-ui/sync-ui.c:2005 msgid "Connection certificate has expired" msgstr "Forbindelsescertifikatet er udløbet" #: ../src/gtk-ui/sync-ui.c:2007 msgid "Connection certificate is invalid" msgstr "Forbindelsescertifikat er ugyldigt" #: ../src/gtk-ui/sync-ui.c:2010 msgid "Connection failed" msgstr "Forbindelse mislykkedes" #: ../src/gtk-ui/sync-ui.c:2012 msgid "URL is bad" msgstr "URL er ugyldig" #: ../src/gtk-ui/sync-ui.c:2014 msgid "Server not found" msgstr "Server ikke fundet" #: ../src/gtk-ui/sync-ui.c:2016 #, c-format msgid "Error %d" msgstr "Fejl %d" #: ../src/gtk-ui/sync-ui.c:2026 msgid "Sync D-Bus service exited unexpectedly" msgstr "Synkroniserings-D-Bus-tjeneste afsluttede ikke som forventet" #: ../src/gtk-ui/sync-ui.c:2029 ../src/gtk-ui/sync-ui.c:2080 msgid "Sync Failed" msgstr "Synkronisering mislykkedes" #: ../src/gtk-ui/sync-ui.c:2072 msgid "Sync complete" msgstr "Synkronisering gennemført" #: ../src/gtk-ui/sync-ui.c:2077 msgid "Sync canceled" msgstr "Synkronisering annulleret" #. NOTE extra1 can be error here #: ../src/gtk-ui/sync-ui.c:2095 msgid "Ending sync" msgstr "Afslutter synkronisering" #. TRANSLATORS: placeholder is a source name (e.g. 'Calendar') in a progress text #: ../src/gtk-ui/sync-ui.c:2119 #, c-format msgid "Preparing '%s'" msgstr "Forbereder \"%s\"" #. TRANSLATORS: placeholder is a source name in a progress text #: ../src/gtk-ui/sync-ui.c:2131 #, c-format msgid "Sending '%s'" msgstr "Sender \"%s\"" #. TRANSLATORS: placeholder is a source name in a progress text #: ../src/gtk-ui/sync-ui.c:2143 #, c-format msgid "Receiving '%s'" msgstr "Modtager \"%s\"" #: ../src/gtk-ui/ui.glade.h:1 msgid "Data" msgstr "Data" #: ../src/gtk-ui/ui.glade.h:2 msgid "No sync service in use" msgstr "Ingen synkroniseringstjeneste i brug" #: ../src/gtk-ui/ui.glade.h:3 msgid "Sync failure" msgstr "Synkroniseringsfejl" #: ../src/gtk-ui/ui.glade.h:4 msgid "Type of Sync" msgstr "Synkroniseringstype" #: ../src/gtk-ui/ui.glade.h:5 msgid "Manual setup" msgstr "Manuel opsætning" #: ../src/gtk-ui/ui.glade.h:6 msgid "Supported services" msgstr "Understøttede tjenester" #: ../src/gtk-ui/ui.glade.h:7 msgid "Add new service" msgstr "Tilføj ny tjeneste" #: ../src/gtk-ui/ui.glade.h:8 msgid "Back to sync" msgstr "Tilbage til synkronisering" #: ../src/gtk-ui/ui.glade.h:9 msgid "" "Change sync\n" "service" msgstr "" "Skift synkroniserings-\n" "tjeneste" #: ../src/gtk-ui/ui.glade.h:11 msgid "Delete all local data and replace it with remote data" msgstr "Slet alle lokale data og erstat dem med fjerndata" #: ../src/gtk-ui/ui.glade.h:12 msgid "Delete all remote data and replace it with local data" msgstr "Slet alle fjerndata og erstat dem med lokale data" #: ../src/gtk-ui/ui.glade.h:13 msgid "Delete this service" msgstr "Slet denne tjeneste" #: ../src/gtk-ui/ui.glade.h:14 msgid "Edit service settings" msgstr "Redigér tjenesteindstillinger" #: ../src/gtk-ui/ui.glade.h:15 msgid "" "If you don't see your service above but know that your sync provider uses " "SyncML\n" "you can setup a service manually." msgstr "" "Hvis du ikke kan se din tjeneste ovenfor, men ved at din synkroniseringsudbyder " "bruger SyncML,\n" "kan du sætte en tjeneste manuelt." #: ../src/gtk-ui/ui.glade.h:18 msgid "Merge local and remote data (recommended)" msgstr "Læg lokale og fjerndata sammen (anbefalet)" #: ../src/gtk-ui/ui.glade.h:19 msgid "Password" msgstr "Adgangskode" #: ../src/gtk-ui/ui.glade.h:20 msgid "Reset original server settings" msgstr "Nulstil til originale serverindstillinger" #: ../src/gtk-ui/ui.glade.h:21 msgid "Save and use this service" msgstr "Gem og brug denne tjeneste" #: ../src/gtk-ui/ui.glade.h:22 msgid "Select sync service" msgstr "Vælg synkroniseringstjeneste" #: ../src/gtk-ui/ui.glade.h:23 msgid "Server settings" msgstr "Tjenesteopsætning" #: ../src/gtk-ui/ui.glade.h:24 msgid "Service name" msgstr "Tjenestenavn" #: ../src/gtk-ui/ui.glade.h:25 msgid "" "Sorry, you need an internet\n" "connection to sync." msgstr "" "Beklager, du skal bruge en internetforbindelse\n" "for at synkronisere." #: ../src/gtk-ui/ui.glade.h:27 msgid "Stop using this service" msgstr "Stop med at bruge denne tjeneste" #: ../src/gtk-ui/ui.glade.h:30 msgid "" "Synchronization is not available (D-Bus service does not answer), sorry." msgstr "" "Synkronisering er ikke tilgængelig (D-Bus-tjenesten svarer ikke), beklager." #: ../src/gtk-ui/ui.glade.h:31 msgid "" "To sync you'll need a network connection and an account with a sync " "service.\n" "We support the following services: " msgstr "" "For at synkronisere skal du have en netværksforbindelse og en konto med " "en synkroniseringstjeneste.\n" "Vi understøtter følgende tjenester: " #: ../src/gtk-ui/ui.glade.h:33 msgid "Username" msgstr "Brugernavn" #: ../src/gtk-ui/ui.glade.h:34 msgid "" "You haven't selected a sync service yet. Sync services let you \n" "synchronize your data between your netbook and a web service." msgstr "" "Du har ikke valgt en synkroniseringstjeneste endnu. Synkroniseringstjenester \n" "lader dig synkronisere dine data mellem din netbook og en webtjeneste." #: ../src/gtk-ui/sync.desktop.in.h:2 ../src/gtk-ui/sync-gtk.desktop.in.h:2 msgid "Up to date" msgstr "Fuldt opdatereret" #: ../src/gtk-ui/sync-gtk.desktop.in.h:1 msgid "Sync (GTK)" msgstr "Synkronisering (GTK)" syncevolution_1.4/po/de.po000066400000000000000000000752631230021373600157410ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # # Translators: # , 2011. # , 2011. msgid "" msgstr "" "Project-Id-Version: syncevolution\n" "Report-Msgid-Bugs-To: https://bugs.meego.com/\n" "POT-Creation-Date: 2011-12-05 10:21-0800\n" "PO-Revision-Date: 2011-12-06 11:21+0000\n" "Last-Translator: GLS_Translator_DEU1 \n" "Language-Team: German \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: de\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" #. TRANSLATORS: this is the application name that may be used by e.g. #. the windowmanager #: ../src/gtk-ui/main.c:40 ../src/gtk-ui/ui.glade.h:38 #: ../src/gtk-ui/sync.desktop.in.h:1 #: ../src/gnome-bluetooth/syncevolution.c:112 msgid "Sync" msgstr "Synchronisation" #: ../src/gtk-ui/sync-ui.c:266 msgid "Contacts" msgstr "Kontakte" #: ../src/gtk-ui/sync-ui.c:268 msgid "Appointments" msgstr "Termine" #: ../src/gtk-ui/sync-ui.c:270 ../src/gtk-ui/ui.glade.h:40 msgid "Tasks" msgstr "Aufgaben" #: ../src/gtk-ui/sync-ui.c:272 msgid "Notes" msgstr "Notizen" #. TRANSLATORS: This is a "combination source" for syncing with devices #. * that combine appointments and tasks. the name should match the ones #. * used for calendar and todo above #: ../src/gtk-ui/sync-ui.c:277 msgid "Appointments & Tasks" msgstr "Termine & Aufgaben" #: ../src/gtk-ui/sync-ui.c:349 msgid "Starting sync" msgstr "Sync wird gestartet" #. TRANSLATORS: slow sync confirmation dialog message. Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:387 #, c-format msgid "Do you want to slow sync with %s?" msgstr "Willst du mit %s eine langsame Synchronisation durchführen?" #: ../src/gtk-ui/sync-ui.c:391 msgid "Yes, do slow sync" msgstr "Ja, langsame Synchronisation durchführen" #: ../src/gtk-ui/sync-ui.c:391 msgid "No, cancel sync" msgstr "Nein, Synchronisation abbrechen" #. TRANSLATORS: confirmation dialog for "refresh from peer". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:424 #, c-format msgid "" "Do you want to delete all local data and replace it with data from %s? This " "is not usually advised." msgstr "" "Möchtest du alle lokalen Daten löschen und mit Daten von %s ersetzen? Dies " "ist normalerweise nicht zu empfehlen." #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 msgid "Yes, delete and replace" msgstr "Ja, löschen und ersetzen" #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 #: ../src/gtk-ui/sync-ui.c:1610 msgid "No" msgstr "Nein" #. TRANSLATORS: confirmation dialog for "refresh from local side". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:457 #, c-format msgid "" "Do you want to delete all data in %s and replace it with your local data? " "This is not usually advised." msgstr "" "Möchtest du alle Daten in %s löschen und mit deinen lokalen Daten ersetzen? " "Dies ist normalerweise nicht zu empfehlen." #: ../src/gtk-ui/sync-ui.c:491 msgid "Trying to cancel sync" msgstr "Abbruchversuch der Synchronisation" #: ../src/gtk-ui/sync-ui.c:533 msgid "No service or device selected" msgstr "Kein Service oder Gerät gewählt" #. TRANSLATORS: This is the title on main view. Placeholder is #. * the service name. Example: "Google - synced just now" #: ../src/gtk-ui/sync-ui.c:541 #, c-format msgid "%s - synced just now" msgstr "%s - Synchronisation fand vor wenigen Sekunden statt" #: ../src/gtk-ui/sync-ui.c:545 #, c-format msgid "%s - synced a minute ago" msgstr "%s - Synchronisation fand vor einer Minute statt" #: ../src/gtk-ui/sync-ui.c:549 #, c-format msgid "%s - synced %ld minutes ago" msgstr "%s - Synchronisation fand vor %ld Minuten statt" #: ../src/gtk-ui/sync-ui.c:554 #, c-format msgid "%s - synced an hour ago" msgstr "%s - Synchronisation fand vor einer Stunde statt" #: ../src/gtk-ui/sync-ui.c:558 #, c-format msgid "%s - synced %ld hours ago" msgstr "%s - Synchronisation fand vor %ld Stunden statt" #: ../src/gtk-ui/sync-ui.c:563 #, c-format msgid "%s - synced a day ago" msgstr "%s - Synchronisation fand vor einem Tag statt" #: ../src/gtk-ui/sync-ui.c:567 #, c-format msgid "%s - synced %ld days ago" msgstr "%s - Synchronisation fand vor %ld Tagen statt" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "You've just restored a backup. The changes have not been " #. * "synced with %s yet" #: ../src/gtk-ui/sync-ui.c:616 ../src/gtk-ui/sync-ui.c:701 msgid "Sync now" msgstr "Jetzt syncen" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "A normal sync is not possible at this time..." message. #. * "Other options" will open Emergency view #: ../src/gtk-ui/sync-ui.c:622 ../src/gtk-ui/ui.glade.h:37 msgid "Slow sync" msgstr "" "Langsame\n" "Synchronisation" #: ../src/gtk-ui/sync-ui.c:623 msgid "Other options..." msgstr "Andere Optionen..." #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * when no service is selected. Will open configuration view #: ../src/gtk-ui/sync-ui.c:628 msgid "Select sync service" msgstr "Synchronisations-Service wählen" #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * login to service fails. Will open configuration view for this service #: ../src/gtk-ui/sync-ui.c:633 msgid "Edit service settings" msgstr "Service-Einstellungen bearbeiten" #: ../src/gtk-ui/sync-ui.c:709 msgid "" "You haven't selected a sync service or device yet. Sync services let you " "synchronize your data between your netbook and a web service. You can also " "sync directly with some devices." msgstr "" "Es ist noch kein Synchronisations-Service oder Gerät ausgewählt. Sync-" "Services synchronisieren die Daten deines Netbooks mit einem Web-Service. " "Mit einigen Geräten kannst du auch direkt synchronisieren." #: ../src/gtk-ui/sync-ui.c:729 msgid "Sync again" msgstr "Erneut syncen" #: ../src/gtk-ui/sync-ui.c:748 msgid "Restoring" msgstr "Wiederherstellung" #: ../src/gtk-ui/sync-ui.c:750 msgid "Syncing" msgstr "Syncen..." #. TRANSLATORS: This is for the button in main view, right side. #. Keep line length below ~20 characters, use two lines if needed #: ../src/gtk-ui/sync-ui.c:762 ../src/gtk-ui/sync-ui.c:3407 msgid "Cancel sync" msgstr "" "Synchronisation\n" "abbrechen" #: ../src/gtk-ui/sync-ui.c:927 msgid "Back to sync" msgstr "Zurück zur Synchronisation" #. TRANSLATORS: label for checkbutton/toggle in main view. #. * Please stick to similar length strings or break the line with #. * "\n" if absolutely needed #: ../src/gtk-ui/sync-ui.c:1229 msgid "Automatic sync" msgstr "" "Automatische\n" "Synchronisation" #. This is the expander label in emergency view. It summarizes the #. * currently selected data sources. First placeholder is service/device #. * name, second a comma separeted list of sources. #. * E.g. "Affected data: Google Contacts, Appointments" #: ../src/gtk-ui/sync-ui.c:1524 #, c-format msgid "Affected data: %s %s" msgstr "Betroffene Daten: %s %s" #: ../src/gtk-ui/sync-ui.c:1529 #, c-format msgid "Affected data: none" msgstr "Betroffene Daten: Keine" #. TRANSLATORS: confirmation for restoring a backup. placeholder is the #. * backup time string defined below #: ../src/gtk-ui/sync-ui.c:1607 #, c-format msgid "" "Do you want to restore the backup from %s? All changes you have made since " "then will be lost." msgstr "" "Soll die Sicherheitskopie von %s wiederhergestellt werden? Alle seither " "gemachten Änderungen gehen dann verloren." #: ../src/gtk-ui/sync-ui.c:1610 msgid "Yes, restore" msgstr "Ja, wiederherstellen" #. TRANSLATORS: date/time for strftime(), used in emergency view backup #. * label. Any time format that shows date and time is good. #: ../src/gtk-ui/sync-ui.c:1642 #, c-format msgid "%x %X" msgstr "%x %X" #. TRANSLATORS: label for a backup in emergency view. Placeholder is #. * service or device name #: ../src/gtk-ui/sync-ui.c:1661 #, c-format msgid "Backed up before syncing with %s" msgstr "Sicherheitskopie wurde erstellt vor Synchronisation mit %s" #: ../src/gtk-ui/sync-ui.c:1678 msgid "Restore" msgstr "Wiederherstellen" #. TRANSLATORS: this is an explanation in Emergency view. #. * Placeholder is a service/device name #: ../src/gtk-ui/sync-ui.c:1785 #, c-format msgid "" "A normal sync with %s is not possible at this time. You can do a slow two-" "way sync or start from scratch. You can also restore a backup, but a slow " "sync or starting from scratch will still be required before normal sync is " "possible." msgstr "" "Eine normale Synchronisation mit %s ist zur Zeit nicht möglich. Du kannst " "eine langsame Zwei-Wege-Synchronisation durchführen, neu anfangen, oder eine" " Sicherheitskopie wiederherstellen. Eine langsame Synchronisation oder ein " "Neuanfang sind jedoch erforderlich, bevor eine normale Synchronisation " "mäglich ist." #: ../src/gtk-ui/sync-ui.c:1795 #, c-format msgid "" "If something has gone horribly wrong, you can try a slow sync, start from " "scratch or restore from backup." msgstr "" "Falls etwas fehlgeschlagen ist, kannst du eine langsame Synchronisation " "versuchen, neu anfangen oder von der Sicherheitskopie Daten " "wiederherstellen." #. TRANSLATORS: These are a buttons in Emergency view. Placeholder is a #. * service/device name. Please don't use too long lines, but feel free to #. * use several lines. #: ../src/gtk-ui/sync-ui.c:1804 #, c-format msgid "" "Delete all your local\n" "data and replace with\n" "data from %s" msgstr "" "Alle lokalen Daten\n" "löschen und ersetzen mit\n" "Daten von %s" #: ../src/gtk-ui/sync-ui.c:1810 #, c-format msgid "" "Delete all data on\n" "%s and replace\n" "with your local data" msgstr "" "Alle Daten auf %s\n" "löschen und ersetzen\n" "mit deinen lokalen Daten" #: ../src/gtk-ui/sync-ui.c:2275 msgid "Failed to get list of supported services from SyncEvolution" msgstr "" "Liste der unterstützten Services konnte nicht von SyncEvolution empfangen " "werden." #: ../src/gtk-ui/sync-ui.c:2329 msgid "" "There was a problem communicating with the sync process. Please try again " "later." msgstr "" "Bei der Kommunikation mit dem Sync-Service trat ein Problem auf. Versuche es" " später nochmals." #: ../src/gtk-ui/sync-ui.c:2388 msgid "Restore failed" msgstr "Verbindung fehlgeschlagen" #: ../src/gtk-ui/sync-ui.c:2391 ../src/gtk-ui/sync-ui.c:3276 msgid "Sync failed" msgstr "Sync fehlgeschlagen" #: ../src/gtk-ui/sync-ui.c:2397 msgid "Restore complete" msgstr "Wiederherstellung beendet" #: ../src/gtk-ui/sync-ui.c:2400 msgid "Sync complete" msgstr "Sync beendet" #: ../src/gtk-ui/sync-ui.c:2492 #, c-format msgid "Preparing '%s'" msgstr "'%s' wird vorbereitet" #: ../src/gtk-ui/sync-ui.c:2495 #, c-format msgid "Receiving '%s'" msgstr "'%s' wird empfangen" #: ../src/gtk-ui/sync-ui.c:2498 #, c-format msgid "Sending '%s'" msgstr "'%s' wird gesendet" #: ../src/gtk-ui/sync-ui.c:2619 #, c-format msgid "There was one remote rejection." msgid_plural "There were %ld remote rejections." msgstr[0] "Es gab eine Remote-Ablehnung." msgstr[1] "Es gab %ld Remote-Ablehnungen." #: ../src/gtk-ui/sync-ui.c:2624 #, c-format msgid "There was one local rejection." msgid_plural "There were %ld local rejections." msgstr[0] "Es gab eine lokale Ablehnung." msgstr[1] "Es gab %ld lokale Ablehnungen." #: ../src/gtk-ui/sync-ui.c:2629 #, c-format msgid "There were %ld local rejections and %ld remote rejections." msgstr "Es gab %ld lokale Ablehnungen und %ld Remote-Ablehnungen." #: ../src/gtk-ui/sync-ui.c:2634 #, c-format msgid "Last time: No changes." msgstr "Letztes Mal: Keine Änderungen." #: ../src/gtk-ui/sync-ui.c:2636 #, c-format msgid "Last time: Sent one change." msgid_plural "Last time: Sent %ld changes." msgstr[0] "Letztes Mal: Eine Änderung abgeschickt." msgstr[1] "Letztes Mal: %ld Änderungen abgeschickt." #. This is about changes made to the local data. Not all of these #. changes were requested by the remote server, so "applied" #. is a better word than "received" (bug #5185). #: ../src/gtk-ui/sync-ui.c:2644 #, c-format msgid "Last time: Applied one change." msgid_plural "Last time: Applied %ld changes." msgstr[0] "Letztes Mal: Eine Änderung angewandt." msgstr[1] "Letztes Mal: %ld Änderungen angewandt." #: ../src/gtk-ui/sync-ui.c:2649 #, c-format msgid "Last time: Applied %ld changes and sent %ld changes." msgstr "" "Letztes Mal: %ld Änderungen angewandt und %ld Änderungen abgeschickt." #. TRANSLATORS: the placeholder is a error message (hopefully) #. * explaining the problem #: ../src/gtk-ui/sync-ui.c:2856 #, c-format msgid "" "There was a problem with last sync:\n" "%s" msgstr "" "Bei der letzten Synchronisation trat ein Problem auf:\n" "%s" #: ../src/gtk-ui/sync-ui.c:2866 #, c-format msgid "" "You've just restored a backup. The changes have not been synced with %s yet" msgstr "" "Du hast soeben eine Sicherheitskopie erstellt. Die Änderungen wurden noch " "nicht mit %s synchronisiert." #: ../src/gtk-ui/sync-ui.c:3154 msgid "Waiting for current operation to finish..." msgstr "Warten auf Beenden des laufenden Vorgangs..." #. TRANSLATORS: next strings are error messages. #: ../src/gtk-ui/sync-ui.c:3188 msgid "" "A normal sync is not possible at this time. The server suggests a slow sync," " but this might not always be what you want if both ends already have data." msgstr "" "Eine normale Synchronisation ist zur Zeit nicht möglich. Der Server schlägt " "eine langsame Synchronisation vor, doch wenn beide Enden bereits Daten " "haben, ist dies vielleicht nicht immer wünschenswert." #: ../src/gtk-ui/sync-ui.c:3192 msgid "The sync process died unexpectedly." msgstr "Sync-Prozess wurde unerwartet abgebrochen." #: ../src/gtk-ui/sync-ui.c:3197 msgid "" "Password request was not answered. You can save the password in the settings" " to prevent the request." msgstr "" "Passwortanfrage wurde nicht beantwortet. Du kannst das Passwort in den " "Einstellungen speichern, um die Anfrage zu vermeiden." #. TODO use the service device name here, this is a remote problem #: ../src/gtk-ui/sync-ui.c:3201 msgid "There was a problem processing sync request. Trying again may help." msgstr "" "Beim Bearbeiten der Synchronisationsanfrage trat ein Problem auf. Es wird " "empfohlen, es noch einmal zu versuchen." #: ../src/gtk-ui/sync-ui.c:3207 msgid "" "Failed to login. Could there be a problem with your username or password?" msgstr "" "Anmeldung gescheitert. Es könnte ein Problem mit dem Benutzernamen oder dem " "Kennwort bestehen." #: ../src/gtk-ui/sync-ui.c:3210 msgid "Forbidden" msgstr "Verboten" #. TRANSLATORS: data source means e.g. calendar or addressbook #: ../src/gtk-ui/sync-ui.c:3216 msgid "" "A data source could not be found. Could there be a problem with the " "settings?" msgstr "" "Die Datenquelle wurde nicht gefunden. Könnte es ein Problem bei den " "Einstellungen geben?" #: ../src/gtk-ui/sync-ui.c:3220 msgid "Remote database error" msgstr "Fehler bei Remote-Datenbank" #. This can happen when EDS is borked, restart it may help... #: ../src/gtk-ui/sync-ui.c:3223 msgid "" "There is a problem with the local database. Syncing again or rebooting may " "help." msgstr "" "Es gibt ein Problem mit der lokalen Datenbank. Es wird empfohlen, noch " "einmal zu synchronisieren oder den Computer neu zu starten" #: ../src/gtk-ui/sync-ui.c:3226 msgid "No space on disk" msgstr "Kein Platz auf der Festplatte" #: ../src/gtk-ui/sync-ui.c:3228 msgid "Failed to process SyncML" msgstr "SyncML konnte nicht verarbeitet werden" #: ../src/gtk-ui/sync-ui.c:3230 msgid "Server authorization failed" msgstr "Serverauthentifizierung fehlgeschlagen" #: ../src/gtk-ui/sync-ui.c:3232 msgid "Failed to parse configuration file" msgstr "Konfigurationsdatei konnte nicht analysiert werden" #: ../src/gtk-ui/sync-ui.c:3234 msgid "Failed to read configuration file" msgstr "Konfigurationsdatei konnte nicht gelesen werden" #: ../src/gtk-ui/sync-ui.c:3236 msgid "No configuration found" msgstr "Keine Konfiguration gefunden" #: ../src/gtk-ui/sync-ui.c:3238 msgid "No configuration file found" msgstr "Keine Konfigurationsdatei gefunden" #: ../src/gtk-ui/sync-ui.c:3240 msgid "Server sent bad content" msgstr "Der Server hat ungültigen Inhalt gesendet" #: ../src/gtk-ui/sync-ui.c:3242 msgid "Connection certificate has expired" msgstr "Das Zertifikat der Verbindung ist abgelaufen" #: ../src/gtk-ui/sync-ui.c:3244 msgid "Connection certificate is invalid" msgstr "Das Zertifikat der Verbindung ist ungültig" #: ../src/gtk-ui/sync-ui.c:3252 msgid "" "We were unable to connect to the server. The problem could be temporary or " "there could be something wrong with the settings." msgstr "" "Verbindung mit dem Server konnte nicht hergestellt werden. Das Problem ist " "vielleicht nur temporär, oder es könnte ein Fehler bei den Einstellungen " "vorliegen." #: ../src/gtk-ui/sync-ui.c:3259 msgid "The server URL is bad" msgstr "Server-URL ist nicht korrekt" #: ../src/gtk-ui/sync-ui.c:3264 msgid "The server was not found" msgstr "Der Server wurde nicht gefunden" #: ../src/gtk-ui/sync-ui.c:3266 #, c-format msgid "Error %d" msgstr "Fehler %d" #. TRANSLATORS: password request dialog contents: title, cancel button #. * and ok button #: ../src/gtk-ui/sync-ui.c:3404 msgid "Password is required for sync" msgstr "Kennwort ist zur Synchronisation erforderlich" #: ../src/gtk-ui/sync-ui.c:3408 msgid "Sync with password" msgstr "Synchronisation mit Kennwort" #. TRANSLATORS: password request dialog message, placeholder is service name #: ../src/gtk-ui/sync-ui.c:3418 #, c-format msgid "Please enter password for syncing with %s:" msgstr "Kennwort zur Synchronisation mit %s eingeben:" #. title for the buttons on the right side of main view #: ../src/gtk-ui/ui.glade.h:2 msgid "Actions" msgstr "Handlungen" #. text between the two "start from scratch" buttons in emergency view #: ../src/gtk-ui/ui.glade.h:4 msgid "or" msgstr "oder" #: ../src/gtk-ui/ui.glade.h:5 msgid "Direct sync" msgstr "Direkte Synchronisation" #: ../src/gtk-ui/ui.glade.h:6 msgid "Network sync" msgstr "Netzwerk-Synchronisation" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:8 msgid "Restore from backup" msgstr "Wiederherstellung von der Sicherheitskopie" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:10 msgid "Slow sync" msgstr "Langsame Synchronisation" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:12 msgid "Start from scratch" msgstr "Neu anfangen" #: ../src/gtk-ui/ui.glade.h:13 msgid "" "A slow sync compares items from both sides and tries to merge them. \n" "This may fail in some cases, leading to duplicates or lost information." msgstr "" "Eine langsame Synchronisation vergleicht Elemente beider Seiten und fügt sie zusammen.\n" "Dies kann manchmal fehlschlagen und Duplikate oder verlorene Informationen zur Folge haben." #: ../src/gtk-ui/ui.glade.h:15 msgid "Add new device" msgstr "Gerät zufügen" #: ../src/gtk-ui/ui.glade.h:16 msgid "Add new service" msgstr "Service zufügen" #. explanation of "Restore backup" function #: ../src/gtk-ui/ui.glade.h:18 msgid "" "Backups are made before every time we Sync. Choose a backup to restore. Any " "changes you have made since then will be lost." msgstr "" "Bei jeder Synchronisation wird eine Sicherheitskopie erstellt. Wähle zur " "Wiederherstellung eine Sicherheitskopie. Seither vorgenommene Änderungen " "gehen verloren." #: ../src/gtk-ui/ui.glade.h:19 msgid "Calendar" msgstr "Kalender" #. Button in main view, right side. Keep to below 20 chars per line, feel free #. to use two lines #: ../src/gtk-ui/ui.glade.h:21 msgid "" "Change or edit\n" "sync service" msgstr "" "Sync Service\n" "ändern/bearbeiten" #. close button for settings window #: ../src/gtk-ui/ui.glade.h:24 msgid "Close" msgstr "Schließen" #: ../src/gtk-ui/ui.glade.h:25 msgid "" "Delete all data on Zyb \n" "and replace with your\n" "local information" msgstr "" "Alle Daten auf Zyb löschen\n" "und mit lokaler Information\n" "ersetzen" #: ../src/gtk-ui/ui.glade.h:28 msgid "" "Delete all your local\n" "information and replace\n" "with data from Zyb" msgstr "" "Alle lokalen Informationen\n" "löschen und ersetzen\n" "mit Daten von Zyb" #. button in main view, right side. Keep length to 20 characters or so, use #. two lines if needed #: ../src/gtk-ui/ui.glade.h:32 msgid "" "Fix a sync\n" "emergency" msgstr "" "Einen Sync\n" "Notfall beheben" #: ../src/gtk-ui/ui.glade.h:34 msgid "" "If you don't see your service above but know that your sync provider uses SyncML\n" "you can setup a service manually." msgstr "" "Wenn dein Service nicht aufgelistet ist, aber dein Synchronisationsanbieter SyncML\n" "verwendet, kannst du einen Service manuell konfigurieren." #: ../src/gtk-ui/ui.glade.h:36 msgid "Settings" msgstr "Einstellungen" #: ../src/gtk-ui/ui.glade.h:39 msgid "Sync Emergency" msgstr "Sync Notfall" #: ../src/gtk-ui/ui.glade.h:41 msgid "" "To sync you'll need a network connection and an account with a sync service.\n" "We support the following services: " msgstr "" "Zur Synchronisation brauchst du eine Netzwerkverbindung und ein Konto bei einem\n" "Synchronisations-Service. Wir unterstützen die folgenden Services:" #: ../src/gtk-ui/ui.glade.h:43 msgid "Use Bluetooth to Sync your data from one device to another." msgstr "" "Benutze Bluetooth, um die Daten von einem Gerät mit einem anderen zu " "synchronisieren." #: ../src/gtk-ui/ui.glade.h:44 msgid "You will need to add Bluetooth devices before they can be synced." msgstr "" "Bluetooth-Geräte müssen zugefügt werden, bevor sie synchronisiert werden " "können." #: ../src/gtk-ui/sync.desktop.in.h:2 msgid "Up to date" msgstr "Aktuell bleiben" #: ../src/gtk-ui/sync-gtk.desktop.in.h:1 msgid "SyncEvolution (GTK)" msgstr "SyncEvolution (GTK)" #: ../src/gtk-ui/sync-gtk.desktop.in.h:2 msgid "Synchronize PIM data" msgstr "Synchronize PIM data" #: ../src/gtk-ui/sync-config-widget.c:88 msgid "" "ScheduleWorld enables you to keep your contacts, events, tasks, and notes in" " sync." msgstr "" "ScheduleWorld erlaubt dir, deine Kontakte, Events, Aufgaben und Notizen zu " "synchronisieren." #: ../src/gtk-ui/sync-config-widget.c:91 msgid "" "Google Sync can back up and synchronize your contacts with your Gmail " "contacts." msgstr "" "Google Synchronisation kann dein Addressbuch mit deinen Gmail Kontakten " "synchronisieren und eine Sicherheitskopie davon erstellen." #. TRANSLATORS: Please include the word "demo" (or the equivalent in #. your language): Funambol is going to be a 90 day demo service #. in the future #: ../src/gtk-ui/sync-config-widget.c:97 msgid "" "Back up your contacts and calendar. Sync with a single click, anytime, " "anywhere (DEMO)." msgstr "" "Erstelle Sicherheitskopien deiner Kontakte und deines Kalenders. Sync mit " "einem einzigen Klick, irgendwann, irgendwo (DEMO)." #: ../src/gtk-ui/sync-config-widget.c:100 msgid "" "Mobical Backup and Restore service allows you to securely back up your " "personal mobile data for free." msgstr "" "Mit Mobical Backup und Restore Service kannst du gratis eine " "Sicherheitskopie deiner persönlichen mobilen Daten erstellen." #: ../src/gtk-ui/sync-config-widget.c:103 msgid "" "ZYB is a simple way for people to store and share mobile information online." msgstr "" "Mit ZYB können mobile Informationen online geteilt und gespeichert werden." #: ../src/gtk-ui/sync-config-widget.c:106 msgid "" "Memotoo lets you access your personal data from any computer connected to " "the Internet." msgstr "" "Memotoo gibt Zugriff auf persönliche Daten von jedem Computer, der mit dem " "Internet verbunden ist." #: ../src/gtk-ui/sync-config-widget.c:195 msgid "Sorry, failed to save the configuration" msgstr "Das Speichern der Konfiguration ist leider fehlgeschlagen" #: ../src/gtk-ui/sync-config-widget.c:445 msgid "Service must have a name and server URL" msgstr "Der Service muss einen Namen und eine URL haben" #. TRANSLATORS: error dialog when creating a new sync configuration #: ../src/gtk-ui/sync-config-widget.c:451 msgid "A username is required for this service" msgstr "Für diesen Service ist ein Benutzername erforderlich" #: ../src/gtk-ui/sync-config-widget.c:493 #, c-format msgid "" "Do you want to reset the settings for %s? This will not remove any synced " "information on either end." msgstr "" "Sollen die Einstellungen für %s neu eingerichtet werden? Es wird dabei keine" " synchronisierte Information gelöscht." #. TRANSLATORS: buttons in reset-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:497 msgid "Yes, reset" msgstr "Ja, neu einrichten" #: ../src/gtk-ui/sync-config-widget.c:498 #: ../src/gtk-ui/sync-config-widget.c:509 msgid "No, keep settings" msgstr "Nein, Einstellungen behalten" #: ../src/gtk-ui/sync-config-widget.c:503 #, c-format msgid "" "Do you want to delete the settings for %s? This will not remove any synced " "information on either end but it will remove these settings." msgstr "" "Sollen die Einstellungen für %s gelöscht werden? Dabei werden keine " "synchronisierten Informationen gelöscht, jedoch wird die Service-" "Konfiguration gelöscht." #. TRANSLATORS: buttons in delete-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:508 msgid "Yes, delete" msgstr "Ja, löschen" #: ../src/gtk-ui/sync-config-widget.c:539 msgid "Reset settings" msgstr "Einstellungen zurücksetzen" #: ../src/gtk-ui/sync-config-widget.c:542 msgid "Delete settings" msgstr "Einstellungen löschen" #: ../src/gtk-ui/sync-config-widget.c:552 msgid "Save and use" msgstr "Speichern und benutzen" #: ../src/gtk-ui/sync-config-widget.c:555 msgid "" "Save and replace\n" "current service" msgstr "" "Diesen Service\n" "speichern und ersetzen" #: ../src/gtk-ui/sync-config-widget.c:563 msgid "Stop using device" msgstr "Service nicht mehr benutzen" #: ../src/gtk-ui/sync-config-widget.c:566 msgid "Stop using service" msgstr "Service nicht mehr benutzen" #. TRANSLATORS: label for an entry in service configuration form. #. * Placeholder is a source name. #. * Example: "Appointments URI" #: ../src/gtk-ui/sync-config-widget.c:749 #, c-format msgid "%s URI" msgstr "URI von %s" #. TRANSLATORS: toggles in service configuration form, placeholder is service #. * or device name #: ../src/gtk-ui/sync-config-widget.c:936 #, c-format msgid "Send changes to %s" msgstr "Änderungen senden an %s" #: ../src/gtk-ui/sync-config-widget.c:941 #, c-format msgid "Receive changes from %s" msgstr "Änderungen von %s empfangen" #: ../src/gtk-ui/sync-config-widget.c:957 msgid "Sync" msgstr "Sync" #. TRANSLATORS: label of a entry in service configuration #: ../src/gtk-ui/sync-config-widget.c:973 msgid "Server address" msgstr "Serveradresse" #. TRANSLATORS: explanation before a device template combobox. #. * Placeholder is a device name like 'Nokia N85' or 'Syncevolution #. * Client' #: ../src/gtk-ui/sync-config-widget.c:1049 #, c-format msgid "" "This device looks like it might be a '%s'. If this is not correct, please " "take a look at the list of supported devices and pick yours if it is listed" msgstr "" "Dieses Gerät scheint ein '%s' zu sein. Sollte das nicht stimmen, schau dir " "bitte die Liste der unterstützten Geräte an, um dein Gerät auszuwählen." #: ../src/gtk-ui/sync-config-widget.c:1055 #: ../src/gtk-ui/sync-config-widget.c:1915 msgid "" "We don't know what this device is exactly. Please take a look at the list of" " supported devices and pick yours if it is listed" msgstr "" "Dieses Gerät kann nicht genau identifiziert werden. Schau dir bitte die " "Liste der unterstützten Geräte an, um dein Gerät auszuwählen." #: ../src/gtk-ui/sync-config-widget.c:1207 #, c-format msgid "%s - Bluetooth device" msgstr "%s - Bluetooth-Gerät" #. TRANSLATORS: service title for services that are not based on a #. * template in service list, the placeholder is the name of the service #: ../src/gtk-ui/sync-config-widget.c:1213 #, c-format msgid "%s - manually setup" msgstr "%s - manuell einrichten" #. TRANSLATORS: link button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1886 msgid "Launch website" msgstr "Website starten" #. TRANSLATORS: button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1895 msgid "Set up now" msgstr "Jetzt einrichten" #: ../src/gtk-ui/sync-config-widget.c:1953 msgid "Use these settings" msgstr "Diese Einstellungen verwenden" #. TRANSLATORS: labels of entries in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1991 msgid "Username" msgstr "Benutzername" #: ../src/gtk-ui/sync-config-widget.c:2006 msgid "Password" msgstr "Kennwort" #. TRANSLATORS: warning in service configuration form for people #. who have modified the configuration via other means. #: ../src/gtk-ui/sync-config-widget.c:2029 msgid "" "Current configuration is more complex than what can be shown here. Changes " "to sync mode or synced data types will overwrite that configuration." msgstr "" "Die Service-Konfiguration ist komplexer als hier angezeigt werden kann. " "Änderungen am Synchronisationsmodus oder den synchronisierten Datentypen " "werden diese Konfiguration überschreiben." #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2048 msgid "Hide server settings" msgstr "Servereinstellungen " #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2068 msgid "Show server settings" msgstr "Servereinstellungen anzeigen" #: ../src/gnome-bluetooth/syncevolution.c:110 msgid "Sync in the Sync application" msgstr "Sync in der Sync Anwendung" #: ../src/syncevo-dbus-server.cpp:6653 #, c-format msgid "%s is syncing" msgstr "%s Synchronisation" #: ../src/syncevo-dbus-server.cpp:6654 #, c-format msgid "We have just started to sync your computer with the %s sync service." msgstr "" "Die Synchronisation deines Computers mit dem %s Sync-Service wurde " "gestartet." #. if sync is successfully started and done #: ../src/syncevo-dbus-server.cpp:6670 #, c-format msgid "%s sync complete" msgstr "%s Synchronisation abgeschlossen" #: ../src/syncevo-dbus-server.cpp:6671 #, c-format msgid "We have just finished syncing your computer with the %s sync service." msgstr "" "Die Synchronisation deines Computers mit dem %s Sync-Service ist " "abgeschlossen." #. if sync is successfully started and has errors, or not started successful #. with a fatal problem #: ../src/syncevo-dbus-server.cpp:6676 msgid "Sync problem." msgstr "Sync-Problem" #: ../src/syncevo-dbus-server.cpp:6677 msgid "Sorry, there's a problem with your sync that you need to attend to." msgstr "Ein Problem mit der Synchronisation bedarf deiner Aufmerksamkeit." #: ../src/syncevo-dbus-server.cpp:6758 msgid "View" msgstr "Anzeigen" #. Use "default" as ID because that is what mutter-moblin #. recognizes: it then skips the action instead of adding it #. in addition to its own "Dismiss" button (always added). #: ../src/syncevo-dbus-server.cpp:6762 msgid "Dismiss" msgstr "Ignorieren" syncevolution_1.4/po/en_GB.po000066400000000000000000000712741230021373600163210ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # # Translators: # Margie Foster , 2011. msgid "" msgstr "" "Project-Id-Version: syncevolution\n" "Report-Msgid-Bugs-To: https://bugs.meego.com/\n" "POT-Creation-Date: 2011-12-05 10:21-0800\n" "PO-Revision-Date: 2011-12-05 19:25+0000\n" "Last-Translator: margie \n" "Language-Team: English (United Kingdom) (http://www.transifex.net/projects/p/meego/team/en_GB/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: en_GB\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" #. TRANSLATORS: this is the application name that may be used by e.g. #. the windowmanager #: ../src/gtk-ui/main.c:40 ../src/gtk-ui/ui.glade.h:38 #: ../src/gtk-ui/sync.desktop.in.h:1 #: ../src/gnome-bluetooth/syncevolution.c:112 msgid "Sync" msgstr "Sync" #: ../src/gtk-ui/sync-ui.c:266 msgid "Contacts" msgstr "Contacts" #: ../src/gtk-ui/sync-ui.c:268 msgid "Appointments" msgstr "Appointments" #: ../src/gtk-ui/sync-ui.c:270 ../src/gtk-ui/ui.glade.h:40 msgid "Tasks" msgstr "Tasks" #: ../src/gtk-ui/sync-ui.c:272 msgid "Notes" msgstr "Notes" #. TRANSLATORS: This is a "combination source" for syncing with devices #. * that combine appointments and tasks. the name should match the ones #. * used for calendar and todo above #: ../src/gtk-ui/sync-ui.c:277 msgid "Appointments & Tasks" msgstr "Appointments & Tasks" #: ../src/gtk-ui/sync-ui.c:349 msgid "Starting sync" msgstr "Starting sync" #. TRANSLATORS: slow sync confirmation dialog message. Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:387 #, c-format msgid "Do you want to slow sync with %s?" msgstr "Do you want to slow sync with %s?" #: ../src/gtk-ui/sync-ui.c:391 msgid "Yes, do slow sync" msgstr "Yes, do slow sync" #: ../src/gtk-ui/sync-ui.c:391 msgid "No, cancel sync" msgstr "No, cancel sync" #. TRANSLATORS: confirmation dialog for "refresh from peer". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:424 #, c-format msgid "" "Do you want to delete all local data and replace it with data from %s? This " "is not usually advised." msgstr "" "Do you want to delete all local data and replace it with data from %s? This " "is not usually advised." #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 msgid "Yes, delete and replace" msgstr "Yes, delete and replace" #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 #: ../src/gtk-ui/sync-ui.c:1610 msgid "No" msgstr "No" #. TRANSLATORS: confirmation dialog for "refresh from local side". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:457 #, c-format msgid "" "Do you want to delete all data in %s and replace it with your local data? " "This is not usually advised." msgstr "" "Do you want to delete all data in %s and replace it with your local data? " "This is not usually advised." #: ../src/gtk-ui/sync-ui.c:491 msgid "Trying to cancel sync" msgstr "Trying to cancel sync" #: ../src/gtk-ui/sync-ui.c:533 msgid "No service or device selected" msgstr "No service or device selected" #. TRANSLATORS: This is the title on main view. Placeholder is #. * the service name. Example: "Google - synced just now" #: ../src/gtk-ui/sync-ui.c:541 #, c-format msgid "%s - synced just now" msgstr "%s - synced just now" #: ../src/gtk-ui/sync-ui.c:545 #, c-format msgid "%s - synced a minute ago" msgstr "%s - synced a minute ago" #: ../src/gtk-ui/sync-ui.c:549 #, c-format msgid "%s - synced %ld minutes ago" msgstr "%s - synced %ld minutes ago" #: ../src/gtk-ui/sync-ui.c:554 #, c-format msgid "%s - synced an hour ago" msgstr "%s - synced an hour ago" #: ../src/gtk-ui/sync-ui.c:558 #, c-format msgid "%s - synced %ld hours ago" msgstr "%s - synced %ld hours ago" #: ../src/gtk-ui/sync-ui.c:563 #, c-format msgid "%s - synced a day ago" msgstr "%s - synced a day ago" #: ../src/gtk-ui/sync-ui.c:567 #, c-format msgid "%s - synced %ld days ago" msgstr "%s - synced %ld days ago" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "You've just restored a backup. The changes have not been " #. * "synced with %s yet" #: ../src/gtk-ui/sync-ui.c:616 ../src/gtk-ui/sync-ui.c:701 msgid "Sync now" msgstr "Sync now" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "A normal sync is not possible at this time..." message. #. * "Other options" will open Emergency view #: ../src/gtk-ui/sync-ui.c:622 ../src/gtk-ui/ui.glade.h:37 msgid "Slow sync" msgstr "Slow sync" #: ../src/gtk-ui/sync-ui.c:623 msgid "Other options..." msgstr "Other options..." #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * when no service is selected. Will open configuration view #: ../src/gtk-ui/sync-ui.c:628 msgid "Select sync service" msgstr "Select sync service" #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * login to service fails. Will open configuration view for this service #: ../src/gtk-ui/sync-ui.c:633 msgid "Edit service settings" msgstr "Edit service settings" #: ../src/gtk-ui/sync-ui.c:709 msgid "" "You haven't selected a sync service or device yet. Sync services let you " "synchronize your data between your netbook and a web service. You can also " "sync directly with some devices." msgstr "" "You haven't selected a sync service or device yet. Sync services let you " "synchronise your data between your netbook and a web service. You can also " "sync directly with some devices." #: ../src/gtk-ui/sync-ui.c:729 msgid "Sync again" msgstr "Sync again" #: ../src/gtk-ui/sync-ui.c:748 msgid "Restoring" msgstr "Restoring" #: ../src/gtk-ui/sync-ui.c:750 msgid "Syncing" msgstr "Syncing" #. TRANSLATORS: This is for the button in main view, right side. #. Keep line length below ~20 characters, use two lines if needed #: ../src/gtk-ui/sync-ui.c:762 ../src/gtk-ui/sync-ui.c:3407 msgid "Cancel sync" msgstr "Cancel sync" #: ../src/gtk-ui/sync-ui.c:927 msgid "Back to sync" msgstr "Back to sync" #. TRANSLATORS: label for checkbutton/toggle in main view. #. * Please stick to similar length strings or break the line with #. * "\n" if absolutely needed #: ../src/gtk-ui/sync-ui.c:1229 msgid "Automatic sync" msgstr "Automatic sync" #. This is the expander label in emergency view. It summarizes the #. * currently selected data sources. First placeholder is service/device #. * name, second a comma separeted list of sources. #. * E.g. "Affected data: Google Contacts, Appointments" #: ../src/gtk-ui/sync-ui.c:1524 #, c-format msgid "Affected data: %s %s" msgstr "Affected data: %s %s" #: ../src/gtk-ui/sync-ui.c:1529 #, c-format msgid "Affected data: none" msgstr "Affected data: none" #. TRANSLATORS: confirmation for restoring a backup. placeholder is the #. * backup time string defined below #: ../src/gtk-ui/sync-ui.c:1607 #, c-format msgid "" "Do you want to restore the backup from %s? All changes you have made since " "then will be lost." msgstr "" "Do you want to restore the backup from %s? All changes you have made since " "then will be lost." #: ../src/gtk-ui/sync-ui.c:1610 msgid "Yes, restore" msgstr "Yes, restore" #. TRANSLATORS: date/time for strftime(), used in emergency view backup #. * label. Any time format that shows date and time is good. #: ../src/gtk-ui/sync-ui.c:1642 #, c-format msgid "%x %X" msgstr "%x %X" #. TRANSLATORS: label for a backup in emergency view. Placeholder is #. * service or device name #: ../src/gtk-ui/sync-ui.c:1661 #, c-format msgid "Backed up before syncing with %s" msgstr "Backed up before syncing with %s" #: ../src/gtk-ui/sync-ui.c:1678 msgid "Restore" msgstr "Restore" #. TRANSLATORS: this is an explanation in Emergency view. #. * Placeholder is a service/device name #: ../src/gtk-ui/sync-ui.c:1785 #, c-format msgid "" "A normal sync with %s is not possible at this time. You can do a slow two-" "way sync or start from scratch. You can also restore a backup, but a slow " "sync or starting from scratch will still be required before normal sync is " "possible." msgstr "" "A normal sync with %s is not possible at this time. You can do a slow two-" "way sync or start from scratch. You can also restore a backup, but a slow " "sync or starting from scratch will still be required before normal sync is " "possible." #: ../src/gtk-ui/sync-ui.c:1795 #, c-format msgid "" "If something has gone horribly wrong, you can try a slow sync, start from " "scratch or restore from backup." msgstr "" "If something has gone horribly wrong, you can try a slow sync, start from " "scratch or restore from backup." #. TRANSLATORS: These are a buttons in Emergency view. Placeholder is a #. * service/device name. Please don't use too long lines, but feel free to #. * use several lines. #: ../src/gtk-ui/sync-ui.c:1804 #, c-format msgid "" "Delete all your local\n" "data and replace with\n" "data from %s" msgstr "" "Delete all your local\n" "data and replace with\n" "data from %s" #: ../src/gtk-ui/sync-ui.c:1810 #, c-format msgid "" "Delete all data on\n" "%s and replace\n" "with your local data" msgstr "" "Delete all data on\n" "%s and replace\n" "with your local data" #: ../src/gtk-ui/sync-ui.c:2275 msgid "Failed to get list of supported services from SyncEvolution" msgstr "Failed to get list of supported services from SyncEvolution" #: ../src/gtk-ui/sync-ui.c:2329 msgid "" "There was a problem communicating with the sync process. Please try again " "later." msgstr "" "There was a problem communicating with the sync process. Please try again " "later." #: ../src/gtk-ui/sync-ui.c:2388 msgid "Restore failed" msgstr "Restore failed" #: ../src/gtk-ui/sync-ui.c:2391 ../src/gtk-ui/sync-ui.c:3276 msgid "Sync failed" msgstr "Sync failed" #: ../src/gtk-ui/sync-ui.c:2397 msgid "Restore complete" msgstr "Restore complete" #: ../src/gtk-ui/sync-ui.c:2400 msgid "Sync complete" msgstr "Sync complete" #: ../src/gtk-ui/sync-ui.c:2492 #, c-format msgid "Preparing '%s'" msgstr "Preparing '%s'" #: ../src/gtk-ui/sync-ui.c:2495 #, c-format msgid "Receiving '%s'" msgstr "Receiving '%s'" #: ../src/gtk-ui/sync-ui.c:2498 #, c-format msgid "Sending '%s'" msgstr "Sending '%s'" #: ../src/gtk-ui/sync-ui.c:2619 #, c-format msgid "There was one remote rejection." msgid_plural "There were %ld remote rejections." msgstr[0] "There was one remote rejection." msgstr[1] "There were %ld remote rejections." #: ../src/gtk-ui/sync-ui.c:2624 #, c-format msgid "There was one local rejection." msgid_plural "There were %ld local rejections." msgstr[0] "There was one local rejection." msgstr[1] "There were %ld local rejections." #: ../src/gtk-ui/sync-ui.c:2629 #, c-format msgid "There were %ld local rejections and %ld remote rejections." msgstr "There were %ld local rejections and %ld remote rejections." #: ../src/gtk-ui/sync-ui.c:2634 #, c-format msgid "Last time: No changes." msgstr "Last time: No changes." #: ../src/gtk-ui/sync-ui.c:2636 #, c-format msgid "Last time: Sent one change." msgid_plural "Last time: Sent %ld changes." msgstr[0] "Last time: Sent one change." msgstr[1] "Last time: Sent %ld changes." #. This is about changes made to the local data. Not all of these #. changes were requested by the remote server, so "applied" #. is a better word than "received" (bug #5185). #: ../src/gtk-ui/sync-ui.c:2644 #, c-format msgid "Last time: Applied one change." msgid_plural "Last time: Applied %ld changes." msgstr[0] "Last time: Applied one change." msgstr[1] "Last time: Applied %ld changes." #: ../src/gtk-ui/sync-ui.c:2649 #, c-format msgid "Last time: Applied %ld changes and sent %ld changes." msgstr "Last time: Applied %ld changes and sent %ld changes." #. TRANSLATORS: the placeholder is a error message (hopefully) #. * explaining the problem #: ../src/gtk-ui/sync-ui.c:2856 #, c-format msgid "" "There was a problem with last sync:\n" "%s" msgstr "" "There was a problem with last sync:\n" "%s" #: ../src/gtk-ui/sync-ui.c:2866 #, c-format msgid "" "You've just restored a backup. The changes have not been synced with %s yet" msgstr "" "You've just restored a backup. The changes have not been synced with %s yet" #: ../src/gtk-ui/sync-ui.c:3154 msgid "Waiting for current operation to finish..." msgstr "Waiting for current operation to finish..." #. TRANSLATORS: next strings are error messages. #: ../src/gtk-ui/sync-ui.c:3188 msgid "" "A normal sync is not possible at this time. The server suggests a slow sync," " but this might not always be what you want if both ends already have data." msgstr "" "A normal sync is not possible at this time. The server suggests a slow sync," " but this might not always be what you want if both ends already have data." #: ../src/gtk-ui/sync-ui.c:3192 msgid "The sync process died unexpectedly." msgstr "The sync process died unexpectedly." #: ../src/gtk-ui/sync-ui.c:3197 msgid "" "Password request was not answered. You can save the password in the settings" " to prevent the request." msgstr "" "Password request was not answered. You can save the password in the settings" " to prevent the request." #. TODO use the service device name here, this is a remote problem #: ../src/gtk-ui/sync-ui.c:3201 msgid "There was a problem processing sync request. Trying again may help." msgstr "There was a problem processing sync request. Trying again may help." #: ../src/gtk-ui/sync-ui.c:3207 msgid "" "Failed to login. Could there be a problem with your username or password?" msgstr "" "Failed to login. Could there be a problem with your username or password?" #: ../src/gtk-ui/sync-ui.c:3210 msgid "Forbidden" msgstr "Forbidden" #. TRANSLATORS: data source means e.g. calendar or addressbook #: ../src/gtk-ui/sync-ui.c:3216 msgid "" "A data source could not be found. Could there be a problem with the " "settings?" msgstr "" "A data source could not be found. Could there be a problem with the " "settings?" #: ../src/gtk-ui/sync-ui.c:3220 msgid "Remote database error" msgstr "Remote database error" #. This can happen when EDS is borked, restart it may help... #: ../src/gtk-ui/sync-ui.c:3223 msgid "" "There is a problem with the local database. Syncing again or rebooting may " "help." msgstr "" "There is a problem with the local database. Syncing again or rebooting may " "help." #: ../src/gtk-ui/sync-ui.c:3226 msgid "No space on disk" msgstr "No space on disk" #: ../src/gtk-ui/sync-ui.c:3228 msgid "Failed to process SyncML" msgstr "Failed to process SyncML" #: ../src/gtk-ui/sync-ui.c:3230 msgid "Server authorization failed" msgstr "Server authorisation failed" #: ../src/gtk-ui/sync-ui.c:3232 msgid "Failed to parse configuration file" msgstr "Failed to parse configuration file" #: ../src/gtk-ui/sync-ui.c:3234 msgid "Failed to read configuration file" msgstr "Failed to read configuration file" #: ../src/gtk-ui/sync-ui.c:3236 msgid "No configuration found" msgstr "No configuration found" #: ../src/gtk-ui/sync-ui.c:3238 msgid "No configuration file found" msgstr "No configuration file found" #: ../src/gtk-ui/sync-ui.c:3240 msgid "Server sent bad content" msgstr "Server sent bad content" #: ../src/gtk-ui/sync-ui.c:3242 msgid "Connection certificate has expired" msgstr "Connection certificate has expired" #: ../src/gtk-ui/sync-ui.c:3244 msgid "Connection certificate is invalid" msgstr "Connection certificate is invalid" #: ../src/gtk-ui/sync-ui.c:3252 msgid "" "We were unable to connect to the server. The problem could be temporary or " "there could be something wrong with the settings." msgstr "" "We were unable to connect to the server. The problem could be temporary or " "there could be something wrong with the settings." #: ../src/gtk-ui/sync-ui.c:3259 msgid "The server URL is bad" msgstr "The server URL is bad" #: ../src/gtk-ui/sync-ui.c:3264 msgid "The server was not found" msgstr "The server was not found" #: ../src/gtk-ui/sync-ui.c:3266 #, c-format msgid "Error %d" msgstr "Error %d" #. TRANSLATORS: password request dialog contents: title, cancel button #. * and ok button #: ../src/gtk-ui/sync-ui.c:3404 msgid "Password is required for sync" msgstr "Password is required for sync" #: ../src/gtk-ui/sync-ui.c:3408 msgid "Sync with password" msgstr "Sync with password" #. TRANSLATORS: password request dialog message, placeholder is service name #: ../src/gtk-ui/sync-ui.c:3418 #, c-format msgid "Please enter password for syncing with %s:" msgstr "Please enter password for syncing with %s:" #. title for the buttons on the right side of main view #: ../src/gtk-ui/ui.glade.h:2 msgid "Actions" msgstr "Actions" #. text between the two "start from scratch" buttons in emergency view #: ../src/gtk-ui/ui.glade.h:4 msgid "or" msgstr "or" #: ../src/gtk-ui/ui.glade.h:5 msgid "Direct sync" msgstr "Direct sync" #: ../src/gtk-ui/ui.glade.h:6 msgid "Network sync" msgstr "Network sync" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:8 msgid "Restore from backup" msgstr "Restore from backup" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:10 msgid "Slow sync" msgstr "Slow sync" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:12 msgid "Start from scratch" msgstr "Start from scratch" #: ../src/gtk-ui/ui.glade.h:13 msgid "" "A slow sync compares items from both sides and tries to merge them. \n" "This may fail in some cases, leading to duplicates or lost information." msgstr "" "A slow sync compares items from both sides and tries to merge them. \n" "This may fail in some cases, leading to duplicates or lost information." #: ../src/gtk-ui/ui.glade.h:15 msgid "Add new device" msgstr "Add new device" #: ../src/gtk-ui/ui.glade.h:16 msgid "Add new service" msgstr "Add new service" #. explanation of "Restore backup" function #: ../src/gtk-ui/ui.glade.h:18 msgid "" "Backups are made before every time we Sync. Choose a backup to restore. Any " "changes you have made since then will be lost." msgstr "" "Backups are made before every time we Sync. Choose a backup to restore. Any " "changes you have made since then will be lost." #: ../src/gtk-ui/ui.glade.h:19 msgid "Calendar" msgstr "Calendar" #. Button in main view, right side. Keep to below 20 chars per line, feel free #. to use two lines #: ../src/gtk-ui/ui.glade.h:21 msgid "" "Change or edit\n" "sync service" msgstr "" "Change or edit\n" "sync service" #. close button for settings window #: ../src/gtk-ui/ui.glade.h:24 msgid "Close" msgstr "Close" #: ../src/gtk-ui/ui.glade.h:25 msgid "" "Delete all data on Zyb \n" "and replace with your\n" "local information" msgstr "" "Delete all data on Zyb \n" "and replace with your\n" "local information" #: ../src/gtk-ui/ui.glade.h:28 msgid "" "Delete all your local\n" "information and replace\n" "with data from Zyb" msgstr "" "Delete all your local\n" "information and replace\n" "with data from Zyb" #. button in main view, right side. Keep length to 20 characters or so, use #. two lines if needed #: ../src/gtk-ui/ui.glade.h:32 msgid "" "Fix a sync\n" "emergency" msgstr "" "Fix a sync\n" "emergency" #: ../src/gtk-ui/ui.glade.h:34 msgid "" "If you don't see your service above but know that your sync provider uses SyncML\n" "you can setup a service manually." msgstr "" "If you don't see your service above but know that your sync provider uses SyncML\n" "you can setup a service manually." #: ../src/gtk-ui/ui.glade.h:36 msgid "Settings" msgstr "Settings" #: ../src/gtk-ui/ui.glade.h:39 msgid "Sync Emergency" msgstr "Sync Emergency" #: ../src/gtk-ui/ui.glade.h:41 msgid "" "To sync you'll need a network connection and an account with a sync service.\n" "We support the following services: " msgstr "" "To sync you'll need a network connection and an account with a sync service.\n" "We support the following services: " #: ../src/gtk-ui/ui.glade.h:43 msgid "Use Bluetooth to Sync your data from one device to another." msgstr "Use Bluetooth to Sync your data from one device to another." #: ../src/gtk-ui/ui.glade.h:44 msgid "You will need to add Bluetooth devices before they can be synced." msgstr "You will need to add Bluetooth devices before they can be synced." #: ../src/gtk-ui/sync.desktop.in.h:2 msgid "Up to date" msgstr "Up to date" #: ../src/gtk-ui/sync-gtk.desktop.in.h:1 msgid "SyncEvolution (GTK)" msgstr "SyncEvolution (GTK)" #: ../src/gtk-ui/sync-gtk.desktop.in.h:2 msgid "Synchronize PIM data" msgstr "Synchronize PIM data" #: ../src/gtk-ui/sync-config-widget.c:88 msgid "" "ScheduleWorld enables you to keep your contacts, events, tasks, and notes in" " sync." msgstr "" "ScheduleWorld enables you to keep your contacts, events, tasks, and notes in" " sync." #: ../src/gtk-ui/sync-config-widget.c:91 msgid "" "Google Sync can back up and synchronize your contacts with your Gmail " "contacts." msgstr "" "Google Sync can back up and synchronise your contacts with your Gmail " "contacts." #. TRANSLATORS: Please include the word "demo" (or the equivalent in #. your language): Funambol is going to be a 90 day demo service #. in the future #: ../src/gtk-ui/sync-config-widget.c:97 msgid "" "Back up your contacts and calendar. Sync with a single click, anytime, " "anywhere (DEMO)." msgstr "" "Back up your contacts and calendar. Sync with a single click, anytime, " "anywhere (DEMO)." #: ../src/gtk-ui/sync-config-widget.c:100 msgid "" "Mobical Backup and Restore service allows you to securely back up your " "personal mobile data for free." msgstr "" "Mobical Backup and Restore service allows you to securely back up your " "personal mobile data for free." #: ../src/gtk-ui/sync-config-widget.c:103 msgid "" "ZYB is a simple way for people to store and share mobile information online." msgstr "" "ZYB is a simple way for people to store and share mobile information online." #: ../src/gtk-ui/sync-config-widget.c:106 msgid "" "Memotoo lets you access your personal data from any computer connected to " "the Internet." msgstr "" "Memotoo lets you access your personal data from any computer connected to " "the Internet." #: ../src/gtk-ui/sync-config-widget.c:195 msgid "Sorry, failed to save the configuration" msgstr "Sorry, failed to save the configuration" #: ../src/gtk-ui/sync-config-widget.c:445 msgid "Service must have a name and server URL" msgstr "Service must have a name and server URL" #. TRANSLATORS: error dialog when creating a new sync configuration #: ../src/gtk-ui/sync-config-widget.c:451 msgid "A username is required for this service" msgstr "A username is required for this service" #: ../src/gtk-ui/sync-config-widget.c:493 #, c-format msgid "" "Do you want to reset the settings for %s? This will not remove any synced " "information on either end." msgstr "" "Do you want to reset the settings for %s? This will not remove any synced " "information on either end." #. TRANSLATORS: buttons in reset-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:497 msgid "Yes, reset" msgstr "Yes, reset" #: ../src/gtk-ui/sync-config-widget.c:498 #: ../src/gtk-ui/sync-config-widget.c:509 msgid "No, keep settings" msgstr "No, keep settings" #: ../src/gtk-ui/sync-config-widget.c:503 #, c-format msgid "" "Do you want to delete the settings for %s? This will not remove any synced " "information on either end but it will remove these settings." msgstr "" "Do you want to delete the settings for %s? This will not remove any synced " "information on either end but it will remove these settings." #. TRANSLATORS: buttons in delete-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:508 msgid "Yes, delete" msgstr "Yes, delete" #: ../src/gtk-ui/sync-config-widget.c:539 msgid "Reset settings" msgstr "Reset settings" #: ../src/gtk-ui/sync-config-widget.c:542 msgid "Delete settings" msgstr "Delete settings" #: ../src/gtk-ui/sync-config-widget.c:552 msgid "Save and use" msgstr "Save and use" #: ../src/gtk-ui/sync-config-widget.c:555 msgid "" "Save and replace\n" "current service" msgstr "" "Save and replace\n" "current service" #: ../src/gtk-ui/sync-config-widget.c:563 msgid "Stop using device" msgstr "Stop using device" #: ../src/gtk-ui/sync-config-widget.c:566 msgid "Stop using service" msgstr "Stop using service" #. TRANSLATORS: label for an entry in service configuration form. #. * Placeholder is a source name. #. * Example: "Appointments URI" #: ../src/gtk-ui/sync-config-widget.c:749 #, c-format msgid "%s URI" msgstr "%s URI" #. TRANSLATORS: toggles in service configuration form, placeholder is service #. * or device name #: ../src/gtk-ui/sync-config-widget.c:936 #, c-format msgid "Send changes to %s" msgstr "Send changes to %s" #: ../src/gtk-ui/sync-config-widget.c:941 #, c-format msgid "Receive changes from %s" msgstr "Receive changes from %s" #: ../src/gtk-ui/sync-config-widget.c:957 msgid "Sync" msgstr "Sync" #. TRANSLATORS: label of a entry in service configuration #: ../src/gtk-ui/sync-config-widget.c:973 msgid "Server address" msgstr "Server address" #. TRANSLATORS: explanation before a device template combobox. #. * Placeholder is a device name like 'Nokia N85' or 'Syncevolution #. * Client' #: ../src/gtk-ui/sync-config-widget.c:1049 #, c-format msgid "" "This device looks like it might be a '%s'. If this is not correct, please " "take a look at the list of supported devices and pick yours if it is listed" msgstr "" "This device looks like it might be a '%s'. If this is not correct, please " "take a look at the list of supported devices and pick yours if it is listed" #: ../src/gtk-ui/sync-config-widget.c:1055 #: ../src/gtk-ui/sync-config-widget.c:1915 msgid "" "We don't know what this device is exactly. Please take a look at the list of" " supported devices and pick yours if it is listed" msgstr "" "We don't know what this device is exactly. Please take a look at the list of" " supported devices and pick yours if it is listed" #: ../src/gtk-ui/sync-config-widget.c:1207 #, c-format msgid "%s - Bluetooth device" msgstr "%s - Bluetooth device" #. TRANSLATORS: service title for services that are not based on a #. * template in service list, the placeholder is the name of the service #: ../src/gtk-ui/sync-config-widget.c:1213 #, c-format msgid "%s - manually setup" msgstr "%s - manually setup" #. TRANSLATORS: link button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1886 msgid "Launch website" msgstr "Launch website" #. TRANSLATORS: button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1895 msgid "Set up now" msgstr "Set up now" #: ../src/gtk-ui/sync-config-widget.c:1953 msgid "Use these settings" msgstr "Use these settings" #. TRANSLATORS: labels of entries in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1991 msgid "Username" msgstr "Username" #: ../src/gtk-ui/sync-config-widget.c:2006 msgid "Password" msgstr "Password" #. TRANSLATORS: warning in service configuration form for people #. who have modified the configuration via other means. #: ../src/gtk-ui/sync-config-widget.c:2029 msgid "" "Current configuration is more complex than what can be shown here. Changes " "to sync mode or synced data types will overwrite that configuration." msgstr "" "Current configuration is more complex than what can be shown here. Changes " "to sync mode or synced data types will overwrite that configuration." #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2048 msgid "Hide server settings" msgstr "Hide server settings" #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2068 msgid "Show server settings" msgstr "Show server settings" #: ../src/gnome-bluetooth/syncevolution.c:110 msgid "Sync in the Sync application" msgstr "Sync in the Sync application" #: ../src/syncevo-dbus-server.cpp:6653 #, c-format msgid "%s is syncing" msgstr "%s is syncing" #: ../src/syncevo-dbus-server.cpp:6654 #, c-format msgid "We have just started to sync your computer with the %s sync service." msgstr "We have just started to sync your computer with the %s sync service." #. if sync is successfully started and done #: ../src/syncevo-dbus-server.cpp:6670 #, c-format msgid "%s sync complete" msgstr "%s sync complete" #: ../src/syncevo-dbus-server.cpp:6671 #, c-format msgid "We have just finished syncing your computer with the %s sync service." msgstr "We have just finished syncing your computer with the %s sync service." #. if sync is successfully started and has errors, or not started successful #. with a fatal problem #: ../src/syncevo-dbus-server.cpp:6676 msgid "Sync problem." msgstr "Sync problem." #: ../src/syncevo-dbus-server.cpp:6677 msgid "Sorry, there's a problem with your sync that you need to attend to." msgstr "Sorry, there's a problem with your sync that you need to attend to." #: ../src/syncevo-dbus-server.cpp:6758 msgid "View" msgstr "View" #. Use "default" as ID because that is what mutter-moblin #. recognizes: it then skips the action instead of adding it #. in addition to its own "Dismiss" button (always added). #: ../src/syncevo-dbus-server.cpp:6762 msgid "Dismiss" msgstr "Dismiss" syncevolution_1.4/po/en_US.po000066400000000000000000000712651230021373600163600ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # # Translators: # Margie Foster , 2011. msgid "" msgstr "" "Project-Id-Version: syncevolution\n" "Report-Msgid-Bugs-To: https://bugs.meego.com/\n" "POT-Creation-Date: 2011-12-05 10:21-0800\n" "PO-Revision-Date: 2011-12-05 22:05+0000\n" "Last-Translator: margie \n" "Language-Team: English (United States) (http://www.transifex.net/projects/p/meego/team/en_US/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: en_US\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" #. TRANSLATORS: this is the application name that may be used by e.g. #. the windowmanager #: ../src/gtk-ui/main.c:40 ../src/gtk-ui/ui.glade.h:38 #: ../src/gtk-ui/sync.desktop.in.h:1 #: ../src/gnome-bluetooth/syncevolution.c:112 msgid "Sync" msgstr "Sync" #: ../src/gtk-ui/sync-ui.c:266 msgid "Contacts" msgstr "Contacts" #: ../src/gtk-ui/sync-ui.c:268 msgid "Appointments" msgstr "Appointments" #: ../src/gtk-ui/sync-ui.c:270 ../src/gtk-ui/ui.glade.h:40 msgid "Tasks" msgstr "Tasks" #: ../src/gtk-ui/sync-ui.c:272 msgid "Notes" msgstr "Notes" #. TRANSLATORS: This is a "combination source" for syncing with devices #. * that combine appointments and tasks. the name should match the ones #. * used for calendar and todo above #: ../src/gtk-ui/sync-ui.c:277 msgid "Appointments & Tasks" msgstr "Appointments & Tasks" #: ../src/gtk-ui/sync-ui.c:349 msgid "Starting sync" msgstr "Starting sync" #. TRANSLATORS: slow sync confirmation dialog message. Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:387 #, c-format msgid "Do you want to slow sync with %s?" msgstr "Do you want to slow sync with %s?" #: ../src/gtk-ui/sync-ui.c:391 msgid "Yes, do slow sync" msgstr "Yes, do slow sync" #: ../src/gtk-ui/sync-ui.c:391 msgid "No, cancel sync" msgstr "No, cancel sync" #. TRANSLATORS: confirmation dialog for "refresh from peer". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:424 #, c-format msgid "" "Do you want to delete all local data and replace it with data from %s? This " "is not usually advised." msgstr "" "Do you want to delete all local data and replace it with data from %s? This " "is not usually advised." #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 msgid "Yes, delete and replace" msgstr "Yes, delete and replace" #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 #: ../src/gtk-ui/sync-ui.c:1610 msgid "No" msgstr "No" #. TRANSLATORS: confirmation dialog for "refresh from local side". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:457 #, c-format msgid "" "Do you want to delete all data in %s and replace it with your local data? " "This is not usually advised." msgstr "" "Do you want to delete all data in %s and replace it with your local data? " "This is not usually advised." #: ../src/gtk-ui/sync-ui.c:491 msgid "Trying to cancel sync" msgstr "Trying to cancel sync" #: ../src/gtk-ui/sync-ui.c:533 msgid "No service or device selected" msgstr "No service or device selected" #. TRANSLATORS: This is the title on main view. Placeholder is #. * the service name. Example: "Google - synced just now" #: ../src/gtk-ui/sync-ui.c:541 #, c-format msgid "%s - synced just now" msgstr "%s - synced just now" #: ../src/gtk-ui/sync-ui.c:545 #, c-format msgid "%s - synced a minute ago" msgstr "%s - synced a minute ago" #: ../src/gtk-ui/sync-ui.c:549 #, c-format msgid "%s - synced %ld minutes ago" msgstr "%s - synced %ld minutes ago" #: ../src/gtk-ui/sync-ui.c:554 #, c-format msgid "%s - synced an hour ago" msgstr "%s - synced an hour ago" #: ../src/gtk-ui/sync-ui.c:558 #, c-format msgid "%s - synced %ld hours ago" msgstr "%s - synced %ld hours ago" #: ../src/gtk-ui/sync-ui.c:563 #, c-format msgid "%s - synced a day ago" msgstr "%s - synced a day ago" #: ../src/gtk-ui/sync-ui.c:567 #, c-format msgid "%s - synced %ld days ago" msgstr "%s - synced %ld days ago" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "You've just restored a backup. The changes have not been " #. * "synced with %s yet" #: ../src/gtk-ui/sync-ui.c:616 ../src/gtk-ui/sync-ui.c:701 msgid "Sync now" msgstr "Sync now" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "A normal sync is not possible at this time..." message. #. * "Other options" will open Emergency view #: ../src/gtk-ui/sync-ui.c:622 ../src/gtk-ui/ui.glade.h:37 msgid "Slow sync" msgstr "Slow sync" #: ../src/gtk-ui/sync-ui.c:623 msgid "Other options..." msgstr "Other options..." #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * when no service is selected. Will open configuration view #: ../src/gtk-ui/sync-ui.c:628 msgid "Select sync service" msgstr "Select sync service" #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * login to service fails. Will open configuration view for this service #: ../src/gtk-ui/sync-ui.c:633 msgid "Edit service settings" msgstr "Edit service settings" #: ../src/gtk-ui/sync-ui.c:709 msgid "" "You haven't selected a sync service or device yet. Sync services let you " "synchronize your data between your netbook and a web service. You can also " "sync directly with some devices." msgstr "" "You haven't selected a sync service or device yet. Sync services let you " "synchronize your data between your netbook and a web service. You can also " "sync directly with some devices." #: ../src/gtk-ui/sync-ui.c:729 msgid "Sync again" msgstr "Sync again" #: ../src/gtk-ui/sync-ui.c:748 msgid "Restoring" msgstr "Restoring" #: ../src/gtk-ui/sync-ui.c:750 msgid "Syncing" msgstr "Syncing" #. TRANSLATORS: This is for the button in main view, right side. #. Keep line length below ~20 characters, use two lines if needed #: ../src/gtk-ui/sync-ui.c:762 ../src/gtk-ui/sync-ui.c:3407 msgid "Cancel sync" msgstr "Cancel sync" #: ../src/gtk-ui/sync-ui.c:927 msgid "Back to sync" msgstr "Back to sync" #. TRANSLATORS: label for checkbutton/toggle in main view. #. * Please stick to similar length strings or break the line with #. * "\n" if absolutely needed #: ../src/gtk-ui/sync-ui.c:1229 msgid "Automatic sync" msgstr "Automatic sync" #. This is the expander label in emergency view. It summarizes the #. * currently selected data sources. First placeholder is service/device #. * name, second a comma separeted list of sources. #. * E.g. "Affected data: Google Contacts, Appointments" #: ../src/gtk-ui/sync-ui.c:1524 #, c-format msgid "Affected data: %s %s" msgstr "Affected data: %s %s" #: ../src/gtk-ui/sync-ui.c:1529 #, c-format msgid "Affected data: none" msgstr "Affected data: none" #. TRANSLATORS: confirmation for restoring a backup. placeholder is the #. * backup time string defined below #: ../src/gtk-ui/sync-ui.c:1607 #, c-format msgid "" "Do you want to restore the backup from %s? All changes you have made since " "then will be lost." msgstr "" "Do you want to restore the backup from %s? All changes you have made since " "then will be lost." #: ../src/gtk-ui/sync-ui.c:1610 msgid "Yes, restore" msgstr "Yes, restore" #. TRANSLATORS: date/time for strftime(), used in emergency view backup #. * label. Any time format that shows date and time is good. #: ../src/gtk-ui/sync-ui.c:1642 #, c-format msgid "%x %X" msgstr "%x %X" #. TRANSLATORS: label for a backup in emergency view. Placeholder is #. * service or device name #: ../src/gtk-ui/sync-ui.c:1661 #, c-format msgid "Backed up before syncing with %s" msgstr "Backed up before syncing with %s" #: ../src/gtk-ui/sync-ui.c:1678 msgid "Restore" msgstr "Restore" #. TRANSLATORS: this is an explanation in Emergency view. #. * Placeholder is a service/device name #: ../src/gtk-ui/sync-ui.c:1785 #, c-format msgid "" "A normal sync with %s is not possible at this time. You can do a slow two-" "way sync or start from scratch. You can also restore a backup, but a slow " "sync or starting from scratch will still be required before normal sync is " "possible." msgstr "" "A normal sync with %s is not possible at this time. You can do a slow two-" "way sync or start from scratch. You can also restore a backup, but a slow " "sync or starting from scratch will still be required before normal sync is " "possible." #: ../src/gtk-ui/sync-ui.c:1795 #, c-format msgid "" "If something has gone horribly wrong, you can try a slow sync, start from " "scratch or restore from backup." msgstr "" "If something has gone horribly wrong, you can try a slow sync, start from " "scratch or restore from backup." #. TRANSLATORS: These are a buttons in Emergency view. Placeholder is a #. * service/device name. Please don't use too long lines, but feel free to #. * use several lines. #: ../src/gtk-ui/sync-ui.c:1804 #, c-format msgid "" "Delete all your local\n" "data and replace with\n" "data from %s" msgstr "" "Delete all your local\n" "data and replace with\n" "data from %s" #: ../src/gtk-ui/sync-ui.c:1810 #, c-format msgid "" "Delete all data on\n" "%s and replace\n" "with your local data" msgstr "" "Delete all data on\n" "%s and replace\n" "with your local data" #: ../src/gtk-ui/sync-ui.c:2275 msgid "Failed to get list of supported services from SyncEvolution" msgstr "Failed to get list of supported services from SyncEvolution" #: ../src/gtk-ui/sync-ui.c:2329 msgid "" "There was a problem communicating with the sync process. Please try again " "later." msgstr "" "There was a problem communicating with the sync process. Please try again " "later." #: ../src/gtk-ui/sync-ui.c:2388 msgid "Restore failed" msgstr "Restore failed" #: ../src/gtk-ui/sync-ui.c:2391 ../src/gtk-ui/sync-ui.c:3276 msgid "Sync failed" msgstr "Sync failed" #: ../src/gtk-ui/sync-ui.c:2397 msgid "Restore complete" msgstr "Restore complete" #: ../src/gtk-ui/sync-ui.c:2400 msgid "Sync complete" msgstr "Sync complete" #: ../src/gtk-ui/sync-ui.c:2492 #, c-format msgid "Preparing '%s'" msgstr "Preparing '%s'" #: ../src/gtk-ui/sync-ui.c:2495 #, c-format msgid "Receiving '%s'" msgstr "Receiving '%s'" #: ../src/gtk-ui/sync-ui.c:2498 #, c-format msgid "Sending '%s'" msgstr "Sending '%s'" #: ../src/gtk-ui/sync-ui.c:2619 #, c-format msgid "There was one remote rejection." msgid_plural "There were %ld remote rejections." msgstr[0] "There was one remote rejection." msgstr[1] "There were %ld remote rejections." #: ../src/gtk-ui/sync-ui.c:2624 #, c-format msgid "There was one local rejection." msgid_plural "There were %ld local rejections." msgstr[0] "There was one local rejection." msgstr[1] "There were %ld local rejections." #: ../src/gtk-ui/sync-ui.c:2629 #, c-format msgid "There were %ld local rejections and %ld remote rejections." msgstr "There were %ld local rejections and %ld remote rejections." #: ../src/gtk-ui/sync-ui.c:2634 #, c-format msgid "Last time: No changes." msgstr "Last time: No changes." #: ../src/gtk-ui/sync-ui.c:2636 #, c-format msgid "Last time: Sent one change." msgid_plural "Last time: Sent %ld changes." msgstr[0] "Last time: Sent one change." msgstr[1] "Last time: Sent %ld changes." #. This is about changes made to the local data. Not all of these #. changes were requested by the remote server, so "applied" #. is a better word than "received" (bug #5185). #: ../src/gtk-ui/sync-ui.c:2644 #, c-format msgid "Last time: Applied one change." msgid_plural "Last time: Applied %ld changes." msgstr[0] "Last time: Applied one change." msgstr[1] "Last time: Applied %ld changes." #: ../src/gtk-ui/sync-ui.c:2649 #, c-format msgid "Last time: Applied %ld changes and sent %ld changes." msgstr "Last time: Applied %ld changes and sent %ld changes." #. TRANSLATORS: the placeholder is a error message (hopefully) #. * explaining the problem #: ../src/gtk-ui/sync-ui.c:2856 #, c-format msgid "" "There was a problem with last sync:\n" "%s" msgstr "" "There was a problem with last sync:\n" "%s" #: ../src/gtk-ui/sync-ui.c:2866 #, c-format msgid "" "You've just restored a backup. The changes have not been synced with %s yet" msgstr "" "You've just restored a backup. The changes have not been synced with %s yet" #: ../src/gtk-ui/sync-ui.c:3154 msgid "Waiting for current operation to finish..." msgstr "Waiting for current operation to finish..." #. TRANSLATORS: next strings are error messages. #: ../src/gtk-ui/sync-ui.c:3188 msgid "" "A normal sync is not possible at this time. The server suggests a slow sync," " but this might not always be what you want if both ends already have data." msgstr "" "A normal sync is not possible at this time. The server suggests a slow sync," " but this might not always be what you want if both ends already have data." #: ../src/gtk-ui/sync-ui.c:3192 msgid "The sync process died unexpectedly." msgstr "The sync process died unexpectedly." #: ../src/gtk-ui/sync-ui.c:3197 msgid "" "Password request was not answered. You can save the password in the settings" " to prevent the request." msgstr "" "Password request was not answered. You can save the password in the settings" " to prevent the request." #. TODO use the service device name here, this is a remote problem #: ../src/gtk-ui/sync-ui.c:3201 msgid "There was a problem processing sync request. Trying again may help." msgstr "There was a problem processing sync request. Trying again may help." #: ../src/gtk-ui/sync-ui.c:3207 msgid "" "Failed to login. Could there be a problem with your username or password?" msgstr "" "Failed to log in. Could there be a problem with your username or password?" #: ../src/gtk-ui/sync-ui.c:3210 msgid "Forbidden" msgstr "Forbidden" #. TRANSLATORS: data source means e.g. calendar or addressbook #: ../src/gtk-ui/sync-ui.c:3216 msgid "" "A data source could not be found. Could there be a problem with the " "settings?" msgstr "" "A data source could not be found. Could there be a problem with the " "settings?" #: ../src/gtk-ui/sync-ui.c:3220 msgid "Remote database error" msgstr "Remote database error" #. This can happen when EDS is borked, restart it may help... #: ../src/gtk-ui/sync-ui.c:3223 msgid "" "There is a problem with the local database. Syncing again or rebooting may " "help." msgstr "" "There is a problem with the local database. Syncing again or rebooting may " "help." #: ../src/gtk-ui/sync-ui.c:3226 msgid "No space on disk" msgstr "No space on disk" #: ../src/gtk-ui/sync-ui.c:3228 msgid "Failed to process SyncML" msgstr "Failed to process SyncML" #: ../src/gtk-ui/sync-ui.c:3230 msgid "Server authorization failed" msgstr "Server authorization failed" #: ../src/gtk-ui/sync-ui.c:3232 msgid "Failed to parse configuration file" msgstr "Failed to parse configuration file" #: ../src/gtk-ui/sync-ui.c:3234 msgid "Failed to read configuration file" msgstr "Failed to read configuration file" #: ../src/gtk-ui/sync-ui.c:3236 msgid "No configuration found" msgstr "No configuration found" #: ../src/gtk-ui/sync-ui.c:3238 msgid "No configuration file found" msgstr "No configuration file found" #: ../src/gtk-ui/sync-ui.c:3240 msgid "Server sent bad content" msgstr "Server sent bad content" #: ../src/gtk-ui/sync-ui.c:3242 msgid "Connection certificate has expired" msgstr "Connection certificate has expired" #: ../src/gtk-ui/sync-ui.c:3244 msgid "Connection certificate is invalid" msgstr "Connection certificate is invalid" #: ../src/gtk-ui/sync-ui.c:3252 msgid "" "We were unable to connect to the server. The problem could be temporary or " "there could be something wrong with the settings." msgstr "" "We were unable to connect to the server. The problem could be temporary or " "there could be something wrong with the settings." #: ../src/gtk-ui/sync-ui.c:3259 msgid "The server URL is bad" msgstr "The server URL is bad" #: ../src/gtk-ui/sync-ui.c:3264 msgid "The server was not found" msgstr "The server was not found" #: ../src/gtk-ui/sync-ui.c:3266 #, c-format msgid "Error %d" msgstr "Error %d" #. TRANSLATORS: password request dialog contents: title, cancel button #. * and ok button #: ../src/gtk-ui/sync-ui.c:3404 msgid "Password is required for sync" msgstr "Password is required for sync" #: ../src/gtk-ui/sync-ui.c:3408 msgid "Sync with password" msgstr "Sync with password" #. TRANSLATORS: password request dialog message, placeholder is service name #: ../src/gtk-ui/sync-ui.c:3418 #, c-format msgid "Please enter password for syncing with %s:" msgstr "Please enter password for syncing with %s:" #. title for the buttons on the right side of main view #: ../src/gtk-ui/ui.glade.h:2 msgid "Actions" msgstr "Actions" #. text between the two "start from scratch" buttons in emergency view #: ../src/gtk-ui/ui.glade.h:4 msgid "or" msgstr "or" #: ../src/gtk-ui/ui.glade.h:5 msgid "Direct sync" msgstr "Direct sync" #: ../src/gtk-ui/ui.glade.h:6 msgid "Network sync" msgstr "Network sync" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:8 msgid "Restore from backup" msgstr "Restore from backup" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:10 msgid "Slow sync" msgstr "Slow sync" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:12 msgid "Start from scratch" msgstr "Start from scratch" #: ../src/gtk-ui/ui.glade.h:13 msgid "" "A slow sync compares items from both sides and tries to merge them. \n" "This may fail in some cases, leading to duplicates or lost information." msgstr "" "A slow sync compares items from both sides and tries to merge them. \n" "This may fail in some cases, leading to duplicates or lost information." #: ../src/gtk-ui/ui.glade.h:15 msgid "Add new device" msgstr "Add new device" #: ../src/gtk-ui/ui.glade.h:16 msgid "Add new service" msgstr "Add new service" #. explanation of "Restore backup" function #: ../src/gtk-ui/ui.glade.h:18 msgid "" "Backups are made before every time we Sync. Choose a backup to restore. Any " "changes you have made since then will be lost." msgstr "" "Backups are made before every sync. Choose a backup to restore. Any changes " "you have made since then will be lost." #: ../src/gtk-ui/ui.glade.h:19 msgid "Calendar" msgstr "Calendar" #. Button in main view, right side. Keep to below 20 chars per line, feel free #. to use two lines #: ../src/gtk-ui/ui.glade.h:21 msgid "" "Change or edit\n" "sync service" msgstr "" "Change or edit\n" "sync service" #. close button for settings window #: ../src/gtk-ui/ui.glade.h:24 msgid "Close" msgstr "Close" #: ../src/gtk-ui/ui.glade.h:25 msgid "" "Delete all data on Zyb \n" "and replace with your\n" "local information" msgstr "" "Delete all data on Zyb \n" "and replace with your\n" "local information" #: ../src/gtk-ui/ui.glade.h:28 msgid "" "Delete all your local\n" "information and replace\n" "with data from Zyb" msgstr "" "Delete all your local\n" "information and replace\n" "with data from Zyb" #. button in main view, right side. Keep length to 20 characters or so, use #. two lines if needed #: ../src/gtk-ui/ui.glade.h:32 msgid "" "Fix a sync\n" "emergency" msgstr "" "Fix a sync\n" "emergency" #: ../src/gtk-ui/ui.glade.h:34 msgid "" "If you don't see your service above but know that your sync provider uses SyncML\n" "you can setup a service manually." msgstr "" "If you don't see your service above but know that your sync provider uses SyncML\n" "you can setup a service manually." #: ../src/gtk-ui/ui.glade.h:36 msgid "Settings" msgstr "Settings" #: ../src/gtk-ui/ui.glade.h:39 msgid "Sync Emergency" msgstr "Sync Emergency" #: ../src/gtk-ui/ui.glade.h:41 msgid "" "To sync you'll need a network connection and an account with a sync service.\n" "We support the following services: " msgstr "" "To sync you'll need a network connection and an account with a sync service.\n" "We support the following services: " #: ../src/gtk-ui/ui.glade.h:43 msgid "Use Bluetooth to Sync your data from one device to another." msgstr "Use Bluetooth to sync your data from one device to another." #: ../src/gtk-ui/ui.glade.h:44 msgid "You will need to add Bluetooth devices before they can be synced." msgstr "You will need to add Bluetooth devices before they can be synced." #: ../src/gtk-ui/sync.desktop.in.h:2 msgid "Up to date" msgstr "Up to date" #: ../src/gtk-ui/sync-gtk.desktop.in.h:1 msgid "SyncEvolution (GTK)" msgstr "SyncEvolution (GTK)" #: ../src/gtk-ui/sync-gtk.desktop.in.h:2 msgid "Synchronize PIM data" msgstr "Synchronize PIM data" #: ../src/gtk-ui/sync-config-widget.c:88 msgid "" "ScheduleWorld enables you to keep your contacts, events, tasks, and notes in" " sync." msgstr "" "ScheduleWorld enables you to keep your contacts, events, tasks, and notes in" " sync." #: ../src/gtk-ui/sync-config-widget.c:91 msgid "" "Google Sync can back up and synchronize your contacts with your Gmail " "contacts." msgstr "" "Google Sync can back up and synchronize your contacts with your Gmail " "contacts." #. TRANSLATORS: Please include the word "demo" (or the equivalent in #. your language): Funambol is going to be a 90 day demo service #. in the future #: ../src/gtk-ui/sync-config-widget.c:97 msgid "" "Back up your contacts and calendar. Sync with a single click, anytime, " "anywhere (DEMO)." msgstr "" "Back up your contacts and calendar. Sync with a single click, anytime, " "anywhere (DEMO)." #: ../src/gtk-ui/sync-config-widget.c:100 msgid "" "Mobical Backup and Restore service allows you to securely back up your " "personal mobile data for free." msgstr "" "Mobical Backup and Restore service allows you to securely back up your " "personal mobile data for free." #: ../src/gtk-ui/sync-config-widget.c:103 msgid "" "ZYB is a simple way for people to store and share mobile information online." msgstr "" "ZYB is a simple way for people to store and share mobile information online." #: ../src/gtk-ui/sync-config-widget.c:106 msgid "" "Memotoo lets you access your personal data from any computer connected to " "the Internet." msgstr "" "Memotoo lets you access your personal data from any computer connected to " "the Internet." #: ../src/gtk-ui/sync-config-widget.c:195 msgid "Sorry, failed to save the configuration" msgstr "Sorry, failed to save the configuration" #: ../src/gtk-ui/sync-config-widget.c:445 msgid "Service must have a name and server URL" msgstr "Service must have a name and server URL" #. TRANSLATORS: error dialog when creating a new sync configuration #: ../src/gtk-ui/sync-config-widget.c:451 msgid "A username is required for this service" msgstr "A username is required for this service" #: ../src/gtk-ui/sync-config-widget.c:493 #, c-format msgid "" "Do you want to reset the settings for %s? This will not remove any synced " "information on either end." msgstr "" "Do you want to reset the settings for %s? This will not remove any synced " "information on either end." #. TRANSLATORS: buttons in reset-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:497 msgid "Yes, reset" msgstr "Yes, reset" #: ../src/gtk-ui/sync-config-widget.c:498 #: ../src/gtk-ui/sync-config-widget.c:509 msgid "No, keep settings" msgstr "No, keep settings" #: ../src/gtk-ui/sync-config-widget.c:503 #, c-format msgid "" "Do you want to delete the settings for %s? This will not remove any synced " "information on either end but it will remove these settings." msgstr "" "Do you want to delete the settings for %s? This will not remove any synced " "information on either end but it will remove these settings." #. TRANSLATORS: buttons in delete-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:508 msgid "Yes, delete" msgstr "Yes, delete" #: ../src/gtk-ui/sync-config-widget.c:539 msgid "Reset settings" msgstr "Reset settings" #: ../src/gtk-ui/sync-config-widget.c:542 msgid "Delete settings" msgstr "Delete settings" #: ../src/gtk-ui/sync-config-widget.c:552 msgid "Save and use" msgstr "Save and use" #: ../src/gtk-ui/sync-config-widget.c:555 msgid "" "Save and replace\n" "current service" msgstr "" "Save and replace\n" "current service" #: ../src/gtk-ui/sync-config-widget.c:563 msgid "Stop using device" msgstr "Stop using device" #: ../src/gtk-ui/sync-config-widget.c:566 msgid "Stop using service" msgstr "Stop using service" #. TRANSLATORS: label for an entry in service configuration form. #. * Placeholder is a source name. #. * Example: "Appointments URI" #: ../src/gtk-ui/sync-config-widget.c:749 #, c-format msgid "%s URI" msgstr "%s URI" #. TRANSLATORS: toggles in service configuration form, placeholder is service #. * or device name #: ../src/gtk-ui/sync-config-widget.c:936 #, c-format msgid "Send changes to %s" msgstr "Send changes to %s" #: ../src/gtk-ui/sync-config-widget.c:941 #, c-format msgid "Receive changes from %s" msgstr "Receive changes from %s" #: ../src/gtk-ui/sync-config-widget.c:957 msgid "Sync" msgstr "Sync" #. TRANSLATORS: label of a entry in service configuration #: ../src/gtk-ui/sync-config-widget.c:973 msgid "Server address" msgstr "Server address" #. TRANSLATORS: explanation before a device template combobox. #. * Placeholder is a device name like 'Nokia N85' or 'Syncevolution #. * Client' #: ../src/gtk-ui/sync-config-widget.c:1049 #, c-format msgid "" "This device looks like it might be a '%s'. If this is not correct, please " "take a look at the list of supported devices and pick yours if it is listed" msgstr "" "This device looks like it might be a '%s'. If this is not correct, please " "take a look at the list of supported devices and pick yours if it is listed" #: ../src/gtk-ui/sync-config-widget.c:1055 #: ../src/gtk-ui/sync-config-widget.c:1915 msgid "" "We don't know what this device is exactly. Please take a look at the list of" " supported devices and pick yours if it is listed" msgstr "" "We don't know what this device is exactly. Please take a look at the list of" " supported devices and pick yours if it is listed" #: ../src/gtk-ui/sync-config-widget.c:1207 #, c-format msgid "%s - Bluetooth device" msgstr "%s - Bluetooth device" #. TRANSLATORS: service title for services that are not based on a #. * template in service list, the placeholder is the name of the service #: ../src/gtk-ui/sync-config-widget.c:1213 #, c-format msgid "%s - manually setup" msgstr "%s - manually set up" #. TRANSLATORS: link button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1886 msgid "Launch website" msgstr "Launch website" #. TRANSLATORS: button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1895 msgid "Set up now" msgstr "Set up now" #: ../src/gtk-ui/sync-config-widget.c:1953 msgid "Use these settings" msgstr "Use these settings" #. TRANSLATORS: labels of entries in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1991 msgid "Username" msgstr "Username" #: ../src/gtk-ui/sync-config-widget.c:2006 msgid "Password" msgstr "Password" #. TRANSLATORS: warning in service configuration form for people #. who have modified the configuration via other means. #: ../src/gtk-ui/sync-config-widget.c:2029 msgid "" "Current configuration is more complex than what can be shown here. Changes " "to sync mode or synced data types will overwrite that configuration." msgstr "" "Current configuration is more complex than what can be shown here. Changes " "to sync mode or synced data types will overwrite that configuration." #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2048 msgid "Hide server settings" msgstr "Hide server settings" #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2068 msgid "Show server settings" msgstr "Show server settings" #: ../src/gnome-bluetooth/syncevolution.c:110 msgid "Sync in the Sync application" msgstr "Sync in the Sync application" #: ../src/syncevo-dbus-server.cpp:6653 #, c-format msgid "%s is syncing" msgstr "%s is syncing" #: ../src/syncevo-dbus-server.cpp:6654 #, c-format msgid "We have just started to sync your computer with the %s sync service." msgstr "We have just started to sync your computer with the %s sync service." #. if sync is successfully started and done #: ../src/syncevo-dbus-server.cpp:6670 #, c-format msgid "%s sync complete" msgstr "%s sync complete" #: ../src/syncevo-dbus-server.cpp:6671 #, c-format msgid "We have just finished syncing your computer with the %s sync service." msgstr "We have just finished syncing your computer with the %s sync service." #. if sync is successfully started and has errors, or not started successful #. with a fatal problem #: ../src/syncevo-dbus-server.cpp:6676 msgid "Sync problem." msgstr "Sync problem." #: ../src/syncevo-dbus-server.cpp:6677 msgid "Sorry, there's a problem with your sync that you need to attend to." msgstr "Sorry, there's a problem with your sync that you need to attend to." #: ../src/syncevo-dbus-server.cpp:6758 msgid "View" msgstr "View" #. Use "default" as ID because that is what mutter-moblin #. recognizes: it then skips the action instead of adding it #. in addition to its own "Dismiss" button (always added). #: ../src/syncevo-dbus-server.cpp:6762 msgid "Dismiss" msgstr "Dismiss" syncevolution_1.4/po/es.po000066400000000000000000000747601230021373600157610ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # # Translators: # , 2011. # , 2011. msgid "" msgstr "" "Project-Id-Version: syncevolution\n" "Report-Msgid-Bugs-To: https://bugs.meego.com/\n" "POT-Creation-Date: 2011-12-05 10:21-0800\n" "PO-Revision-Date: 2011-12-09 00:30+0000\n" "Last-Translator: GLS_ESP \n" "Language-Team: Spanish (Castilian) (http://www.transifex.net/projects/p/meego/team/es/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: es\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" #. TRANSLATORS: this is the application name that may be used by e.g. #. the windowmanager #: ../src/gtk-ui/main.c:40 ../src/gtk-ui/ui.glade.h:38 #: ../src/gtk-ui/sync.desktop.in.h:1 #: ../src/gnome-bluetooth/syncevolution.c:112 msgid "Sync" msgstr "Sincronizador" #: ../src/gtk-ui/sync-ui.c:266 msgid "Contacts" msgstr "Contactos" #: ../src/gtk-ui/sync-ui.c:268 msgid "Appointments" msgstr "Citas" #: ../src/gtk-ui/sync-ui.c:270 ../src/gtk-ui/ui.glade.h:40 msgid "Tasks" msgstr "Tareas" #: ../src/gtk-ui/sync-ui.c:272 msgid "Notes" msgstr "Notas" #. TRANSLATORS: This is a "combination source" for syncing with devices #. * that combine appointments and tasks. the name should match the ones #. * used for calendar and todo above #: ../src/gtk-ui/sync-ui.c:277 msgid "Appointments & Tasks" msgstr "Citas y tareas" #: ../src/gtk-ui/sync-ui.c:349 msgid "Starting sync" msgstr "Iniciando..." #. TRANSLATORS: slow sync confirmation dialog message. Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:387 #, c-format msgid "Do you want to slow sync with %s?" msgstr "Deseas una sincronización lenta con %s?" #: ../src/gtk-ui/sync-ui.c:391 msgid "Yes, do slow sync" msgstr "Sí, realizar sincronización lenta" #: ../src/gtk-ui/sync-ui.c:391 msgid "No, cancel sync" msgstr "No, cancelar la sincronización" #. TRANSLATORS: confirmation dialog for "refresh from peer". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:424 #, c-format msgid "" "Do you want to delete all local data and replace it with data from %s? This " "is not usually advised." msgstr "" "¿Quieres borrar todos los datos locales y sustituirlos por los datos de %s? " "Normalmente, no es recomendable." #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 msgid "Yes, delete and replace" msgstr "Sí, borrar y reemplazar" #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 #: ../src/gtk-ui/sync-ui.c:1610 msgid "No" msgstr "No" #. TRANSLATORS: confirmation dialog for "refresh from local side". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:457 #, c-format msgid "" "Do you want to delete all data in %s and replace it with your local data? " "This is not usually advised." msgstr "" "¿Quieres borrar todos los datos de %s y sustituirlos por los datos locales? " "Normalmente, no es recomendable." #: ../src/gtk-ui/sync-ui.c:491 msgid "Trying to cancel sync" msgstr "Estamos intentando cancelar la sincronización" #: ../src/gtk-ui/sync-ui.c:533 msgid "No service or device selected" msgstr "No se ha seleccionado ningún servicio o dispositivo" #. TRANSLATORS: This is the title on main view. Placeholder is #. * the service name. Example: "Google - synced just now" #: ../src/gtk-ui/sync-ui.c:541 #, c-format msgid "%s - synced just now" msgstr "%s - se sincronizo hace unos segundos" #: ../src/gtk-ui/sync-ui.c:545 #, c-format msgid "%s - synced a minute ago" msgstr "%s - se sincronizo hace un minuto" #: ../src/gtk-ui/sync-ui.c:549 #, c-format msgid "%s - synced %ld minutes ago" msgstr "%s - se sincronizo hace %ld minutos" #: ../src/gtk-ui/sync-ui.c:554 #, c-format msgid "%s - synced an hour ago" msgstr "%s - se sincronizo hace una hora" #: ../src/gtk-ui/sync-ui.c:558 #, c-format msgid "%s - synced %ld hours ago" msgstr "%s - se sincronizo hace %ld horas" #: ../src/gtk-ui/sync-ui.c:563 #, c-format msgid "%s - synced a day ago" msgstr "%s - se sincronizo hace un día" #: ../src/gtk-ui/sync-ui.c:567 #, c-format msgid "%s - synced %ld days ago" msgstr "%s - se sincronizo hace %ld días" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "You've just restored a backup. The changes have not been " #. * "synced with %s yet" #: ../src/gtk-ui/sync-ui.c:616 ../src/gtk-ui/sync-ui.c:701 msgid "Sync now" msgstr "Sincronizar" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "A normal sync is not possible at this time..." message. #. * "Other options" will open Emergency view #: ../src/gtk-ui/sync-ui.c:622 ../src/gtk-ui/ui.glade.h:37 msgid "Slow sync" msgstr "Sincronización lenta" #: ../src/gtk-ui/sync-ui.c:623 msgid "Other options..." msgstr "Otras opciones..." #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * when no service is selected. Will open configuration view #: ../src/gtk-ui/sync-ui.c:628 msgid "Select sync service" msgstr "Seleccionar un servicio" #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * login to service fails. Will open configuration view for this service #: ../src/gtk-ui/sync-ui.c:633 msgid "Edit service settings" msgstr "Editar la configuración del servicio" #: ../src/gtk-ui/sync-ui.c:709 msgid "" "You haven't selected a sync service or device yet. Sync services let you " "synchronize your data between your netbook and a web service. You can also " "sync directly with some devices." msgstr "" "Todavía no has seleccionado ningún servicio o dispositivo de sincronización." " Estos servicios te permiten sincronizar los datos entre el miniportátil y " "un servicio web. También puede hacerse directamente con algunos " "dispositivos." #: ../src/gtk-ui/sync-ui.c:729 msgid "Sync again" msgstr "¡Sincronízate!" #: ../src/gtk-ui/sync-ui.c:748 msgid "Restoring" msgstr "Restaurando" #: ../src/gtk-ui/sync-ui.c:750 msgid "Syncing" msgstr "Sincronizando..." #. TRANSLATORS: This is for the button in main view, right side. #. Keep line length below ~20 characters, use two lines if needed #: ../src/gtk-ui/sync-ui.c:762 ../src/gtk-ui/sync-ui.c:3407 msgid "Cancel sync" msgstr "Cancelar la sincronización" #: ../src/gtk-ui/sync-ui.c:927 msgid "Back to sync" msgstr "Volver a la sincronización" #. TRANSLATORS: label for checkbutton/toggle in main view. #. * Please stick to similar length strings or break the line with #. * "\n" if absolutely needed #: ../src/gtk-ui/sync-ui.c:1229 msgid "Automatic sync" msgstr "" "Sincronización\n" "automática" #. This is the expander label in emergency view. It summarizes the #. * currently selected data sources. First placeholder is service/device #. * name, second a comma separeted list of sources. #. * E.g. "Affected data: Google Contacts, Appointments" #: ../src/gtk-ui/sync-ui.c:1524 #, c-format msgid "Affected data: %s %s" msgstr "Datos afectados: %s %s" #: ../src/gtk-ui/sync-ui.c:1529 #, c-format msgid "Affected data: none" msgstr "Datos afectados: ninguno" #. TRANSLATORS: confirmation for restoring a backup. placeholder is the #. * backup time string defined below #: ../src/gtk-ui/sync-ui.c:1607 #, c-format msgid "" "Do you want to restore the backup from %s? All changes you have made since " "then will be lost." msgstr "" "¿Deseas restaurar la copia de seguridad de %s? Todos los cambios que hayas " "realizado desde entonces se perderán." #: ../src/gtk-ui/sync-ui.c:1610 msgid "Yes, restore" msgstr "Sí, restaurar" #. TRANSLATORS: date/time for strftime(), used in emergency view backup #. * label. Any time format that shows date and time is good. #: ../src/gtk-ui/sync-ui.c:1642 #, c-format msgid "%x %X" msgstr "%x %X" #. TRANSLATORS: label for a backup in emergency view. Placeholder is #. * service or device name #: ../src/gtk-ui/sync-ui.c:1661 #, c-format msgid "Backed up before syncing with %s" msgstr "Copia de seguridad realizada ante de sincronizar con %s" #: ../src/gtk-ui/sync-ui.c:1678 msgid "Restore" msgstr "Restaurar" #. TRANSLATORS: this is an explanation in Emergency view. #. * Placeholder is a service/device name #: ../src/gtk-ui/sync-ui.c:1785 #, c-format msgid "" "A normal sync with %s is not possible at this time. You can do a slow two-" "way sync or start from scratch. You can also restore a backup, but a slow " "sync or starting from scratch will still be required before normal sync is " "possible." msgstr "" "Una sincronización normal con %s no es posible en este momento. Puedes " "realizar una sincronización lenta de dos vías o iniciar de cero. También " "puedes restaurar una copia de seguridad, pero una sincronización lenta o " "iniciar de cero igual será necesaria antes de que sea posible una " "sincronización normal." #: ../src/gtk-ui/sync-ui.c:1795 #, c-format msgid "" "If something has gone horribly wrong, you can try a slow sync, start from " "scratch or restore from backup." msgstr "" "Si algo realmente malo sucede, puedes intentar una sincronización lenta, " "empezar de cero o restaurar la copia de seguridad." #. TRANSLATORS: These are a buttons in Emergency view. Placeholder is a #. * service/device name. Please don't use too long lines, but feel free to #. * use several lines. #: ../src/gtk-ui/sync-ui.c:1804 #, c-format msgid "" "Delete all your local\n" "data and replace with\n" "data from %s" msgstr "" "Borrar todos los datos\n" "locales y reemplazarlos con\n" "datos de %s" #: ../src/gtk-ui/sync-ui.c:1810 #, c-format msgid "" "Delete all data on\n" "%s and replace\n" "with your local data" msgstr "" "Borrar todos los datos en\n" "%s y reemplazarlos\n" "con tus datos locales" #: ../src/gtk-ui/sync-ui.c:2275 msgid "Failed to get list of supported services from SyncEvolution" msgstr "Error al obtener la lista de servicios compatibles de SyncEvolution" #: ../src/gtk-ui/sync-ui.c:2329 msgid "" "There was a problem communicating with the sync process. Please try again " "later." msgstr "" "Ha ocurrido un problema de comunicación en el proceso de sincronización. " "Inténtalo más tarde." #: ../src/gtk-ui/sync-ui.c:2388 msgid "Restore failed" msgstr "Restaurar falló" #: ../src/gtk-ui/sync-ui.c:2391 ../src/gtk-ui/sync-ui.c:3276 msgid "Sync failed" msgstr "Sincronización falló" #: ../src/gtk-ui/sync-ui.c:2397 msgid "Restore complete" msgstr "Restaurado completo" #: ../src/gtk-ui/sync-ui.c:2400 msgid "Sync complete" msgstr "¡Sincronizado!" #: ../src/gtk-ui/sync-ui.c:2492 #, c-format msgid "Preparing '%s'" msgstr "Preparando '%s'" #: ../src/gtk-ui/sync-ui.c:2495 #, c-format msgid "Receiving '%s'" msgstr "Recibiendo '%s'" #: ../src/gtk-ui/sync-ui.c:2498 #, c-format msgid "Sending '%s'" msgstr "Enviando '%s'" #: ../src/gtk-ui/sync-ui.c:2619 #, c-format msgid "There was one remote rejection." msgid_plural "There were %ld remote rejections." msgstr[0] "Hubo un rechazo remoto." msgstr[1] "Hubo %ld rechazos remotos." #: ../src/gtk-ui/sync-ui.c:2624 #, c-format msgid "There was one local rejection." msgid_plural "There were %ld local rejections." msgstr[0] "Hubo un rechazo local." msgstr[1] "Hubo %ld rechazos locales." #: ../src/gtk-ui/sync-ui.c:2629 #, c-format msgid "There were %ld local rejections and %ld remote rejections." msgstr "Se han producido %ld rechazos locales y %ld rechazos remotos." #: ../src/gtk-ui/sync-ui.c:2634 #, c-format msgid "Last time: No changes." msgstr "La última vez: no hubo cambios." #: ../src/gtk-ui/sync-ui.c:2636 #, c-format msgid "Last time: Sent one change." msgid_plural "Last time: Sent %ld changes." msgstr[0] "La última vez: se envió un cambio." msgstr[1] "La última vez: se enviaron %ld cambios." #. This is about changes made to the local data. Not all of these #. changes were requested by the remote server, so "applied" #. is a better word than "received" (bug #5185). #: ../src/gtk-ui/sync-ui.c:2644 #, c-format msgid "Last time: Applied one change." msgid_plural "Last time: Applied %ld changes." msgstr[0] "La última vez: se aplicó un cambio." msgstr[1] "La última vez: se aplicaron %ld cambios." #: ../src/gtk-ui/sync-ui.c:2649 #, c-format msgid "Last time: Applied %ld changes and sent %ld changes." msgstr "La última vez: se aplicaron %ld cambios y se enviaron %ld cambios." #. TRANSLATORS: the placeholder is a error message (hopefully) #. * explaining the problem #: ../src/gtk-ui/sync-ui.c:2856 #, c-format msgid "" "There was a problem with last sync:\n" "%s" msgstr "" "Hubo un problema en la última sincronización:\n" "%s" #: ../src/gtk-ui/sync-ui.c:2866 #, c-format msgid "" "You've just restored a backup. The changes have not been synced with %s yet" msgstr "" "Has restaurado una copia de resguardo. Los cambios aún no se sincronizan con" " %s" #: ../src/gtk-ui/sync-ui.c:3154 msgid "Waiting for current operation to finish..." msgstr "Esperando que la operación actual termine..." #. TRANSLATORS: next strings are error messages. #: ../src/gtk-ui/sync-ui.c:3188 msgid "" "A normal sync is not possible at this time. The server suggests a slow sync," " but this might not always be what you want if both ends already have data." msgstr "" "Una sincronización normal no es posible en este momento. El servidor sugiere" " una sincronización lenta, pero esto no es necesariamente lo que deseas si " "ambas partes ya tienen datos." #: ../src/gtk-ui/sync-ui.c:3192 msgid "The sync process died unexpectedly." msgstr "El servicio de sincronización se cerró inesperadamente." #: ../src/gtk-ui/sync-ui.c:3197 msgid "" "Password request was not answered. You can save the password in the settings" " to prevent the request." msgstr "" "No se ha respondido a la solicitud de contraseña. Para evitar que aparezca " "otra vez, se puede guardar la contraseña en los ajustes." #. TODO use the service device name here, this is a remote problem #: ../src/gtk-ui/sync-ui.c:3201 msgid "There was a problem processing sync request. Trying again may help." msgstr "" "Ha ocurrido un problema al procesar la solicitud de sincronización. Intentar" " otra vez puede ayudar." #: ../src/gtk-ui/sync-ui.c:3207 msgid "" "Failed to login. Could there be a problem with your username or password?" msgstr "" "Acceso fallido. ¿Podría haber algún problema con tu usuario o contraseña?" #: ../src/gtk-ui/sync-ui.c:3210 msgid "Forbidden" msgstr "Prohibido" #. TRANSLATORS: data source means e.g. calendar or addressbook #: ../src/gtk-ui/sync-ui.c:3216 msgid "" "A data source could not be found. Could there be a problem with the " "settings?" msgstr "" "No se pudo hallar la fuente de datos. ¿Podría haber algún problema con la " "configuración?" #: ../src/gtk-ui/sync-ui.c:3220 msgid "Remote database error" msgstr "Error en la base de datos remota" #. This can happen when EDS is borked, restart it may help... #: ../src/gtk-ui/sync-ui.c:3223 msgid "" "There is a problem with the local database. Syncing again or rebooting may " "help." msgstr "" "Hay un problema con la base de datos local. Sincronizar otra vez o reiniciar" " puede ayudar." #: ../src/gtk-ui/sync-ui.c:3226 msgid "No space on disk" msgstr "No queda espacio" #: ../src/gtk-ui/sync-ui.c:3228 msgid "Failed to process SyncML" msgstr "Error al procesar SyncML" #: ../src/gtk-ui/sync-ui.c:3230 msgid "Server authorization failed" msgstr "El servidor no ha aceptado la autorización" #: ../src/gtk-ui/sync-ui.c:3232 msgid "Failed to parse configuration file" msgstr "Error al analizar la sintaxis del archivo de configuración" #: ../src/gtk-ui/sync-ui.c:3234 msgid "Failed to read configuration file" msgstr "Error al leer del archivo de configuración" #: ../src/gtk-ui/sync-ui.c:3236 msgid "No configuration found" msgstr "No se ha encontrado la configuración" #: ../src/gtk-ui/sync-ui.c:3238 msgid "No configuration file found" msgstr "No se ha encontrado el archivo de configuración" #: ../src/gtk-ui/sync-ui.c:3240 msgid "Server sent bad content" msgstr "El servidor envió un contenido no válido" #: ../src/gtk-ui/sync-ui.c:3242 msgid "Connection certificate has expired" msgstr "El certificado de conexión está vencido" #: ../src/gtk-ui/sync-ui.c:3244 msgid "Connection certificate is invalid" msgstr "El certificado de conexión no es válido" #: ../src/gtk-ui/sync-ui.c:3252 msgid "" "We were unable to connect to the server. The problem could be temporary or " "there could be something wrong with the settings." msgstr "" "No hemos podido conectarnos con el servidor. El problema podría ser temporal" " o podría haber algún problema con la configuración." #: ../src/gtk-ui/sync-ui.c:3259 msgid "The server URL is bad" msgstr "La URL del servidor es incorrecta" #: ../src/gtk-ui/sync-ui.c:3264 msgid "The server was not found" msgstr "No se ha encontrado el servidor" #: ../src/gtk-ui/sync-ui.c:3266 #, c-format msgid "Error %d" msgstr "Error: %d" #. TRANSLATORS: password request dialog contents: title, cancel button #. * and ok button #: ../src/gtk-ui/sync-ui.c:3404 msgid "Password is required for sync" msgstr "Se requiere contraseña para la sincronización" #: ../src/gtk-ui/sync-ui.c:3408 msgid "Sync with password" msgstr "Sincronización con contraseña" #. TRANSLATORS: password request dialog message, placeholder is service name #: ../src/gtk-ui/sync-ui.c:3418 #, c-format msgid "Please enter password for syncing with %s:" msgstr "Por favor ingresa la contraseña para sincronizar con %s:" #. title for the buttons on the right side of main view #: ../src/gtk-ui/ui.glade.h:2 msgid "Actions" msgstr "Acciones" #. text between the two "start from scratch" buttons in emergency view #: ../src/gtk-ui/ui.glade.h:4 msgid "or" msgstr "o" #: ../src/gtk-ui/ui.glade.h:5 msgid "Direct sync" msgstr "Sincronización directa" #: ../src/gtk-ui/ui.glade.h:6 msgid "Network sync" msgstr "Sincronización de red" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:8 msgid "Restore from backup" msgstr "Restaurar de la copia de seguridad" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:10 msgid "Slow sync" msgstr "Sincronización lenta" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:12 msgid "Start from scratch" msgstr "Comenzar desde cero" #: ../src/gtk-ui/ui.glade.h:13 msgid "" "A slow sync compares items from both sides and tries to merge them. \n" "This may fail in some cases, leading to duplicates or lost information." msgstr "" "Una sincronización lenta compara elementos de ambos lados e intenta combinarlos. \n" "Esto puede fallar en algunos casos, creando duplicados o perdida de información." #: ../src/gtk-ui/ui.glade.h:15 msgid "Add new device" msgstr "Agregar dispositivo" #: ../src/gtk-ui/ui.glade.h:16 msgid "Add new service" msgstr "Agregar servicio" #. explanation of "Restore backup" function #: ../src/gtk-ui/ui.glade.h:18 msgid "" "Backups are made before every time we Sync. Choose a backup to restore. Any " "changes you have made since then will be lost." msgstr "" "Las copias de seguridad son realizadas antes cada vez que sincronizamos. " "Elije una copia de seguridad para restaurar. Cualquier cambio que hayas " "realizado desde entonces se perderá." #: ../src/gtk-ui/ui.glade.h:19 msgid "Calendar" msgstr "Calendario" #. Button in main view, right side. Keep to below 20 chars per line, feel free #. to use two lines #: ../src/gtk-ui/ui.glade.h:21 msgid "" "Change or edit\n" "sync service" msgstr "" "Cambiar o editar\n" "servicio sincronizador" #. close button for settings window #: ../src/gtk-ui/ui.glade.h:24 msgid "Close" msgstr "Cerrar" #: ../src/gtk-ui/ui.glade.h:25 msgid "" "Delete all data on Zyb \n" "and replace with your\n" "local information" msgstr "" "Borrar todos los datos en Zyb \n" "y sustituirlos con tu\n" "información local" #: ../src/gtk-ui/ui.glade.h:28 msgid "" "Delete all your local\n" "information and replace\n" "with data from Zyb" msgstr "" "Borrar todos los datos\n" "locales y reemplazarlos con\n" "datos de Zyb" #. button in main view, right side. Keep length to 20 characters or so, use #. two lines if needed #: ../src/gtk-ui/ui.glade.h:32 msgid "" "Fix a sync\n" "emergency" msgstr "" "Arreglar urgencia\n" "en sincronización" #: ../src/gtk-ui/ui.glade.h:34 msgid "" "If you don't see your service above but know that your sync provider uses SyncML\n" "you can setup a service manually." msgstr "" "Si no puedes ver tú servicio de sincronización arriba, pero sabes que tú proveedor emplea SyncML\n" "puedes configurar el servicio manualmente." #: ../src/gtk-ui/ui.glade.h:36 msgid "Settings" msgstr "Configuración" #: ../src/gtk-ui/ui.glade.h:39 msgid "Sync Emergency" msgstr "Sincronización de emergencia" #: ../src/gtk-ui/ui.glade.h:41 msgid "" "To sync you'll need a network connection and an account with a sync service.\n" "We support the following services: " msgstr "" "Para ejecutar la sincronización hacen falta una conexión de red y una cuenta en un servicio de sincronización. \n" "Son compatibles los siguientes servicios:" #: ../src/gtk-ui/ui.glade.h:43 msgid "Use Bluetooth to Sync your data from one device to another." msgstr "" "Utiliza Bluetooth para sincronizar tus datos a partir de un dispositivo a " "otro. " #: ../src/gtk-ui/ui.glade.h:44 msgid "You will need to add Bluetooth devices before they can be synced." msgstr "" "Necesitarás agregar dispositivos Bluetooth antes de que puedan ser " "sincronizados." #: ../src/gtk-ui/sync.desktop.in.h:2 msgid "Up to date" msgstr "Al día" #: ../src/gtk-ui/sync-gtk.desktop.in.h:1 msgid "SyncEvolution (GTK)" msgstr "SyncEvolution (GTK)" #: ../src/gtk-ui/sync-gtk.desktop.in.h:2 msgid "Synchronize PIM data" msgstr "Sincronizar los datos del gestor de información personal (GIP)" #: ../src/gtk-ui/sync-config-widget.c:88 msgid "" "ScheduleWorld enables you to keep your contacts, events, tasks, and notes in" " sync." msgstr "" "ScheduleWorld te permite sincronizar los contactos, eventos, tareas y notas." #: ../src/gtk-ui/sync-config-widget.c:91 msgid "" "Google Sync can back up and synchronize your contacts with your Gmail " "contacts." msgstr "" "Google Sync puede hacer copia de resguardo y sincroniza tus contactos y con " "los que tengas en Gmail." #. TRANSLATORS: Please include the word "demo" (or the equivalent in #. your language): Funambol is going to be a 90 day demo service #. in the future #: ../src/gtk-ui/sync-config-widget.c:97 msgid "" "Back up your contacts and calendar. Sync with a single click, anytime, " "anywhere (DEMO)." msgstr "" "Resguarda tus contactos y el calendario. Sincronízalos con un clic, en " "cualquier momento y desde donde sea (DEMO)." #: ../src/gtk-ui/sync-config-widget.c:100 msgid "" "Mobical Backup and Restore service allows you to securely back up your " "personal mobile data for free." msgstr "" "El servicio Mobical Backup and Restore te permite realizar copias de " "resguardo seguras de la información de tu móvil gratis." #: ../src/gtk-ui/sync-config-widget.c:103 msgid "" "ZYB is a simple way for people to store and share mobile information online." msgstr "" "ZYB es una manera simple para que la gente almacene y comparta la " "información móvil en línea." #: ../src/gtk-ui/sync-config-widget.c:106 msgid "" "Memotoo lets you access your personal data from any computer connected to " "the Internet." msgstr "" "Memotoo te permite acceder a tus datos personales desde cualquier " "computadora conectada con Internet." #: ../src/gtk-ui/sync-config-widget.c:195 msgid "Sorry, failed to save the configuration" msgstr "Lo sentimos, no se pudo guardar la configuración" #: ../src/gtk-ui/sync-config-widget.c:445 msgid "Service must have a name and server URL" msgstr "El servicio debe llamarse de alguna manera y tener una URL" #. TRANSLATORS: error dialog when creating a new sync configuration #: ../src/gtk-ui/sync-config-widget.c:451 msgid "A username is required for this service" msgstr "Este servicio requiere un nombre de usuario" #: ../src/gtk-ui/sync-config-widget.c:493 #, c-format msgid "" "Do you want to reset the settings for %s? This will not remove any synced " "information on either end." msgstr "" "¿Deseas reajustar la configuración para %s? Esto no removerá ninguna " "información sincronizada en las partes." #. TRANSLATORS: buttons in reset-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:497 msgid "Yes, reset" msgstr "Sí, reiniciar" #: ../src/gtk-ui/sync-config-widget.c:498 #: ../src/gtk-ui/sync-config-widget.c:509 msgid "No, keep settings" msgstr "No, mantener configuración" #: ../src/gtk-ui/sync-config-widget.c:503 #, c-format msgid "" "Do you want to delete the settings for %s? This will not remove any synced " "information on either end but it will remove these settings." msgstr "" "¿Quieres eliminar la configuración de %s? Esto no eliminará ninguna " "información sincronizada en ninguno de los dos lados, pero eliminará esta " "configuración. " #. TRANSLATORS: buttons in delete-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:508 msgid "Yes, delete" msgstr "Sí, borrar" #: ../src/gtk-ui/sync-config-widget.c:539 msgid "Reset settings" msgstr "Devolver la configuración a sus valores originales" #: ../src/gtk-ui/sync-config-widget.c:542 msgid "Delete settings" msgstr "Borrar la configuración" #: ../src/gtk-ui/sync-config-widget.c:552 msgid "Save and use" msgstr "Guardar y usar" #: ../src/gtk-ui/sync-config-widget.c:555 msgid "" "Save and replace\n" "current service" msgstr "" "Guardar y reemplazar\n" "servicio actual" #: ../src/gtk-ui/sync-config-widget.c:563 msgid "Stop using device" msgstr "Dejar de usar este dispositivo" #: ../src/gtk-ui/sync-config-widget.c:566 msgid "Stop using service" msgstr "Dejar de usar este servicio" #. TRANSLATORS: label for an entry in service configuration form. #. * Placeholder is a source name. #. * Example: "Appointments URI" #: ../src/gtk-ui/sync-config-widget.c:749 #, c-format msgid "%s URI" msgstr "URI de %s" #. TRANSLATORS: toggles in service configuration form, placeholder is service #. * or device name #: ../src/gtk-ui/sync-config-widget.c:936 #, c-format msgid "Send changes to %s" msgstr "Enviar cambios a %s" #: ../src/gtk-ui/sync-config-widget.c:941 #, c-format msgid "Receive changes from %s" msgstr "Recibir cambio de %s" #: ../src/gtk-ui/sync-config-widget.c:957 msgid "Sync" msgstr "Sincronizar" #. TRANSLATORS: label of a entry in service configuration #: ../src/gtk-ui/sync-config-widget.c:973 msgid "Server address" msgstr "Dirección del servidor" #. TRANSLATORS: explanation before a device template combobox. #. * Placeholder is a device name like 'Nokia N85' or 'Syncevolution #. * Client' #: ../src/gtk-ui/sync-config-widget.c:1049 #, c-format msgid "" "This device looks like it might be a '%s'. If this is not correct, please " "take a look at the list of supported devices and pick yours if it is listed" msgstr "" "Parece que este dispositivo es: %s'. Si no es cierto, mira la lista de los " "dispositivos compatibles y selecciona el correcto, si aparece." #: ../src/gtk-ui/sync-config-widget.c:1055 #: ../src/gtk-ui/sync-config-widget.c:1915 msgid "" "We don't know what this device is exactly. Please take a look at the list of" " supported devices and pick yours if it is listed" msgstr "" "No sabemos qué dispositivo es éste. Mira la lista de los dispositivos " "compatibles y selecciona el correcto, si aparece." #: ../src/gtk-ui/sync-config-widget.c:1207 #, c-format msgid "%s - Bluetooth device" msgstr "%s - Dispositivo Bluetooth" #. TRANSLATORS: service title for services that are not based on a #. * template in service list, the placeholder is the name of the service #: ../src/gtk-ui/sync-config-widget.c:1213 #, c-format msgid "%s - manually setup" msgstr "%s - configurar manualmente" #. TRANSLATORS: link button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1886 msgid "Launch website" msgstr "Abrir el sitio web" #. TRANSLATORS: button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1895 msgid "Set up now" msgstr "Configurar ahora" #: ../src/gtk-ui/sync-config-widget.c:1953 msgid "Use these settings" msgstr "Usar estos ajustes" #. TRANSLATORS: labels of entries in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1991 msgid "Username" msgstr "Usuario" #: ../src/gtk-ui/sync-config-widget.c:2006 msgid "Password" msgstr "Contraseña" #. TRANSLATORS: warning in service configuration form for people #. who have modified the configuration via other means. #: ../src/gtk-ui/sync-config-widget.c:2029 msgid "" "Current configuration is more complex than what can be shown here. Changes " "to sync mode or synced data types will overwrite that configuration." msgstr "" "La configuración es más compleja de lo que podemos mostrar aquí. Cambios en " "el modo de sincronización o los tipos de datos sincronizados sobrescribirán " "esa configuración." #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2048 msgid "Hide server settings" msgstr "Ocultar la configuración del servidor" #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2068 msgid "Show server settings" msgstr "Mostrar la configuración del servidor" #: ../src/gnome-bluetooth/syncevolution.c:110 msgid "Sync in the Sync application" msgstr "Sincronizar en la aplicación de sincronización" #: ../src/syncevo-dbus-server.cpp:6653 #, c-format msgid "%s is syncing" msgstr "%s está sincronizándose" #: ../src/syncevo-dbus-server.cpp:6654 #, c-format msgid "We have just started to sync your computer with the %s sync service." msgstr "" "Hemos empezado a sincronizar esta máquina con el servicio de sincronización " "%s." #. if sync is successfully started and done #: ../src/syncevo-dbus-server.cpp:6670 #, c-format msgid "%s sync complete" msgstr "Se ha completado la sincronización de %s" #: ../src/syncevo-dbus-server.cpp:6671 #, c-format msgid "We have just finished syncing your computer with the %s sync service." msgstr "" "Hemos terminado de sincronizar esta máquina con el servicio de " "sincronización %s." #. if sync is successfully started and has errors, or not started successful #. with a fatal problem #: ../src/syncevo-dbus-server.cpp:6676 msgid "Sync problem." msgstr "Problemas en la sincronización." #: ../src/syncevo-dbus-server.cpp:6677 msgid "Sorry, there's a problem with your sync that you need to attend to." msgstr "" "Ha ocurrido un problema durante la sincronización que debes solucionar." #: ../src/syncevo-dbus-server.cpp:6758 msgid "View" msgstr "Ver" #. Use "default" as ID because that is what mutter-moblin #. recognizes: it then skips the action instead of adding it #. in addition to its own "Dismiss" button (always added). #: ../src/syncevo-dbus-server.cpp:6762 msgid "Dismiss" msgstr "Cerrar" syncevolution_1.4/po/fi.po000066400000000000000000000733351230021373600157450ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # # Translators: # Eija Lapinleimu , 2011. # , 2011. msgid "" msgstr "" "Project-Id-Version: syncevolution\n" "Report-Msgid-Bugs-To: https://bugs.meego.com/\n" "POT-Creation-Date: 2011-12-05 10:21-0800\n" "PO-Revision-Date: 2011-12-06 18:07+0000\n" "Last-Translator: GLS_Translator_FIN1 \n" "Language-Team: Finnish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: fi\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" #. TRANSLATORS: this is the application name that may be used by e.g. #. the windowmanager #: ../src/gtk-ui/main.c:40 ../src/gtk-ui/ui.glade.h:38 #: ../src/gtk-ui/sync.desktop.in.h:1 #: ../src/gnome-bluetooth/syncevolution.c:112 msgid "Sync" msgstr "Synkkaus" #: ../src/gtk-ui/sync-ui.c:266 msgid "Contacts" msgstr "Yhteyshenkilöt" #: ../src/gtk-ui/sync-ui.c:268 msgid "Appointments" msgstr "Tapaamiset" #: ../src/gtk-ui/sync-ui.c:270 ../src/gtk-ui/ui.glade.h:40 msgid "Tasks" msgstr "Tehtävät" #: ../src/gtk-ui/sync-ui.c:272 msgid "Notes" msgstr "Huomautukset" #. TRANSLATORS: This is a "combination source" for syncing with devices #. * that combine appointments and tasks. the name should match the ones #. * used for calendar and todo above #: ../src/gtk-ui/sync-ui.c:277 msgid "Appointments & Tasks" msgstr "Tapaamiset ja tehtävät" #: ../src/gtk-ui/sync-ui.c:349 msgid "Starting sync" msgstr "Aloitetaan synkkaus" #. TRANSLATORS: slow sync confirmation dialog message. Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:387 #, c-format msgid "Do you want to slow sync with %s?" msgstr "Haluatko hitaan synkkauksen kohteen %s kanssa?" #: ../src/gtk-ui/sync-ui.c:391 msgid "Yes, do slow sync" msgstr "Kyllä, tehdään hidas synkkaus" #: ../src/gtk-ui/sync-ui.c:391 msgid "No, cancel sync" msgstr "Ei, peru synkkaus" #. TRANSLATORS: confirmation dialog for "refresh from peer". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:424 #, c-format msgid "" "Do you want to delete all local data and replace it with data from %s? This " "is not usually advised." msgstr "" "Haluatko poistaa kaikki paikalliset tiedot ja korvata ne tiedoilla kohteesta" " %s? Tämä ei ole yleensä suositeltavaa." #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 msgid "Yes, delete and replace" msgstr "Kyllä, poista ja korvaa" #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 #: ../src/gtk-ui/sync-ui.c:1610 msgid "No" msgstr "Ei" #. TRANSLATORS: confirmation dialog for "refresh from local side". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:457 #, c-format msgid "" "Do you want to delete all data in %s and replace it with your local data? " "This is not usually advised." msgstr "" "Haluatko poistaa kaikki tiedot kohteessa %s ja korvata ne paikallisilla " "tiedoillasi? Tämä ei ole yleensä suositeltavaa." #: ../src/gtk-ui/sync-ui.c:491 msgid "Trying to cancel sync" msgstr "Yritetään perua synkkaus" #: ../src/gtk-ui/sync-ui.c:533 msgid "No service or device selected" msgstr "Palvelua tai laitetta ei ole valittuna" #. TRANSLATORS: This is the title on main view. Placeholder is #. * the service name. Example: "Google - synced just now" #: ../src/gtk-ui/sync-ui.c:541 #, c-format msgid "%s - synced just now" msgstr "%s - synkataan juuri nyt" #: ../src/gtk-ui/sync-ui.c:545 #, c-format msgid "%s - synced a minute ago" msgstr "%s - Synkattu viimeksi minuutti sitten" #: ../src/gtk-ui/sync-ui.c:549 #, c-format msgid "%s - synced %ld minutes ago" msgstr "%s - Synkattu viimeksi %ld minuuttia sitten" #: ../src/gtk-ui/sync-ui.c:554 #, c-format msgid "%s - synced an hour ago" msgstr "%s - Synkattu viimeksi tunti sitten" #: ../src/gtk-ui/sync-ui.c:558 #, c-format msgid "%s - synced %ld hours ago" msgstr "%s - Synkattu viimeksi %ld tuntia sitten" #: ../src/gtk-ui/sync-ui.c:563 #, c-format msgid "%s - synced a day ago" msgstr "%s - Synkattu viimeksi eilen" #: ../src/gtk-ui/sync-ui.c:567 #, c-format msgid "%s - synced %ld days ago" msgstr "%s - Synkattu viimeksi %ld päivää sitten" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "You've just restored a backup. The changes have not been " #. * "synced with %s yet" #: ../src/gtk-ui/sync-ui.c:616 ../src/gtk-ui/sync-ui.c:701 msgid "Sync now" msgstr "Synkkaa nyt" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "A normal sync is not possible at this time..." message. #. * "Other options" will open Emergency view #: ../src/gtk-ui/sync-ui.c:622 ../src/gtk-ui/ui.glade.h:37 msgid "Slow sync" msgstr "" "Hidas\n" "synkkaus" #: ../src/gtk-ui/sync-ui.c:623 msgid "Other options..." msgstr "Muut asetukset..." #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * when no service is selected. Will open configuration view #: ../src/gtk-ui/sync-ui.c:628 msgid "Select sync service" msgstr "Määritä synkkauspalvelu" #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * login to service fails. Will open configuration view for this service #: ../src/gtk-ui/sync-ui.c:633 msgid "Edit service settings" msgstr "Muuta palveluasetuksia" #: ../src/gtk-ui/sync-ui.c:709 msgid "" "You haven't selected a sync service or device yet. Sync services let you " "synchronize your data between your netbook and a web service. You can also " "sync directly with some devices." msgstr "" "Synkkauspalvelua tai laitetta ei ole vielä valittu. Synkkauspalvelu " "mahdollistaa Netbookisi ja verkkopalvelusi tietojen synkkauksen. Voit myös " "synkata suoraan joitakin laitteita." #: ../src/gtk-ui/sync-ui.c:729 msgid "Sync again" msgstr "Synkkaa taas" #: ../src/gtk-ui/sync-ui.c:748 msgid "Restoring" msgstr "Palautetaan" #: ../src/gtk-ui/sync-ui.c:750 msgid "Syncing" msgstr "Synkkaus käynnissä" #. TRANSLATORS: This is for the button in main view, right side. #. Keep line length below ~20 characters, use two lines if needed #: ../src/gtk-ui/sync-ui.c:762 ../src/gtk-ui/sync-ui.c:3407 msgid "Cancel sync" msgstr "Peru synkkaus" #: ../src/gtk-ui/sync-ui.c:927 msgid "Back to sync" msgstr "Takaisin synkkaukseen" #. TRANSLATORS: label for checkbutton/toggle in main view. #. * Please stick to similar length strings or break the line with #. * "\n" if absolutely needed #: ../src/gtk-ui/sync-ui.c:1229 msgid "Automatic sync" msgstr "Automaattinen synkkaus" #. This is the expander label in emergency view. It summarizes the #. * currently selected data sources. First placeholder is service/device #. * name, second a comma separeted list of sources. #. * E.g. "Affected data: Google Contacts, Appointments" #: ../src/gtk-ui/sync-ui.c:1524 #, c-format msgid "Affected data: %s %s" msgstr "Koskee tietoja: %s %s" #: ../src/gtk-ui/sync-ui.c:1529 #, c-format msgid "Affected data: none" msgstr "Koskee tietoja: ei mitään" #. TRANSLATORS: confirmation for restoring a backup. placeholder is the #. * backup time string defined below #: ../src/gtk-ui/sync-ui.c:1607 #, c-format msgid "" "Do you want to restore the backup from %s? All changes you have made since " "then will be lost." msgstr "" "Haluatko palauttaa varmuuskopioinnin kohteesta %s? Menetät kaikki tämän " "jälkeen tekemäsi muutokset." #: ../src/gtk-ui/sync-ui.c:1610 msgid "Yes, restore" msgstr "Kyllä, palauta" #. TRANSLATORS: date/time for strftime(), used in emergency view backup #. * label. Any time format that shows date and time is good. #: ../src/gtk-ui/sync-ui.c:1642 #, c-format msgid "%x %X" msgstr "%x %X" #. TRANSLATORS: label for a backup in emergency view. Placeholder is #. * service or device name #: ../src/gtk-ui/sync-ui.c:1661 #, c-format msgid "Backed up before syncing with %s" msgstr "Varmuuskopioitu ennen sunkkausta kohteen %s kanssa" #: ../src/gtk-ui/sync-ui.c:1678 msgid "Restore" msgstr "Palauttaa" #. TRANSLATORS: this is an explanation in Emergency view. #. * Placeholder is a service/device name #: ../src/gtk-ui/sync-ui.c:1785 #, c-format msgid "" "A normal sync with %s is not possible at this time. You can do a slow two-" "way sync or start from scratch. You can also restore a backup, but a slow " "sync or starting from scratch will still be required before normal sync is " "possible." msgstr "" "Normaali synkkaus kohteen %s ei ole mahdollinen täällä kertaa. Voit tehdä " "hitaan kaksisuuntaisen synkkauksen tai aloittaa nollasta. Voit myös " "palauttaa varmuuskopion, mutta joko hidas synkkaus tai aloittaminen nollasta" " vaaditaan, ennen kuin normaali synkkaus on mahdollinen." #: ../src/gtk-ui/sync-ui.c:1795 #, c-format msgid "" "If something has gone horribly wrong, you can try a slow sync, start from " "scratch or restore from backup." msgstr "" "Jos jotakin on mennyt järkyttävän pieleen, voit yrittää hidasta synkkausta, " "aloittamista nollasta tai palauttaa varmuuskopiosta." #. TRANSLATORS: These are a buttons in Emergency view. Placeholder is a #. * service/device name. Please don't use too long lines, but feel free to #. * use several lines. #: ../src/gtk-ui/sync-ui.c:1804 #, c-format msgid "" "Delete all your local\n" "data and replace with\n" "data from %s" msgstr "" "Poista kaikki paikalliset\n" " tiedot ja korvaa ne \n" "tiedoilla kohteesta %s" #: ../src/gtk-ui/sync-ui.c:1810 #, c-format msgid "" "Delete all data on\n" "%s and replace\n" "with your local data" msgstr "" "Poista kaikki tiedot kohteesta\n" "%s ja korvaa \n" "paikallisilla tiedoilla" #: ../src/gtk-ui/sync-ui.c:2275 msgid "Failed to get list of supported services from SyncEvolution" msgstr "Tuettujen palveluiden listan saaminen SyncEvolutionista epäonnistui" #: ../src/gtk-ui/sync-ui.c:2329 msgid "" "There was a problem communicating with the sync process. Please try again " "later." msgstr "" "Ilmeni onglema kommunikoitaessa synkkausprosessin kanssa. Yritä myöhemmin " "uudelleen." #: ../src/gtk-ui/sync-ui.c:2388 msgid "Restore failed" msgstr "Palauttaminen epäonnistui" #: ../src/gtk-ui/sync-ui.c:2391 ../src/gtk-ui/sync-ui.c:3276 msgid "Sync failed" msgstr "Synkkaus epäonnistui" #: ../src/gtk-ui/sync-ui.c:2397 msgid "Restore complete" msgstr "Palautus valmis" #: ../src/gtk-ui/sync-ui.c:2400 msgid "Sync complete" msgstr "Synkkaus valmis" #: ../src/gtk-ui/sync-ui.c:2492 #, c-format msgid "Preparing '%s'" msgstr "Valmistellaan: '%s'" #: ../src/gtk-ui/sync-ui.c:2495 #, c-format msgid "Receiving '%s'" msgstr "Vastaanotetaan: '%s'" #: ../src/gtk-ui/sync-ui.c:2498 #, c-format msgid "Sending '%s'" msgstr "Lähetetään: '%s'" #: ../src/gtk-ui/sync-ui.c:2619 #, c-format msgid "There was one remote rejection." msgid_plural "There were %ld remote rejections." msgstr[0] "Tapahtui yksi etähylkäys" msgstr[1] "Tapahtui %ld etähylkäystä" #: ../src/gtk-ui/sync-ui.c:2624 #, c-format msgid "There was one local rejection." msgid_plural "There were %ld local rejections." msgstr[0] "Tapahtui yksi paikallinen hylkäys" msgstr[1] "Tapahtui %ld paikallista hylkäystä" #: ../src/gtk-ui/sync-ui.c:2629 #, c-format msgid "There were %ld local rejections and %ld remote rejections." msgstr "Tapahtui %ld paikallista hylkäystä ja %ld etähylkäystä" #: ../src/gtk-ui/sync-ui.c:2634 #, c-format msgid "Last time: No changes." msgstr "Viime kerta: ei muutoksia" #: ../src/gtk-ui/sync-ui.c:2636 #, c-format msgid "Last time: Sent one change." msgid_plural "Last time: Sent %ld changes." msgstr[0] "Viime kerralla: yksi muutos lähetetty." msgstr[1] "Viime kerralla: %ld muutosta lähetetty." #. This is about changes made to the local data. Not all of these #. changes were requested by the remote server, so "applied" #. is a better word than "received" (bug #5185). #: ../src/gtk-ui/sync-ui.c:2644 #, c-format msgid "Last time: Applied one change." msgid_plural "Last time: Applied %ld changes." msgstr[0] "Viime kerralla: yksi muutos sovellettu." msgstr[1] "Viime kerralla: %ld muutosta sovellettu." #: ../src/gtk-ui/sync-ui.c:2649 #, c-format msgid "Last time: Applied %ld changes and sent %ld changes." msgstr "Viime kerralla: Sovellettu %ld muutosta ja lähetetty %ld muutosta." #. TRANSLATORS: the placeholder is a error message (hopefully) #. * explaining the problem #: ../src/gtk-ui/sync-ui.c:2856 #, c-format msgid "" "There was a problem with last sync:\n" "%s" msgstr "" "Ilmeni onglema viimeisen synkkauksen kanssa :\n" "%s" #: ../src/gtk-ui/sync-ui.c:2866 #, c-format msgid "" "You've just restored a backup. The changes have not been synced with %s yet" msgstr "" "Olet juuri palauttanut varmuuskopion. Muutoksia ei ole vielä synkattu " "kohteen %s kanssa." #: ../src/gtk-ui/sync-ui.c:3154 msgid "Waiting for current operation to finish..." msgstr "Nykyisen toiminnon päättymistä odotetaan..." #. TRANSLATORS: next strings are error messages. #: ../src/gtk-ui/sync-ui.c:3188 msgid "" "A normal sync is not possible at this time. The server suggests a slow sync," " but this might not always be what you want if both ends already have data." msgstr "" "Normaali synkkaus ei ole mahdollinen tällä kertaa. Palvelin suosittelee " "hidasta synkkausta, mutta tämä ei aina ehkä ole sitä, mitä haluat, jos " "molemmissa päissä on jo tietoja." #: ../src/gtk-ui/sync-ui.c:3192 msgid "The sync process died unexpectedly." msgstr "Synkkauspalvelu loppui odottamattomasti" #: ../src/gtk-ui/sync-ui.c:3197 msgid "" "Password request was not answered. You can save the password in the settings" " to prevent the request." msgstr "" "Salasanapyyntöön ei vastattu. Voit tallentaa salasanan asetuksissa " "estääksesi pyynnön." #. TODO use the service device name here, this is a remote problem #: ../src/gtk-ui/sync-ui.c:3201 msgid "There was a problem processing sync request. Trying again may help." msgstr "" "Synkkauspyynnön prosessoimisessa ilmeni ongelma. Voi auttaa, jos yrität " "uudelleen." #: ../src/gtk-ui/sync-ui.c:3207 msgid "" "Failed to login. Could there be a problem with your username or password?" msgstr "" "Sisäänkirjautuminen ei onnistunut. Voisiko käyttäjänimen tai salasanan " "kanssa olla ongelma?" #: ../src/gtk-ui/sync-ui.c:3210 msgid "Forbidden" msgstr "Kielletty" #. TRANSLATORS: data source means e.g. calendar or addressbook #: ../src/gtk-ui/sync-ui.c:3216 msgid "" "A data source could not be found. Could there be a problem with the " "settings?" msgstr "Tietolähdettä ei löydy. Voisiko asetusten kanssa olla ongelma?" #: ../src/gtk-ui/sync-ui.c:3220 msgid "Remote database error" msgstr "Etätietokantavirhe" #. This can happen when EDS is borked, restart it may help... #: ../src/gtk-ui/sync-ui.c:3223 msgid "" "There is a problem with the local database. Syncing again or rebooting may " "help." msgstr "" "Paikallisen tietokannan kanssa on ongelmia. Voi auttaa, jos synkkaat " "uudelleen tai buuttaat." #: ../src/gtk-ui/sync-ui.c:3226 msgid "No space on disk" msgstr "Levykkeen tila lopussa" #: ../src/gtk-ui/sync-ui.c:3228 msgid "Failed to process SyncML" msgstr "SyncML:n käsittely epäonnistui" #: ../src/gtk-ui/sync-ui.c:3230 msgid "Server authorization failed" msgstr "Serverin varmennus epäonnistui" #: ../src/gtk-ui/sync-ui.c:3232 msgid "Failed to parse configuration file" msgstr "Asetustiedoston jäsennys epäonnistui" #: ../src/gtk-ui/sync-ui.c:3234 msgid "Failed to read configuration file" msgstr "Asetustiedoston lukeminen epäonnistui" #: ../src/gtk-ui/sync-ui.c:3236 msgid "No configuration found" msgstr "Asetuksia ei löydy" #: ../src/gtk-ui/sync-ui.c:3238 msgid "No configuration file found" msgstr "Asetustiedostoa ei löytynyt" #: ../src/gtk-ui/sync-ui.c:3240 msgid "Server sent bad content" msgstr "Serveri lähetti virheellistä sisältöä" #: ../src/gtk-ui/sync-ui.c:3242 msgid "Connection certificate has expired" msgstr "Yhteyden varmenne on vanhentunut" #: ../src/gtk-ui/sync-ui.c:3244 msgid "Connection certificate is invalid" msgstr "Yhteyden varmenne on virheellinen" #: ../src/gtk-ui/sync-ui.c:3252 msgid "" "We were unable to connect to the server. The problem could be temporary or " "there could be something wrong with the settings." msgstr "" "Palvelimeen ei saada yhteyttä. Ongelma voi olla tilapäinen tai palvelimen " "asetuksissa voi olla virhe. " #: ../src/gtk-ui/sync-ui.c:3259 msgid "The server URL is bad" msgstr "Serverin URL on virheellinen" #: ../src/gtk-ui/sync-ui.c:3264 msgid "The server was not found" msgstr "Serveriä ei löydy" #: ../src/gtk-ui/sync-ui.c:3266 #, c-format msgid "Error %d" msgstr "Virhe: %d" #. TRANSLATORS: password request dialog contents: title, cancel button #. * and ok button #: ../src/gtk-ui/sync-ui.c:3404 msgid "Password is required for sync" msgstr "Synkkaukseen vaaditaan salasana." #: ../src/gtk-ui/sync-ui.c:3408 msgid "Sync with password" msgstr "Synkkaa salasanan kanssa" #. TRANSLATORS: password request dialog message, placeholder is service name #: ../src/gtk-ui/sync-ui.c:3418 #, c-format msgid "Please enter password for syncing with %s:" msgstr "Anna salasana synkkaukseen kohteen %s kanssa:" #. title for the buttons on the right side of main view #: ../src/gtk-ui/ui.glade.h:2 msgid "Actions" msgstr "Toiminnot" #. text between the two "start from scratch" buttons in emergency view #: ../src/gtk-ui/ui.glade.h:4 msgid "or" msgstr "tai" #: ../src/gtk-ui/ui.glade.h:5 msgid "Direct sync" msgstr "Suora synkkaus" #: ../src/gtk-ui/ui.glade.h:6 msgid "Network sync" msgstr "Verkon synkkaus" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:8 msgid "Restore from backup" msgstr "Palauta varmuuskopiosta" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:10 msgid "Slow sync" msgstr "Hidas synkkaus" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:12 msgid "Start from scratch" msgstr "Aloita nollasta" #: ../src/gtk-ui/ui.glade.h:13 msgid "" "A slow sync compares items from both sides and tries to merge them. \n" "This may fail in some cases, leading to duplicates or lost information." msgstr "" "Hidas synkkaus vertailee kohteita molemmista päistä ja yrittää yhdistää ne. \n" "Tämä saattaa joissakin tapauksissa epäonnistua, johtaa kaksoiskappeleiden muodostumiseen tai tietojen menettämiseen." #: ../src/gtk-ui/ui.glade.h:15 msgid "Add new device" msgstr "Lisää uusi laite" #: ../src/gtk-ui/ui.glade.h:16 msgid "Add new service" msgstr "Lisää uusi palvelu" #. explanation of "Restore backup" function #: ../src/gtk-ui/ui.glade.h:18 msgid "" "Backups are made before every time we Sync. Choose a backup to restore. Any " "changes you have made since then will be lost." msgstr "" "Varmuuskopiot tehdään ennen jokaista synkkausta. Valitse varmuuskopio " "palautettavaksi. Kaikki tämän jälkeen tekemäsi muutokset menetetään. " #: ../src/gtk-ui/ui.glade.h:19 msgid "Calendar" msgstr "Kalenteri" #. Button in main view, right side. Keep to below 20 chars per line, feel free #. to use two lines #: ../src/gtk-ui/ui.glade.h:21 msgid "" "Change or edit\n" "sync service" msgstr "" "Muuta tai muokkaa\n" "synkkauspalvelua" #. close button for settings window #: ../src/gtk-ui/ui.glade.h:24 msgid "Close" msgstr "Sulje" #: ../src/gtk-ui/ui.glade.h:25 msgid "" "Delete all data on Zyb \n" "and replace with your\n" "local information" msgstr "" "Poista kaikki tiedot kohteessa Zyb \n" "ja korvaa ne omilla\n" "paikallisilla tiedoilla" #: ../src/gtk-ui/ui.glade.h:28 msgid "" "Delete all your local\n" "information and replace\n" "with data from Zyb" msgstr "" "Poista kaikki paikalliset\n" "tiedot ja korvaa\n" "ne Zybin tiedoilla" #. button in main view, right side. Keep length to 20 characters or so, use #. two lines if needed #: ../src/gtk-ui/ui.glade.h:32 msgid "" "Fix a sync\n" "emergency" msgstr "" "Korjaa sync\n" "hätätila" #: ../src/gtk-ui/ui.glade.h:34 msgid "" "If you don't see your service above but know that your sync provider uses SyncML\n" "you can setup a service manually." msgstr "" "Jos et näe palveluasi yllä mutta tiedät, että synkkauspalvelun tarjoajasi käyttää\n" "SyncML:ää, voit määrittää palvelun manuaalisesti." #: ../src/gtk-ui/ui.glade.h:36 msgid "Settings" msgstr "Asetukset" #: ../src/gtk-ui/ui.glade.h:39 msgid "Sync Emergency" msgstr "Synkkauksen hätätila" #: ../src/gtk-ui/ui.glade.h:41 msgid "" "To sync you'll need a network connection and an account with a sync service.\n" "We support the following services: " msgstr "" "Synkkaus edellyttää verkkoyhteyttä ja synkkauspalvelutiliä.\n" "Me tuemme seuraavia palveluita:" #: ../src/gtk-ui/ui.glade.h:43 msgid "Use Bluetooth to Sync your data from one device to another." msgstr "Käytä Bluetoothia ja synkkaa tiedot yhdeltä laitteelta toiselle." #: ../src/gtk-ui/ui.glade.h:44 msgid "You will need to add Bluetooth devices before they can be synced." msgstr "Sinun täytyy lisätä Bluetooth-laitteet ennen kuin niitä voi synkata." #: ../src/gtk-ui/sync.desktop.in.h:2 msgid "Up to date" msgstr "Ajantasalla" #: ../src/gtk-ui/sync-gtk.desktop.in.h:1 msgid "SyncEvolution (GTK)" msgstr "SyncEvolution (GTK)" #: ../src/gtk-ui/sync-gtk.desktop.in.h:2 msgid "Synchronize PIM data" msgstr "Synkronoi PIM-tiedot" #: ../src/gtk-ui/sync-config-widget.c:88 msgid "" "ScheduleWorld enables you to keep your contacts, events, tasks, and notes in" " sync." msgstr "" "ScheduleWorld mahdollistaa kontaktien,tapahtumien,tehtävien, ja viestien " "synkkaamisen. " #: ../src/gtk-ui/sync-config-widget.c:91 msgid "" "Google Sync can back up and synchronize your contacts with your Gmail " "contacts." msgstr "Google Sync voi kopioida ja synkata kontaktisi Gmail-kontakteihisi" #. TRANSLATORS: Please include the word "demo" (or the equivalent in #. your language): Funambol is going to be a 90 day demo service #. in the future #: ../src/gtk-ui/sync-config-widget.c:97 msgid "" "Back up your contacts and calendar. Sync with a single click, anytime, " "anywhere (DEMO)." msgstr "" "Varmuuskopioi kontaktisi ja kalenterisi. Synkkaa yhdellä klikkauksella, " "milloin ja missä tahansa (DEMO)." #: ../src/gtk-ui/sync-config-widget.c:100 msgid "" "Mobical Backup and Restore service allows you to securely back up your " "personal mobile data for free." msgstr "" "Mobical-varmuuskopio- ja palautuspalvelun avulla voit varmuuskopioida omat " "mobiilitietosi ilmaiseksi ja varmasti." #: ../src/gtk-ui/sync-config-widget.c:103 msgid "" "ZYB is a simple way for people to store and share mobile information online." msgstr "" "ZYB on yksinkertainen tapa tallentaa ja jakaa mobiilitietoja verkossa." #: ../src/gtk-ui/sync-config-widget.c:106 msgid "" "Memotoo lets you access your personal data from any computer connected to " "the Internet." msgstr "" "Mometoon avulla voi käyttää omia tietoja kaikilta koneilta, joilla on " "Internet-yhteys. " #: ../src/gtk-ui/sync-config-widget.c:195 msgid "Sorry, failed to save the configuration" msgstr "Konfiguraation tallennus epäonnistui" #: ../src/gtk-ui/sync-config-widget.c:445 msgid "Service must have a name and server URL" msgstr "Palvelulla pitää olla nimi ja palvelimen URL" #. TRANSLATORS: error dialog when creating a new sync configuration #: ../src/gtk-ui/sync-config-widget.c:451 msgid "A username is required for this service" msgstr "Tähän palveluun vaaditaan käyttäjätunnus" #: ../src/gtk-ui/sync-config-widget.c:493 #, c-format msgid "" "Do you want to reset the settings for %s? This will not remove any synced " "information on either end." msgstr "" "Haluatko nollata kohteen %s asetukset? Tämä ei poista mitään synkattuja " "tietoja kummassakaan päässä." #. TRANSLATORS: buttons in reset-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:497 msgid "Yes, reset" msgstr "Kyllä, nollaa." #: ../src/gtk-ui/sync-config-widget.c:498 #: ../src/gtk-ui/sync-config-widget.c:509 msgid "No, keep settings" msgstr "Ei, säilytä asetukset" #: ../src/gtk-ui/sync-config-widget.c:503 #, c-format msgid "" "Do you want to delete the settings for %s? This will not remove any synced " "information on either end but it will remove these settings." msgstr "" "Haluatko poistaa kohteen %s asetukset? Tämä ei poista mitään synkattuja " "tietoja kummassakaan päässä, mutta poistaa nämä asetukset. " #. TRANSLATORS: buttons in delete-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:508 msgid "Yes, delete" msgstr "Kyllä, poista" #: ../src/gtk-ui/sync-config-widget.c:539 msgid "Reset settings" msgstr "Nollaa asetukset" #: ../src/gtk-ui/sync-config-widget.c:542 msgid "Delete settings" msgstr "Poista asetukset" #: ../src/gtk-ui/sync-config-widget.c:552 msgid "Save and use" msgstr "Tallenna ja käytä" #: ../src/gtk-ui/sync-config-widget.c:555 msgid "" "Save and replace\n" "current service" msgstr "" "Tallenna ja korvaa\n" "nykyinen palvelu" #: ../src/gtk-ui/sync-config-widget.c:563 msgid "Stop using device" msgstr "Lopeta laitteen käyttö" #: ../src/gtk-ui/sync-config-widget.c:566 msgid "Stop using service" msgstr "Lopeta palvelun käyttö" #. TRANSLATORS: label for an entry in service configuration form. #. * Placeholder is a source name. #. * Example: "Appointments URI" #: ../src/gtk-ui/sync-config-widget.c:749 #, c-format msgid "%s URI" msgstr "Kohteen %s URI" #. TRANSLATORS: toggles in service configuration form, placeholder is service #. * or device name #: ../src/gtk-ui/sync-config-widget.c:936 #, c-format msgid "Send changes to %s" msgstr "Lähetä muutokset kohteeseen %s" #: ../src/gtk-ui/sync-config-widget.c:941 #, c-format msgid "Receive changes from %s" msgstr "Vastaanota muutokset kohteesta %s" #: ../src/gtk-ui/sync-config-widget.c:957 msgid "Sync" msgstr "Synkkaa" #. TRANSLATORS: label of a entry in service configuration #: ../src/gtk-ui/sync-config-widget.c:973 msgid "Server address" msgstr "Serverin osoite" #. TRANSLATORS: explanation before a device template combobox. #. * Placeholder is a device name like 'Nokia N85' or 'Syncevolution #. * Client' #: ../src/gtk-ui/sync-config-widget.c:1049 #, c-format msgid "" "This device looks like it might be a '%s'. If this is not correct, please " "take a look at the list of supported devices and pick yours if it is listed" msgstr "" "Tämä laite näyttää siltä, että se saattaisi olla '%s'. Jos tämä ei ole " "oikein, katso tätä tuettujen laitteiden luetteloa ja poimi omasi, jos se on " "luettelossa." #: ../src/gtk-ui/sync-config-widget.c:1055 #: ../src/gtk-ui/sync-config-widget.c:1915 msgid "" "We don't know what this device is exactly. Please take a look at the list of" " supported devices and pick yours if it is listed" msgstr "" "Emme tiedä täsmälleen, mikä tämä laite on. Katso luetteloa tuetuista ja " "poimi omasi, jos se on luettelossa." #: ../src/gtk-ui/sync-config-widget.c:1207 #, c-format msgid "%s - Bluetooth device" msgstr "%s - Bluetooth-laite" #. TRANSLATORS: service title for services that are not based on a #. * template in service list, the placeholder is the name of the service #: ../src/gtk-ui/sync-config-widget.c:1213 #, c-format msgid "%s - manually setup" msgstr "%s - manuaalinen asetus" #. TRANSLATORS: link button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1886 msgid "Launch website" msgstr "Siirry sivustoon" #. TRANSLATORS: button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1895 msgid "Set up now" msgstr "Tee asetukset nyt" #: ../src/gtk-ui/sync-config-widget.c:1953 msgid "Use these settings" msgstr "Käytä näitä asetuksia" #. TRANSLATORS: labels of entries in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1991 msgid "Username" msgstr "Käyttäjätunnus" #: ../src/gtk-ui/sync-config-widget.c:2006 msgid "Password" msgstr "Salasana" #. TRANSLATORS: warning in service configuration form for people #. who have modified the configuration via other means. #: ../src/gtk-ui/sync-config-widget.c:2029 msgid "" "Current configuration is more complex than what can be shown here. Changes " "to sync mode or synced data types will overwrite that configuration." msgstr "" "Nykyinen konfiguraatio on monimutkaisempi kuin täällä voidaan näyttää. " "Muutokset synkkaustilaan tai synkattuihin tietotyyppeihin korvaavat tämän " "konfiguroinnin. " #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2048 msgid "Hide server settings" msgstr "Piilota serverin asetukset" #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2068 msgid "Show server settings" msgstr "Näytä serverin asetukset" #: ../src/gnome-bluetooth/syncevolution.c:110 msgid "Sync in the Sync application" msgstr "Synkkaa synkkaussovelluksessa. " #: ../src/syncevo-dbus-server.cpp:6653 #, c-format msgid "%s is syncing" msgstr "Synkkaus käynnissä kohteessa %s" #: ../src/syncevo-dbus-server.cpp:6654 #, c-format msgid "We have just started to sync your computer with the %s sync service." msgstr "Tietokoneesi synkkaus on juuri aloitettu %s-synkkauspalvelulla." #. if sync is successfully started and done #: ../src/syncevo-dbus-server.cpp:6670 #, c-format msgid "%s sync complete" msgstr "Kohteen %s synkkaus valmis" #: ../src/syncevo-dbus-server.cpp:6671 #, c-format msgid "We have just finished syncing your computer with the %s sync service." msgstr "Tietokoneesi synkkaus on juuri lopetettu %s-synkkauspalvelulla." #. if sync is successfully started and has errors, or not started successful #. with a fatal problem #: ../src/syncevo-dbus-server.cpp:6676 msgid "Sync problem." msgstr "Synkkauksen ongelma" #: ../src/syncevo-dbus-server.cpp:6677 msgid "Sorry, there's a problem with your sync that you need to attend to." msgstr "Sorry, ilmeni ongelma synkkauksessa, jota sinun täytyy seurata." #: ../src/syncevo-dbus-server.cpp:6758 msgid "View" msgstr "Näytä" #. Use "default" as ID because that is what mutter-moblin #. recognizes: it then skips the action instead of adding it #. in addition to its own "Dismiss" button (always added). #: ../src/syncevo-dbus-server.cpp:6762 msgid "Dismiss" msgstr "Hylkää" syncevolution_1.4/po/fr.po000066400000000000000000000766401230021373600157600ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # # Translators: # , 2011. # , 2011. # , 2011. msgid "" msgstr "" "Project-Id-Version: syncevolution\n" "Report-Msgid-Bugs-To: https://bugs.meego.com/\n" "POT-Creation-Date: 2011-12-05 10:21-0800\n" "PO-Revision-Date: 2011-12-09 19:05+0000\n" "Last-Translator: GLS_FRA2 \n" "Language-Team: French (http://www.transifex.net/projects/p/meego/team/fr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: fr\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" #. TRANSLATORS: this is the application name that may be used by e.g. #. the windowmanager #: ../src/gtk-ui/main.c:40 ../src/gtk-ui/ui.glade.h:38 #: ../src/gtk-ui/sync.desktop.in.h:1 #: ../src/gnome-bluetooth/syncevolution.c:112 msgid "Sync" msgstr "Synchronisation" #: ../src/gtk-ui/sync-ui.c:266 msgid "Contacts" msgstr "Contacts" #: ../src/gtk-ui/sync-ui.c:268 msgid "Appointments" msgstr "Rendez-vous" #: ../src/gtk-ui/sync-ui.c:270 ../src/gtk-ui/ui.glade.h:40 msgid "Tasks" msgstr "Tâches" #: ../src/gtk-ui/sync-ui.c:272 msgid "Notes" msgstr "Notes" #. TRANSLATORS: This is a "combination source" for syncing with devices #. * that combine appointments and tasks. the name should match the ones #. * used for calendar and todo above #: ../src/gtk-ui/sync-ui.c:277 msgid "Appointments & Tasks" msgstr "Rendez-vous et Tâches" #: ../src/gtk-ui/sync-ui.c:349 msgid "Starting sync" msgstr "Synchronisation en cours de démarrage" #. TRANSLATORS: slow sync confirmation dialog message. Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:387 #, c-format msgid "Do you want to slow sync with %s?" msgstr "Voulez-vous effectuer une synchronisation lente avec %s ?" #: ../src/gtk-ui/sync-ui.c:391 msgid "Yes, do slow sync" msgstr "Oui, effectuer une synchronisation lente" #: ../src/gtk-ui/sync-ui.c:391 msgid "No, cancel sync" msgstr "Non, annuler la synchronisation" #. TRANSLATORS: confirmation dialog for "refresh from peer". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:424 #, c-format msgid "" "Do you want to delete all local data and replace it with data from %s? This " "is not usually advised." msgstr "" "Voulez-vous effacer toutes les données locales et les remplacer avec les " "données de %s ? Ceci est en général une mauvaise idée." #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 msgid "Yes, delete and replace" msgstr "Oui, supprimer et remplacer" #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 #: ../src/gtk-ui/sync-ui.c:1610 msgid "No" msgstr "Non" #. TRANSLATORS: confirmation dialog for "refresh from local side". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:457 #, c-format msgid "" "Do you want to delete all data in %s and replace it with your local data? " "This is not usually advised." msgstr "" "Voulez-vous effacer toutes les données de %s et les remplacer avec vos " "données locales ? Ceci est en général une mauvaise idée." #: ../src/gtk-ui/sync-ui.c:491 msgid "Trying to cancel sync" msgstr "Essai d'annulation de la synchronisation" #: ../src/gtk-ui/sync-ui.c:533 msgid "No service or device selected" msgstr "Aucun service ou périphérique sélectionné" #. TRANSLATORS: This is the title on main view. Placeholder is #. * the service name. Example: "Google - synced just now" #: ../src/gtk-ui/sync-ui.c:541 #, c-format msgid "%s - synced just now" msgstr "%s - dernière synchronisation il y a quelques secondes" #: ../src/gtk-ui/sync-ui.c:545 #, c-format msgid "%s - synced a minute ago" msgstr "%s - dernière synchronisation il y a moins d'une minute" #: ../src/gtk-ui/sync-ui.c:549 #, c-format msgid "%s - synced %ld minutes ago" msgstr "%s - dernière synchronisation il y a %ld minutes" #: ../src/gtk-ui/sync-ui.c:554 #, c-format msgid "%s - synced an hour ago" msgstr "%s - dernière synchronisation il y a moins d'une heure" #: ../src/gtk-ui/sync-ui.c:558 #, c-format msgid "%s - synced %ld hours ago" msgstr "%s - dernière synchronisation il y a %ld heures" #: ../src/gtk-ui/sync-ui.c:563 #, c-format msgid "%s - synced a day ago" msgstr "%s - dernière synchronisation hier" #: ../src/gtk-ui/sync-ui.c:567 #, c-format msgid "%s - synced %ld days ago" msgstr "%s - dernière synchronisation il y a %ld jours" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "You've just restored a backup. The changes have not been " #. * "synced with %s yet" #: ../src/gtk-ui/sync-ui.c:616 ../src/gtk-ui/sync-ui.c:701 msgid "Sync now" msgstr "Synchroniser" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "A normal sync is not possible at this time..." message. #. * "Other options" will open Emergency view #: ../src/gtk-ui/sync-ui.c:622 ../src/gtk-ui/ui.glade.h:37 msgid "Slow sync" msgstr "Synchronisation lente" #: ../src/gtk-ui/sync-ui.c:623 msgid "Other options..." msgstr "Autres options..." #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * when no service is selected. Will open configuration view #: ../src/gtk-ui/sync-ui.c:628 msgid "Select sync service" msgstr "Sélectionner le service de synchronisation" #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * login to service fails. Will open configuration view for this service #: ../src/gtk-ui/sync-ui.c:633 msgid "Edit service settings" msgstr "Modifier les paramètres du service" #: ../src/gtk-ui/sync-ui.c:709 msgid "" "You haven't selected a sync service or device yet. Sync services let you " "synchronize your data between your netbook and a web service. You can also " "sync directly with some devices." msgstr "" "Vous n'avez pas encore sélectionné de service de synchronisation ou de " "périphérique. Les services de synchronisation vous permettent de " "synchroniser vos données de votre netbook à un service web. Vous pouvez " "également synchroniser directement avec certains périphériques." #: ../src/gtk-ui/sync-ui.c:729 msgid "Sync again" msgstr "Synchroniser de nouveau" #: ../src/gtk-ui/sync-ui.c:748 msgid "Restoring" msgstr "Restauration en cours" #: ../src/gtk-ui/sync-ui.c:750 msgid "Syncing" msgstr "Synchronisation en cours" #. TRANSLATORS: This is for the button in main view, right side. #. Keep line length below ~20 characters, use two lines if needed #: ../src/gtk-ui/sync-ui.c:762 ../src/gtk-ui/sync-ui.c:3407 msgid "Cancel sync" msgstr "Annuler la synchronisation" #: ../src/gtk-ui/sync-ui.c:927 msgid "Back to sync" msgstr "Retourner à la synchronisation" #. TRANSLATORS: label for checkbutton/toggle in main view. #. * Please stick to similar length strings or break the line with #. * "\n" if absolutely needed #: ../src/gtk-ui/sync-ui.c:1229 msgid "Automatic sync" msgstr "Synchronisation automatique" #. This is the expander label in emergency view. It summarizes the #. * currently selected data sources. First placeholder is service/device #. * name, second a comma separeted list of sources. #. * E.g. "Affected data: Google Contacts, Appointments" #: ../src/gtk-ui/sync-ui.c:1524 #, c-format msgid "Affected data: %s %s" msgstr "Données affectées : %s %s" #: ../src/gtk-ui/sync-ui.c:1529 #, c-format msgid "Affected data: none" msgstr "Données affectées : aucunes" #. TRANSLATORS: confirmation for restoring a backup. placeholder is the #. * backup time string defined below #: ../src/gtk-ui/sync-ui.c:1607 #, c-format msgid "" "Do you want to restore the backup from %s? All changes you have made since " "then will be lost." msgstr "" "Voulez-vous restaurer la sauvegarde de %s ? Toutes les modifications " "effectuées à partir de là seront perdues." #: ../src/gtk-ui/sync-ui.c:1610 msgid "Yes, restore" msgstr "Oui, restaurer" #. TRANSLATORS: date/time for strftime(), used in emergency view backup #. * label. Any time format that shows date and time is good. #: ../src/gtk-ui/sync-ui.c:1642 #, c-format msgid "%x %X" msgstr "%x %X" #. TRANSLATORS: label for a backup in emergency view. Placeholder is #. * service or device name #: ../src/gtk-ui/sync-ui.c:1661 #, c-format msgid "Backed up before syncing with %s" msgstr "Sauvegardé avant la synchronisation avec %s" #: ../src/gtk-ui/sync-ui.c:1678 msgid "Restore" msgstr "Restaurer" #. TRANSLATORS: this is an explanation in Emergency view. #. * Placeholder is a service/device name #: ../src/gtk-ui/sync-ui.c:1785 #, c-format msgid "" "A normal sync with %s is not possible at this time. You can do a slow two-" "way sync or start from scratch. You can also restore a backup, but a slow " "sync or starting from scratch will still be required before normal sync is " "possible." msgstr "" "Une synchronisation normale avec %s est actuellement impossible. Vous pouvez" " effectuer une synchronisation lente à deux files ou alors reprendre à zéro." " Vous pouvez aussi restaurer une sauvegarde, mais une synchronisation lente " "ou une reprise à zéro seront nécessaires avant qu'une synchronisation soit " "possible." #: ../src/gtk-ui/sync-ui.c:1795 #, c-format msgid "" "If something has gone horribly wrong, you can try a slow sync, start from " "scratch or restore from backup." msgstr "" "En cas de problème sérieux, vous pouvez essayer une synchronisation lente, " "reprendre à zéro ou alors restaurer depuis une sauvegarde." #. TRANSLATORS: These are a buttons in Emergency view. Placeholder is a #. * service/device name. Please don't use too long lines, but feel free to #. * use several lines. #: ../src/gtk-ui/sync-ui.c:1804 #, c-format msgid "" "Delete all your local\n" "data and replace with\n" "data from %s" msgstr "" "Supprimer toutes\n" "les données locales\n" "et les remplacer avec\n" "les données de %s" #: ../src/gtk-ui/sync-ui.c:1810 #, c-format msgid "" "Delete all data on\n" "%s and replace\n" "with your local data" msgstr "" "Supprimer toutes les données\n" "sur %s et les remplacer\n" "avec les données locales" #: ../src/gtk-ui/sync-ui.c:2275 msgid "Failed to get list of supported services from SyncEvolution" msgstr "" "Impossible d'obtenir la liste des services pris en charge depuis " "SyncEvolution" #: ../src/gtk-ui/sync-ui.c:2329 msgid "" "There was a problem communicating with the sync process. Please try again " "later." msgstr "" "Un problème est survenu en communiquant avec le processus de " "synchronisation. Veuillez réessayer ultérieurement." #: ../src/gtk-ui/sync-ui.c:2388 msgid "Restore failed" msgstr "Échec de la restauration" #: ../src/gtk-ui/sync-ui.c:2391 ../src/gtk-ui/sync-ui.c:3276 msgid "Sync failed" msgstr "Échec de la synchronisation" #: ../src/gtk-ui/sync-ui.c:2397 msgid "Restore complete" msgstr "Restauration terminée" #: ../src/gtk-ui/sync-ui.c:2400 msgid "Sync complete" msgstr "Synchronisation terminée" #: ../src/gtk-ui/sync-ui.c:2492 #, c-format msgid "Preparing '%s'" msgstr "'%s' en cours de préparation" #: ../src/gtk-ui/sync-ui.c:2495 #, c-format msgid "Receiving '%s'" msgstr "'%s' en cours de réception" #: ../src/gtk-ui/sync-ui.c:2498 #, c-format msgid "Sending '%s'" msgstr "'%s' en cours d'envoi" #: ../src/gtk-ui/sync-ui.c:2619 #, c-format msgid "There was one remote rejection." msgid_plural "There were %ld remote rejections." msgstr[0] "Il y a eu un rejet distant." msgstr[1] "Il y a eu %ld rejets distants." #: ../src/gtk-ui/sync-ui.c:2624 #, c-format msgid "There was one local rejection." msgid_plural "There were %ld local rejections." msgstr[0] "Il y a eu un rejet local." msgstr[1] "Il y a eu %ld rejets locaux." #: ../src/gtk-ui/sync-ui.c:2629 #, c-format msgid "There were %ld local rejections and %ld remote rejections." msgstr "Il y a eu %ld rejets locaux et %ld rejets distants." #: ../src/gtk-ui/sync-ui.c:2634 #, c-format msgid "Last time: No changes." msgstr "Dernière fois : pas de changement. " #: ../src/gtk-ui/sync-ui.c:2636 #, c-format msgid "Last time: Sent one change." msgid_plural "Last time: Sent %ld changes." msgstr[0] "Dernière fois : un changement envoyé." msgstr[1] "Dernière fois : %ld changements envoyés." #. This is about changes made to the local data. Not all of these #. changes were requested by the remote server, so "applied" #. is a better word than "received" (bug #5185). #: ../src/gtk-ui/sync-ui.c:2644 #, c-format msgid "Last time: Applied one change." msgid_plural "Last time: Applied %ld changes." msgstr[0] "Dernière fois : un changement reçu." msgstr[1] "Dernière fois : %ld changements reçus." #: ../src/gtk-ui/sync-ui.c:2649 #, c-format msgid "Last time: Applied %ld changes and sent %ld changes." msgstr "Dernière fois : %ld changements reçus et %ld changements envoyés." #. TRANSLATORS: the placeholder is a error message (hopefully) #. * explaining the problem #: ../src/gtk-ui/sync-ui.c:2856 #, c-format msgid "" "There was a problem with last sync:\n" "%s" msgstr "" "Un problème est survenu lors de la dernière synchronisation :\n" "%s" #: ../src/gtk-ui/sync-ui.c:2866 #, c-format msgid "" "You've just restored a backup. The changes have not been synced with %s yet" msgstr "" "Vous venez de restaurer une sauvegarde. Les modifications n'ont pas encore " "été synchronisées avec %s" #: ../src/gtk-ui/sync-ui.c:3154 msgid "Waiting for current operation to finish..." msgstr "En attente de fin d'opération en cours..." #. TRANSLATORS: next strings are error messages. #: ../src/gtk-ui/sync-ui.c:3188 msgid "" "A normal sync is not possible at this time. The server suggests a slow sync," " but this might not always be what you want if both ends already have data." msgstr "" "Une synchronisation normale est actuellement impossible. Le serveur suggère " "une synchronisation lente, mais cela n'est pas forcément dans votre intérêt " "si les deux côtés contiennent déjà des données." #: ../src/gtk-ui/sync-ui.c:3192 msgid "The sync process died unexpectedly." msgstr "Processus de synchronisation terminé de façon inattendue." #: ../src/gtk-ui/sync-ui.c:3197 msgid "" "Password request was not answered. You can save the password in the settings" " to prevent the request." msgstr "" "La demande de mot de passe n'a pas été prise en compte. Vous pouvez " "enregistrer le mot de passe dans les paramètres pour empêcher la demande." #. TODO use the service device name here, this is a remote problem #: ../src/gtk-ui/sync-ui.c:3201 msgid "There was a problem processing sync request. Trying again may help." msgstr "" "Un problème est survenu lors du traitement de la demande de synchronisation." " Une nouvelle tentative peut se révéler utile." #: ../src/gtk-ui/sync-ui.c:3207 msgid "" "Failed to login. Could there be a problem with your username or password?" msgstr "" "Échec de connexion. Pourrait-il y avoir un problème avec votre nom " "d'utilisateur ou bien votre mot de passe ?" #: ../src/gtk-ui/sync-ui.c:3210 msgid "Forbidden" msgstr "Interdit" #. TRANSLATORS: data source means e.g. calendar or addressbook #: ../src/gtk-ui/sync-ui.c:3216 msgid "" "A data source could not be found. Could there be a problem with the " "settings?" msgstr "" "Une source de données n'a pas pu être trouvée. Pourrait-il y avoir un " "problème avec les paramètres ?" #: ../src/gtk-ui/sync-ui.c:3220 msgid "Remote database error" msgstr "Erreur de base de données à distance" #. This can happen when EDS is borked, restart it may help... #: ../src/gtk-ui/sync-ui.c:3223 msgid "" "There is a problem with the local database. Syncing again or rebooting may " "help." msgstr "" "Il y a un problème avec la base de données locale. Synchroniser de nouveau " "ou bien redémarrer peut aider." #: ../src/gtk-ui/sync-ui.c:3226 msgid "No space on disk" msgstr "Il n'y a plus d'espace disponible sur le disque" #: ../src/gtk-ui/sync-ui.c:3228 msgid "Failed to process SyncML" msgstr "Impossible de terminer SyncML" #: ../src/gtk-ui/sync-ui.c:3230 msgid "Server authorization failed" msgstr "L'autorisation du serveur a échoué" #: ../src/gtk-ui/sync-ui.c:3232 msgid "Failed to parse configuration file" msgstr "Impossible d'analyser le fichier de configuration" #: ../src/gtk-ui/sync-ui.c:3234 msgid "Failed to read configuration file" msgstr "Impossible de lire le fichier de configuration" #: ../src/gtk-ui/sync-ui.c:3236 msgid "No configuration found" msgstr "Aucune configuration trouvée" #: ../src/gtk-ui/sync-ui.c:3238 msgid "No configuration file found" msgstr "Aucun fichier de configuration trouvé" #: ../src/gtk-ui/sync-ui.c:3240 msgid "Server sent bad content" msgstr "Le serveur a transmis des données incorrectes" #: ../src/gtk-ui/sync-ui.c:3242 msgid "Connection certificate has expired" msgstr "Le certificat de la connexion a expiré" #: ../src/gtk-ui/sync-ui.c:3244 msgid "Connection certificate is invalid" msgstr "le certificat de la connexion est invalide" #: ../src/gtk-ui/sync-ui.c:3252 msgid "" "We were unable to connect to the server. The problem could be temporary or " "there could be something wrong with the settings." msgstr "" "Nous n'avons pas pu nous connecter au serveur. Il se peut que le problème " "soit temporaire ou bien qu’il y ait une erreur avec les paramètres." #: ../src/gtk-ui/sync-ui.c:3259 msgid "The server URL is bad" msgstr "L'URL du serveur est mauvaise" #: ../src/gtk-ui/sync-ui.c:3264 msgid "The server was not found" msgstr "Le serveur n'a pas été trouvé" #: ../src/gtk-ui/sync-ui.c:3266 #, c-format msgid "Error %d" msgstr "Erreur %d" #. TRANSLATORS: password request dialog contents: title, cancel button #. * and ok button #: ../src/gtk-ui/sync-ui.c:3404 msgid "Password is required for sync" msgstr "Mot de passe nécessaire pour la synchronisation" #: ../src/gtk-ui/sync-ui.c:3408 msgid "Sync with password" msgstr "Synchronisation avec le mot de passe..." #. TRANSLATORS: password request dialog message, placeholder is service name #: ../src/gtk-ui/sync-ui.c:3418 #, c-format msgid "Please enter password for syncing with %s:" msgstr "Veuillez saisir le mot de passe pour synchroniser avec %s :" #. title for the buttons on the right side of main view #: ../src/gtk-ui/ui.glade.h:2 msgid "Actions" msgstr "Actions" #. text between the two "start from scratch" buttons in emergency view #: ../src/gtk-ui/ui.glade.h:4 msgid "or" msgstr "ou" #: ../src/gtk-ui/ui.glade.h:5 msgid "Direct sync" msgstr "Synchronisation directe" #: ../src/gtk-ui/ui.glade.h:6 msgid "Network sync" msgstr "Synchronisation réseau" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:8 msgid "Restore from backup" msgstr "Restaurer depuis une sauvegarde" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:10 msgid "Slow sync" msgstr "Synchronisation lente" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:12 msgid "Start from scratch" msgstr "Reprendre à zéro" #: ../src/gtk-ui/ui.glade.h:13 msgid "" "A slow sync compares items from both sides and tries to merge them. \n" "This may fail in some cases, leading to duplicates or lost information." msgstr "" "Une synchronisation lente compare les articles des deux côtés et tente de les fusionner. \n" "Cela peut échouer dans certains cas, provoquant des duplicatas ou une perte d'information. " #: ../src/gtk-ui/ui.glade.h:15 msgid "Add new device" msgstr "Nouveau peripherique" #: ../src/gtk-ui/ui.glade.h:16 msgid "Add new service" msgstr "Nouveau service" #. explanation of "Restore backup" function #: ../src/gtk-ui/ui.glade.h:18 msgid "" "Backups are made before every time we Sync. Choose a backup to restore. Any " "changes you have made since then will be lost." msgstr "" "Les sauvegardes sont effectuées à chaque synchronisation. Choisissez une " "sauvegarde à restaurer. Toutes les modifications effectuées à partir de là " "seront perdues." #: ../src/gtk-ui/ui.glade.h:19 msgid "Calendar" msgstr "Calendrier" #. Button in main view, right side. Keep to below 20 chars per line, feel free #. to use two lines #: ../src/gtk-ui/ui.glade.h:21 msgid "" "Change or edit\n" "sync service" msgstr "" "Changer ou modifier\n" "le service de\n" "synchronisation" #. close button for settings window #: ../src/gtk-ui/ui.glade.h:24 msgid "Close" msgstr "Fermer" #: ../src/gtk-ui/ui.glade.h:25 msgid "" "Delete all data on Zyb \n" "and replace with your\n" "local information" msgstr "" "Supprimer toutes\n" "les données sur Zyb\n" "et les remplacer avec\n" "les données locales " #: ../src/gtk-ui/ui.glade.h:28 msgid "" "Delete all your local\n" "information and replace\n" "with data from Zyb" msgstr "" "Supprimer toutes\n" "les données locales\n" "et les remplacer avec\n" "les données de Zyb" #. button in main view, right side. Keep length to 20 characters or so, use #. two lines if needed #: ../src/gtk-ui/ui.glade.h:32 msgid "" "Fix a sync\n" "emergency" msgstr "" "Réparer une\n" "urgence de\n" "synchronisation" #: ../src/gtk-ui/ui.glade.h:34 msgid "" "If you don't see your service above but know that your sync provider uses SyncML\n" "you can setup a service manually." msgstr "" "Si vous ne voyez pas votre service mais savez que votre fournisseur de services\n" "de synchronisation utilise SyncML, vous pouvez configurer un service manuellement." #: ../src/gtk-ui/ui.glade.h:36 msgid "Settings" msgstr "Paramètres" #: ../src/gtk-ui/ui.glade.h:39 msgid "Sync Emergency" msgstr "Urgence de synchronisation" #: ../src/gtk-ui/ui.glade.h:41 msgid "" "To sync you'll need a network connection and an account with a sync service.\n" "We support the following services: " msgstr "" "Pour synchroniser vous avez besoin d'une connexion réseau et d'un compte avec un service de synchronisation.\n" "Nous prenons en charge les services suivants :" #: ../src/gtk-ui/ui.glade.h:43 msgid "Use Bluetooth to Sync your data from one device to another." msgstr "" "Utiliser Bluetooth pour synchroniser vos données d'un périphérique à " "l'autre." #: ../src/gtk-ui/ui.glade.h:44 msgid "You will need to add Bluetooth devices before they can be synced." msgstr "" "Vous devrez ajouter des périphériques Bluetooth avant de pouvoir les " "synchroniser. " #: ../src/gtk-ui/sync.desktop.in.h:2 msgid "Up to date" msgstr "À jour" #: ../src/gtk-ui/sync-gtk.desktop.in.h:1 msgid "SyncEvolution (GTK)" msgstr "SyncEvolution (GTK)" #: ../src/gtk-ui/sync-gtk.desktop.in.h:2 msgid "Synchronize PIM data" msgstr "Synchroniser les données du gestionnaire d'informations personnelles" #: ../src/gtk-ui/sync-config-widget.c:88 msgid "" "ScheduleWorld enables you to keep your contacts, events, tasks, and notes in" " sync." msgstr "" "ScheduleWorld vous permet de garder vos contacts, événements, tâches et " "notes synchronisés." #: ../src/gtk-ui/sync-config-widget.c:91 msgid "" "Google Sync can back up and synchronize your contacts with your Gmail " "contacts." msgstr "" "Google Sync permet de sauvegarder et synchroniser votre carnet d'adresses " "avec vos contacts Gmail." #. TRANSLATORS: Please include the word "demo" (or the equivalent in #. your language): Funambol is going to be a 90 day demo service #. in the future #: ../src/gtk-ui/sync-config-widget.c:97 msgid "" "Back up your contacts and calendar. Sync with a single click, anytime, " "anywhere (DEMO)." msgstr "" "Sauvegarde de vos contacts et calendrier. Synchronisation en un seul clic, " "n'importe quand, n'importe où (DEMO)." #: ../src/gtk-ui/sync-config-widget.c:100 msgid "" "Mobical Backup and Restore service allows you to securely back up your " "personal mobile data for free." msgstr "" "Le service Mobical de sauvegarde et de restauration vous permet de " "sauvegarder vos données personnelles mobiles gratuitement et en toute " "sécurité." #: ../src/gtk-ui/sync-config-widget.c:103 msgid "" "ZYB is a simple way for people to store and share mobile information online." msgstr "" "ZYB est une façon simple de stocker et de partager les informations mobiles " "en ligne." #: ../src/gtk-ui/sync-config-widget.c:106 msgid "" "Memotoo lets you access your personal data from any computer connected to " "the Internet." msgstr "" "Memotoo vous permet d'accéder à vos données personnelles depuis n'importe " "quel ordinateur connecté à Internet." #: ../src/gtk-ui/sync-config-widget.c:195 msgid "Sorry, failed to save the configuration" msgstr "Désolé, la sauvegarde de la configuration a échoué" #: ../src/gtk-ui/sync-config-widget.c:445 msgid "Service must have a name and server URL" msgstr "Le service doit avoir un nom et une URL de serveur" #. TRANSLATORS: error dialog when creating a new sync configuration #: ../src/gtk-ui/sync-config-widget.c:451 msgid "A username is required for this service" msgstr "Un nom d'utilisateur est nécessaire pour ce service" #: ../src/gtk-ui/sync-config-widget.c:493 #, c-format msgid "" "Do you want to reset the settings for %s? This will not remove any synced " "information on either end." msgstr "" "Voulez-vous réinitialiser les paramètres pour %s ? Cela ne supprimera aucune" " information synchronisée de part et d’autre. " #. TRANSLATORS: buttons in reset-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:497 msgid "Yes, reset" msgstr "Oui, réinitialiser" #: ../src/gtk-ui/sync-config-widget.c:498 #: ../src/gtk-ui/sync-config-widget.c:509 msgid "No, keep settings" msgstr "Non, conserver les paramètres" #: ../src/gtk-ui/sync-config-widget.c:503 #, c-format msgid "" "Do you want to delete the settings for %s? This will not remove any synced " "information on either end but it will remove these settings." msgstr "" "Voulez-vous supprimer les paramètres pour %s ? Cela ne supprimera aucune " "information synchronisée de part et d’autre mais supprimera ces paramètres." #. TRANSLATORS: buttons in delete-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:508 msgid "Yes, delete" msgstr "Oui, supprimer" #: ../src/gtk-ui/sync-config-widget.c:539 msgid "Reset settings" msgstr "Rétablir les paramètres" #: ../src/gtk-ui/sync-config-widget.c:542 msgid "Delete settings" msgstr "Supprimer les paramètres" #: ../src/gtk-ui/sync-config-widget.c:552 msgid "Save and use" msgstr "Sauvegarder et utiliser" #: ../src/gtk-ui/sync-config-widget.c:555 msgid "" "Save and replace\n" "current service" msgstr "" "Sauvegarder et utiliser\n" "le service actuel" #: ../src/gtk-ui/sync-config-widget.c:563 msgid "Stop using device" msgstr "Ne plus utiliser ce périphérique" #: ../src/gtk-ui/sync-config-widget.c:566 msgid "Stop using service" msgstr "Ne plus utiliser ce service" #. TRANSLATORS: label for an entry in service configuration form. #. * Placeholder is a source name. #. * Example: "Appointments URI" #: ../src/gtk-ui/sync-config-widget.c:749 #, c-format msgid "%s URI" msgstr "%s URI" #. TRANSLATORS: toggles in service configuration form, placeholder is service #. * or device name #: ../src/gtk-ui/sync-config-widget.c:936 #, c-format msgid "Send changes to %s" msgstr "Envoyer les modifications à %s" #: ../src/gtk-ui/sync-config-widget.c:941 #, c-format msgid "Receive changes from %s" msgstr "Recevoir les modifications de %s" #: ../src/gtk-ui/sync-config-widget.c:957 msgid "Sync" msgstr "Synchroniser" #. TRANSLATORS: label of a entry in service configuration #: ../src/gtk-ui/sync-config-widget.c:973 msgid "Server address" msgstr "Adresse du serveur" #. TRANSLATORS: explanation before a device template combobox. #. * Placeholder is a device name like 'Nokia N85' or 'Syncevolution #. * Client' #: ../src/gtk-ui/sync-config-widget.c:1049 #, c-format msgid "" "This device looks like it might be a '%s'. If this is not correct, please " "take a look at the list of supported devices and pick yours if it is listed" msgstr "" "Il semble que le périphérique est un '%s'. Si ce n'est pas correct, veuillez" " consulter la liste de périphériques pris en charge et sélectionnez le votre" " s'il apparait dans la liste" #: ../src/gtk-ui/sync-config-widget.c:1055 #: ../src/gtk-ui/sync-config-widget.c:1915 msgid "" "We don't know what this device is exactly. Please take a look at the list of" " supported devices and pick yours if it is listed" msgstr "" "Nous ne savons pas exactement ce qu’est ce périphérique. Veuillez consulter " "la liste de périphériques pris en charge et sélectionnez le votre s'il " "apparait dans la liste" #: ../src/gtk-ui/sync-config-widget.c:1207 #, c-format msgid "%s - Bluetooth device" msgstr "%s - Périphérique Bluetooth" #. TRANSLATORS: service title for services that are not based on a #. * template in service list, the placeholder is the name of the service #: ../src/gtk-ui/sync-config-widget.c:1213 #, c-format msgid "%s - manually setup" msgstr "%s - configurer manuellement" #. TRANSLATORS: link button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1886 msgid "Launch website" msgstr "Lancer le site Web" #. TRANSLATORS: button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1895 msgid "Set up now" msgstr "Configurer" #: ../src/gtk-ui/sync-config-widget.c:1953 msgid "Use these settings" msgstr "Utiliser ces paramètres" #. TRANSLATORS: labels of entries in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1991 msgid "Username" msgstr "Nom d'utilisateur" #: ../src/gtk-ui/sync-config-widget.c:2006 msgid "Password" msgstr "Mot de passe" #. TRANSLATORS: warning in service configuration form for people #. who have modified the configuration via other means. #: ../src/gtk-ui/sync-config-widget.c:2029 msgid "" "Current configuration is more complex than what can be shown here. Changes " "to sync mode or synced data types will overwrite that configuration." msgstr "" "La configuration actuelle est plus complexe que ce qui peut être affiché " "ici. Les modifications apportées au mode synchronisation ou aux types de " "données synchronisées écraseront cette configuration." #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2048 msgid "Hide server settings" msgstr "Masquer les paramètres du serveur" #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2068 msgid "Show server settings" msgstr "Afficher les paramètres du serveur" #: ../src/gnome-bluetooth/syncevolution.c:110 msgid "Sync in the Sync application" msgstr "Synchroniser dans l'application Synchronisation" #: ../src/syncevo-dbus-server.cpp:6653 #, c-format msgid "%s is syncing" msgstr "%s est en cours de synchronisation" #: ../src/syncevo-dbus-server.cpp:6654 #, c-format msgid "We have just started to sync your computer with the %s sync service." msgstr "" "Démarrage de la synchronisation de votre ordinateur avec le service %s." #. if sync is successfully started and done #: ../src/syncevo-dbus-server.cpp:6670 #, c-format msgid "%s sync complete" msgstr "Synchronisation %s terminée" #: ../src/syncevo-dbus-server.cpp:6671 #, c-format msgid "We have just finished syncing your computer with the %s sync service." msgstr "Synchronisation de votre ordinateur avec le service %s" #. if sync is successfully started and has errors, or not started successful #. with a fatal problem #: ../src/syncevo-dbus-server.cpp:6676 msgid "Sync problem." msgstr "Problème de synchronisation." #: ../src/syncevo-dbus-server.cpp:6677 msgid "Sorry, there's a problem with your sync that you need to attend to." msgstr "Désolé, il y a un problème avec la synchronisation que vous tentez." #: ../src/syncevo-dbus-server.cpp:6758 msgid "View" msgstr "Affichage" #. Use "default" as ID because that is what mutter-moblin #. recognizes: it then skips the action instead of adding it #. in addition to its own "Dismiss" button (always added). #: ../src/syncevo-dbus-server.cpp:6762 msgid "Dismiss" msgstr "Ignorer" syncevolution_1.4/po/gl.po000066400000000000000000000373731230021373600157530ustar00rootroot00000000000000# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # #: ../src/gtk-ui/sync-ui.c:764 # Xosé , 2009. msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: http://moblin.org/projects/syncevolution\n" "POT-Creation-Date: 2009-10-01 19:27+0000\n" "PO-Revision-Date: 2009-10-14 21:52+0200\n" "Last-Translator: Xosé \n" "Language-Team: Galego \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Lokalize 1.0\n" #. TRANSLATORS: this is the application name that may be used by e.g. #. the windowmanager #: ../src/gtk-ui/main.c:31 ../src/gtk-ui/ui.glade.h:28 #: ../src/gtk-ui/sync.desktop.in.h:1 msgid "Sync" msgstr "Sincronizar" #: ../src/gtk-ui/sync-ui.c:259 msgid "Addressbook" msgstr "Caderno de enderezos" #: ../src/gtk-ui/sync-ui.c:261 msgid "Calendar" msgstr "Calendario" #: ../src/gtk-ui/sync-ui.c:263 msgid "Todo" msgstr "Tarefa" #: ../src/gtk-ui/sync-ui.c:265 msgid "Memo" msgstr "Nota" #: ../src/gtk-ui/sync-ui.c:320 msgid "Failed to save current service in GConf configuration system" msgstr "" "Non foi posíbel gravar o servizo actual no sistema de configuración GConf" #: ../src/gtk-ui/sync-ui.c:331 msgid "Failed to save service configuration to SyncEvolution" msgstr "Non foi posíbel gravar a configuración do servizo en SyncEvolution" #: ../src/gtk-ui/sync-ui.c:416 msgid "Failed to get service configuration from SyncEvolution" msgstr "Fallou a obtención da configuración do servizo do SyncEvolution" #: ../src/gtk-ui/sync-ui.c:479 msgid "Failed to remove service configuration from SyncEvolution" msgstr "Fallou a eliminación da configuración do servizo do SyncEvolution" #: ../src/gtk-ui/sync-ui.c:599 msgid "Service must have a name and server URL" msgstr "O servizo ten que ter un nome e un URL de servidor" #. sync is no longer in progress for some reason #: ../src/gtk-ui/sync-ui.c:675 msgid "Failed to cancel: sync was no longer in progress" msgstr "Fallou a cancelación: xa non se está a sincronizar" #: ../src/gtk-ui/sync-ui.c:679 msgid "Failed to cancel sync" msgstr "Fallou a cancelación da sincronización" #: ../src/gtk-ui/sync-ui.c:683 msgid "Canceling sync" msgstr "A cancelar a sincronización" #: ../src/gtk-ui/sync-ui.c:697 msgid "Trying to cancel sync" msgstr "A tentar cancelar a sincronización" #: ../src/gtk-ui/sync-ui.c:704 #, c-format msgid "" "Do you want to delete all local data and replace it with data from %s? This " "is not usually advised." msgstr "" "Quere eliminar todos os datos locais e substituílos por %s? Normalmente non " "se recomenda isto." #: ../src/gtk-ui/sync-ui.c:709 #, c-format msgid "" "Do you want to delete all data in %s and replace it with your local data? " "This is not usually advised." msgstr "" "Quere eliminar todos os datos de %s e substituílos polos seus datos locais? " "Normalmente non se recomenda isto." #: ../src/gtk-ui/sync-ui.c:726 msgid "No, cancel sync" msgstr "Non, cancelar a sincronización" #: ../src/gtk-ui/sync-ui.c:727 msgid "Yes, delete and replace" msgstr "Si, eliminar e substituír" #: ../src/gtk-ui/sync-ui.c:749 msgid "No sources are enabled, not syncing" msgstr "Non se activou ningunha fonte, non se sincroniza" #: ../src/gtk-ui/sync-ui.c:766 msgid "A sync is already in progress" msgstr "Xa hai unha sincronización en proceso" #: ../src/gtk-ui/sync-ui.c:768 msgid "Failed to start sync" msgstr "Foi imposíbel comezar a sincronización" #: ../src/gtk-ui/sync-ui.c:773 msgid "Starting sync" msgstr "A comezar a sincronización" #: ../src/gtk-ui/sync-ui.c:798 msgid "Last synced just seconds ago" msgstr "Sincronizouse por última vez só hai uns segundos" #: ../src/gtk-ui/sync-ui.c:801 msgid "Last synced a minute ago" msgstr "Sincronizouse por última vez hai un minuto" #: ../src/gtk-ui/sync-ui.c:804 #, c-format msgid "Last synced %ld minutes ago" msgstr "Sincronizouse por última vez hai %ld minutos" #: ../src/gtk-ui/sync-ui.c:807 msgid "Last synced an hour ago" msgstr "Sincronizouse por última vez hai unha hora" #: ../src/gtk-ui/sync-ui.c:810 #, c-format msgid "Last synced %ld hours ago" msgstr "Sincronizouse por última vez hai %ld horas" #: ../src/gtk-ui/sync-ui.c:813 msgid "Last synced a day ago" msgstr "Sincronizouse por última vez hai un día" #: ../src/gtk-ui/sync-ui.c:816 #, c-format msgid "Last synced %ld days ago" msgstr "Sincronizouse por última vez hai %ld días" #: ../src/gtk-ui/sync-ui.c:901 msgid "Sync again" msgstr "Sincronizar de novo" #: ../src/gtk-ui/sync-ui.c:903 ../src/gtk-ui/ui.glade.h:29 msgid "Sync now" msgstr "Sincronizar agora" #: ../src/gtk-ui/sync-ui.c:912 msgid "Syncing" msgstr "A sincronizar" #: ../src/gtk-ui/sync-ui.c:918 msgid "Cancel sync" msgstr "Cancelar a sincronización" #. TRANSLATORS: placeholder is a source name, shown with checkboxes in main window #: ../src/gtk-ui/sync-ui.c:1265 #, c-format msgid "%s (not supported by this service)" msgstr "%s (non admitido por este servizo)" #: ../src/gtk-ui/sync-ui.c:1298 #, c-format msgid "There was one remote rejection." msgid_plural "There were %d remote rejections." msgstr[0] "Houbo un rexeitamento remoto." msgstr[1] "Houbo %d rexeitamentos remotos." #: ../src/gtk-ui/sync-ui.c:1303 #, c-format msgid "There was one local rejection." msgid_plural "There were %d local rejections." msgstr[0] "Houbo un rexeitamento local." msgstr[1] "Houbo %d rexeitamentos locais." #: ../src/gtk-ui/sync-ui.c:1308 #, c-format msgid "There were %d local rejections and %d remote rejections." msgstr "Houbo %d rexeitamentos locais e %d rexeitamentos remotos." #: ../src/gtk-ui/sync-ui.c:1313 #, c-format msgid "Last time: No changes." msgstr "Última vez: Sen cambios." #: ../src/gtk-ui/sync-ui.c:1315 #, c-format msgid "Last time: Sent one change." msgid_plural "Last time: Sent %d changes." msgstr[0] "Última vez: Enviouse un cambio." msgstr[1] "Última vez: Enviáronse %d cambios." #. This is about changes made to the local data. Not all of these #. changes were requested by the remote server, so "applied" #. is a better word than "received" (bug #5185). #: ../src/gtk-ui/sync-ui.c:1323 #, c-format msgid "Last time: Applied one change." msgid_plural "Last time: Applied %d changes." msgstr[0] "Última vez: Aplicouse un cambio." msgstr[1] "Última vez: Aplicáronse %d cambios." #: ../src/gtk-ui/sync-ui.c:1328 #, c-format msgid "Last time: Applied %d changes and sent %d changes." msgstr "Última vez: Aplicáronse %d cambios e enviáronse %d cambios." #: ../src/gtk-ui/sync-ui.c:1420 msgid "Failed to get server configuration from SyncEvolution" msgstr "Fallou a obtención da configuración do servidor do SyncEvolution" #: ../src/gtk-ui/sync-ui.c:1472 msgid "" "ScheduleWorld enables you to keep your contacts, events, tasks, and notes in " "sync." msgstr "" "ScheduleWorld permite manter os contactos, eventos, tarefas e notas en " "sincronía." #: ../src/gtk-ui/sync-ui.c:1475 msgid "" "Google Sync can back up and synchronize your Address Book with your Gmail " "contacts." msgstr "" "Google Sync pode crear unha copia de seguranza e sincronizar o seu caderno " "de enderezos cos seus contactos de Gmail." #. TRANSLATORS: Please include the word "demo" (or the equivalent in #. your language): Funambol is going to be a 90 day demo service #. in the future #: ../src/gtk-ui/sync-ui.c:1481 msgid "" "Back up your contacts and calendar. Sync with a singleclick, anytime, " "anywhere (DEMO)." msgstr "" "Faga unha copia de seguranza dos seus contactos e do seu calendario. " "Sincronice con só premer unha tecla, en calquera momento, en calquera lugar " "(DEMOSTRACIÓN)." #: ../src/gtk-ui/sync-ui.c:1509 msgid "New service" msgstr "Novo servizo" #: ../src/gtk-ui/sync-ui.c:1556 msgid "Server URL" msgstr "URL do servidor" #. TRANSLATORS: placeholder is a source name in settings window #: ../src/gtk-ui/sync-ui.c:1578 #, c-format msgid "%s URI" msgstr "URI %s" #: ../src/gtk-ui/sync-ui.c:1715 ../src/gtk-ui/ui.glade.h:17 msgid "Launch website" msgstr "Lanzar un sitio web" #: ../src/gtk-ui/sync-ui.c:1719 msgid "Setup and use" msgstr "Configurar e utilizar" #: ../src/gtk-ui/sync-ui.c:1765 msgid "Failed to get list of manually setup services from SyncEvolution" msgstr "" "Fallou a obtención da listaxe de servizos de configuración manuais do " "SyncEvolution" #: ../src/gtk-ui/sync-ui.c:1806 msgid "Failed to get list of supported services from SyncEvolution" msgstr "Fallou a obtención da listaxe de servizos admitidos do SyncEvolution" #. TODO: this is a hack... SyncEnd should be a signal of it's own, #. not just hacked on top of the syncevolution error codes #: ../src/gtk-ui/sync-ui.c:1967 msgid "Service configuration not found" msgstr "Non se atopou a configuración do servizo" #: ../src/gtk-ui/sync-ui.c:1973 msgid "Not authorized" msgstr "Non ten autorización" #: ../src/gtk-ui/sync-ui.c:1975 msgid "Forbidden" msgstr "Prohibido" #: ../src/gtk-ui/sync-ui.c:1977 msgid "Not found" msgstr "Non se atopa" #: ../src/gtk-ui/sync-ui.c:1979 msgid "Fatal database error" msgstr "Houbo un erro moi grave da base de datos" #: ../src/gtk-ui/sync-ui.c:1981 msgid "Database error" msgstr "Erro da base de datos" #: ../src/gtk-ui/sync-ui.c:1983 msgid "No space left" msgstr "Non hai espazo de abondo" #. TODO identify problem item somehow ? #: ../src/gtk-ui/sync-ui.c:1986 msgid "Failed to process SyncML" msgstr "Fallou o procesamento de SyncML" #: ../src/gtk-ui/sync-ui.c:1988 msgid "Server authorization failed" msgstr "Fallou a autenticación do servidor" #: ../src/gtk-ui/sync-ui.c:1990 msgid "Failed to parse configuration file" msgstr "Fallou a análise do ficheiro de configuración" #: ../src/gtk-ui/sync-ui.c:1992 msgid "Failed to read configuration file" msgstr "Fallou a lectura do ficheiro de configuración" #: ../src/gtk-ui/sync-ui.c:1994 msgid "No configuration found" msgstr "Non se atopou ningunha configuración" #: ../src/gtk-ui/sync-ui.c:1996 msgid "No configuration file found" msgstr "Non se atopou ningún ficheiro de configuración" #: ../src/gtk-ui/sync-ui.c:1998 msgid "Server sent bad content" msgstr "O servidor enviou contento incorrecto" #: ../src/gtk-ui/sync-ui.c:2000 msgid "Transport failure (no connection?)" msgstr "Houbo un fallo do transporte (non hai conexión?)" #: ../src/gtk-ui/sync-ui.c:2002 msgid "Connection timed out" msgstr "A conexión excedeu o tempo de espera" #: ../src/gtk-ui/sync-ui.c:2004 msgid "Connection certificate has expired" msgstr "Caducou o certificado da conexión" #: ../src/gtk-ui/sync-ui.c:2006 msgid "Connection certificate is invalid" msgstr "O certificado da conexión non é válido" #: ../src/gtk-ui/sync-ui.c:2009 msgid "Connection failed" msgstr "Fallou a conexión" #: ../src/gtk-ui/sync-ui.c:2011 msgid "URL is bad" msgstr "O URL é incorrecto" #: ../src/gtk-ui/sync-ui.c:2013 msgid "Server not found" msgstr "Non se atopa o servidor" #: ../src/gtk-ui/sync-ui.c:2015 #, c-format msgid "Error %d" msgstr "Erro %d" #: ../src/gtk-ui/sync-ui.c:2025 msgid "Sync D-Bus service exited unexpectedly" msgstr "O servizo Sync D-Bus saíu inesperadamente" #: ../src/gtk-ui/sync-ui.c:2028 ../src/gtk-ui/sync-ui.c:2079 msgid "Sync Failed" msgstr "Fallou a sincronización" #: ../src/gtk-ui/sync-ui.c:2071 msgid "Sync complete" msgstr "Rematou a sincronización" #: ../src/gtk-ui/sync-ui.c:2076 msgid "Sync canceled" msgstr "Cancelouse a sincronización" #. NOTE extra1 can be error here #: ../src/gtk-ui/sync-ui.c:2094 msgid "Ending sync" msgstr "A rematar a sincronización" #. TRANSLATORS: placeholder is a source name (e.g. 'Calendar') in a progress text #: ../src/gtk-ui/sync-ui.c:2118 #, c-format msgid "Preparing '%s'" msgstr "A preparar \"%s\"" #. TRANSLATORS: placeholder is a source name in a progress text #: ../src/gtk-ui/sync-ui.c:2130 #, c-format msgid "Sending '%s'" msgstr "A enviar \"%s\"" #. TRANSLATORS: placeholder is a source name in a progress text #: ../src/gtk-ui/sync-ui.c:2142 #, c-format msgid "Receiving '%s'" msgstr "A recibir \"%s\"" #: ../src/gtk-ui/ui.glade.h:1 msgid "Data" msgstr "Datos" #: ../src/gtk-ui/ui.glade.h:2 msgid "No sync service in use" msgstr "Non hai ningún servizo de sincronización en uso" #: ../src/gtk-ui/ui.glade.h:3 msgid "Sync failure" msgstr "Fallo da sincronización" #: ../src/gtk-ui/ui.glade.h:4 msgid "Type of Sync" msgstr "Tipo de sincronización" #: ../src/gtk-ui/ui.glade.h:5 msgid "Manual setup" msgstr "Configuración manual" #: ../src/gtk-ui/ui.glade.h:6 msgid "Supported services" msgstr "Servizos admitidos" #: ../src/gtk-ui/ui.glade.h:7 msgid "Add new service" msgstr "Engadir un servizo novo" #: ../src/gtk-ui/ui.glade.h:8 msgid "Back to sync" msgstr "Volver á sincronización" #: ../src/gtk-ui/ui.glade.h:9 msgid "" "Change sync\n" "service" msgstr "" "Mudar de servizo\n" "de sincronización" #: ../src/gtk-ui/ui.glade.h:11 msgid "Delete all local data and replace it with remote data" msgstr "Eliminar todos os datos locais e substituílos por datos remotos" #: ../src/gtk-ui/ui.glade.h:12 msgid "Delete all remote data and replace it with local data" msgstr "Eliminar todos os datos remotos e substituílos por datos locais" #: ../src/gtk-ui/ui.glade.h:13 msgid "Delete this service" msgstr "Eliminar este servizo" #: ../src/gtk-ui/ui.glade.h:14 msgid "Edit service settings" msgstr "Modificar a configuración do servizo" #: ../src/gtk-ui/ui.glade.h:15 msgid "" "If you don't see your service above but know that your sync provider uses " "SyncML\n" "you can setup a service manually." msgstr "" "Se non ve o servizo arriba mais saibe que o fornecedor de sincronización " "emprega SyncML,\n" "pode configurar un servizo manualmente." #: ../src/gtk-ui/ui.glade.h:18 msgid "Merge local and remote data (recommended)" msgstr "Fusionar os datos locais e os remotos (recomendado)" #: ../src/gtk-ui/ui.glade.h:19 msgid "Password" msgstr "Contrasinal" #: ../src/gtk-ui/ui.glade.h:20 msgid "Reset original server settings" msgstr "Restaurar a configuración orixinal do servidor" #: ../src/gtk-ui/ui.glade.h:21 msgid "Save and use this service" msgstr "Gardar e empregar este servizo" #: ../src/gtk-ui/ui.glade.h:22 msgid "Select sync service" msgstr "Seleccionar o servizo de sincronización" #: ../src/gtk-ui/ui.glade.h:23 msgid "Server settings" msgstr "Configuración do servidor" #: ../src/gtk-ui/ui.glade.h:24 msgid "Service name" msgstr "Nome do servizo" #: ../src/gtk-ui/ui.glade.h:25 msgid "" "Sorry, you need an internet\n" "connection to sync." msgstr "" "Desculpe, precisa dunha ligazón\n" "á Internet para sincronizar." #: ../src/gtk-ui/ui.glade.h:27 msgid "Stop using this service" msgstr "Deixar de empregar este servizo" #: ../src/gtk-ui/ui.glade.h:30 msgid "" "Synchronization is not available (D-Bus service does not answer), sorry." msgstr "" "A sincronización non está dispoñíbel (o servizo D-Bus non responde). " "Desculpas." #: ../src/gtk-ui/ui.glade.h:31 msgid "" "To sync you'll need a network connection and an account with a sync " "service.\n" "We support the following services: " msgstr "" "Para sincronizar é precisa unha conexión á rede e unha conta con servizo de " "sincronización.\n" "Admitimos os servizos seguintes: " #: ../src/gtk-ui/ui.glade.h:33 msgid "Username" msgstr "Nome de usuario" #: ../src/gtk-ui/ui.glade.h:34 msgid "" "You haven't selected a sync service yet. Sync services let you \n" "synchronize your data between your netbook and a web service." msgstr "" "Aínda non seleccionou ningún servizo de sincronización. Os servizos de " "sincronización\n" " permiten sincronizar os datos entre o seu portátil e un servizo web." #: ../src/gtk-ui/sync.desktop.in.h:2 ../src/gtk-ui/sync-gtk.desktop.in.h:2 msgid "Up to date" msgstr "Actualizado" #: ../src/gtk-ui/sync-gtk.desktop.in.h:1 msgid "Sync (GTK)" msgstr "Sincronizar (GTK)" syncevolution_1.4/po/hu.po000066400000000000000000000337761230021373600157700ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: syncevolution\n" "Report-Msgid-Bugs-To: http://moblin.org/projects/syncevolution\n" "POT-Creation-Date: 2009-12-21 18:45+0000\n" "PO-Revision-Date: 2010-01-26 16:07+0100\n" "Last-Translator: Gergely Lónyai \n" "Language-Team: Hungarian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n!=1;\n" "X-Poedit-Language: Hungarian\n" "X-Poedit-Country: HUNGARY\n" "X-Poedit-SourceCharset: utf-8\n" #. TRANSLATORS: this is the application name that may be used by e.g. #. the windowmanager #: ../src/gtk-ui/main.c:31 #: ../src/gtk-ui/ui.glade.h:20 #: ../src/gtk-ui/sync.desktop.in.h:1 msgid "Sync" msgstr "Szinkronizálás" #. TRANSLATORS: The name was changed from 'Addressbook' to #. 'Contacts' to match naming in rest of moblin. Please make sure the #. name you use matches the name in the panel and Contacts application. #: ../src/gtk-ui/sync-ui.c:192 msgid "Contacts" msgstr "Kapcsolatok" #: ../src/gtk-ui/sync-ui.c:194 msgid "Calendar" msgstr "Naptár" #: ../src/gtk-ui/sync-ui.c:196 msgid "Todo" msgstr "Teendők listája" #: ../src/gtk-ui/sync-ui.c:198 msgid "Memo" msgstr "Feljegyzés" #. TODO show in UI: failed to abort sync (while syncing) #: ../src/gtk-ui/sync-ui.c:273 msgid "Failed to abort sync" msgstr "Nem sikerült a szinkronizálás" #. TODO show in UI: sync failed (failed to even start) #: ../src/gtk-ui/sync-ui.c:287 msgid "Failed to start sync" msgstr "Nem sikerült a szinkronizálás elindítása" #: ../src/gtk-ui/sync-ui.c:293 msgid "Starting sync" msgstr "Szinkronizálás elindítása" #: ../src/gtk-ui/sync-ui.c:309 msgid "Trying to cancel sync" msgstr "Kísérlet a szinkronizálás leállítására" #: ../src/gtk-ui/sync-ui.c:316 #, c-format msgid "Do you want to delete all local data and replace it with data from %s? This is not usually advised." msgstr "" #: ../src/gtk-ui/sync-ui.c:321 #, c-format msgid "Do you want to delete all data in %s and replace it with your local data? This is not usually advised." msgstr "" #: ../src/gtk-ui/sync-ui.c:338 msgid "No, cancel sync" msgstr "Állítsd le a szinkronizálást" #: ../src/gtk-ui/sync-ui.c:340 msgid "Yes, delete and replace" msgstr "Töröld vagy cseréld le" #: ../src/gtk-ui/sync-ui.c:377 msgid "Last synced just seconds ago" msgstr "Az utolsó szinkronizálás pár másodperce volt" #: ../src/gtk-ui/sync-ui.c:380 msgid "Last synced a minute ago" msgstr "Az utolsó szinkronizálás egy perce volt" #: ../src/gtk-ui/sync-ui.c:383 #, c-format msgid "Last synced %ld minutes ago" msgstr "Az utolsó szinkronizálás %ld perce volt" #: ../src/gtk-ui/sync-ui.c:386 msgid "Last synced an hour ago" msgstr "Az utolsó szinkronizálás pár órája volt" #: ../src/gtk-ui/sync-ui.c:389 #, c-format msgid "Last synced %ld hours ago" msgstr "Az utolsó szinkronizálás %ld órája volt" #: ../src/gtk-ui/sync-ui.c:392 msgid "Last synced a day ago" msgstr "Az utolsó szinkronizálás egy napja volt" #: ../src/gtk-ui/sync-ui.c:395 #, c-format msgid "Last synced %ld days ago" msgstr "Az utolsó szinkronizálás %ld napja volt" #: ../src/gtk-ui/sync-ui.c:480 msgid "Sync again" msgstr "Szinkronizálás újra" #: ../src/gtk-ui/sync-ui.c:482 #: ../src/gtk-ui/ui.glade.h:21 msgid "Sync now" msgstr "Szinkronizálás most" #: ../src/gtk-ui/sync-ui.c:493 msgid "Syncing" msgstr "Szinkronizálás" #: ../src/gtk-ui/sync-ui.c:499 msgid "Cancel sync" msgstr "Szinkronizálás leállítása" #. TRANSLATORS: placeholder is a source name, shown with checkboxes in main window #: ../src/gtk-ui/sync-ui.c:849 #, c-format msgid "%s (not supported by this service)" msgstr "%s (nem támogatott ezen az eszközön)" #: ../src/gtk-ui/sync-ui.c:1046 msgid "Failed to get list of configured services from SyncEvolution" msgstr "" #: ../src/gtk-ui/sync-ui.c:1117 msgid "Failed to get list of supported services from SyncEvolution" msgstr "" #: ../src/gtk-ui/sync-ui.c:1221 msgid "Sync complete" msgstr "A szinkronizálás kész" #: ../src/gtk-ui/sync-ui.c:1310 #, c-format msgid "Preparing '%s'" msgstr "'%s' előkészítése" #: ../src/gtk-ui/sync-ui.c:1313 #, c-format msgid "Receiving '%s'" msgstr "'%s' fogadása" #: ../src/gtk-ui/sync-ui.c:1316 #, c-format msgid "Sending '%s'" msgstr "'%s' küldése" #: ../src/gtk-ui/sync-ui.c:1705 msgid "Waiting for current operation to finish..." msgstr "Várakozás az aktuális művelet befejeződésére..." #: ../src/gtk-ui/sync-ui.c:1750 msgid "Not authorized" msgstr "Nincs jogosultság" #: ../src/gtk-ui/sync-ui.c:1752 msgid "Forbidden" msgstr "Visszautasítva" #: ../src/gtk-ui/sync-ui.c:1754 msgid "Not found" msgstr "Nem található" #: ../src/gtk-ui/sync-ui.c:1756 msgid "Fatal database error" msgstr "Végzetes adatbázishiba" #: ../src/gtk-ui/sync-ui.c:1758 msgid "Database error" msgstr "Adatbázishiba" #: ../src/gtk-ui/sync-ui.c:1760 msgid "No space left" msgstr "Nincs több szabad hely" #. TODO identify problem item somehow ? #: ../src/gtk-ui/sync-ui.c:1763 msgid "Failed to process SyncML" msgstr "SyncML feldolgozása sikertelen" #: ../src/gtk-ui/sync-ui.c:1765 msgid "Server authorization failed" msgstr "A szerver azonosítása sikertelen" #: ../src/gtk-ui/sync-ui.c:1767 msgid "Failed to parse configuration file" msgstr "A konfigurációs fájl feldolgozása sikertelen" #: ../src/gtk-ui/sync-ui.c:1769 msgid "Failed to read configuration file" msgstr "Nem sikerült olvasni a konfigurációs fájlból" #: ../src/gtk-ui/sync-ui.c:1771 msgid "No configuration found" msgstr "A beállítások nem találhatóak" #: ../src/gtk-ui/sync-ui.c:1773 msgid "No configuration file found" msgstr "A konfigurációs fájl nem található" #: ../src/gtk-ui/sync-ui.c:1775 msgid "Server sent bad content" msgstr "A kiszolgáló hibás tartalmat küldött" #: ../src/gtk-ui/sync-ui.c:1777 msgid "Transport failure (no connection?)" msgstr "Az átvitel nem sikerült (nincs kapcsolat?)" #: ../src/gtk-ui/sync-ui.c:1779 msgid "Connection timed out" msgstr "Időtúllépés a kapcsolatban" #: ../src/gtk-ui/sync-ui.c:1781 msgid "Connection certificate has expired" msgstr "A kapcsolat tanúsítványa lejárt" #: ../src/gtk-ui/sync-ui.c:1783 msgid "Connection certificate is invalid" msgstr "A kapcsolat tanúsítványa érvénytelen." #: ../src/gtk-ui/sync-ui.c:1786 msgid "Connection failed" msgstr "A kapcsolódás sikertelen" #: ../src/gtk-ui/sync-ui.c:1788 msgid "URL is bad" msgstr "Az URL hibás" #: ../src/gtk-ui/sync-ui.c:1790 msgid "Server not found" msgstr "A kiszolgáló nem található" #: ../src/gtk-ui/sync-ui.c:1792 #, c-format msgid "Error %d" msgstr "Hiba %d" #. TODO show in UI: server disappeared #: ../src/gtk-ui/sync-ui.c:1803 msgid "Syncevolution.Server D-Bus service exited unexpectedly" msgstr "" #: ../src/gtk-ui/sync-ui.c:1806 msgid "Sync Failed" msgstr "Szinkronizáció sikertelen" #: ../src/gtk-ui/sync-ui-config.c:95 #, c-format msgid "There was one remote rejection." msgid_plural "There were %d remote rejections." msgstr[0] "" msgstr[1] "" #: ../src/gtk-ui/sync-ui-config.c:100 #, c-format msgid "There was one local rejection." msgid_plural "There were %d local rejections." msgstr[0] "" msgstr[1] "" #: ../src/gtk-ui/sync-ui-config.c:105 #, c-format msgid "There were %d local rejections and %d remote rejections." msgstr "" #: ../src/gtk-ui/sync-ui-config.c:110 #, c-format msgid "Last time: No changes." msgstr "Utolsó időpont: nincs változás." #: ../src/gtk-ui/sync-ui-config.c:112 #, c-format msgid "Last time: Sent one change." msgid_plural "Last time: Sent %d changes." msgstr[0] "Utolsó bejegyzés: Egy változtatás elküldve." msgstr[1] "Utolsó bejegyzés: %d változtatás elküldve." #. This is about changes made to the local data. Not all of these #. changes were requested by the remote server, so "applied" #. is a better word than "received" (bug #5185). #: ../src/gtk-ui/sync-ui-config.c:120 #, c-format msgid "Last time: Applied one change." msgid_plural "Last time: Applied %d changes." msgstr[0] "Utolsó bejegyzés: Egy változtatás alkalmazva." msgstr[1] "Utolsó bejegyzés: %d változtatás alkalmazva." #: ../src/gtk-ui/sync-ui-config.c:125 #, c-format msgid "Last time: Applied %d changes and sent %d changes." msgstr "Utolsó bejegyzés: %d változtatás alkalmazva, %d változtatás elküldve" #: ../src/gtk-ui/ui.glade.h:1 msgid "Data" msgstr "Adat" #: ../src/gtk-ui/ui.glade.h:2 msgid "No sync service in use" msgstr "Nincs használva sznkronizálás" #: ../src/gtk-ui/ui.glade.h:3 msgid "Sync failure" msgstr "Szinkronizáció sikertelen" #: ../src/gtk-ui/ui.glade.h:4 msgid "Type of Sync" msgstr "Szinkronizálás típusa" #: ../src/gtk-ui/ui.glade.h:5 msgid "Manual setup" msgstr "Kézi beállítás" #: ../src/gtk-ui/ui.glade.h:6 msgid "Supported services" msgstr "Támogatott eszközök" #: ../src/gtk-ui/ui.glade.h:7 msgid "Add new service" msgstr "Új szolgáltatás felvétele" #: ../src/gtk-ui/ui.glade.h:8 msgid "Back to sync" msgstr "Vissza a szinkronizációhoz" #: ../src/gtk-ui/ui.glade.h:9 msgid "" "Change sync\n" "service" msgstr "" "Szinkronizációs szolgáltatás\n" "megváltoztatása" #: ../src/gtk-ui/ui.glade.h:11 msgid "Delete all local data and replace it with remote data" msgstr "Minden helyi adat törlése, és a távoli adatok letöltése" #: ../src/gtk-ui/ui.glade.h:12 msgid "Delete all remote data and replace it with local data" msgstr "Minden távoli adat törlése, és minden helyi adat letöltése" #: ../src/gtk-ui/ui.glade.h:13 msgid "Edit service settings" msgstr "A szolgáltatás beállításainak módosítása" #: ../src/gtk-ui/ui.glade.h:14 msgid "" "If you don't see your service above but know that your sync provider uses SyncML\n" "you can setup a service manually." msgstr "" #: ../src/gtk-ui/ui.glade.h:16 msgid "Merge local and remote data (recommended)" msgstr "A helyi és távoli adatok összefésülése (ajánlott)" #: ../src/gtk-ui/ui.glade.h:17 msgid "Select sync service" msgstr "Szinkronizációs szolgáltatás kiválasztása" #: ../src/gtk-ui/ui.glade.h:18 msgid "" "Sorry, you need an internet\n" "connection to sync." msgstr "" "Bocsi, de kell egy internetkapcsolat\n" "a szinkronizáláshoz." #: ../src/gtk-ui/ui.glade.h:22 msgid "Sync settings" msgstr "Szinkronizációs beállítások" #: ../src/gtk-ui/ui.glade.h:23 msgid "Synchronization is not available (D-Bus service does not answer), sorry." msgstr "Bocsi, a szinkronizálás jelenleg nem elérhető (a D-Bus szolgáltatás nem válaszol)." #: ../src/gtk-ui/ui.glade.h:24 msgid "" "To sync you'll need a network connection and an account with a sync service.\n" "We support the following services: " msgstr "" #: ../src/gtk-ui/ui.glade.h:26 msgid "" "You haven't selected a sync service yet. Sync services let you \n" "synchronize your data between your netbook and a web service." msgstr "" #: ../src/gtk-ui/sync.desktop.in.h:2 #: ../src/gtk-ui/sync-gtk.desktop.in.h:2 msgid "Up to date" msgstr "Friss" #: ../src/gtk-ui/sync-gtk.desktop.in.h:1 msgid "Sync (GTK)" msgstr "Sync (GTK)" #: ../src/gtk-ui/sync-config-widget.c:48 msgid "ScheduleWorld enables you to keep your contacts, events, tasks, and notes in sync." msgstr "" #: ../src/gtk-ui/sync-config-widget.c:51 msgid "Google Sync can back up and synchronize your Address Book with your Gmail contacts." msgstr "A Google Sync a Címjegyzéket szinkronizálja a Google kapcsolatokkal" #. TRANSLATORS: Please include the word "demo" (or the equivalent in #. your language): Funambol is going to be a 90 day demo service #. in the future #: ../src/gtk-ui/sync-config-widget.c:57 msgid "Back up your contacts and calendar. Sync with a singleclick, anytime, anywhere (DEMO)." msgstr "Kapcsolatok és a naptár mentése. Szinkronizálj egy klikkel bármikor, bárhol (DEMO)." #: ../src/gtk-ui/sync-config-widget.c:227 msgid "Service must have a name and server URL" msgstr "A szolgáltatáshoz adj meg egy nevet és egy kiszolgáló URL címét" #: ../src/gtk-ui/sync-config-widget.c:271 msgid "Reset service" msgstr "Szolgáltatás újraindítása" #: ../src/gtk-ui/sync-config-widget.c:274 msgid "Delete service" msgstr "Szolgáltatás törlése" #: ../src/gtk-ui/sync-config-widget.c:284 msgid "Save and use" msgstr "Mentés és használat" #: ../src/gtk-ui/sync-config-widget.c:287 msgid "" "Save and replace\n" "current service" msgstr "" "Elment és a jelenlegi\n" "szolgáltatás lecserélése" #. TRANSLATORS: label for an entry in service configuration. #. * Placeholder is a source name in settings window. #. * Example: "Addressbook URI" #: ../src/gtk-ui/sync-config-widget.c:355 #, c-format msgid "%s URI" msgstr "URI: %s" #. TRANSLATORS: label of a entry in service configuration #: ../src/gtk-ui/sync-config-widget.c:419 msgid "Server URL" msgstr "Kiszolgáló URL" #. TRANSLATORS: this is the epander label for server settings #: ../src/gtk-ui/sync-config-widget.c:459 msgid "Hide server settings" msgstr "Kiszolgáló beállítások elrejtése" #: ../src/gtk-ui/sync-config-widget.c:461 msgid "Show server settings" msgstr "Kiszolgáló beállítások mutatása" #. TRANSLATORS: service title for services that are not based on a #. * template in service list, the placeholder is the name of the service #: ../src/gtk-ui/sync-config-widget.c:512 #, c-format msgid "%s - manually setup" msgstr "%s - kézi beállítás" #. TRANSLATORS: linkbutton label #: ../src/gtk-ui/sync-config-widget.c:1189 msgid "Launch website" msgstr "Weboldal futtatása" #. TRANSLATORS: button label #: ../src/gtk-ui/sync-config-widget.c:1198 msgid "Set up now" msgstr "Beállítás most" #. TRANSLATORS: labels of entries #: ../src/gtk-ui/sync-config-widget.c:1236 msgid "Username" msgstr "Felhasználónév" #: ../src/gtk-ui/sync-config-widget.c:1251 msgid "Password" msgstr "Jelszó" #: ../src/gtk-ui/sync-config-widget.c:1295 msgid "Stop using service" msgstr "Szolgáltatás használatának leállítása" syncevolution_1.4/po/id.po000066400000000000000000000354411230021373600157370ustar00rootroot00000000000000#: ../src/gtk-ui/sync-ui.c:764 msgid "" msgstr "" "Project-Id-Version: syncevolution\n" "Report-Msgid-Bugs-To: http://moblin.org/projects/syncevolution\n" "POT-Creation-Date: 2009-10-01 19:27+0000\n" "PO-Revision-Date: \n" "Last-Translator: Andika Triwidada \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-Language: Indonesian\n" "X-Poedit-Country: INDONESIA\n" "X-Poedit-SourceCharset: utf-8\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. TRANSLATORS: this is the application name that may be used by e.g. #. the windowmanager #: ../src/gtk-ui/main.c:31 ../src/gtk-ui/ui.glade.h:28 #: ../src/gtk-ui/sync.desktop.in.h:1 msgid "Sync" msgstr "Selaraskan" #: ../src/gtk-ui/sync-ui.c:259 msgid "Addressbook" msgstr "Buku Alamat" #: ../src/gtk-ui/sync-ui.c:261 msgid "Calendar" msgstr "Kalender" #: ../src/gtk-ui/sync-ui.c:263 msgid "Todo" msgstr "Todo" #: ../src/gtk-ui/sync-ui.c:265 msgid "Memo" msgstr "Memo" #: ../src/gtk-ui/sync-ui.c:320 msgid "Failed to save current service in GConf configuration system" msgstr "Gagal menyimpan layanan kini ke sistem konfigurasi GConf" #: ../src/gtk-ui/sync-ui.c:331 msgid "Failed to save service configuration to SyncEvolution" msgstr "Gagal menyimpan konfigurasi layanan ke SyncEvolution" #: ../src/gtk-ui/sync-ui.c:416 msgid "Failed to get service configuration from SyncEvolution" msgstr "Gagal mendapat konfigurasi layanan dari SyncEvolution" #: ../src/gtk-ui/sync-ui.c:479 msgid "Failed to remove service configuration from SyncEvolution" msgstr "Gagal menghapus konfigurasi layanan dari SyncEvolution" #: ../src/gtk-ui/sync-ui.c:599 msgid "Service must have a name and server URL" msgstr "Layanan mesti punya URL server dan nama" #. sync is no longer in progress for some reason #: ../src/gtk-ui/sync-ui.c:675 msgid "Failed to cancel: sync was no longer in progress" msgstr "Gagal membatalkan: sync tak lagi sedang berlangsung" #: ../src/gtk-ui/sync-ui.c:679 msgid "Failed to cancel sync" msgstr "Gagal membatalkan sync" #: ../src/gtk-ui/sync-ui.c:683 msgid "Canceling sync" msgstr "Membatalkan sync" #: ../src/gtk-ui/sync-ui.c:697 msgid "Trying to cancel sync" msgstr "Mencoba membatalkan sync" #: ../src/gtk-ui/sync-ui.c:704 #, c-format msgid "" "Do you want to delete all local data and replace it with data from %s? This " "is not usually advised." msgstr "" "Apakah Anda ingin menghapus semua data lokal dan menggantinya dengan data " "dari %s? Ini biasanya tak disarankan." #: ../src/gtk-ui/sync-ui.c:709 #, c-format msgid "" "Do you want to delete all data in %s and replace it with your local data? " "This is not usually advised." msgstr "" "Apakah Anda ingin menghapus semua data di %s dan menggantinya dengan data " "lokal Anda? Biasanya ini tak disarankan." #: ../src/gtk-ui/sync-ui.c:726 msgid "No, cancel sync" msgstr "Tidak, batalkan sync" #: ../src/gtk-ui/sync-ui.c:727 msgid "Yes, delete and replace" msgstr "Ya, hapus dan gantikan" #: ../src/gtk-ui/sync-ui.c:749 msgid "No sources are enabled, not syncing" msgstr "Tak ada sumber yang diaktifkan, tak menyelaraskan" #: ../src/gtk-ui/sync-ui.c:766 msgid "A sync is already in progress" msgstr "Syns sedang berlangsung" #: ../src/gtk-ui/sync-ui.c:768 msgid "Failed to start sync" msgstr "Gagal memulai sync" #: ../src/gtk-ui/sync-ui.c:773 msgid "Starting sync" msgstr "Memulai sync" #: ../src/gtk-ui/sync-ui.c:798 msgid "Last synced just seconds ago" msgstr "Diselaraskan terakhir beberapa detik yang lalu" #: ../src/gtk-ui/sync-ui.c:801 msgid "Last synced a minute ago" msgstr "Diselaraskan terakhir semenit yang lalu" #: ../src/gtk-ui/sync-ui.c:804 #, c-format msgid "Last synced %ld minutes ago" msgstr "Diselaraskan terakhir %ld menit yang lalu" #: ../src/gtk-ui/sync-ui.c:807 msgid "Last synced an hour ago" msgstr "Diselaraskan terakhir sejam yang lalu" #: ../src/gtk-ui/sync-ui.c:810 #, c-format msgid "Last synced %ld hours ago" msgstr "Diselaraskan terakhir %ld jam yang lalu" #: ../src/gtk-ui/sync-ui.c:813 msgid "Last synced a day ago" msgstr "Diselaraskan terakhir sehari yang lalu" #: ../src/gtk-ui/sync-ui.c:816 #, c-format msgid "Last synced %ld days ago" msgstr "Diselaraskan terakhir %ld hari yang lalu" #: ../src/gtk-ui/sync-ui.c:901 msgid "Sync again" msgstr "Sync lagi" #: ../src/gtk-ui/sync-ui.c:903 ../src/gtk-ui/ui.glade.h:29 msgid "Sync now" msgstr "Sync sekarang" #: ../src/gtk-ui/sync-ui.c:912 msgid "Syncing" msgstr "Menyelaraskan" #: ../src/gtk-ui/sync-ui.c:918 msgid "Cancel sync" msgstr "Batalkan sync" #. TRANSLATORS: placeholder is a source name, shown with checkboxes in main window #: ../src/gtk-ui/sync-ui.c:1265 #, c-format msgid "%s (not supported by this service)" msgstr "%s (tak didukung oleh layanan ini)" #: ../src/gtk-ui/sync-ui.c:1298 #, c-format msgid "There was one remote rejection." msgid_plural "There were %d remote rejections." msgstr[0] "Ada satu penolakan remote." msgstr[1] "Ada %d penolakan remote." #: ../src/gtk-ui/sync-ui.c:1303 #, c-format msgid "There was one local rejection." msgid_plural "There were %d local rejections." msgstr[0] "Ada satu penolakan lokal." msgstr[1] "Ada %d penolakan lokal." #: ../src/gtk-ui/sync-ui.c:1308 #, c-format msgid "There were %d local rejections and %d remote rejections." msgstr "Ada %d penolakan lokal dan %d penolakan " #: ../src/gtk-ui/sync-ui.c:1313 #, c-format msgid "Last time: No changes." msgstr "Terakhir kali: Tak ada perubahan." #: ../src/gtk-ui/sync-ui.c:1315 #, c-format msgid "Last time: Sent one change." msgid_plural "Last time: Sent %d changes." msgstr[0] "Terakhir kali: Dikirim satu perubahan." msgstr[1] "Terakhir kali: Dikirim %d perubahan." #. This is about changes made to the local data. Not all of these #. changes were requested by the remote server, so "applied" #. is a better word than "received" (bug #5185). #: ../src/gtk-ui/sync-ui.c:1323 #, c-format msgid "Last time: Applied one change." msgid_plural "Last time: Applied %d changes." msgstr[0] "Terakhir kali: Diterapkan satu perubahan." msgstr[1] "Terakhir kali: Diterapkan %d perubahan." #: ../src/gtk-ui/sync-ui.c:1328 #, c-format msgid "Last time: Applied %d changes and sent %d changes." msgstr "Terakhir kali: Diterapkan %d perubahan dan dikirim %d perubahan." #: ../src/gtk-ui/sync-ui.c:1420 msgid "Failed to get server configuration from SyncEvolution" msgstr "Gagal mendapat konfigurasi server dari SyncEvolution" #: ../src/gtk-ui/sync-ui.c:1472 msgid "" "ScheduleWorld enables you to keep your contacts, events, tasks, and notes in " "sync." msgstr "" "ScheduleWorld memungkinkan Anda menjaga kontak, peristiwa, tugas, dan " "catatan Anda tetap selaras." #: ../src/gtk-ui/sync-ui.c:1475 msgid "" "Google Sync can back up and synchronize your Address Book with your Gmail " "contacts." msgstr "" "Google Sync dapat membuat cadangan dan menyelaraskan Buku Alamat Anda dengan " "kontak Gmail Anda." #. TRANSLATORS: Please include the word "demo" (or the equivalent in #. your language): Funambol is going to be a 90 day demo service #. in the future #: ../src/gtk-ui/sync-ui.c:1481 msgid "" "Back up your contacts and calendar. Sync with a singleclick, anytime, " "anywhere (DEMO)." msgstr "" "Buat cadangan kontak dan kalender Anda. Selaraskan dengan satu klik, " "kapanpun, dari manapun (DEMO)." #: ../src/gtk-ui/sync-ui.c:1509 msgid "New service" msgstr "Layanan baru" #: ../src/gtk-ui/sync-ui.c:1556 msgid "Server URL" msgstr "URL Server" #. TRANSLATORS: placeholder is a source name in settings window #: ../src/gtk-ui/sync-ui.c:1578 #, c-format msgid "%s URI" msgstr "%s URI" #: ../src/gtk-ui/sync-ui.c:1715 ../src/gtk-ui/ui.glade.h:17 msgid "Launch website" msgstr "Luncurkan situs web" #: ../src/gtk-ui/sync-ui.c:1719 msgid "Setup and use" msgstr "Persiapkan dan gunakan" #: ../src/gtk-ui/sync-ui.c:1765 msgid "Failed to get list of manually setup services from SyncEvolution" msgstr "" "Gagal mendapat daftar layanan yang disiapkan secara manual dari SyncEvolution" #: ../src/gtk-ui/sync-ui.c:1806 msgid "Failed to get list of supported services from SyncEvolution" msgstr "Gagal mendapat daftar layanan yang didukung dari SyncEvolution" #. TODO: this is a hack... SyncEnd should be a signal of it's own, #. not just hacked on top of the syncevolution error codes #: ../src/gtk-ui/sync-ui.c:1967 msgid "Service configuration not found" msgstr "Konfigurasi layanan tak ditemukan" #: ../src/gtk-ui/sync-ui.c:1973 msgid "Not authorized" msgstr "Tidak diijinkan" #: ../src/gtk-ui/sync-ui.c:1975 msgid "Forbidden" msgstr "Terlarang" #: ../src/gtk-ui/sync-ui.c:1977 msgid "Not found" msgstr "Tidak ketemu" #: ../src/gtk-ui/sync-ui.c:1979 msgid "Fatal database error" msgstr "Galat basis data fatal" #: ../src/gtk-ui/sync-ui.c:1981 msgid "Database error" msgstr "Galat basis data" #: ../src/gtk-ui/sync-ui.c:1983 msgid "No space left" msgstr "Tidak ada ruang tersisa" #. TODO identify problem item somehow ? #: ../src/gtk-ui/sync-ui.c:1986 msgid "Failed to process SyncML" msgstr "Gagal memroses SyncML" #: ../src/gtk-ui/sync-ui.c:1988 msgid "Server authorization failed" msgstr "Otorisasi server gagal" #: ../src/gtk-ui/sync-ui.c:1990 msgid "Failed to parse configuration file" msgstr "Gagal mengurai berkas konfigurasi" #: ../src/gtk-ui/sync-ui.c:1992 msgid "Failed to read configuration file" msgstr "Gagal membaca berkas konfigurasi" #: ../src/gtk-ui/sync-ui.c:1994 msgid "No configuration found" msgstr "Konfigurasi tak ditemukan" #: ../src/gtk-ui/sync-ui.c:1996 msgid "No configuration file found" msgstr "Berkas konfigurasi tak ditemukan" #: ../src/gtk-ui/sync-ui.c:1998 msgid "Server sent bad content" msgstr "Server mengirim muatan cacat" #: ../src/gtk-ui/sync-ui.c:2000 msgid "Transport failure (no connection?)" msgstr "Kegagalan transport (tiada koneksi?)" #: ../src/gtk-ui/sync-ui.c:2002 msgid "Connection timed out" msgstr "Koneksi time out" #: ../src/gtk-ui/sync-ui.c:2004 msgid "Connection certificate has expired" msgstr "Sertifikat server sudah kadaluwarsa" #: ../src/gtk-ui/sync-ui.c:2006 msgid "Connection certificate is invalid" msgstr "Sertifikat koneksi tak valid" #: ../src/gtk-ui/sync-ui.c:2009 msgid "Connection failed" msgstr "Koneksi gagal" #: ../src/gtk-ui/sync-ui.c:2011 msgid "URL is bad" msgstr "URL cacat" #: ../src/gtk-ui/sync-ui.c:2013 msgid "Server not found" msgstr "Server tak ditemukan" #: ../src/gtk-ui/sync-ui.c:2015 #, c-format msgid "Error %d" msgstr "Galat %d" #: ../src/gtk-ui/sync-ui.c:2025 msgid "Sync D-Bus service exited unexpectedly" msgstr "Layanan D-Bus Sync keluar tak disangka-sangka" #: ../src/gtk-ui/sync-ui.c:2028 ../src/gtk-ui/sync-ui.c:2079 msgid "Sync Failed" msgstr "Sync Gagal" #: ../src/gtk-ui/sync-ui.c:2071 msgid "Sync complete" msgstr "Sync komplit" #: ../src/gtk-ui/sync-ui.c:2076 msgid "Sync canceled" msgstr "Sync dibatalkan" #. NOTE extra1 can be error here #: ../src/gtk-ui/sync-ui.c:2094 msgid "Ending sync" msgstr "Mengakhiri sync" #. TRANSLATORS: placeholder is a source name (e.g. 'Calendar') in a progress text #: ../src/gtk-ui/sync-ui.c:2118 #, c-format msgid "Preparing '%s'" msgstr "Menyiapkan '%s'" #. TRANSLATORS: placeholder is a source name in a progress text #: ../src/gtk-ui/sync-ui.c:2130 #, c-format msgid "Sending '%s'" msgstr "Mengirim '%s'" #. TRANSLATORS: placeholder is a source name in a progress text #: ../src/gtk-ui/sync-ui.c:2142 #, c-format msgid "Receiving '%s'" msgstr "Menerima '%s'" #: ../src/gtk-ui/ui.glade.h:1 msgid "Data" msgstr "Data" #: ../src/gtk-ui/ui.glade.h:2 msgid "No sync service in use" msgstr "Tak ada layanan sync yang dipakai" #: ../src/gtk-ui/ui.glade.h:3 msgid "Sync failure" msgstr "Kegagalan sync" #: ../src/gtk-ui/ui.glade.h:4 msgid "Type of Sync" msgstr "Tipe Sync" #: ../src/gtk-ui/ui.glade.h:5 msgid "Manual setup" msgstr "Penyiapan manual" #: ../src/gtk-ui/ui.glade.h:6 msgid "Supported services" msgstr "Layanan yang didukung" #: ../src/gtk-ui/ui.glade.h:7 msgid "Add new service" msgstr "Tambah layanan baru" #: ../src/gtk-ui/ui.glade.h:8 msgid "Back to sync" msgstr "Kembali ke sync" #: ../src/gtk-ui/ui.glade.h:9 msgid "" "Change sync\n" "service" msgstr "" "Ubah layanan\n" "sync" #: ../src/gtk-ui/ui.glade.h:11 msgid "Delete all local data and replace it with remote data" msgstr "Hapus semua data lokal dan gantikan dengan data remote" #: ../src/gtk-ui/ui.glade.h:12 msgid "Delete all remote data and replace it with local data" msgstr "Hapus semua data remote dan gantikan dengan data lokal" #: ../src/gtk-ui/ui.glade.h:13 msgid "Delete this service" msgstr "Hapus layanan ini" #: ../src/gtk-ui/ui.glade.h:14 msgid "Edit service settings" msgstr "Edit pengaturan layanan" #: ../src/gtk-ui/ui.glade.h:15 msgid "" "If you don't see your service above but know that your sync provider uses " "SyncML\n" "you can setup a service manually." msgstr "" "Bila Anda tak melihat layanan Anda di atas tapi tahu bahwa penyedia sync " "Anda memakai SyncML\n" "Anda dapat mempersiapkan suatu layanan secara manual." #: ../src/gtk-ui/ui.glade.h:18 msgid "Merge local and remote data (recommended)" msgstr "Gabunga data lokal dan remote (disarankan)" #: ../src/gtk-ui/ui.glade.h:19 msgid "Password" msgstr "Kata sandi" #: ../src/gtk-ui/ui.glade.h:20 msgid "Reset original server settings" msgstr "Tata ulang pengaturan asli server" #: ../src/gtk-ui/ui.glade.h:21 msgid "Save and use this service" msgstr "Simpan dan pakai layanan ini" #: ../src/gtk-ui/ui.glade.h:22 msgid "Select sync service" msgstr "Pilih layanan penyelarasan" #: ../src/gtk-ui/ui.glade.h:23 msgid "Server settings" msgstr "Pengaturan server" #: ../src/gtk-ui/ui.glade.h:24 msgid "Service name" msgstr "Nama layanan" #: ../src/gtk-ui/ui.glade.h:25 msgid "" "Sorry, you need an internet\n" "connection to sync." msgstr "" "Maaf, Anda perlu sambungan\n" "internet untuk sync." #: ../src/gtk-ui/ui.glade.h:27 msgid "Stop using this service" msgstr "Berhenti pakai layanan ini" #: ../src/gtk-ui/ui.glade.h:30 msgid "" "Synchronization is not available (D-Bus service does not answer), sorry." msgstr "Penyelarasan tak tersedia (layanan D-Bus tak menjawab), maaf." #: ../src/gtk-ui/ui.glade.h:31 msgid "" "To sync you'll need a network connection and an account with a sync " "service.\n" "We support the following services: " msgstr "" "Untuk sync Anda perlu koneksi jaringan dan sebuah akun dari layanan sync.\n" "Kami mendukung layanan berikut:" #: ../src/gtk-ui/ui.glade.h:33 msgid "Username" msgstr "Nama pengguna" #: ../src/gtk-ui/ui.glade.h:34 msgid "" "You haven't selected a sync service yet. Sync services let you \n" "synchronize your data between your netbook and a web service." msgstr "" "Anda belum memilih suatu layanan sync. Layanan sync memungkinkan Anda\n" "menyelaraskan data antara netbook Anda dan suatu layanan web." #: ../src/gtk-ui/sync.desktop.in.h:2 ../src/gtk-ui/sync-gtk.desktop.in.h:2 msgid "Up to date" msgstr "Terkini" #: ../src/gtk-ui/sync-gtk.desktop.in.h:1 msgid "Sync (GTK)" msgstr "Sync (GTK)" syncevolution_1.4/po/it.po000066400000000000000000000751711230021373600157630ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # # Translators: # Milo Casagrande , 2009. # , 2011. # , 2011. msgid "" msgstr "" "Project-Id-Version: syncevolution\n" "Report-Msgid-Bugs-To: https://bugs.meego.com/\n" "POT-Creation-Date: 2011-12-05 10:21-0800\n" "PO-Revision-Date: 2011-12-06 00:27+0000\n" "Last-Translator: GLS_Translator_ITA2 \n" "Language-Team: Italian (http://www.transifex.net/projects/p/meego/team/it/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: it\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" #. TRANSLATORS: this is the application name that may be used by e.g. #. the windowmanager #: ../src/gtk-ui/main.c:40 ../src/gtk-ui/ui.glade.h:38 #: ../src/gtk-ui/sync.desktop.in.h:1 #: ../src/gnome-bluetooth/syncevolution.c:112 msgid "Sync" msgstr "Sincronizzazione" #: ../src/gtk-ui/sync-ui.c:266 msgid "Contacts" msgstr "Contatti" #: ../src/gtk-ui/sync-ui.c:268 msgid "Appointments" msgstr "Appuntamenti" #: ../src/gtk-ui/sync-ui.c:270 ../src/gtk-ui/ui.glade.h:40 msgid "Tasks" msgstr "Compiti" #: ../src/gtk-ui/sync-ui.c:272 msgid "Notes" msgstr "Note" #. TRANSLATORS: This is a "combination source" for syncing with devices #. * that combine appointments and tasks. the name should match the ones #. * used for calendar and todo above #: ../src/gtk-ui/sync-ui.c:277 msgid "Appointments & Tasks" msgstr "Appuntamenti e compiti" #: ../src/gtk-ui/sync-ui.c:349 msgid "Starting sync" msgstr "Avvio sincronizzazione" #. TRANSLATORS: slow sync confirmation dialog message. Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:387 #, c-format msgid "Do you want to slow sync with %s?" msgstr "Vuoi effettuare la sincronizzazione lenta con %s?" #: ../src/gtk-ui/sync-ui.c:391 msgid "Yes, do slow sync" msgstr "Sì, effettua la sincronizzazione lenta" #: ../src/gtk-ui/sync-ui.c:391 msgid "No, cancel sync" msgstr "Annulla sincronizzazione" #. TRANSLATORS: confirmation dialog for "refresh from peer". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:424 #, c-format msgid "" "Do you want to delete all local data and replace it with data from %s? This " "is not usually advised." msgstr "" "Eliminare i dati locali e sostituirli con quelli da %s? Solitamente questo " "non è consigliato." #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 msgid "Yes, delete and replace" msgstr "Elimina e sostituisci" #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 #: ../src/gtk-ui/sync-ui.c:1610 msgid "No" msgstr "No" #. TRANSLATORS: confirmation dialog for "refresh from local side". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:457 #, c-format msgid "" "Do you want to delete all data in %s and replace it with your local data? " "This is not usually advised." msgstr "" "Eliminare i dati in %s e sostituirli con quelli locali? Solitamente questo " "non è consigliato." #: ../src/gtk-ui/sync-ui.c:491 msgid "Trying to cancel sync" msgstr "Tentativo di annullare la sincronizzazione" #: ../src/gtk-ui/sync-ui.c:533 msgid "No service or device selected" msgstr "Nessun servizio o dispositivo selezionato" #. TRANSLATORS: This is the title on main view. Placeholder is #. * the service name. Example: "Google - synced just now" #: ../src/gtk-ui/sync-ui.c:541 #, c-format msgid "%s - synced just now" msgstr "%s - Ultima sincronizzazione pochi secondi fa" #: ../src/gtk-ui/sync-ui.c:545 #, c-format msgid "%s - synced a minute ago" msgstr "%s - Ultima sincronizzazione un minuto fa" #: ../src/gtk-ui/sync-ui.c:549 #, c-format msgid "%s - synced %ld minutes ago" msgstr "%s - Ultima sincronizzazione %ld minuti fa" #: ../src/gtk-ui/sync-ui.c:554 #, c-format msgid "%s - synced an hour ago" msgstr "%s - Ultima sincronizzazione un'ora fa" #: ../src/gtk-ui/sync-ui.c:558 #, c-format msgid "%s - synced %ld hours ago" msgstr "%s - Ultima sincronizzazione %ld ore fa" #: ../src/gtk-ui/sync-ui.c:563 #, c-format msgid "%s - synced a day ago" msgstr "%s - Ultima sincronizzazione un giorno fa" #: ../src/gtk-ui/sync-ui.c:567 #, c-format msgid "%s - synced %ld days ago" msgstr "%s - Ultima sincronizzazione %ld giorni fa" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "You've just restored a backup. The changes have not been " #. * "synced with %s yet" #: ../src/gtk-ui/sync-ui.c:616 ../src/gtk-ui/sync-ui.c:701 msgid "Sync now" msgstr "Sincronizza ora" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "A normal sync is not possible at this time..." message. #. * "Other options" will open Emergency view #: ../src/gtk-ui/sync-ui.c:622 ../src/gtk-ui/ui.glade.h:37 msgid "Slow sync" msgstr "Sincronizzazione lenta" #: ../src/gtk-ui/sync-ui.c:623 msgid "Other options..." msgstr "Altre opzioni..." #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * when no service is selected. Will open configuration view #: ../src/gtk-ui/sync-ui.c:628 msgid "Select sync service" msgstr "Seleziona servizio di sincronizzazione" #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * login to service fails. Will open configuration view for this service #: ../src/gtk-ui/sync-ui.c:633 msgid "Edit service settings" msgstr "Modifica impostazioni" #: ../src/gtk-ui/sync-ui.c:709 msgid "" "You haven't selected a sync service or device yet. Sync services let you " "synchronize your data between your netbook and a web service. You can also " "sync directly with some devices." msgstr "" "Non è ancora stato selezionato un servizio di sincronizzazione. Questi " "servizi consentono di sincronizzare i dati tra il proprio netbook e un " "servizio web. E' anche possibile sincronizzare direttamente con alcuni " "dispositivi." #: ../src/gtk-ui/sync-ui.c:729 msgid "Sync again" msgstr "Sincronizza ancora" #: ../src/gtk-ui/sync-ui.c:748 msgid "Restoring" msgstr "Ripristino" #: ../src/gtk-ui/sync-ui.c:750 msgid "Syncing" msgstr "Sincronizzazione" #. TRANSLATORS: This is for the button in main view, right side. #. Keep line length below ~20 characters, use two lines if needed #: ../src/gtk-ui/sync-ui.c:762 ../src/gtk-ui/sync-ui.c:3407 msgid "Cancel sync" msgstr "Annulla sincronizzazione" #: ../src/gtk-ui/sync-ui.c:927 msgid "Back to sync" msgstr "Torna indietro" #. TRANSLATORS: label for checkbutton/toggle in main view. #. * Please stick to similar length strings or break the line with #. * "\n" if absolutely needed #: ../src/gtk-ui/sync-ui.c:1229 msgid "Automatic sync" msgstr "Sincronizzazione automatica" #. This is the expander label in emergency view. It summarizes the #. * currently selected data sources. First placeholder is service/device #. * name, second a comma separeted list of sources. #. * E.g. "Affected data: Google Contacts, Appointments" #: ../src/gtk-ui/sync-ui.c:1524 #, c-format msgid "Affected data: %s %s" msgstr "Dati interessati: %s %s" #: ../src/gtk-ui/sync-ui.c:1529 #, c-format msgid "Affected data: none" msgstr "Dati interessati: nessuno" #. TRANSLATORS: confirmation for restoring a backup. placeholder is the #. * backup time string defined below #: ../src/gtk-ui/sync-ui.c:1607 #, c-format msgid "" "Do you want to restore the backup from %s? All changes you have made since " "then will be lost." msgstr "" "Vuoi ripristinare il backup da %s? Tutte le modifiche apportate da allora " "saranno perse." #: ../src/gtk-ui/sync-ui.c:1610 msgid "Yes, restore" msgstr "Sì, ripristina" #. TRANSLATORS: date/time for strftime(), used in emergency view backup #. * label. Any time format that shows date and time is good. #: ../src/gtk-ui/sync-ui.c:1642 #, c-format msgid "%x %X" msgstr "%x %X" #. TRANSLATORS: label for a backup in emergency view. Placeholder is #. * service or device name #: ../src/gtk-ui/sync-ui.c:1661 #, c-format msgid "Backed up before syncing with %s" msgstr "Back up effettuato prima della sincronizzazione con %s" #: ../src/gtk-ui/sync-ui.c:1678 msgid "Restore" msgstr "Ripristina" #. TRANSLATORS: this is an explanation in Emergency view. #. * Placeholder is a service/device name #: ../src/gtk-ui/sync-ui.c:1785 #, c-format msgid "" "A normal sync with %s is not possible at this time. You can do a slow two-" "way sync or start from scratch. You can also restore a backup, but a slow " "sync or starting from scratch will still be required before normal sync is " "possible." msgstr "" "Una sincronizzazione normale con %s non è possibile in questo momento. Si " "può fare una sincronizzazione lenta a due vie o iniziare da zero. Si può " "anche ripristinare dal backup, ma una sincronizzazione lenta o un inizio da " "zero saranno comunque richiesti prima che sia possibile una sincronizzazione" " normale." #: ../src/gtk-ui/sync-ui.c:1795 #, c-format msgid "" "If something has gone horribly wrong, you can try a slow sync, start from " "scratch or restore from backup." msgstr "" "Se qualcosa è andato terribilmente storto, puoi provare una sincronizzazione" " lenta, iniziare da zero o ripristinare dal backup" #. TRANSLATORS: These are a buttons in Emergency view. Placeholder is a #. * service/device name. Please don't use too long lines, but feel free to #. * use several lines. #: ../src/gtk-ui/sync-ui.c:1804 #, c-format msgid "" "Delete all your local\n" "data and replace with\n" "data from %s" msgstr "" "Eliminare i dati locali\n" "e sostituirli con\n" "quelli da %s" #: ../src/gtk-ui/sync-ui.c:1810 #, c-format msgid "" "Delete all data on\n" "%s and replace\n" "with your local data" msgstr "" "Eliminare i dati su\n" "%s e sostituirli\n" "con quelli locali" #: ../src/gtk-ui/sync-ui.c:2275 msgid "Failed to get list of supported services from SyncEvolution" msgstr "Recupero elenco dei servizi supportati da SyncEvolution non riuscito" #: ../src/gtk-ui/sync-ui.c:2329 msgid "" "There was a problem communicating with the sync process. Please try again " "later." msgstr "" "Si è verificato un problema di comunicazione con il processo di " "sincronizzazione. Si prega di riprovare più tardi." #: ../src/gtk-ui/sync-ui.c:2388 msgid "Restore failed" msgstr "Ripristino non riuscito" #: ../src/gtk-ui/sync-ui.c:2391 ../src/gtk-ui/sync-ui.c:3276 msgid "Sync failed" msgstr "Sincronizzazione non riuscita" #: ../src/gtk-ui/sync-ui.c:2397 msgid "Restore complete" msgstr "Ripristino completato" #: ../src/gtk-ui/sync-ui.c:2400 msgid "Sync complete" msgstr "Sincronizzazione completata" #: ../src/gtk-ui/sync-ui.c:2492 #, c-format msgid "Preparing '%s'" msgstr "Preparazione di '%s'" #: ../src/gtk-ui/sync-ui.c:2495 #, c-format msgid "Receiving '%s'" msgstr "Ricezione di '%s'" #: ../src/gtk-ui/sync-ui.c:2498 #, c-format msgid "Sending '%s'" msgstr "Invio di '%s'" #: ../src/gtk-ui/sync-ui.c:2619 #, c-format msgid "There was one remote rejection." msgid_plural "There were %ld remote rejections." msgstr[0] "Si è verificato un rifiuto remoto." msgstr[1] "Si sono verificati %ld rifiuti remoti." #: ../src/gtk-ui/sync-ui.c:2624 #, c-format msgid "There was one local rejection." msgid_plural "There were %ld local rejections." msgstr[0] "Si è verificato un rifiuto locale." msgstr[1] "Si sono verificati %ld rifiuti locali." #: ../src/gtk-ui/sync-ui.c:2629 #, c-format msgid "There were %ld local rejections and %ld remote rejections." msgstr "Si sono verificati %ld rifiuti locali e %ld remoti." #: ../src/gtk-ui/sync-ui.c:2634 #, c-format msgid "Last time: No changes." msgstr "Ultima volta: nessuna modifica." #: ../src/gtk-ui/sync-ui.c:2636 #, c-format msgid "Last time: Sent one change." msgid_plural "Last time: Sent %ld changes." msgstr[0] "Ultima volta: inviata una modifica." msgstr[1] "Ultima volta: inviate %ld modifiche." #. This is about changes made to the local data. Not all of these #. changes were requested by the remote server, so "applied" #. is a better word than "received" (bug #5185). #: ../src/gtk-ui/sync-ui.c:2644 #, c-format msgid "Last time: Applied one change." msgid_plural "Last time: Applied %ld changes." msgstr[0] "Ultima volta: applicata una modifica." msgstr[1] "Ultima volta: applicate %ld modifiche." #: ../src/gtk-ui/sync-ui.c:2649 #, c-format msgid "Last time: Applied %ld changes and sent %ld changes." msgstr "Ultima volta: applicate %ld modifiche e inviate %ld." #. TRANSLATORS: the placeholder is a error message (hopefully) #. * explaining the problem #: ../src/gtk-ui/sync-ui.c:2856 #, c-format msgid "" "There was a problem with last sync:\n" "%s" msgstr "" "Si è verificato un problema con l'ultima sincronizzazione:\n" "%s" #: ../src/gtk-ui/sync-ui.c:2866 #, c-format msgid "" "You've just restored a backup. The changes have not been synced with %s yet" msgstr "" "Hai appena ripristinato un backup. I cambiamenti non sono ancora stati " "sincronizzati con %s" #: ../src/gtk-ui/sync-ui.c:3154 msgid "Waiting for current operation to finish..." msgstr "In attesa che termini l'operazione..." #. TRANSLATORS: next strings are error messages. #: ../src/gtk-ui/sync-ui.c:3188 msgid "" "A normal sync is not possible at this time. The server suggests a slow sync," " but this might not always be what you want if both ends already have data." msgstr "" "Una sincronizzazione normale non è possibile in questo momento. Il server " "suggerisce una sincronizzazione lenta, ma questo potrebbe non essere sempre " "quello che vuoi, se entrambe le estremità hanno dati." #: ../src/gtk-ui/sync-ui.c:3192 msgid "The sync process died unexpectedly." msgstr "Il servizio di sincronizzazione è terminato inaspettatamente." #: ../src/gtk-ui/sync-ui.c:3197 msgid "" "Password request was not answered. You can save the password in the settings" " to prevent the request." msgstr "" "La richiesta della password ha ricevuto risposta. È possibile salvare la " "password nelle impostazioni per evitare la richiesta." #. TODO use the service device name here, this is a remote problem #: ../src/gtk-ui/sync-ui.c:3201 msgid "There was a problem processing sync request. Trying again may help." msgstr "" "C'è stato un problema di elaborazione delle richieste di sincronizzazione. " "Riprovare potrebbe risolvere il problema." #: ../src/gtk-ui/sync-ui.c:3207 msgid "" "Failed to login. Could there be a problem with your username or password?" msgstr "" "Impossibile effettuare il login. Ci potrebbe essere un problema con il tuo " "nome utente o la password?" #: ../src/gtk-ui/sync-ui.c:3210 msgid "Forbidden" msgstr "Proibito" #. TRANSLATORS: data source means e.g. calendar or addressbook #: ../src/gtk-ui/sync-ui.c:3216 msgid "" "A data source could not be found. Could there be a problem with the " "settings?" msgstr "" "Impossibile trovare la sorgente. Ci potrebbe essere un problema con le " "impostazioni?" #: ../src/gtk-ui/sync-ui.c:3220 msgid "Remote database error" msgstr "Errore remoto nel database" #. This can happen when EDS is borked, restart it may help... #: ../src/gtk-ui/sync-ui.c:3223 msgid "" "There is a problem with the local database. Syncing again or rebooting may " "help." msgstr "" "C'è un problema con il database locale. Sincronizzare di nuovo o riavviare." #: ../src/gtk-ui/sync-ui.c:3226 msgid "No space on disk" msgstr "Spazio esaurito sul disco" #: ../src/gtk-ui/sync-ui.c:3228 msgid "Failed to process SyncML" msgstr "Elaborazione SyncML non riuscita" #: ../src/gtk-ui/sync-ui.c:3230 msgid "Server authorization failed" msgstr "Autorizzazione server non riuscita" #: ../src/gtk-ui/sync-ui.c:3232 msgid "Failed to parse configuration file" msgstr "Analisi del file di configurazione non riuscita" #: ../src/gtk-ui/sync-ui.c:3234 msgid "Failed to read configuration file" msgstr "Lettura del file di configurazione non riuscita" #: ../src/gtk-ui/sync-ui.c:3236 msgid "No configuration found" msgstr "Non è stata trovata alcuna configurazione" #: ../src/gtk-ui/sync-ui.c:3238 msgid "No configuration file found" msgstr "Non è stato trovato alcun file di configurazione" #: ../src/gtk-ui/sync-ui.c:3240 msgid "Server sent bad content" msgstr "Il server ha inviato dei contenuti errati" #: ../src/gtk-ui/sync-ui.c:3242 msgid "Connection certificate has expired" msgstr "Il certificato della connessione è scaduto" #: ../src/gtk-ui/sync-ui.c:3244 msgid "Connection certificate is invalid" msgstr "Il certificato della connessione non è valido" #: ../src/gtk-ui/sync-ui.c:3252 msgid "" "We were unable to connect to the server. The problem could be temporary or " "there could be something wrong with the settings." msgstr "" "Non siamo stati in grado di connetterci al server. Il problema potrebbe " "essere temporaneo o ci potrebbe essere qualcosa di sbagliato con le " "impostazioni." #: ../src/gtk-ui/sync-ui.c:3259 msgid "The server URL is bad" msgstr "L'URL del server non è corretto" #: ../src/gtk-ui/sync-ui.c:3264 msgid "The server was not found" msgstr "Il server non è stato trovato" #: ../src/gtk-ui/sync-ui.c:3266 #, c-format msgid "Error %d" msgstr "Errore %d" #. TRANSLATORS: password request dialog contents: title, cancel button #. * and ok button #: ../src/gtk-ui/sync-ui.c:3404 msgid "Password is required for sync" msgstr "Password necessaria per la sincronizzazione" #: ../src/gtk-ui/sync-ui.c:3408 msgid "Sync with password" msgstr "Sincronizza con password" #. TRANSLATORS: password request dialog message, placeholder is service name #: ../src/gtk-ui/sync-ui.c:3418 #, c-format msgid "Please enter password for syncing with %s:" msgstr "Inserire la password per la sincronizzazione con %s:" #. title for the buttons on the right side of main view #: ../src/gtk-ui/ui.glade.h:2 msgid "Actions" msgstr "Azioni" #. text between the two "start from scratch" buttons in emergency view #: ../src/gtk-ui/ui.glade.h:4 msgid "or" msgstr "oppure" #: ../src/gtk-ui/ui.glade.h:5 msgid "Direct sync" msgstr "Sincronizzazione diretta" #: ../src/gtk-ui/ui.glade.h:6 msgid "Network sync" msgstr "Sincronizzazione di rete" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:8 msgid "Restore from backup" msgstr "Ripristina dal backup" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:10 msgid "Slow sync" msgstr "Sincronizzazione lenta" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:12 msgid "Start from scratch" msgstr "Inizia da zero" #: ../src/gtk-ui/ui.glade.h:13 msgid "" "A slow sync compares items from both sides and tries to merge them. \n" "This may fail in some cases, leading to duplicates or lost information." msgstr "" "Una sincronizzazione lenta compara gli elementi da entrambe le parti e tenta di metterli insieme. \n" "Questo potrebbe non riuscire in alcuni casi, portando a duplicati o a informazioni perse." #: ../src/gtk-ui/ui.glade.h:15 msgid "Add new device" msgstr "Aggiungi un nuovo dispositivo" #: ../src/gtk-ui/ui.glade.h:16 msgid "Add new service" msgstr "Aggiungi servizio" #. explanation of "Restore backup" function #: ../src/gtk-ui/ui.glade.h:18 msgid "" "Backups are made before every time we Sync. Choose a backup to restore. Any " "changes you have made since then will be lost." msgstr "" "I backup sono effettuati prima di ogni sincronizzazione. Scegli un backup da" " ripristinare. Ogni modifica fatta da allora andrà persa." #: ../src/gtk-ui/ui.glade.h:19 msgid "Calendar" msgstr "Calendario" #. Button in main view, right side. Keep to below 20 chars per line, feel free #. to use two lines #: ../src/gtk-ui/ui.glade.h:21 msgid "" "Change or edit\n" "sync service" msgstr "" "Cambia o modifica\n" "sincronizzazione" #. close button for settings window #: ../src/gtk-ui/ui.glade.h:24 msgid "Close" msgstr "Chiudi" #: ../src/gtk-ui/ui.glade.h:25 msgid "" "Delete all data on Zyb \n" "and replace with your\n" "local information" msgstr "" "Eliminare i dati su Zyb \n" "e sostituirli con quelli locali" #: ../src/gtk-ui/ui.glade.h:28 msgid "" "Delete all your local\n" "information and replace\n" "with data from Zyb" msgstr "" "Eliminare i dati locali\n" "e sostituirli con quelli da Zyb" #. button in main view, right side. Keep length to 20 characters or so, use #. two lines if needed #: ../src/gtk-ui/ui.glade.h:32 msgid "" "Fix a sync\n" "emergency" msgstr "" "Risolvi una\n" "sincronizzazione di\n" "emergenza" #: ../src/gtk-ui/ui.glade.h:34 msgid "" "If you don't see your service above but know that your sync provider uses SyncML\n" "you can setup a service manually." msgstr "" "Se non vengono visualizzati i propri servizi, ma si è certi che il proprio provider\n" "usa SyncML, è possibile impostare un servizio manualmente." #: ../src/gtk-ui/ui.glade.h:36 msgid "Settings" msgstr "Impostazioni" #: ../src/gtk-ui/ui.glade.h:39 msgid "Sync Emergency" msgstr "Sincronizzazione di emergenza" #: ../src/gtk-ui/ui.glade.h:41 msgid "" "To sync you'll need a network connection and an account with a sync service.\n" "We support the following services: " msgstr "" "Per effettuare la sincronizzazione sono necessari una connessione e un account con un servizio di sincronizzazione.\n" "I servizi supportati sono: " #: ../src/gtk-ui/ui.glade.h:43 msgid "Use Bluetooth to Sync your data from one device to another." msgstr "Usa Blootooth per sincronizzare i dati da un dispositivo all'altro." #: ../src/gtk-ui/ui.glade.h:44 msgid "You will need to add Bluetooth devices before they can be synced." msgstr "" "Devi aggiungere i dispositivi Bluetooth prima che possano essere " "sincronizzati." #: ../src/gtk-ui/sync.desktop.in.h:2 msgid "Up to date" msgstr "Aggiornato" #: ../src/gtk-ui/sync-gtk.desktop.in.h:1 msgid "SyncEvolution (GTK)" msgstr "SyncEvolution (GTK)" #: ../src/gtk-ui/sync-gtk.desktop.in.h:2 msgid "Synchronize PIM data" msgstr "Sincronizza dati PIM" #: ../src/gtk-ui/sync-config-widget.c:88 msgid "" "ScheduleWorld enables you to keep your contacts, events, tasks, and notes in" " sync." msgstr "" "ScheduleWorld consente di tenere sincronizzati contatti, eventi, attività e " "note." #: ../src/gtk-ui/sync-config-widget.c:91 msgid "" "Google Sync can back up and synchronize your contacts with your Gmail " "contacts." msgstr "" "Google Sync è in grado di eseguire backup di sicurezza e di sincronizzare la" " propria rubrica con i contatti di Gmail." #. TRANSLATORS: Please include the word "demo" (or the equivalent in #. your language): Funambol is going to be a 90 day demo service #. in the future #: ../src/gtk-ui/sync-config-widget.c:97 msgid "" "Back up your contacts and calendar. Sync with a single click, anytime, " "anywhere (DEMO)." msgstr "" "Eseguire una copia di sicurezza dei propri contatti e del proprio " "calendario. Sincronizzare con un solo clic, sempre e ovunque (dimostrativo)." #: ../src/gtk-ui/sync-config-widget.c:100 msgid "" "Mobical Backup and Restore service allows you to securely back up your " "personal mobile data for free." msgstr "" "Il servizio Mobical Backup and Restore ti permette di effettuare backup dei " "tuoi mobile data gratuitamente e in tutta sicurezza." #: ../src/gtk-ui/sync-config-widget.c:103 msgid "" "ZYB is a simple way for people to store and share mobile information online." msgstr "" "ZYB è un modo semplice di memorizzare e condividere le tue informazioni " "mobili online." #: ../src/gtk-ui/sync-config-widget.c:106 msgid "" "Memotoo lets you access your personal data from any computer connected to " "the Internet." msgstr "" "Memotoo ti permettere di accedere ai tuoi dati personali da qualsiasi " "computer connesso a internet." #: ../src/gtk-ui/sync-config-widget.c:195 msgid "Sorry, failed to save the configuration" msgstr "Siamo spiacenti, impossibile salvare la configurazione" #: ../src/gtk-ui/sync-config-widget.c:445 msgid "Service must have a name and server URL" msgstr "Il servizio deve avere un nome e un indirizzo del server" #. TRANSLATORS: error dialog when creating a new sync configuration #: ../src/gtk-ui/sync-config-widget.c:451 msgid "A username is required for this service" msgstr "Il nome utente è necessario per questo servizio" #: ../src/gtk-ui/sync-config-widget.c:493 #, c-format msgid "" "Do you want to reset the settings for %s? This will not remove any synced " "information on either end." msgstr "" "Vuoi ripristinare le impostazioni per %s? Questa operazione non rimuoverà " "nessuna informazione sincronizzata." #. TRANSLATORS: buttons in reset-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:497 msgid "Yes, reset" msgstr "Sì, ripristina" #: ../src/gtk-ui/sync-config-widget.c:498 #: ../src/gtk-ui/sync-config-widget.c:509 msgid "No, keep settings" msgstr "No, mantieni le impostazioni" #: ../src/gtk-ui/sync-config-widget.c:503 #, c-format msgid "" "Do you want to delete the settings for %s? This will not remove any synced " "information on either end but it will remove these settings." msgstr "" "Vuoi eliminare le impostazioni per %s? Questa operazione non rimuoverà " "nessuna informazione sincronizzata ma rimuoverà le configurazioni per questo" " servizio." #. TRANSLATORS: buttons in delete-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:508 msgid "Yes, delete" msgstr "Sì, elimina" #: ../src/gtk-ui/sync-config-widget.c:539 msgid "Reset settings" msgstr "Reimposta le impostazioni" #: ../src/gtk-ui/sync-config-widget.c:542 msgid "Delete settings" msgstr "Elimina le impostazioni" #: ../src/gtk-ui/sync-config-widget.c:552 msgid "Save and use" msgstr "Salva e usa" #: ../src/gtk-ui/sync-config-widget.c:555 msgid "" "Save and replace\n" "current service" msgstr "" "Salva e sostituisci\n" " il servizio" #: ../src/gtk-ui/sync-config-widget.c:563 msgid "Stop using device" msgstr "Non usare il servizio" #: ../src/gtk-ui/sync-config-widget.c:566 msgid "Stop using service" msgstr "Non usare il servizio" #. TRANSLATORS: label for an entry in service configuration form. #. * Placeholder is a source name. #. * Example: "Appointments URI" #: ../src/gtk-ui/sync-config-widget.c:749 #, c-format msgid "%s URI" msgstr "URI di %s" #. TRANSLATORS: toggles in service configuration form, placeholder is service #. * or device name #: ../src/gtk-ui/sync-config-widget.c:936 #, c-format msgid "Send changes to %s" msgstr "Invia modifiche a %s" #: ../src/gtk-ui/sync-config-widget.c:941 #, c-format msgid "Receive changes from %s" msgstr "Ricevi le modifiche per %s" #: ../src/gtk-ui/sync-config-widget.c:957 msgid "Sync" msgstr "Sincronizzazione" #. TRANSLATORS: label of a entry in service configuration #: ../src/gtk-ui/sync-config-widget.c:973 msgid "Server address" msgstr "Indirizzo server" #. TRANSLATORS: explanation before a device template combobox. #. * Placeholder is a device name like 'Nokia N85' or 'Syncevolution #. * Client' #: ../src/gtk-ui/sync-config-widget.c:1049 #, c-format msgid "" "This device looks like it might be a '%s'. If this is not correct, please " "take a look at the list of supported devices and pick yours if it is listed" msgstr "" "Il dispositivo sembra essere un '%s'. Se questo non è corretto, si prega di " "dare un'occhiata alla lista dei dispositivi supportati e scegliere quello " "più appropriato" #: ../src/gtk-ui/sync-config-widget.c:1055 #: ../src/gtk-ui/sync-config-widget.c:1915 msgid "" "We don't know what this device is exactly. Please take a look at the list of" " supported devices and pick yours if it is listed" msgstr "" "Non sappiamo che sia esattamente questo dispositivo. Si prega di dare " "un'occhiata alla lista delle periferiche supportate e scegliere quella " "appropriata se è elencata" #: ../src/gtk-ui/sync-config-widget.c:1207 #, c-format msgid "%s - Bluetooth device" msgstr "%s - Dispositivo Bluetooth" #. TRANSLATORS: service title for services that are not based on a #. * template in service list, the placeholder is the name of the service #: ../src/gtk-ui/sync-config-widget.c:1213 #, c-format msgid "%s - manually setup" msgstr "%s - configura manualmente" #. TRANSLATORS: link button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1886 msgid "Launch website" msgstr "Visita il sito web" #. TRANSLATORS: button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1895 msgid "Set up now" msgstr "Configura adesso" #: ../src/gtk-ui/sync-config-widget.c:1953 msgid "Use these settings" msgstr "Usare queste impostazioni" #. TRANSLATORS: labels of entries in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1991 msgid "Username" msgstr "Nome utente" #: ../src/gtk-ui/sync-config-widget.c:2006 msgid "Password" msgstr "Password" #. TRANSLATORS: warning in service configuration form for people #. who have modified the configuration via other means. #: ../src/gtk-ui/sync-config-widget.c:2029 msgid "" "Current configuration is more complex than what can be shown here. Changes " "to sync mode or synced data types will overwrite that configuration." msgstr "" "La configurazione del servizio attuale è più complessa di quello che può " "essere mostrato qui. Le modifiche alla modalità di sincronizzazione o ai " "tipi di dati sincronizzati sovrascriveranno quella configurazione." #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2048 msgid "Hide server settings" msgstr "Nascondi impostazioni server" #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2068 msgid "Show server settings" msgstr "Mostra impostazioni server" #: ../src/gnome-bluetooth/syncevolution.c:110 msgid "Sync in the Sync application" msgstr "Sincronizza nell'applicazione di sincronizzazione" #: ../src/syncevo-dbus-server.cpp:6653 #, c-format msgid "%s is syncing" msgstr "%s è in fase di sincronizzazione" #: ../src/syncevo-dbus-server.cpp:6654 #, c-format msgid "We have just started to sync your computer with the %s sync service." msgstr "" "Abbiamo appena iniziato a sincronizzare il computer con il servizio di " "sincronizzazione %s." #. if sync is successfully started and done #: ../src/syncevo-dbus-server.cpp:6670 #, c-format msgid "%s sync complete" msgstr "Sincronizzazione di %s completata" #: ../src/syncevo-dbus-server.cpp:6671 #, c-format msgid "We have just finished syncing your computer with the %s sync service." msgstr "" "Abbiamo finito di sincronizzare il computer con il servizio di " "sincronizzazione %s." #. if sync is successfully started and has errors, or not started successful #. with a fatal problem #: ../src/syncevo-dbus-server.cpp:6676 msgid "Sync problem." msgstr "Problema di sincronizzazione." #: ../src/syncevo-dbus-server.cpp:6677 msgid "Sorry, there's a problem with your sync that you need to attend to." msgstr "C'è un problema con la sincronizzazione che richiede attenzione." #: ../src/syncevo-dbus-server.cpp:6758 msgid "View" msgstr "Visualizza" #. Use "default" as ID because that is what mutter-moblin #. recognizes: it then skips the action instead of adding it #. in addition to its own "Dismiss" button (always added). #: ../src/syncevo-dbus-server.cpp:6762 msgid "Dismiss" msgstr "Scarta" syncevolution_1.4/po/ja.po000066400000000000000000000767701230021373600157470ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # # Translators: # , 2011. msgid "" msgstr "" "Project-Id-Version: syncevolution\n" "Report-Msgid-Bugs-To: https://bugs.meego.com/\n" "POT-Creation-Date: 2011-12-05 10:21-0800\n" "PO-Revision-Date: 2011-12-06 06:05+0000\n" "Last-Translator: GLS_Translator_JPN \n" "Language-Team: Japanese (http://www.transifex.net/projects/p/meego/team/ja/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ja\n" "Plural-Forms: nplurals=1; plural=0\n" #. TRANSLATORS: this is the application name that may be used by e.g. #. the windowmanager #: ../src/gtk-ui/main.c:40 ../src/gtk-ui/ui.glade.h:38 #: ../src/gtk-ui/sync.desktop.in.h:1 #: ../src/gnome-bluetooth/syncevolution.c:112 msgid "Sync" msgstr "同期化" #: ../src/gtk-ui/sync-ui.c:266 msgid "Contacts" msgstr "連絡先" #: ../src/gtk-ui/sync-ui.c:268 msgid "Appointments" msgstr "予定" #: ../src/gtk-ui/sync-ui.c:270 ../src/gtk-ui/ui.glade.h:40 msgid "Tasks" msgstr "タスク" #: ../src/gtk-ui/sync-ui.c:272 msgid "Notes" msgstr "メモ" #. TRANSLATORS: This is a "combination source" for syncing with devices #. * that combine appointments and tasks. the name should match the ones #. * used for calendar and todo above #: ../src/gtk-ui/sync-ui.c:277 msgid "Appointments & Tasks" msgstr "予定とタスク" #: ../src/gtk-ui/sync-ui.c:349 msgid "Starting sync" msgstr "同期化を開始しています" #. TRANSLATORS: slow sync confirmation dialog message. Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:387 #, c-format msgid "Do you want to slow sync with %s?" msgstr "%s で低速同期しますか?" #: ../src/gtk-ui/sync-ui.c:391 msgid "Yes, do slow sync" msgstr "はい、低速同期します" #: ../src/gtk-ui/sync-ui.c:391 msgid "No, cancel sync" msgstr "いいえ、同期化をキャンセルする" #. TRANSLATORS: confirmation dialog for "refresh from peer". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:424 #, c-format msgid "" "Do you want to delete all local data and replace it with data from %s? This " "is not usually advised." msgstr "ローカルデータをすべて削除して、%s のデータに置き換えますか?通常は推奨されません。" #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 msgid "Yes, delete and replace" msgstr "はい、削除して置き換える" #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 #: ../src/gtk-ui/sync-ui.c:1610 msgid "No" msgstr "いいえ" #. TRANSLATORS: confirmation dialog for "refresh from local side". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:457 #, c-format msgid "" "Do you want to delete all data in %s and replace it with your local data? " "This is not usually advised." msgstr "%s のデータをすべて削除して、ローカルデータに置き換えますか?通常は推奨されません。" #: ../src/gtk-ui/sync-ui.c:491 msgid "Trying to cancel sync" msgstr "同期化のキャンセルを試行しています" #: ../src/gtk-ui/sync-ui.c:533 msgid "No service or device selected" msgstr "サービスまたはデバイスが選択されていません" #. TRANSLATORS: This is the title on main view. Placeholder is #. * the service name. Example: "Google - synced just now" #: ../src/gtk-ui/sync-ui.c:541 #, c-format msgid "%s - synced just now" msgstr "%s - 同期されました" #: ../src/gtk-ui/sync-ui.c:545 #, c-format msgid "%s - synced a minute ago" msgstr "%s - 1 分前に同期されました" #: ../src/gtk-ui/sync-ui.c:549 #, c-format msgid "%s - synced %ld minutes ago" msgstr "%s - %ld 分前に同期されました" #: ../src/gtk-ui/sync-ui.c:554 #, c-format msgid "%s - synced an hour ago" msgstr "%s - 1 時間前に同期されました" #: ../src/gtk-ui/sync-ui.c:558 #, c-format msgid "%s - synced %ld hours ago" msgstr "%s - %ld 時間前に同期されました" #: ../src/gtk-ui/sync-ui.c:563 #, c-format msgid "%s - synced a day ago" msgstr "%s - 前日に同期されました" #: ../src/gtk-ui/sync-ui.c:567 #, c-format msgid "%s - synced %ld days ago" msgstr "%s - %ld 日前に同期されました" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "You've just restored a backup. The changes have not been " #. * "synced with %s yet" #: ../src/gtk-ui/sync-ui.c:616 ../src/gtk-ui/sync-ui.c:701 msgid "Sync now" msgstr "今、同期化する" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "A normal sync is not possible at this time..." message. #. * "Other options" will open Emergency view #: ../src/gtk-ui/sync-ui.c:622 ../src/gtk-ui/ui.glade.h:37 msgid "Slow sync" msgstr "低速同期" #: ../src/gtk-ui/sync-ui.c:623 msgid "Other options..." msgstr "他のオプション" #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * when no service is selected. Will open configuration view #: ../src/gtk-ui/sync-ui.c:628 msgid "Select sync service" msgstr "同期化サービスを選択" #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * login to service fails. Will open configuration view for this service #: ../src/gtk-ui/sync-ui.c:633 msgid "Edit service settings" msgstr "サービス設定の編集" #: ../src/gtk-ui/sync-ui.c:709 msgid "" "You haven't selected a sync service or device yet. Sync services let you " "synchronize your data between your netbook and a web service. You can also " "sync directly with some devices." msgstr "" "同期化サービスまたはデバイスがまだ選択されていません。同期化サービスでネットブックと Web " "サービス間のデータを同期できます。また、他の一部のデバイスと直接同期することも可能です。" #: ../src/gtk-ui/sync-ui.c:729 msgid "Sync again" msgstr "もう1度同期化" #: ../src/gtk-ui/sync-ui.c:748 msgid "Restoring" msgstr "復元中" #: ../src/gtk-ui/sync-ui.c:750 msgid "Syncing" msgstr "同期化中" #. TRANSLATORS: This is for the button in main view, right side. #. Keep line length below ~20 characters, use two lines if needed #: ../src/gtk-ui/sync-ui.c:762 ../src/gtk-ui/sync-ui.c:3407 msgid "Cancel sync" msgstr "同期化のキャンセル" #: ../src/gtk-ui/sync-ui.c:927 msgid "Back to sync" msgstr "同期化に戻る" #. TRANSLATORS: label for checkbutton/toggle in main view. #. * Please stick to similar length strings or break the line with #. * "\n" if absolutely needed #: ../src/gtk-ui/sync-ui.c:1229 msgid "Automatic sync" msgstr "自動同期" #. This is the expander label in emergency view. It summarizes the #. * currently selected data sources. First placeholder is service/device #. * name, second a comma separeted list of sources. #. * E.g. "Affected data: Google Contacts, Appointments" #: ../src/gtk-ui/sync-ui.c:1524 #, c-format msgid "Affected data: %s %s" msgstr "影響を受けたデータ: %s %s" #: ../src/gtk-ui/sync-ui.c:1529 #, c-format msgid "Affected data: none" msgstr "影響を受けたデータ: なし" #. TRANSLATORS: confirmation for restoring a backup. placeholder is the #. * backup time string defined below #: ../src/gtk-ui/sync-ui.c:1607 #, c-format msgid "" "Do you want to restore the backup from %s? All changes you have made since " "then will be lost." msgstr "%s からバックアップを復元しますか? これまでに変更した内容はすべて失われてしまいます。" #: ../src/gtk-ui/sync-ui.c:1610 msgid "Yes, restore" msgstr "はい、復元します" #. TRANSLATORS: date/time for strftime(), used in emergency view backup #. * label. Any time format that shows date and time is good. #: ../src/gtk-ui/sync-ui.c:1642 #, c-format msgid "%x %X" msgstr "%x %X" #. TRANSLATORS: label for a backup in emergency view. Placeholder is #. * service or device name #: ../src/gtk-ui/sync-ui.c:1661 #, c-format msgid "Backed up before syncing with %s" msgstr "%s で同期する前にバックアップされます" #: ../src/gtk-ui/sync-ui.c:1678 msgid "Restore" msgstr "復元" #. TRANSLATORS: this is an explanation in Emergency view. #. * Placeholder is a service/device name #: ../src/gtk-ui/sync-ui.c:1785 #, c-format msgid "" "A normal sync with %s is not possible at this time. You can do a slow two-" "way sync or start from scratch. You can also restore a backup, but a slow " "sync or starting from scratch will still be required before normal sync is " "possible." msgstr "" "%s " "での通常同期は現在できません。低速で双方向同期するか、最初からやり直すことになります。また、バックアップを復元することもできますが、通常の同期を行う前に低速同期を行うか、最初からやり直す必要があります。" #: ../src/gtk-ui/sync-ui.c:1795 #, c-format msgid "" "If something has gone horribly wrong, you can try a slow sync, start from " "scratch or restore from backup." msgstr "深刻な問題が生じた場合は、低速同期を行うか、最初からやり直すか、バックアップから復元できます。" #. TRANSLATORS: These are a buttons in Emergency view. Placeholder is a #. * service/device name. Please don't use too long lines, but feel free to #. * use several lines. #: ../src/gtk-ui/sync-ui.c:1804 #, c-format msgid "" "Delete all your local\n" "data and replace with\n" "data from %s" msgstr "" "ローカルデータをすべて\n" "削除して、%s のデータに\n" "置き換えます" #: ../src/gtk-ui/sync-ui.c:1810 #, c-format msgid "" "Delete all data on\n" "%s and replace\n" "with your local data" msgstr "" "%s のデータをすべて削除\n" "して、ローカルデータに\n" "置き換えます" #: ../src/gtk-ui/sync-ui.c:2275 msgid "Failed to get list of supported services from SyncEvolution" msgstr "サポートされているサービスのリストを SyncEvolution から入手するのに失敗しました" #: ../src/gtk-ui/sync-ui.c:2329 msgid "" "There was a problem communicating with the sync process. Please try again " "later." msgstr "同期化プロセスへの通信に問題がありました。時間を置いてからもう一度試してみてください。" #: ../src/gtk-ui/sync-ui.c:2388 msgid "Restore failed" msgstr "復元に失敗しました" #: ../src/gtk-ui/sync-ui.c:2391 ../src/gtk-ui/sync-ui.c:3276 msgid "Sync failed" msgstr "同期に失敗しました" #: ../src/gtk-ui/sync-ui.c:2397 msgid "Restore complete" msgstr "復元の完了" #: ../src/gtk-ui/sync-ui.c:2400 msgid "Sync complete" msgstr "同期化の完了" #: ../src/gtk-ui/sync-ui.c:2492 #, c-format msgid "Preparing '%s'" msgstr "待機中です:%s" #: ../src/gtk-ui/sync-ui.c:2495 #, c-format msgid "Receiving '%s'" msgstr "受信中です:%s" #: ../src/gtk-ui/sync-ui.c:2498 #, c-format msgid "Sending '%s'" msgstr "送信中です:%s" #: ../src/gtk-ui/sync-ui.c:2619 #, c-format msgid "There was one remote rejection." msgid_plural "There were %ld remote rejections." msgstr[0] "リモートで接続拒否が 1 件ありました。@リモートで接続拒否が %ldl 件ありました。" #: ../src/gtk-ui/sync-ui.c:2624 #, c-format msgid "There was one local rejection." msgid_plural "There were %ld local rejections." msgstr[0] "ローカルで接続拒否が 1 件ありました。@ローカルで接続拒否が %ld 件ありました。" #: ../src/gtk-ui/sync-ui.c:2629 #, c-format msgid "There were %ld local rejections and %ld remote rejections." msgstr "ローカルの接続拒否が %ld 件、およびリモートの接続拒否が %ld 件あります。" #: ../src/gtk-ui/sync-ui.c:2634 #, c-format msgid "Last time: No changes." msgstr "前回:変更なし。" #: ../src/gtk-ui/sync-ui.c:2636 #, c-format msgid "Last time: Sent one change." msgid_plural "Last time: Sent %ld changes." msgstr[0] "前回:1 箇所の変更が送信されました。@前回:%ld 箇所の変更が送信されました。" #. This is about changes made to the local data. Not all of these #. changes were requested by the remote server, so "applied" #. is a better word than "received" (bug #5185). #: ../src/gtk-ui/sync-ui.c:2644 #, c-format msgid "Last time: Applied one change." msgid_plural "Last time: Applied %ld changes." msgstr[0] "前回:1 箇所の変更を適用しました。@前回:%ld 箇所の変更を受信しました。" #: ../src/gtk-ui/sync-ui.c:2649 #, c-format msgid "Last time: Applied %ld changes and sent %ld changes." msgstr "前回:%ld 箇所の変更を適用し、%ld 箇所の変更を送信しました。" #. TRANSLATORS: the placeholder is a error message (hopefully) #. * explaining the problem #: ../src/gtk-ui/sync-ui.c:2856 #, c-format msgid "" "There was a problem with last sync:\n" "%s" msgstr "" "前回の同期化に次の問題がありました:\n" "%s" #: ../src/gtk-ui/sync-ui.c:2866 #, c-format msgid "" "You've just restored a backup. The changes have not been synced with %s yet" msgstr "バックアップの復元が完了しました。変更内容はまだ %s と同期されていませn" #: ../src/gtk-ui/sync-ui.c:3154 msgid "Waiting for current operation to finish..." msgstr "実行中の操作の終了を待機しています..." #. TRANSLATORS: next strings are error messages. #: ../src/gtk-ui/sync-ui.c:3188 msgid "" "A normal sync is not possible at this time. The server suggests a slow sync," " but this might not always be what you want if both ends already have data." msgstr "通常同期は現在できません。サーバーは低速同期を推奨していますが、双方に既にデータがある場合は期待通りの結果にならない場合があります。" #: ../src/gtk-ui/sync-ui.c:3192 msgid "The sync process died unexpectedly." msgstr "同期サービスが突然終了しました。" #: ../src/gtk-ui/sync-ui.c:3197 msgid "" "Password request was not answered. You can save the password in the settings" " to prevent the request." msgstr "パスワード要求に答えていません。設定でパスワードを保存すると、パスワードが要求されなようにできます。" #. TODO use the service device name here, this is a remote problem #: ../src/gtk-ui/sync-ui.c:3201 msgid "There was a problem processing sync request. Trying again may help." msgstr "同期化要求の処理に問題がありました。もう一度試してみてください。" #: ../src/gtk-ui/sync-ui.c:3207 msgid "" "Failed to login. Could there be a problem with your username or password?" msgstr "ログインに失敗しました。ユーザー名またはパスワードに問題がある可能性があります。" #: ../src/gtk-ui/sync-ui.c:3210 msgid "Forbidden" msgstr "アクセスが禁止されています" #. TRANSLATORS: data source means e.g. calendar or addressbook #: ../src/gtk-ui/sync-ui.c:3216 msgid "" "A data source could not be found. Could there be a problem with the " "settings?" msgstr "データのソースが見つかりません。設定に問題がある可能性があります。" #: ../src/gtk-ui/sync-ui.c:3220 msgid "Remote database error" msgstr "リモート データベースのエラー" #. This can happen when EDS is borked, restart it may help... #: ../src/gtk-ui/sync-ui.c:3223 msgid "" "There is a problem with the local database. Syncing again or rebooting may " "help." msgstr "ローカル データベースに問題があります。再度同期を行うか、再起動で解決できるかも知れません。" #: ../src/gtk-ui/sync-ui.c:3226 msgid "No space on disk" msgstr "ディスクに空き領域がありません" #: ../src/gtk-ui/sync-ui.c:3228 msgid "Failed to process SyncML" msgstr "SyncML プロセスに失敗しました" #: ../src/gtk-ui/sync-ui.c:3230 msgid "Server authorization failed" msgstr "サーバー認証に失敗しました" #: ../src/gtk-ui/sync-ui.c:3232 msgid "Failed to parse configuration file" msgstr "設定ファイルの中断に失敗しました" #: ../src/gtk-ui/sync-ui.c:3234 msgid "Failed to read configuration file" msgstr "設定ファイルの読み込みに失敗しました" #: ../src/gtk-ui/sync-ui.c:3236 msgid "No configuration found" msgstr "設定項目が見つかりません" #: ../src/gtk-ui/sync-ui.c:3238 msgid "No configuration file found" msgstr "設定ファイルが見つかりません" #: ../src/gtk-ui/sync-ui.c:3240 msgid "Server sent bad content" msgstr "サーバーが悪いコンテンツを送信しました" #: ../src/gtk-ui/sync-ui.c:3242 msgid "Connection certificate has expired" msgstr "接続証明書の期限が切れています" #: ../src/gtk-ui/sync-ui.c:3244 msgid "Connection certificate is invalid" msgstr "接続証明書が無効です" #: ../src/gtk-ui/sync-ui.c:3252 msgid "" "We were unable to connect to the server. The problem could be temporary or " "there could be something wrong with the settings." msgstr "サーバーに接続できません。障害は一時的である可能性もありますが、サーバーの設定に問題がある可能性もあります。" #: ../src/gtk-ui/sync-ui.c:3259 msgid "The server URL is bad" msgstr "サーバーの URL が正しくありません" #: ../src/gtk-ui/sync-ui.c:3264 msgid "The server was not found" msgstr "サーバーが見つかりませんでした" #: ../src/gtk-ui/sync-ui.c:3266 #, c-format msgid "Error %d" msgstr "エラー:%d" #. TRANSLATORS: password request dialog contents: title, cancel button #. * and ok button #: ../src/gtk-ui/sync-ui.c:3404 msgid "Password is required for sync" msgstr "同期にはパスワードが必要です" #: ../src/gtk-ui/sync-ui.c:3408 msgid "Sync with password" msgstr "パスワード付き同期" #. TRANSLATORS: password request dialog message, placeholder is service name #: ../src/gtk-ui/sync-ui.c:3418 #, c-format msgid "Please enter password for syncing with %s:" msgstr "%s と同期するためのパスワードを入力してください:" #. title for the buttons on the right side of main view #: ../src/gtk-ui/ui.glade.h:2 msgid "Actions" msgstr "操作" #. text between the two "start from scratch" buttons in emergency view #: ../src/gtk-ui/ui.glade.h:4 msgid "or" msgstr "または" #: ../src/gtk-ui/ui.glade.h:5 msgid "Direct sync" msgstr "直接同期" #: ../src/gtk-ui/ui.glade.h:6 msgid "Network sync" msgstr "ネットワーク同期" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:8 msgid "Restore from backup" msgstr "バックアップから復元" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:10 msgid "Slow sync" msgstr "低速同期" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:12 msgid "Start from scratch" msgstr "最初からやり直す" #: ../src/gtk-ui/ui.glade.h:13 msgid "" "A slow sync compares items from both sides and tries to merge them. \n" "This may fail in some cases, leading to duplicates or lost information." msgstr "" "低速同期では双方のアイテムが比較され、マージが試行されます。\n" "失敗した場合、情報の重複や損失が起きる可能性があります。" #: ../src/gtk-ui/ui.glade.h:15 msgid "Add new device" msgstr "新規デバイス" #: ../src/gtk-ui/ui.glade.h:16 msgid "Add new service" msgstr "新規サービス" #. explanation of "Restore backup" function #: ../src/gtk-ui/ui.glade.h:18 msgid "" "Backups are made before every time we Sync. Choose a backup to restore. Any " "changes you have made since then will be lost." msgstr "バックアップは同期を実行する前に必ず実行されます。バックアップの復元を選択してください。バックアップ時以降の変更内容は失われます。" #: ../src/gtk-ui/ui.glade.h:19 msgid "Calendar" msgstr "カレンダー" #. Button in main view, right side. Keep to below 20 chars per line, feel free #. to use two lines #: ../src/gtk-ui/ui.glade.h:21 msgid "" "Change or edit\n" "sync service" msgstr "" "同期化サービスの\n" "変更または編集" #. close button for settings window #: ../src/gtk-ui/ui.glade.h:24 msgid "Close" msgstr "閉じる" #: ../src/gtk-ui/ui.glade.h:25 msgid "" "Delete all data on Zyb \n" "and replace with your\n" "local information" msgstr "" "Zyb 上のデータをすべて\n" "削除して、ローカル情報\n" "に置き換えます" #: ../src/gtk-ui/ui.glade.h:28 msgid "" "Delete all your local\n" "information and replace\n" "with data from Zyb" msgstr "" "ローカル情報をすべてl\n" "削除して、Zyb のデータl\n" "に置き換えます" #. button in main view, right side. Keep length to 20 characters or so, use #. two lines if needed #: ../src/gtk-ui/ui.glade.h:32 msgid "" "Fix a sync\n" "emergency" msgstr "" "緊急同期化\n" "を修正" #: ../src/gtk-ui/ui.glade.h:34 msgid "" "If you don't see your service above but know that your sync provider uses SyncML\n" "you can setup a service manually." msgstr "" "ご利用の同期化プロバイダーが SyncML を使用していてるのにサービスが上に\n" "表示されない場合は、サービスを手動で設定してください。" #: ../src/gtk-ui/ui.glade.h:36 msgid "Settings" msgstr "設定" #: ../src/gtk-ui/ui.glade.h:39 msgid "Sync Emergency" msgstr "緊急同期化" #: ../src/gtk-ui/ui.glade.h:41 msgid "" "To sync you'll need a network connection and an account with a sync service.\n" "We support the following services: " msgstr "" "同期化機能を使用するには、ネットワーク接続と同期化サービスのアカウントが\n" "必要です。以下が、サポートされているサービスです。" #: ../src/gtk-ui/ui.glade.h:43 msgid "Use Bluetooth to Sync your data from one device to another." msgstr "Bluetooth を使用してデバイス同士のデータを同期します。" #: ../src/gtk-ui/ui.glade.h:44 msgid "You will need to add Bluetooth devices before they can be synced." msgstr "同期を行う前に Bluetooth デバイスを追加する必要があります。" #: ../src/gtk-ui/sync.desktop.in.h:2 msgid "Up to date" msgstr "最新の情報に更新" #: ../src/gtk-ui/sync-gtk.desktop.in.h:1 msgid "SyncEvolution (GTK)" msgstr "SyncEvolution (GTK)" #: ../src/gtk-ui/sync-gtk.desktop.in.h:2 msgid "Synchronize PIM data" msgstr "PIM データの同期" #: ../src/gtk-ui/sync-config-widget.c:88 msgid "" "ScheduleWorld enables you to keep your contacts, events, tasks, and notes in" " sync." msgstr "ScheduleWorld では、連絡先、イベント、タスク、メモを同期させておくことができます。" #: ../src/gtk-ui/sync-config-widget.c:91 msgid "" "Google Sync can back up and synchronize your contacts with your Gmail " "contacts." msgstr "Google Sync では、連絡先をバックアップし、Gmail の連絡先と同期できます。" #. TRANSLATORS: Please include the word "demo" (or the equivalent in #. your language): Funambol is going to be a 90 day demo service #. in the future #: ../src/gtk-ui/sync-config-widget.c:97 msgid "" "Back up your contacts and calendar. Sync with a single click, anytime, " "anywhere (DEMO)." msgstr "連絡先とカレンダーをバックアップします。いつでも、どこでも、クリック 1 つで同期します (デモ)。" #: ../src/gtk-ui/sync-config-widget.c:100 msgid "" "Mobical Backup and Restore service allows you to securely back up your " "personal mobile data for free." msgstr "Mobical のバックアップおよび復元サービスを使用すると、無料で個人の携帯電話データを安全にバックアップできます。" #: ../src/gtk-ui/sync-config-widget.c:103 msgid "" "ZYB is a simple way for people to store and share mobile information online." msgstr "ZYB を使用すると携帯電話の情報をオンラインで簡単に保存したり共有できます。" #: ../src/gtk-ui/sync-config-widget.c:106 msgid "" "Memotoo lets you access your personal data from any computer connected to " "the Internet." msgstr "Memotoo を使用すると、インターネットに接続したコンピュータから自分の個人データにアクセスできるようになります。" #: ../src/gtk-ui/sync-config-widget.c:195 msgid "Sorry, failed to save the configuration" msgstr "設置の保存に失敗しました" #: ../src/gtk-ui/sync-config-widget.c:445 msgid "Service must have a name and server URL" msgstr "サービスには名前とサーバーの URL が必要です" #. TRANSLATORS: error dialog when creating a new sync configuration #: ../src/gtk-ui/sync-config-widget.c:451 msgid "A username is required for this service" msgstr "このサービスにはユーザー名が必要です" #: ../src/gtk-ui/sync-config-widget.c:493 #, c-format msgid "" "Do you want to reset the settings for %s? This will not remove any synced " "information on either end." msgstr "%s の設定内容をリセットしますか? この操作を行っても双方の同期された情報は一切削除されません。" #. TRANSLATORS: buttons in reset-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:497 msgid "Yes, reset" msgstr "はい、リセットします" #: ../src/gtk-ui/sync-config-widget.c:498 #: ../src/gtk-ui/sync-config-widget.c:509 msgid "No, keep settings" msgstr "いいえ、設定をそのままにします" #: ../src/gtk-ui/sync-config-widget.c:503 #, c-format msgid "" "Do you want to delete the settings for %s? This will not remove any synced " "information on either end but it will remove these settings." msgstr "%s の設定を削除しますか? この操作を行っても双方の同期した情報は一切削除されませんが、どちら側の設定も削除されます。" #. TRANSLATORS: buttons in delete-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:508 msgid "Yes, delete" msgstr "はい、削除します" #: ../src/gtk-ui/sync-config-widget.c:539 msgid "Reset settings" msgstr "設定のリセット" #: ../src/gtk-ui/sync-config-widget.c:542 msgid "Delete settings" msgstr "設定の削除" #: ../src/gtk-ui/sync-config-widget.c:552 msgid "Save and use" msgstr "保存して使用します" #: ../src/gtk-ui/sync-config-widget.c:555 msgid "" "Save and replace\n" "current service" msgstr "" "保存してこのサービス\n" "を置き換えます" #: ../src/gtk-ui/sync-config-widget.c:563 msgid "Stop using device" msgstr "デバイスの使用を中止する" #: ../src/gtk-ui/sync-config-widget.c:566 msgid "Stop using service" msgstr "サービスの使用を中止する" #. TRANSLATORS: label for an entry in service configuration form. #. * Placeholder is a source name. #. * Example: "Appointments URI" #: ../src/gtk-ui/sync-config-widget.c:749 #, c-format msgid "%s URI" msgstr "%s の URI" #. TRANSLATORS: toggles in service configuration form, placeholder is service #. * or device name #: ../src/gtk-ui/sync-config-widget.c:936 #, c-format msgid "Send changes to %s" msgstr "%s に変更を送信します" #: ../src/gtk-ui/sync-config-widget.c:941 #, c-format msgid "Receive changes from %s" msgstr "%s から変更を受信します" #: ../src/gtk-ui/sync-config-widget.c:957 msgid "Sync" msgstr "同期化" #. TRANSLATORS: label of a entry in service configuration #: ../src/gtk-ui/sync-config-widget.c:973 msgid "Server address" msgstr "サーバー・アドレス" #. TRANSLATORS: explanation before a device template combobox. #. * Placeholder is a device name like 'Nokia N85' or 'Syncevolution #. * Client' #: ../src/gtk-ui/sync-config-widget.c:1049 #, c-format msgid "" "This device looks like it might be a '%s'. If this is not correct, please " "take a look at the list of supported devices and pick yours if it is listed" msgstr "このデバイスは「%s」と判断されました。正しくない場合は、サポートされているデバイスのリストからご利用のデバイスを選択してください。" #: ../src/gtk-ui/sync-config-widget.c:1055 #: ../src/gtk-ui/sync-config-widget.c:1915 msgid "" "We don't know what this device is exactly. Please take a look at the list of" " supported devices and pick yours if it is listed" msgstr "このデバイスを判別できません。サポートされているデバイスのリストからご利用のデバイスを選択してください。" #: ../src/gtk-ui/sync-config-widget.c:1207 #, c-format msgid "%s - Bluetooth device" msgstr "%s - Bluetooth デバイス" #. TRANSLATORS: service title for services that are not based on a #. * template in service list, the placeholder is the name of the service #: ../src/gtk-ui/sync-config-widget.c:1213 #, c-format msgid "%s - manually setup" msgstr "%s - 手動設定" #. TRANSLATORS: link button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1886 msgid "Launch website" msgstr "Web サイトにアクセス" #. TRANSLATORS: button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1895 msgid "Set up now" msgstr "すぐに設定する" #: ../src/gtk-ui/sync-config-widget.c:1953 msgid "Use these settings" msgstr "これらの設定を使用する" #. TRANSLATORS: labels of entries in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1991 msgid "Username" msgstr "ユーザー名" #: ../src/gtk-ui/sync-config-widget.c:2006 msgid "Password" msgstr "パスワード" #. TRANSLATORS: warning in service configuration form for people #. who have modified the configuration via other means. #: ../src/gtk-ui/sync-config-widget.c:2029 msgid "" "Current configuration is more complex than what can be shown here. Changes " "to sync mode or synced data types will overwrite that configuration." msgstr "現在のサービス設定はここに表示できる内容よりも複雑です。同期モードまたは同期データへの変更は、その設定内容を上書きします。" #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2048 msgid "Hide server settings" msgstr "サーバの設定を隠す" #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2068 msgid "Show server settings" msgstr "サーバの設定を表示する" #: ../src/gnome-bluetooth/syncevolution.c:110 msgid "Sync in the Sync application" msgstr "同期化アプリケーション内の同期" #: ../src/syncevo-dbus-server.cpp:6653 #, c-format msgid "%s is syncing" msgstr "%s は同期中です。" #: ../src/syncevo-dbus-server.cpp:6654 #, c-format msgid "We have just started to sync your computer with the %s sync service." msgstr "お使いのデバイスと %s 同期化サービスの同期を開始しました。" #. if sync is successfully started and done #: ../src/syncevo-dbus-server.cpp:6670 #, c-format msgid "%s sync complete" msgstr "%s の同期化が完了しました" #: ../src/syncevo-dbus-server.cpp:6671 #, c-format msgid "We have just finished syncing your computer with the %s sync service." msgstr "ご利用のデバイスと %s 同期化サービスの同期が完了しました。" #. if sync is successfully started and has errors, or not started successful #. with a fatal problem #: ../src/syncevo-dbus-server.cpp:6676 msgid "Sync problem." msgstr "同期化に問題があります。" #: ../src/syncevo-dbus-server.cpp:6677 msgid "Sorry, there's a problem with your sync that you need to attend to." msgstr "接続対象の同期化サービスに問題があります。" #: ../src/syncevo-dbus-server.cpp:6758 msgid "View" msgstr "表示" #. Use "default" as ID because that is what mutter-moblin #. recognizes: it then skips the action instead of adding it #. in addition to its own "Dismiss" button (always added). #: ../src/syncevo-dbus-server.cpp:6762 msgid "Dismiss" msgstr "閉じる" syncevolution_1.4/po/ko.po000066400000000000000000000751621230021373600157600ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # # Translators: # , 2011. # , 2011. # , 2011. msgid "" msgstr "" "Project-Id-Version: syncevolution\n" "Report-Msgid-Bugs-To: https://bugs.meego.com/\n" "POT-Creation-Date: 2011-12-05 10:21-0800\n" "PO-Revision-Date: 2011-12-08 21:49+0000\n" "Last-Translator: GLS_KOR \n" "Language-Team: Korean (http://www.transifex.net/projects/p/meego/team/ko/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ko\n" "Plural-Forms: nplurals=1; plural=0\n" #. TRANSLATORS: this is the application name that may be used by e.g. #. the windowmanager #: ../src/gtk-ui/main.c:40 ../src/gtk-ui/ui.glade.h:38 #: ../src/gtk-ui/sync.desktop.in.h:1 #: ../src/gnome-bluetooth/syncevolution.c:112 msgid "Sync" msgstr "동기화" #: ../src/gtk-ui/sync-ui.c:266 msgid "Contacts" msgstr "연락처" #: ../src/gtk-ui/sync-ui.c:268 msgid "Appointments" msgstr "약속" #: ../src/gtk-ui/sync-ui.c:270 ../src/gtk-ui/ui.glade.h:40 msgid "Tasks" msgstr "작업" #: ../src/gtk-ui/sync-ui.c:272 msgid "Notes" msgstr "메모" #. TRANSLATORS: This is a "combination source" for syncing with devices #. * that combine appointments and tasks. the name should match the ones #. * used for calendar and todo above #: ../src/gtk-ui/sync-ui.c:277 msgid "Appointments & Tasks" msgstr "약속 및 작업" #: ../src/gtk-ui/sync-ui.c:349 msgid "Starting sync" msgstr "동기화 시작 " #. TRANSLATORS: slow sync confirmation dialog message. Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:387 #, c-format msgid "Do you want to slow sync with %s?" msgstr " %s과의 동기화를 늦추시겠습니까?" #: ../src/gtk-ui/sync-ui.c:391 msgid "Yes, do slow sync" msgstr "예, 동기화를 늦춰주십시오." #: ../src/gtk-ui/sync-ui.c:391 msgid "No, cancel sync" msgstr "아니요, 동기화를 취소하겠습니다." #. TRANSLATORS: confirmation dialog for "refresh from peer". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:424 #, c-format msgid "" "Do you want to delete all local data and replace it with data from %s? This " "is not usually advised." msgstr "모든 로컬 데이터을 삭제하고 %s로 부터의 데이터로 바꾸시겠습니까? 항상 권하지는 않습니다" #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 msgid "Yes, delete and replace" msgstr "예, 삭제하시고 변경하십시오" #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 #: ../src/gtk-ui/sync-ui.c:1610 msgid "No" msgstr "아니요." #. TRANSLATORS: confirmation dialog for "refresh from local side". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:457 #, c-format msgid "" "Do you want to delete all data in %s and replace it with your local data? " "This is not usually advised." msgstr "%s에 있는 모든 데이터를 삭제하고 당신의 로컬 데이터로 바꾸시겠습니까? 항상 권하지는 않습니다." #: ../src/gtk-ui/sync-ui.c:491 msgid "Trying to cancel sync" msgstr "동기화 취소 시도" #: ../src/gtk-ui/sync-ui.c:533 msgid "No service or device selected" msgstr "서비스나 장치가 선택되지 않았음" #. TRANSLATORS: This is the title on main view. Placeholder is #. * the service name. Example: "Google - synced just now" #: ../src/gtk-ui/sync-ui.c:541 #, c-format msgid "%s - synced just now" msgstr "%s - 방금 동기화됨" #: ../src/gtk-ui/sync-ui.c:545 #, c-format msgid "%s - synced a minute ago" msgstr "%s - 일 분 전 동기화됨" #: ../src/gtk-ui/sync-ui.c:549 #, c-format msgid "%s - synced %ld minutes ago" msgstr "%s - %ld 분 전 동기화됨" #: ../src/gtk-ui/sync-ui.c:554 #, c-format msgid "%s - synced an hour ago" msgstr "%s - 한 시간 전 동기화됨" #: ../src/gtk-ui/sync-ui.c:558 #, c-format msgid "%s - synced %ld hours ago" msgstr "%s - %ld 시간 전 동기화됨" #: ../src/gtk-ui/sync-ui.c:563 #, c-format msgid "%s - synced a day ago" msgstr "%s - 하루 전 동기화됨" #: ../src/gtk-ui/sync-ui.c:567 #, c-format msgid "%s - synced %ld days ago" msgstr "%s - %ld 일 전 동기화됨" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "You've just restored a backup. The changes have not been " #. * "synced with %s yet" #: ../src/gtk-ui/sync-ui.c:616 ../src/gtk-ui/sync-ui.c:701 msgid "Sync now" msgstr "지금 동기화 시도" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "A normal sync is not possible at this time..." message. #. * "Other options" will open Emergency view #: ../src/gtk-ui/sync-ui.c:622 ../src/gtk-ui/ui.glade.h:37 msgid "Slow sync" msgstr "느린 동기화" #: ../src/gtk-ui/sync-ui.c:623 msgid "Other options..." msgstr "기타 옵션..." #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * when no service is selected. Will open configuration view #: ../src/gtk-ui/sync-ui.c:628 msgid "Select sync service" msgstr "동기화 서비스를 선택하십시오." #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * login to service fails. Will open configuration view for this service #: ../src/gtk-ui/sync-ui.c:633 msgid "Edit service settings" msgstr "서비스 설정 편집" #: ../src/gtk-ui/sync-ui.c:709 msgid "" "You haven't selected a sync service or device yet. Sync services let you " "synchronize your data between your netbook and a web service. You can also " "sync directly with some devices." msgstr "" "동기화 서비스나 장치를 아직 선택하지 않았습니다. 동기화 서비스는 넷북과 웹 서비스 사이의 데이터를 동기화 시켜줍니다. 또한 일부 장치를" " 사용하여 직접 동기화할 수도 있습니다. " #: ../src/gtk-ui/sync-ui.c:729 msgid "Sync again" msgstr "다시 동기화 시도" #: ../src/gtk-ui/sync-ui.c:748 msgid "Restoring" msgstr "복원중" #: ../src/gtk-ui/sync-ui.c:750 msgid "Syncing" msgstr "동기화 시도중" #. TRANSLATORS: This is for the button in main view, right side. #. Keep line length below ~20 characters, use two lines if needed #: ../src/gtk-ui/sync-ui.c:762 ../src/gtk-ui/sync-ui.c:3407 msgid "Cancel sync" msgstr "동기화 취소" #: ../src/gtk-ui/sync-ui.c:927 msgid "Back to sync" msgstr "동기화로 다시 돌아가기" #. TRANSLATORS: label for checkbutton/toggle in main view. #. * Please stick to similar length strings or break the line with #. * "\n" if absolutely needed #: ../src/gtk-ui/sync-ui.c:1229 msgid "Automatic sync" msgstr "자동 동기화" #. This is the expander label in emergency view. It summarizes the #. * currently selected data sources. First placeholder is service/device #. * name, second a comma separeted list of sources. #. * E.g. "Affected data: Google Contacts, Appointments" #: ../src/gtk-ui/sync-ui.c:1524 #, c-format msgid "Affected data: %s %s" msgstr "영향받은 데이터: %s %s" #: ../src/gtk-ui/sync-ui.c:1529 #, c-format msgid "Affected data: none" msgstr "영향받은 데이터: 없음" #. TRANSLATORS: confirmation for restoring a backup. placeholder is the #. * backup time string defined below #: ../src/gtk-ui/sync-ui.c:1607 #, c-format msgid "" "Do you want to restore the backup from %s? All changes you have made since " "then will be lost." msgstr "%s에서 백업을 복원하시겠습니까? 그 이후의 변경 내용은 모두 손실됩니다." #: ../src/gtk-ui/sync-ui.c:1610 msgid "Yes, restore" msgstr "네, 복원해 주십시오." #. TRANSLATORS: date/time for strftime(), used in emergency view backup #. * label. Any time format that shows date and time is good. #: ../src/gtk-ui/sync-ui.c:1642 #, c-format msgid "%x %X" msgstr "%x %X" #. TRANSLATORS: label for a backup in emergency view. Placeholder is #. * service or device name #: ../src/gtk-ui/sync-ui.c:1661 #, c-format msgid "Backed up before syncing with %s" msgstr "%s와 동기화하기 전에 백업되었습니다." #: ../src/gtk-ui/sync-ui.c:1678 msgid "Restore" msgstr "복원" #. TRANSLATORS: this is an explanation in Emergency view. #. * Placeholder is a service/device name #: ../src/gtk-ui/sync-ui.c:1785 #, c-format msgid "" "A normal sync with %s is not possible at this time. You can do a slow two-" "way sync or start from scratch. You can also restore a backup, but a slow " "sync or starting from scratch will still be required before normal sync is " "possible." msgstr "" "지금은 %s와의 정상적인 동기화가 가능하지 않습니다. 느린 양방향 동기화를 하거나 처음부터 새로 시작할 수 있습니다. 백업을 복원할 수도" " 있지만, 정상적인 동기화가 가능해지기 전까지는 느린 동기화를 하거나 처음부터 새로 시작해야 합니다." #: ../src/gtk-ui/sync-ui.c:1795 #, c-format msgid "" "If something has gone horribly wrong, you can try a slow sync, start from " "scratch or restore from backup." msgstr "뭔가 아주 잘못되었을 경우, 느린 동기화를 시도해보거나 처음부터 새로 시작하거나 백업으로부터 복원할 수 있습니다." #. TRANSLATORS: These are a buttons in Emergency view. Placeholder is a #. * service/device name. Please don't use too long lines, but feel free to #. * use several lines. #: ../src/gtk-ui/sync-ui.c:1804 #, c-format msgid "" "Delete all your local\n" "data and replace with\n" "data from %s" msgstr "" "모든 로컬 데이터를\n" "삭제하고 %s의 데이터로\n" "교체하십시오." #: ../src/gtk-ui/sync-ui.c:1810 #, c-format msgid "" "Delete all data on\n" "%s and replace\n" "with your local data" msgstr "" "%s의 데이터를 모두\n" "삭제하고 로컬 데이터로\n" "교체하십시오." #: ../src/gtk-ui/sync-ui.c:2275 msgid "Failed to get list of supported services from SyncEvolution" msgstr "SyncEvolution으로 부터 지원 서비스 목록을 불러오기를 실패했습니다." #: ../src/gtk-ui/sync-ui.c:2329 msgid "" "There was a problem communicating with the sync process. Please try again " "later." msgstr "동기화 프로세스와의 통신에 문제가 있습니다. 나중에 다시 시도하십시오." #: ../src/gtk-ui/sync-ui.c:2388 msgid "Restore failed" msgstr "복원 실패" #: ../src/gtk-ui/sync-ui.c:2391 ../src/gtk-ui/sync-ui.c:3276 msgid "Sync failed" msgstr "동기화 실패" #: ../src/gtk-ui/sync-ui.c:2397 msgid "Restore complete" msgstr "복원 완료" #: ../src/gtk-ui/sync-ui.c:2400 msgid "Sync complete" msgstr "동기화 완료" #: ../src/gtk-ui/sync-ui.c:2492 #, c-format msgid "Preparing '%s'" msgstr "'%s' 준비하는 중" #: ../src/gtk-ui/sync-ui.c:2495 #, c-format msgid "Receiving '%s'" msgstr "'%s' 받는 중" #: ../src/gtk-ui/sync-ui.c:2498 #, c-format msgid "Sending '%s'" msgstr "'%s' 보내는 중" #: ../src/gtk-ui/sync-ui.c:2619 #, c-format msgid "There was one remote rejection." msgid_plural "There were %ld remote rejections." msgstr[0] "%ld 개의 원격 거부가 있었습니다." #: ../src/gtk-ui/sync-ui.c:2624 #, c-format msgid "There was one local rejection." msgid_plural "There were %ld local rejections." msgstr[0] "%ld 개의 로컬 거부가 있었습니다." #: ../src/gtk-ui/sync-ui.c:2629 #, c-format msgid "There were %ld local rejections and %ld remote rejections." msgstr "%ld 개의 로컬 거부와 %ld 개의 원격 거부가 있었습니다. " #: ../src/gtk-ui/sync-ui.c:2634 #, c-format msgid "Last time: No changes." msgstr "지난 시간: 변동 사항 없음." #: ../src/gtk-ui/sync-ui.c:2636 #, c-format msgid "Last time: Sent one change." msgid_plural "Last time: Sent %ld changes." msgstr[0] "최근: %ld 개의 변경 사항을 보냈습니다." #. This is about changes made to the local data. Not all of these #. changes were requested by the remote server, so "applied" #. is a better word than "received" (bug #5185). #: ../src/gtk-ui/sync-ui.c:2644 #, c-format msgid "Last time: Applied one change." msgid_plural "Last time: Applied %ld changes." msgstr[0] "최근: %ld 개의 변경 사항을 적용했습니다." #: ../src/gtk-ui/sync-ui.c:2649 #, c-format msgid "Last time: Applied %ld changes and sent %ld changes." msgstr "최근: %ld 개의 변경 내용을 적용했고, %ld 개의 변경 내용을 보냈습니다." #. TRANSLATORS: the placeholder is a error message (hopefully) #. * explaining the problem #: ../src/gtk-ui/sync-ui.c:2856 #, c-format msgid "" "There was a problem with last sync:\n" "%s" msgstr "" "최근 동기화에 문제가 있습니다.\n" "%s" #: ../src/gtk-ui/sync-ui.c:2866 #, c-format msgid "" "You've just restored a backup. The changes have not been synced with %s yet" msgstr "방금 백업을 복원했습니다. 변경 내용은 아직 %s와 동기화되지 않았습니다." #: ../src/gtk-ui/sync-ui.c:3154 msgid "Waiting for current operation to finish..." msgstr "현재 작동이 끝나기를 기다리고 있습니다..." #. TRANSLATORS: next strings are error messages. #: ../src/gtk-ui/sync-ui.c:3188 msgid "" "A normal sync is not possible at this time. The server suggests a slow sync," " but this might not always be what you want if both ends already have data." msgstr "" "지금은 정상적인 동기화가 가능하지 않습니다. 서버는 느린 동기화를 제안하지만, 이미 양쪽 끝에 데이터가 있는 경우 이것이 항상 귀하가 " "원하시는 방법이 아닐 수도 있습니다." #: ../src/gtk-ui/sync-ui.c:3192 msgid "The sync process died unexpectedly." msgstr "동기화 프로세스가 갑자기 종료했습니다." #: ../src/gtk-ui/sync-ui.c:3197 msgid "" "Password request was not answered. You can save the password in the settings" " to prevent the request." msgstr "암호 요청에 응답하지 않았습니다. 설정에 암호를 저장하면 요청을 하지 않습니다." #. TODO use the service device name here, this is a remote problem #: ../src/gtk-ui/sync-ui.c:3201 msgid "There was a problem processing sync request. Trying again may help." msgstr "동기화 요청을 처리하는 중에 문제가 발생했습니다. 다시 시도하면 문제가 해결될 수도 있습니다." #: ../src/gtk-ui/sync-ui.c:3207 msgid "" "Failed to login. Could there be a problem with your username or password?" msgstr "로그인하지 못했습니다. 사용자 이름이나 암호에 문제가 있습니까?" #: ../src/gtk-ui/sync-ui.c:3210 msgid "Forbidden" msgstr "금지" #. TRANSLATORS: data source means e.g. calendar or addressbook #: ../src/gtk-ui/sync-ui.c:3216 msgid "" "A data source could not be found. Could there be a problem with the " "settings?" msgstr "데이터 원본을 찾을 수 없습니다. 서버 설정에 문제가 있습니까?" #: ../src/gtk-ui/sync-ui.c:3220 msgid "Remote database error" msgstr "원격 데이터베이스 오류" #. This can happen when EDS is borked, restart it may help... #: ../src/gtk-ui/sync-ui.c:3223 msgid "" "There is a problem with the local database. Syncing again or rebooting may " "help." msgstr "이것은 로컬 데이터베이스 문제입니다. 다시 동기화하거나 재부트하면 문제가 해결될 수도 있습니다." #: ../src/gtk-ui/sync-ui.c:3226 msgid "No space on disk" msgstr "디스크에 남은 공간이 없음" #: ../src/gtk-ui/sync-ui.c:3228 msgid "Failed to process SyncML" msgstr "SyncML 프로세스 실패" #: ../src/gtk-ui/sync-ui.c:3230 msgid "Server authorization failed" msgstr "서버 권한 부여 실패" #: ../src/gtk-ui/sync-ui.c:3232 msgid "Failed to parse configuration file" msgstr "구성 파일 구문 분석 실패" #: ../src/gtk-ui/sync-ui.c:3234 msgid "Failed to read configuration file" msgstr "구성요소 파일 읽기 실패" #: ../src/gtk-ui/sync-ui.c:3236 msgid "No configuration found" msgstr "구성요소를 찾을수 없습니다." #: ../src/gtk-ui/sync-ui.c:3238 msgid "No configuration file found" msgstr "구성요소 파일을 찾지 못했습니다." #: ../src/gtk-ui/sync-ui.c:3240 msgid "Server sent bad content" msgstr "서버가 안좋은 컨텐츠를 보냈습니다." #: ../src/gtk-ui/sync-ui.c:3242 msgid "Connection certificate has expired" msgstr "연결 인증이 만료되었습니다. " #: ../src/gtk-ui/sync-ui.c:3244 msgid "Connection certificate is invalid" msgstr "연결 인증이 유효하지 않습니다." #: ../src/gtk-ui/sync-ui.c:3252 msgid "" "We were unable to connect to the server. The problem could be temporary or " "there could be something wrong with the settings." msgstr "서버에 연결할 수 없습니다. 이 문제는 일시적이거나 설정에 문제가 있을 수 있습니다." #: ../src/gtk-ui/sync-ui.c:3259 msgid "The server URL is bad" msgstr "서버 URL이 잘못되었습니다." #: ../src/gtk-ui/sync-ui.c:3264 msgid "The server was not found" msgstr "서버를 찾을 수 없습니다." #: ../src/gtk-ui/sync-ui.c:3266 #, c-format msgid "Error %d" msgstr "오류 %d" #. TRANSLATORS: password request dialog contents: title, cancel button #. * and ok button #: ../src/gtk-ui/sync-ui.c:3404 msgid "Password is required for sync" msgstr "동기화하려면 암호가 필요합니다." #: ../src/gtk-ui/sync-ui.c:3408 msgid "Sync with password" msgstr "암호 동기화" #. TRANSLATORS: password request dialog message, placeholder is service name #: ../src/gtk-ui/sync-ui.c:3418 #, c-format msgid "Please enter password for syncing with %s:" msgstr "%s 동기화 암호를 입력하십시오." #. title for the buttons on the right side of main view #: ../src/gtk-ui/ui.glade.h:2 msgid "Actions" msgstr "동작" #. text between the two "start from scratch" buttons in emergency view #: ../src/gtk-ui/ui.glade.h:4 msgid "or" msgstr "또는" #: ../src/gtk-ui/ui.glade.h:5 msgid "Direct sync" msgstr "직접 동기화" #: ../src/gtk-ui/ui.glade.h:6 msgid "Network sync" msgstr "네트워크 동기화" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:8 msgid "Restore from backup" msgstr "백업으로부터 복원" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:10 msgid "Slow sync" msgstr "느린 동기화" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:12 msgid "Start from scratch" msgstr "처음부터 새로 시작" #: ../src/gtk-ui/ui.glade.h:13 msgid "" "A slow sync compares items from both sides and tries to merge them. \n" "This may fail in some cases, leading to duplicates or lost information." msgstr "" "느린 동기화는 양방향의 항목을 비교한 다음, 이들은 병합하려고 합니다.\n" "이것은 일부의 경우 실패할 수 있으며 그 결과 복제되거나 또는 정보의 손실을 가져올 수 있습니다." #: ../src/gtk-ui/ui.glade.h:15 msgid "Add new device" msgstr "새로운 장치 추가" #: ../src/gtk-ui/ui.glade.h:16 msgid "Add new service" msgstr "새로운 서비스 추가" #. explanation of "Restore backup" function #: ../src/gtk-ui/ui.glade.h:18 msgid "" "Backups are made before every time we Sync. Choose a backup to restore. Any " "changes you have made since then will be lost." msgstr "백업은 매번 동기화하기 전에 만들어집니다. 복원할 백업을 선택하십시오. 그 이후의 변경 내용은 모두 손실됩니다." #: ../src/gtk-ui/ui.glade.h:19 msgid "Calendar" msgstr "캘린더" #. Button in main view, right side. Keep to below 20 chars per line, feel free #. to use two lines #: ../src/gtk-ui/ui.glade.h:21 msgid "" "Change or edit\n" "sync service" msgstr "" "동기화 서비스\n" "변경 또는 편집" #. close button for settings window #: ../src/gtk-ui/ui.glade.h:24 msgid "Close" msgstr "닫기" #: ../src/gtk-ui/ui.glade.h:25 msgid "" "Delete all data on Zyb \n" "and replace with your\n" "local information" msgstr "" "Zyb의 모든 데이터를\n" "삭제하고 로컬 정보로\n" "교체하십시오." #: ../src/gtk-ui/ui.glade.h:28 msgid "" "Delete all your local\n" "information and replace\n" "with data from Zyb" msgstr "" "모든 로컬 정보를\n" "삭제하고 Zyb 데이터로\n" "교체하십시오." #. button in main view, right side. Keep length to 20 characters or so, use #. two lines if needed #: ../src/gtk-ui/ui.glade.h:32 msgid "" "Fix a sync\n" "emergency" msgstr "" "동기화 수리\n" "비상시" #: ../src/gtk-ui/ui.glade.h:34 msgid "" "If you don't see your service above but know that your sync provider uses SyncML\n" "you can setup a service manually." msgstr "" "만약 당신의 서비스가 보이지는 않지만 당신의 동기화 제공자가 \n" "SyncML을 사용하는것을 알고 있다면\n" "서비스를 수동으로 설정할 수 있습니다." #: ../src/gtk-ui/ui.glade.h:36 msgid "Settings" msgstr "설정값" #: ../src/gtk-ui/ui.glade.h:39 msgid "Sync Emergency" msgstr "동기화 비상" #: ../src/gtk-ui/ui.glade.h:41 msgid "" "To sync you'll need a network connection and an account with a sync service.\n" "We support the following services: " msgstr "" "동기화를 하려면 네트워크 연결과 계정 서비스가 필요합니다.\n" "다음 서비스를 지원합니다." #: ../src/gtk-ui/ui.glade.h:43 msgid "Use Bluetooth to Sync your data from one device to another." msgstr "데이터를 한 장치에서 다른 장치로 동기화하려면 블루투스를 사용하십시오." #: ../src/gtk-ui/ui.glade.h:44 msgid "You will need to add Bluetooth devices before they can be synced." msgstr "데이터가 동기화되기 전에 블루투스를 추가해야 합니다." #: ../src/gtk-ui/sync.desktop.in.h:2 msgid "Up to date" msgstr "최신" #: ../src/gtk-ui/sync-gtk.desktop.in.h:1 msgid "SyncEvolution (GTK)" msgstr "SyncEvolution (GTK)" #: ../src/gtk-ui/sync-gtk.desktop.in.h:2 msgid "Synchronize PIM data" msgstr "PIM 데이터 동기화" #: ../src/gtk-ui/sync-config-widget.c:88 msgid "" "ScheduleWorld enables you to keep your contacts, events, tasks, and notes in" " sync." msgstr "scheduleworld에서 귀사의 연락처, 이벤트, 작업 및 동기화 노트를 보존할 수 있습니다. " #: ../src/gtk-ui/sync-config-widget.c:91 msgid "" "Google Sync can back up and synchronize your contacts with your Gmail " "contacts." msgstr "Google Sync에서 귀하의 연락처 기록을 Gmail 연락처로 백업하거나 동기화할 수 있습니다. " #. TRANSLATORS: Please include the word "demo" (or the equivalent in #. your language): Funambol is going to be a 90 day demo service #. in the future #: ../src/gtk-ui/sync-config-widget.c:97 msgid "" "Back up your contacts and calendar. Sync with a single click, anytime, " "anywhere (DEMO)." msgstr "귀하의 연락처 및 일정을 백업합니다. 한 번의 클릭으로 언제 어디서나 동기화가 가능합니다(DEMO). " #: ../src/gtk-ui/sync-config-widget.c:100 msgid "" "Mobical Backup and Restore service allows you to securely back up your " "personal mobile data for free." msgstr "Mobical 백업과 복원 서비스를 사용하면 개인용 모바일 데이터를 무료로 안전하게 백업할 수 있습니다." #: ../src/gtk-ui/sync-config-widget.c:103 msgid "" "ZYB is a simple way for people to store and share mobile information online." msgstr "ZYB는 모바일 정보를 온라인으로 저장하고 공유할 수 있는 간단한 방법입니다." #: ../src/gtk-ui/sync-config-widget.c:106 msgid "" "Memotoo lets you access your personal data from any computer connected to " "the Internet." msgstr "Memotoo를 사용하면 인터넷에 연결된 컴퓨터에서 개인용 데이터를 액세스할 수 있습니다." #: ../src/gtk-ui/sync-config-widget.c:195 msgid "Sorry, failed to save the configuration" msgstr "죄송합니다만 구성을 저장하지 못했습니다." #: ../src/gtk-ui/sync-config-widget.c:445 msgid "Service must have a name and server URL" msgstr "서비스는 반듯이 이름 및 서버 URL이 있어야 합니다." #. TRANSLATORS: error dialog when creating a new sync configuration #: ../src/gtk-ui/sync-config-widget.c:451 msgid "A username is required for this service" msgstr "이 서비스를 이용하려면 사용자 이름을 입력해야 합니다." #: ../src/gtk-ui/sync-config-widget.c:493 #, c-format msgid "" "Do you want to reset the settings for %s? This will not remove any synced " "information on either end." msgstr "%s의 설정값을 다시 설정하시겠습니까? 이것은 동기화된 정보를 어느 한쪽에서도 제거하지 않습니다." #. TRANSLATORS: buttons in reset-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:497 msgid "Yes, reset" msgstr "예, 재설정합니다." #: ../src/gtk-ui/sync-config-widget.c:498 #: ../src/gtk-ui/sync-config-widget.c:509 msgid "No, keep settings" msgstr "아니요, 설정값을 유지합니다." #: ../src/gtk-ui/sync-config-widget.c:503 #, c-format msgid "" "Do you want to delete the settings for %s? This will not remove any synced " "information on either end but it will remove these settings." msgstr "%s의 설정값을 삭제하시겠습니까? 이것은 동기화된 정보를 어느 한쪽에서도 제거하지는 않지만 이 설정값들은 제거합니다." #. TRANSLATORS: buttons in delete-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:508 msgid "Yes, delete" msgstr "예, 삭제합니다." #: ../src/gtk-ui/sync-config-widget.c:539 msgid "Reset settings" msgstr "설정값 재설정" #: ../src/gtk-ui/sync-config-widget.c:542 msgid "Delete settings" msgstr "설정값 삭제" #: ../src/gtk-ui/sync-config-widget.c:552 msgid "Save and use" msgstr "저장 및 사용" #: ../src/gtk-ui/sync-config-widget.c:555 msgid "" "Save and replace\n" "current service" msgstr "" "현재 서비스를\n" "저장하고 교체합니다." #: ../src/gtk-ui/sync-config-widget.c:563 msgid "Stop using device" msgstr "장치 사용 중단" #: ../src/gtk-ui/sync-config-widget.c:566 msgid "Stop using service" msgstr "서비스 사용 중단" #. TRANSLATORS: label for an entry in service configuration form. #. * Placeholder is a source name. #. * Example: "Appointments URI" #: ../src/gtk-ui/sync-config-widget.c:749 #, c-format msgid "%s URI" msgstr "%s URI" #. TRANSLATORS: toggles in service configuration form, placeholder is service #. * or device name #: ../src/gtk-ui/sync-config-widget.c:936 #, c-format msgid "Send changes to %s" msgstr "%s으로 변경 내용을 보냅니다." #: ../src/gtk-ui/sync-config-widget.c:941 #, c-format msgid "Receive changes from %s" msgstr "%s에서 변경 내용을 받습니다." #: ../src/gtk-ui/sync-config-widget.c:957 msgid "Sync" msgstr "동기화" #. TRANSLATORS: label of a entry in service configuration #: ../src/gtk-ui/sync-config-widget.c:973 msgid "Server address" msgstr "서버 주소" #. TRANSLATORS: explanation before a device template combobox. #. * Placeholder is a device name like 'Nokia N85' or 'Syncevolution #. * Client' #: ../src/gtk-ui/sync-config-widget.c:1049 #, c-format msgid "" "This device looks like it might be a '%s'. If this is not correct, please " "take a look at the list of supported devices and pick yours if it is listed" msgstr "이 장치는 '%s'인 것 같습니다. 그렇지 않으면 지원 장치 목록에서 열거된 것이 있는지 찾아보십시오." #: ../src/gtk-ui/sync-config-widget.c:1055 #: ../src/gtk-ui/sync-config-widget.c:1915 msgid "" "We don't know what this device is exactly. Please take a look at the list of" " supported devices and pick yours if it is listed" msgstr "이 장치가 정확히 무엇인지 모르겠습니다. 지원 장치 목록에서 열거된 것이 있는지 찾아보십시오." #: ../src/gtk-ui/sync-config-widget.c:1207 #, c-format msgid "%s - Bluetooth device" msgstr "%s - 블루투스 장치" #. TRANSLATORS: service title for services that are not based on a #. * template in service list, the placeholder is the name of the service #: ../src/gtk-ui/sync-config-widget.c:1213 #, c-format msgid "%s - manually setup" msgstr "%s - 수동으로 설정" #. TRANSLATORS: link button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1886 msgid "Launch website" msgstr "웹사이트 시작" #. TRANSLATORS: button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1895 msgid "Set up now" msgstr "지금 설정" #: ../src/gtk-ui/sync-config-widget.c:1953 msgid "Use these settings" msgstr "이 설정값들을 사용하십시오." #. TRANSLATORS: labels of entries in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1991 msgid "Username" msgstr "사용자 이름" #: ../src/gtk-ui/sync-config-widget.c:2006 msgid "Password" msgstr "암호" #. TRANSLATORS: warning in service configuration form for people #. who have modified the configuration via other means. #: ../src/gtk-ui/sync-config-widget.c:2029 msgid "" "Current configuration is more complex than what can be shown here. Changes " "to sync mode or synced data types will overwrite that configuration." msgstr "" "현재 구성은 여기에 표시될 수 있는 것보다 더 복잡합니다. 동기화 모드 또는 동기화된 데이터 유형을 변경하면 그 구성을 덮어쓰게 됩니다." #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2048 msgid "Hide server settings" msgstr "서버 설정값 숨김" #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2068 msgid "Show server settings" msgstr "서버 설정값 표시" #: ../src/gnome-bluetooth/syncevolution.c:110 msgid "Sync in the Sync application" msgstr "동기화 응용 프로그램에서 동기화" #: ../src/syncevo-dbus-server.cpp:6653 #, c-format msgid "%s is syncing" msgstr "%s 동기화 중" #: ../src/syncevo-dbus-server.cpp:6654 #, c-format msgid "We have just started to sync your computer with the %s sync service." msgstr "%s 동기화 서비스로 방금 귀하의 컴퓨터를 동기화하기 시작했습니다." #. if sync is successfully started and done #: ../src/syncevo-dbus-server.cpp:6670 #, c-format msgid "%s sync complete" msgstr "%s 동기화 완료" #: ../src/syncevo-dbus-server.cpp:6671 #, c-format msgid "We have just finished syncing your computer with the %s sync service." msgstr "%s 동기화 서비스로 방금 귀하의 컴퓨터 동기화를 마쳤습니다." #. if sync is successfully started and has errors, or not started successful #. with a fatal problem #: ../src/syncevo-dbus-server.cpp:6676 msgid "Sync problem." msgstr "동기화 문제" #: ../src/syncevo-dbus-server.cpp:6677 msgid "Sorry, there's a problem with your sync that you need to attend to." msgstr "죄송합니다. 동기화에 문제가 있으니 주의를 요합니다." #: ../src/syncevo-dbus-server.cpp:6758 msgid "View" msgstr "보기" #. Use "default" as ID because that is what mutter-moblin #. recognizes: it then skips the action instead of adding it #. in addition to its own "Dismiss" button (always added). #: ../src/syncevo-dbus-server.cpp:6762 msgid "Dismiss" msgstr "종료" syncevolution_1.4/po/nl.po000066400000000000000000000734521230021373600157600ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # # Translators: # Jiri Najman , 2011. # , 2011. msgid "" msgstr "" "Project-Id-Version: syncevolution\n" "Report-Msgid-Bugs-To: https://bugs.meego.com/\n" "POT-Creation-Date: 2011-12-05 10:21-0800\n" "PO-Revision-Date: 2011-12-06 12:22+0000\n" "Last-Translator: GLS_Translator_NLD1 \n" "Language-Team: Dutch (http://www.transifex.net/projects/p/meego/team/nl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: nl\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" #. TRANSLATORS: this is the application name that may be used by e.g. #. the windowmanager #: ../src/gtk-ui/main.c:40 ../src/gtk-ui/ui.glade.h:38 #: ../src/gtk-ui/sync.desktop.in.h:1 #: ../src/gnome-bluetooth/syncevolution.c:112 msgid "Sync" msgstr "Synchronisatie" #: ../src/gtk-ui/sync-ui.c:266 msgid "Contacts" msgstr "Contacten" #: ../src/gtk-ui/sync-ui.c:268 msgid "Appointments" msgstr "Afspraken" #: ../src/gtk-ui/sync-ui.c:270 ../src/gtk-ui/ui.glade.h:40 msgid "Tasks" msgstr "Taken" #: ../src/gtk-ui/sync-ui.c:272 msgid "Notes" msgstr "Opmerkingen" #. TRANSLATORS: This is a "combination source" for syncing with devices #. * that combine appointments and tasks. the name should match the ones #. * used for calendar and todo above #: ../src/gtk-ui/sync-ui.c:277 msgid "Appointments & Tasks" msgstr "Afspraken & Taken" #: ../src/gtk-ui/sync-ui.c:349 msgid "Starting sync" msgstr "Synchronisatie wordt gestart" #. TRANSLATORS: slow sync confirmation dialog message. Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:387 #, c-format msgid "Do you want to slow sync with %s?" msgstr "Wilt u langzaam synchroniseren met %s?" #: ../src/gtk-ui/sync-ui.c:391 msgid "Yes, do slow sync" msgstr "Ja, voer een synchronisatie uit" #: ../src/gtk-ui/sync-ui.c:391 msgid "No, cancel sync" msgstr "Nee, breek synchronisatie af" #. TRANSLATORS: confirmation dialog for "refresh from peer". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:424 #, c-format msgid "" "Do you want to delete all local data and replace it with data from %s? This " "is not usually advised." msgstr "" "Wil je alle lokale data verwijderen en vervangen met data van %s? Dit is " "niet aan te raden." #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 msgid "Yes, delete and replace" msgstr "Ja, verwijder en vervang" #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 #: ../src/gtk-ui/sync-ui.c:1610 msgid "No" msgstr "Nee" #. TRANSLATORS: confirmation dialog for "refresh from local side". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:457 #, c-format msgid "" "Do you want to delete all data in %s and replace it with your local data? " "This is not usually advised." msgstr "" "Wil je alle data in %s verwijderen en vervangen met lokale data? Dit is niet" " aan te raden." #: ../src/gtk-ui/sync-ui.c:491 msgid "Trying to cancel sync" msgstr "Proberen syncrhonisatie af te breken" #: ../src/gtk-ui/sync-ui.c:533 msgid "No service or device selected" msgstr "Geen service of apparaat geselecteerd" #. TRANSLATORS: This is the title on main view. Placeholder is #. * the service name. Example: "Google - synced just now" #: ../src/gtk-ui/sync-ui.c:541 #, c-format msgid "%s - synced just now" msgstr "%s - just klaar met synchroniseren" #: ../src/gtk-ui/sync-ui.c:545 #, c-format msgid "%s - synced a minute ago" msgstr "%s - een minuut geleden gesynchroniseerd" #: ../src/gtk-ui/sync-ui.c:549 #, c-format msgid "%s - synced %ld minutes ago" msgstr "%s - %ld minuten geleden gesynchroniseerd" #: ../src/gtk-ui/sync-ui.c:554 #, c-format msgid "%s - synced an hour ago" msgstr "%s - een uur geleden gesynchroniseerd" #: ../src/gtk-ui/sync-ui.c:558 #, c-format msgid "%s - synced %ld hours ago" msgstr "%s - %ld uren geleden gesynchroniseerd" #: ../src/gtk-ui/sync-ui.c:563 #, c-format msgid "%s - synced a day ago" msgstr "%s - een dag geleden gesynchroniseerd" #: ../src/gtk-ui/sync-ui.c:567 #, c-format msgid "%s - synced %ld days ago" msgstr "%s - %ld dagen geleden gesynchroniseerd" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "You've just restored a backup. The changes have not been " #. * "synced with %s yet" #: ../src/gtk-ui/sync-ui.c:616 ../src/gtk-ui/sync-ui.c:701 msgid "Sync now" msgstr "Synchroniseer nu" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "A normal sync is not possible at this time..." message. #. * "Other options" will open Emergency view #: ../src/gtk-ui/sync-ui.c:622 ../src/gtk-ui/ui.glade.h:37 msgid "Slow sync" msgstr "Synchronisatie" #: ../src/gtk-ui/sync-ui.c:623 msgid "Other options..." msgstr "Andere opties..." #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * when no service is selected. Will open configuration view #: ../src/gtk-ui/sync-ui.c:628 msgid "Select sync service" msgstr "Selecteer synchronisatie service" #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * login to service fails. Will open configuration view for this service #: ../src/gtk-ui/sync-ui.c:633 msgid "Edit service settings" msgstr "Bewerk service instellingen" #: ../src/gtk-ui/sync-ui.c:709 msgid "" "You haven't selected a sync service or device yet. Sync services let you " "synchronize your data between your netbook and a web service. You can also " "sync directly with some devices." msgstr "" "Je hebt geen synchronisatie service of apparaat geselecteerd. Synchronisatie" " service laten je synchroniseren tussen je netbook en een web service." #: ../src/gtk-ui/sync-ui.c:729 msgid "Sync again" msgstr "Synchroniseer opnieuw" #: ../src/gtk-ui/sync-ui.c:748 msgid "Restoring" msgstr "Herstellen" #: ../src/gtk-ui/sync-ui.c:750 msgid "Syncing" msgstr "Synchronisatie bezig" #. TRANSLATORS: This is for the button in main view, right side. #. Keep line length below ~20 characters, use two lines if needed #: ../src/gtk-ui/sync-ui.c:762 ../src/gtk-ui/sync-ui.c:3407 msgid "Cancel sync" msgstr "Synchronisatie afbreken" #: ../src/gtk-ui/sync-ui.c:927 msgid "Back to sync" msgstr "Terug naar synchronisatie" #. TRANSLATORS: label for checkbutton/toggle in main view. #. * Please stick to similar length strings or break the line with #. * "\n" if absolutely needed #: ../src/gtk-ui/sync-ui.c:1229 msgid "Automatic sync" msgstr "Automatische synchronisatie" #. This is the expander label in emergency view. It summarizes the #. * currently selected data sources. First placeholder is service/device #. * name, second a comma separeted list of sources. #. * E.g. "Affected data: Google Contacts, Appointments" #: ../src/gtk-ui/sync-ui.c:1524 #, c-format msgid "Affected data: %s %s" msgstr "Betroffen gegevens: %s %s" #: ../src/gtk-ui/sync-ui.c:1529 #, c-format msgid "Affected data: none" msgstr "Betroffen gegevens: geen" #. TRANSLATORS: confirmation for restoring a backup. placeholder is the #. * backup time string defined below #: ../src/gtk-ui/sync-ui.c:1607 #, c-format msgid "" "Do you want to restore the backup from %s? All changes you have made since " "then will be lost." msgstr "" "Wilt u de backup terugzetten van % s? Alle wijzigingen die u sindsdien hebt " "gemaakt gaan verloren." #: ../src/gtk-ui/sync-ui.c:1610 msgid "Yes, restore" msgstr "Ja, herstellen" #. TRANSLATORS: date/time for strftime(), used in emergency view backup #. * label. Any time format that shows date and time is good. #: ../src/gtk-ui/sync-ui.c:1642 #, c-format msgid "%x %X" msgstr "%x %X" #. TRANSLATORS: label for a backup in emergency view. Placeholder is #. * service or device name #: ../src/gtk-ui/sync-ui.c:1661 #, c-format msgid "Backed up before syncing with %s" msgstr "Gebackupt vóór synchroniseren met %s" #: ../src/gtk-ui/sync-ui.c:1678 msgid "Restore" msgstr "Herstellen" #. TRANSLATORS: this is an explanation in Emergency view. #. * Placeholder is a service/device name #: ../src/gtk-ui/sync-ui.c:1785 #, c-format msgid "" "A normal sync with %s is not possible at this time. You can do a slow two-" "way sync or start from scratch. You can also restore a backup, but a slow " "sync or starting from scratch will still be required before normal sync is " "possible." msgstr "" "Een normale synchronisatie met %s is niet mogelijk op dit moment. U kunt een" " langzame twee-weg synchronisatie uitvoeren of weer starten vanaf het begin." " U kunt ook een back-up terugzetten, maar een langzame twee-weg " "synchronisatie of starten vanaf het begin is nog steeds noodzakelijk voordat" " een normale synchronisatie mogelijk is." #: ../src/gtk-ui/sync-ui.c:1795 #, c-format msgid "" "If something has gone horribly wrong, you can try a slow sync, start from " "scratch or restore from backup." msgstr "" "Als iets verschrikkelijk mis is gegaan, kunt u een langzame synchronisatie " "proberen, starten vanaf het begin of herstellen vanaf een backup." #. TRANSLATORS: These are a buttons in Emergency view. Placeholder is a #. * service/device name. Please don't use too long lines, but feel free to #. * use several lines. #: ../src/gtk-ui/sync-ui.c:1804 #, c-format msgid "" "Delete all your local\n" "data and replace with\n" "data from %s" msgstr "" "Verwijder al uw lokale\n" "data en vervang het\n" "met data van %s" #: ../src/gtk-ui/sync-ui.c:1810 #, c-format msgid "" "Delete all data on\n" "%s and replace\n" "with your local data" msgstr "" "Verwijder alle data op\n" "%s en vervang het\n" "met uw lokale data" #: ../src/gtk-ui/sync-ui.c:2275 msgid "Failed to get list of supported services from SyncEvolution" msgstr "Fout bij het verkrijgen van ondersteunde services uit SyncEvolution" #: ../src/gtk-ui/sync-ui.c:2329 msgid "" "There was a problem communicating with the sync process. Please try again " "later." msgstr "" "Er was een probleem met de synchronisatie communicatie. Probeer het later " "nog eens." #: ../src/gtk-ui/sync-ui.c:2388 msgid "Restore failed" msgstr "Herstellen mislukt" #: ../src/gtk-ui/sync-ui.c:2391 ../src/gtk-ui/sync-ui.c:3276 msgid "Sync failed" msgstr "Synchronisatie mislukt" #: ../src/gtk-ui/sync-ui.c:2397 msgid "Restore complete" msgstr "Herstellen afgerond" #: ../src/gtk-ui/sync-ui.c:2400 msgid "Sync complete" msgstr "Synchronisatie afgerond" #: ../src/gtk-ui/sync-ui.c:2492 #, c-format msgid "Preparing '%s'" msgstr "'%s' voorbereiden" #: ../src/gtk-ui/sync-ui.c:2495 #, c-format msgid "Receiving '%s'" msgstr "'%s' ontvangen" #: ../src/gtk-ui/sync-ui.c:2498 #, c-format msgid "Sending '%s'" msgstr "'%s' versturen" #: ../src/gtk-ui/sync-ui.c:2619 #, c-format msgid "There was one remote rejection." msgid_plural "There were %ld remote rejections." msgstr[0] "Er waren %ld externe afwijzingen." msgstr[1] "Er waren %ld externe verwerpingen." #: ../src/gtk-ui/sync-ui.c:2624 #, c-format msgid "There was one local rejection." msgid_plural "There were %ld local rejections." msgstr[0] "Er was een lokale afwijzing." msgstr[1] "Er waren %ld lokale afwijzingen." #: ../src/gtk-ui/sync-ui.c:2629 #, c-format msgid "There were %ld local rejections and %ld remote rejections." msgstr "Er waren %ld lokale en %ld externe afwijzingen." #: ../src/gtk-ui/sync-ui.c:2634 #, c-format msgid "Last time: No changes." msgstr "Laatste keer: geen veranderingen." #: ../src/gtk-ui/sync-ui.c:2636 #, c-format msgid "Last time: Sent one change." msgid_plural "Last time: Sent %ld changes." msgstr[0] "Laatste keer: één verandering verstuurd." msgstr[1] "Laatste keer: %ld veranderingen verstuurd." #. This is about changes made to the local data. Not all of these #. changes were requested by the remote server, so "applied" #. is a better word than "received" (bug #5185). #: ../src/gtk-ui/sync-ui.c:2644 #, c-format msgid "Last time: Applied one change." msgid_plural "Last time: Applied %ld changes." msgstr[0] "Laatste keer: één verandering uitgevoerd." msgstr[1] "Laatste keer: %ld veranderingen uitgevoerd." #: ../src/gtk-ui/sync-ui.c:2649 #, c-format msgid "Last time: Applied %ld changes and sent %ld changes." msgstr "Laatste keer: %ld wijzigingen uitgevoerd, %ld verzonden." #. TRANSLATORS: the placeholder is a error message (hopefully) #. * explaining the problem #: ../src/gtk-ui/sync-ui.c:2856 #, c-format msgid "" "There was a problem with last sync:\n" "%s" msgstr "" "Er was een probleem met de laatste synchonisatie:\n" "%s" #: ../src/gtk-ui/sync-ui.c:2866 #, c-format msgid "" "You've just restored a backup. The changes have not been synced with %s yet" msgstr "" "Je hebt zojuist hersteld van een backup. De veranderingen zijn nog niet met " "%s gesynchroniseerd" #: ../src/gtk-ui/sync-ui.c:3154 msgid "Waiting for current operation to finish..." msgstr "Wachten op het voltooien van de huidige operatie..." #. TRANSLATORS: next strings are error messages. #: ../src/gtk-ui/sync-ui.c:3188 msgid "" "A normal sync is not possible at this time. The server suggests a slow sync," " but this might not always be what you want if both ends already have data." msgstr "Een normale synchronisatie is niet mogelijk op dit moment." #: ../src/gtk-ui/sync-ui.c:3192 msgid "The sync process died unexpectedly." msgstr "De synchronisatie service hing onverwachts op" #: ../src/gtk-ui/sync-ui.c:3197 msgid "" "Password request was not answered. You can save the password in the settings" " to prevent the request." msgstr "" "Wachtwoord was niet beantwoord. Je kunt het wachtwoord in de instellingen " "opslaan om dit probleem te voorkomen." #. TODO use the service device name here, this is a remote problem #: ../src/gtk-ui/sync-ui.c:3201 msgid "There was a problem processing sync request. Trying again may help." msgstr "" "Er is een probleem met de synchronisatie. Opnieuw proberen kan wellicht " "helpen." #: ../src/gtk-ui/sync-ui.c:3207 msgid "" "Failed to login. Could there be a problem with your username or password?" msgstr "" "Inloggen Mislukt. Is er een probleem met uw gebruikersnaam of wachtwoord?" #: ../src/gtk-ui/sync-ui.c:3210 msgid "Forbidden" msgstr "Verboden" #. TRANSLATORS: data source means e.g. calendar or addressbook #: ../src/gtk-ui/sync-ui.c:3216 msgid "" "A data source could not be found. Could there be a problem with the " "settings?" msgstr "" "De bron kon niet worden gevonden. Is er een probleem met de " "serverinstellingen?" #: ../src/gtk-ui/sync-ui.c:3220 msgid "Remote database error" msgstr "Service database fout" #. This can happen when EDS is borked, restart it may help... #: ../src/gtk-ui/sync-ui.c:3223 msgid "" "There is a problem with the local database. Syncing again or rebooting may " "help." msgstr "" "Er is een probleem met de lokale database. Opnieuw synchroniseren of " "herstarten kan helpen." #: ../src/gtk-ui/sync-ui.c:3226 msgid "No space on disk" msgstr "Geen schijf ruimte beschikbaar" #: ../src/gtk-ui/sync-ui.c:3228 msgid "Failed to process SyncML" msgstr "Fout bij verwerken van SyncML" #: ../src/gtk-ui/sync-ui.c:3230 msgid "Server authorization failed" msgstr "Server authorisatie mislukt" #: ../src/gtk-ui/sync-ui.c:3232 msgid "Failed to parse configuration file" msgstr "Fout bij verwerken van configuratie bestand" #: ../src/gtk-ui/sync-ui.c:3234 msgid "Failed to read configuration file" msgstr "Fout bij lezen van configuratie bestand" #: ../src/gtk-ui/sync-ui.c:3236 msgid "No configuration found" msgstr "Geen configuratie gevonden" #: ../src/gtk-ui/sync-ui.c:3238 msgid "No configuration file found" msgstr "Geen configuratie bestand gevonden" #: ../src/gtk-ui/sync-ui.c:3240 msgid "Server sent bad content" msgstr "Server verstuurde foute gegevens" #: ../src/gtk-ui/sync-ui.c:3242 msgid "Connection certificate has expired" msgstr "Verbindingscertificaat is verlopen" #: ../src/gtk-ui/sync-ui.c:3244 msgid "Connection certificate is invalid" msgstr "Verbindingscertificaat is ongeldig" #: ../src/gtk-ui/sync-ui.c:3252 msgid "" "We were unable to connect to the server. The problem could be temporary or " "there could be something wrong with the settings." msgstr "" "We waren niet in staat om verbinding te maken met de server. Het probleem " "kan tijdelijk zijn of er kan iets mis zijn met de serverinstellingen." #: ../src/gtk-ui/sync-ui.c:3259 msgid "The server URL is bad" msgstr "De server URL is niet geldig" #: ../src/gtk-ui/sync-ui.c:3264 msgid "The server was not found" msgstr "Server niet gevonden" #: ../src/gtk-ui/sync-ui.c:3266 #, c-format msgid "Error %d" msgstr "Fout %d" #. TRANSLATORS: password request dialog contents: title, cancel button #. * and ok button #: ../src/gtk-ui/sync-ui.c:3404 msgid "Password is required for sync" msgstr "Een wachtwoord is vereist voor synchronisatie" #: ../src/gtk-ui/sync-ui.c:3408 msgid "Sync with password" msgstr "Synchroniseer met een wachtwoord" #. TRANSLATORS: password request dialog message, placeholder is service name #: ../src/gtk-ui/sync-ui.c:3418 #, c-format msgid "Please enter password for syncing with %s:" msgstr "Voer een wachtwoord in voor synchroniseren met %s:" #. title for the buttons on the right side of main view #: ../src/gtk-ui/ui.glade.h:2 msgid "Actions" msgstr "Acties" #. text between the two "start from scratch" buttons in emergency view #: ../src/gtk-ui/ui.glade.h:4 msgid "or" msgstr "of" #: ../src/gtk-ui/ui.glade.h:5 msgid "Direct sync" msgstr "Direct synchroniseren" #: ../src/gtk-ui/ui.glade.h:6 msgid "Network sync" msgstr "Netwerk synchronisatie" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:8 msgid "Restore from backup" msgstr "Herstel vanaf backup" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:10 msgid "Slow sync" msgstr "Langzame synchronisatie" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:12 msgid "Start from scratch" msgstr "Starten vanaf het begin" #: ../src/gtk-ui/ui.glade.h:13 msgid "" "A slow sync compares items from both sides and tries to merge them. \n" "This may fail in some cases, leading to duplicates or lost information." msgstr "" "Een langzame synchronisatie vergelijkt items van beide kanten en\n" "probeert ze samen te voegen. Soms mislukt dit, en dit kan leiden tot\n" "duplicaten of verloren gegevens." #: ../src/gtk-ui/ui.glade.h:15 msgid "Add new device" msgstr "Nieuw apparaat" #: ../src/gtk-ui/ui.glade.h:16 msgid "Add new service" msgstr "Nieuwe service " #. explanation of "Restore backup" function #: ../src/gtk-ui/ui.glade.h:18 msgid "" "Backups are made before every time we Sync. Choose a backup to restore. Any " "changes you have made since then will be lost." msgstr "" "Backups worden gemaakt vóór elke synchronisatie. Kies een backup om te " "herstellen. Eventuele wijzigingen die u hebt gemaakt sindsdien zullen " "verloren gaan." #: ../src/gtk-ui/ui.glade.h:19 msgid "Calendar" msgstr "Kalender" #. Button in main view, right side. Keep to below 20 chars per line, feel free #. to use two lines #: ../src/gtk-ui/ui.glade.h:21 msgid "" "Change or edit\n" "sync service" msgstr "" "Wijzig of bewerk\n" "synchronisatie\n" "service" #. close button for settings window #: ../src/gtk-ui/ui.glade.h:24 msgid "Close" msgstr "Sluiten" #: ../src/gtk-ui/ui.glade.h:25 msgid "" "Delete all data on Zyb \n" "and replace with your\n" "local information" msgstr "" "Verwijder alle data op\n" "Zyb en vervang het\n" "met lokale data" #: ../src/gtk-ui/ui.glade.h:28 msgid "" "Delete all your local\n" "information and replace\n" "with data from Zyb" msgstr "" "Verwijder alle lokale\n" "data en vervang het\n" "met data van Zyb" #. button in main view, right side. Keep length to 20 characters or so, use #. two lines if needed #: ../src/gtk-ui/ui.glade.h:32 msgid "" "Fix a sync\n" "emergency" msgstr "" "Los een synchroni-\n" "satie noodgeval op" #: ../src/gtk-ui/ui.glade.h:34 msgid "" "If you don't see your service above but know that your sync provider uses SyncML\n" "you can setup a service manually." msgstr "" "Als je niet hierboven je service ziet maar je weet dat je service provider SyncML gebruikt,\n" "kun je handmatig een service opzetten." #: ../src/gtk-ui/ui.glade.h:36 msgid "Settings" msgstr "Instellingen" #: ../src/gtk-ui/ui.glade.h:39 msgid "Sync Emergency" msgstr "Synchronisatie noodgeval" #: ../src/gtk-ui/ui.glade.h:41 msgid "" "To sync you'll need a network connection and an account with a sync service.\n" "We support the following services: " msgstr "" "Om te synchroniseren moet je een netwerkverbinding hebben en een account met een\n" "synchronisatie service. De volgende services zijn ondersteund:" #: ../src/gtk-ui/ui.glade.h:43 msgid "Use Bluetooth to Sync your data from one device to another." msgstr "" "Gebruik Bluetooth voor het synchroniseren van gegevens van het ene apparaat " "naar het andere." #: ../src/gtk-ui/ui.glade.h:44 msgid "You will need to add Bluetooth devices before they can be synced." msgstr "" "U moet Bluetooth-apparaten toevoegen voordat ze kunnen worden " "gesynchroniseerd." #: ../src/gtk-ui/sync.desktop.in.h:2 msgid "Up to date" msgstr "Niet veroudert" #: ../src/gtk-ui/sync-gtk.desktop.in.h:1 msgid "SyncEvolution (GTK)" msgstr "SyncEvolution (GTK)" #: ../src/gtk-ui/sync-gtk.desktop.in.h:2 msgid "Synchronize PIM data" msgstr "PIM-data synchroniseren" #: ../src/gtk-ui/sync-config-widget.c:88 msgid "" "ScheduleWorld enables you to keep your contacts, events, tasks, and notes in" " sync." msgstr "" "ScheduleWorld kan je contacten, gebeurtenissen, taken en notities " "synchroniseren." #: ../src/gtk-ui/sync-config-widget.c:91 msgid "" "Google Sync can back up and synchronize your contacts with your Gmail " "contacts." msgstr "" "Google Sync kan je adresboek en Gmail contacten synchroniseren en er backups" " van maken." #. TRANSLATORS: Please include the word "demo" (or the equivalent in #. your language): Funambol is going to be a 90 day demo service #. in the future #: ../src/gtk-ui/sync-config-widget.c:97 msgid "" "Back up your contacts and calendar. Sync with a single click, anytime, " "anywhere (DEMO)." msgstr "" "Backup je adresboek en kalender. Synchroniseer met een muisklik, elk moment," " overal (DEMO)." #: ../src/gtk-ui/sync-config-widget.c:100 msgid "" "Mobical Backup and Restore service allows you to securely back up your " "personal mobile data for free." msgstr "" "Met Mobical Backup and Herstel service kunt u uw persoonlijke mobiele data " "veilig en gratis backuppen." #: ../src/gtk-ui/sync-config-widget.c:103 msgid "" "ZYB is a simple way for people to store and share mobile information online." msgstr "" "Met ZYB kan men eenvoudige mobiele informatie online opslaan en delen." #: ../src/gtk-ui/sync-config-widget.c:106 msgid "" "Memotoo lets you access your personal data from any computer connected to " "the Internet." msgstr "" "Met Memotoo kunt u toegang krijgen tot uw persoonlijke gegevens vanaf elke " "computer die aangesloten is op het internet." #: ../src/gtk-ui/sync-config-widget.c:195 msgid "Sorry, failed to save the configuration" msgstr "Fout bij het opslaan van het configuratie bestand" #: ../src/gtk-ui/sync-config-widget.c:445 msgid "Service must have a name and server URL" msgstr "Service moet een naam en server URL hebben" #. TRANSLATORS: error dialog when creating a new sync configuration #: ../src/gtk-ui/sync-config-widget.c:451 msgid "A username is required for this service" msgstr "Voor deze service is een gebruikersnaam vereist" #: ../src/gtk-ui/sync-config-widget.c:493 #, c-format msgid "" "Do you want to reset the settings for %s? This will not remove any synced " "information on either end." msgstr "" "Wilt u de instellingen voor %s resetten? Data dat al gesynchroniseerde is " "zal niet worden verwijderd." #. TRANSLATORS: buttons in reset-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:497 msgid "Yes, reset" msgstr "Ja, reset" #: ../src/gtk-ui/sync-config-widget.c:498 #: ../src/gtk-ui/sync-config-widget.c:509 msgid "No, keep settings" msgstr "Nee, behoud de instellingen" #: ../src/gtk-ui/sync-config-widget.c:503 #, c-format msgid "" "Do you want to delete the settings for %s? This will not remove any synced " "information on either end but it will remove these settings." msgstr "" "Wilt u de instellingen voor %s verwijderen? Data dat al gesynchroniseerde is" " zal niet worden verwijderd, alleen deze service configuratie zal worden " "verwijdert." #. TRANSLATORS: buttons in delete-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:508 msgid "Yes, delete" msgstr "Ja, verwijderen" #: ../src/gtk-ui/sync-config-widget.c:539 msgid "Reset settings" msgstr "Instellingen wissen" #: ../src/gtk-ui/sync-config-widget.c:542 msgid "Delete settings" msgstr "Verwijder instellingen" #: ../src/gtk-ui/sync-config-widget.c:552 msgid "Save and use" msgstr "Opslaan en gebruik" #: ../src/gtk-ui/sync-config-widget.c:555 msgid "" "Save and replace\n" "current service" msgstr "" "Opslaan en vervang\n" "de huidige service" #: ../src/gtk-ui/sync-config-widget.c:563 msgid "Stop using device" msgstr "Stop apparaat gebruik" #: ../src/gtk-ui/sync-config-widget.c:566 msgid "Stop using service" msgstr "Stop service gebruik" #. TRANSLATORS: label for an entry in service configuration form. #. * Placeholder is a source name. #. * Example: "Appointments URI" #: ../src/gtk-ui/sync-config-widget.c:749 #, c-format msgid "%s URI" msgstr "%s URI" #. TRANSLATORS: toggles in service configuration form, placeholder is service #. * or device name #: ../src/gtk-ui/sync-config-widget.c:936 #, c-format msgid "Send changes to %s" msgstr "Stuur wijzigingen naar '%s'" #: ../src/gtk-ui/sync-config-widget.c:941 #, c-format msgid "Receive changes from %s" msgstr "Ontvang veranderingen van %s" #: ../src/gtk-ui/sync-config-widget.c:957 msgid "Sync" msgstr "Synchronisatie" #. TRANSLATORS: label of a entry in service configuration #: ../src/gtk-ui/sync-config-widget.c:973 msgid "Server address" msgstr "Server adres" #. TRANSLATORS: explanation before a device template combobox. #. * Placeholder is a device name like 'Nokia N85' or 'Syncevolution #. * Client' #: ../src/gtk-ui/sync-config-widget.c:1049 #, c-format msgid "" "This device looks like it might be a '%s'. If this is not correct, please " "take a look at the list of supported devices and pick yours if it is listed" msgstr "" "Dit apparaat ziet eruit als een '%s'. Als dit niet correct is, kijk dan in " "de lijst met ondersteunde apparaten en kies de juiste." #: ../src/gtk-ui/sync-config-widget.c:1055 #: ../src/gtk-ui/sync-config-widget.c:1915 msgid "" "We don't know what this device is exactly. Please take a look at the list of" " supported devices and pick yours if it is listed" msgstr "" "We kunnen niet zien wat voor apparaat dit is. Kijk naar de lijst van " "ondersteunde apparaten en kies de juiste." #: ../src/gtk-ui/sync-config-widget.c:1207 #, c-format msgid "%s - Bluetooth device" msgstr "%s - Bluetooth-apparaat" #. TRANSLATORS: service title for services that are not based on a #. * template in service list, the placeholder is the name of the service #: ../src/gtk-ui/sync-config-widget.c:1213 #, c-format msgid "%s - manually setup" msgstr "%s - handmatig instellen" #. TRANSLATORS: link button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1886 msgid "Launch website" msgstr "Start website" #. TRANSLATORS: button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1895 msgid "Set up now" msgstr "Nu instellen" #: ../src/gtk-ui/sync-config-widget.c:1953 msgid "Use these settings" msgstr "Deze instellingen gebruiken" #. TRANSLATORS: labels of entries in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1991 msgid "Username" msgstr "gebruikersnaam" #: ../src/gtk-ui/sync-config-widget.c:2006 msgid "Password" msgstr "Wachtwoord" #. TRANSLATORS: warning in service configuration form for people #. who have modified the configuration via other means. #: ../src/gtk-ui/sync-config-widget.c:2029 msgid "" "Current configuration is more complex than what can be shown here. Changes " "to sync mode or synced data types will overwrite that configuration." msgstr "" "De huidige configuratie is complexer dan wat hier kan worden weergegeven. " "Wijzigingen aan de synchronisatie modus of aan de gesynchroniseerd data " "types overschrijft de configuratie." #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2048 msgid "Hide server settings" msgstr "Server instellingen verbergen" #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2068 msgid "Show server settings" msgstr "Server instellingen weergeven" #: ../src/gnome-bluetooth/syncevolution.c:110 msgid "Sync in the Sync application" msgstr "Synchroniseer in de synchronisatie toepassing" #: ../src/syncevo-dbus-server.cpp:6653 #, c-format msgid "%s is syncing" msgstr "%s synchronisatie bezig" #: ../src/syncevo-dbus-server.cpp:6654 #, c-format msgid "We have just started to sync your computer with the %s sync service." msgstr "We hebben de synchronisatie met service %s en je computer gestart." #. if sync is successfully started and done #: ../src/syncevo-dbus-server.cpp:6670 #, c-format msgid "%s sync complete" msgstr "%s synchronisatie afgerond" #: ../src/syncevo-dbus-server.cpp:6671 #, c-format msgid "We have just finished syncing your computer with the %s sync service." msgstr "Synchronisatie met service %s en je computer is voltooid." #. if sync is successfully started and has errors, or not started successful #. with a fatal problem #: ../src/syncevo-dbus-server.cpp:6676 msgid "Sync problem." msgstr "Synchronisatie probleem." #: ../src/syncevo-dbus-server.cpp:6677 msgid "Sorry, there's a problem with your sync that you need to attend to." msgstr "" "Sorry, er is een probleem met de synchronisatie dat naar ingekeken moet " "worden." #: ../src/syncevo-dbus-server.cpp:6758 msgid "View" msgstr "Bekijk" #. Use "default" as ID because that is what mutter-moblin #. recognizes: it then skips the action instead of adding it #. in addition to its own "Dismiss" button (always added). #: ../src/syncevo-dbus-server.cpp:6762 msgid "Dismiss" msgstr "Gezien" syncevolution_1.4/po/pl.po000066400000000000000000000753551230021373600157660ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # # Translators: # Andrzej Zaborowski , 2009. # Romuald Pawlikowski , 2011. # , 2011. msgid "" msgstr "" "Project-Id-Version: syncevolution\n" "Report-Msgid-Bugs-To: https://bugs.meego.com/\n" "POT-Creation-Date: 2011-12-05 10:21-0800\n" "PO-Revision-Date: 2011-12-06 18:11+0000\n" "Last-Translator: GLS_Translator_PLK2 \n" "Language-Team: Polish (http://www.transifex.net/projects/p/meego/team/pl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: pl\n" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n" #. TRANSLATORS: this is the application name that may be used by e.g. #. the windowmanager #: ../src/gtk-ui/main.c:40 ../src/gtk-ui/ui.glade.h:38 #: ../src/gtk-ui/sync.desktop.in.h:1 #: ../src/gnome-bluetooth/syncevolution.c:112 msgid "Sync" msgstr "Synchronizuj" #: ../src/gtk-ui/sync-ui.c:266 msgid "Contacts" msgstr "Kontakty" #: ../src/gtk-ui/sync-ui.c:268 msgid "Appointments" msgstr "Spotkania" #: ../src/gtk-ui/sync-ui.c:270 ../src/gtk-ui/ui.glade.h:40 msgid "Tasks" msgstr "Zadania" #: ../src/gtk-ui/sync-ui.c:272 msgid "Notes" msgstr "Notatki" #. TRANSLATORS: This is a "combination source" for syncing with devices #. * that combine appointments and tasks. the name should match the ones #. * used for calendar and todo above #: ../src/gtk-ui/sync-ui.c:277 msgid "Appointments & Tasks" msgstr "Spotkania i zadania" #: ../src/gtk-ui/sync-ui.c:349 msgid "Starting sync" msgstr "Rozpoczynanie synchronizacji" #. TRANSLATORS: slow sync confirmation dialog message. Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:387 #, c-format msgid "Do you want to slow sync with %s?" msgstr "Czy chcesz dokonać wolnej synchronizacji z %s?" #: ../src/gtk-ui/sync-ui.c:391 msgid "Yes, do slow sync" msgstr "Tak, wykonaj wolną synchronizację" #: ../src/gtk-ui/sync-ui.c:391 msgid "No, cancel sync" msgstr "Nie, anuluj synchronizację" #. TRANSLATORS: confirmation dialog for "refresh from peer". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:424 #, c-format msgid "" "Do you want to delete all local data and replace it with data from %s? This " "is not usually advised." msgstr "" "Czy chcesz usunąć wszystkie lokalne dane i zastąpić je danymi z %s? Nie jest" " to zwykle zalecane." #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 msgid "Yes, delete and replace" msgstr "Tak, usuń i zastąp" #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 #: ../src/gtk-ui/sync-ui.c:1610 msgid "No" msgstr "Nie" #. TRANSLATORS: confirmation dialog for "refresh from local side". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:457 #, c-format msgid "" "Do you want to delete all data in %s and replace it with your local data? " "This is not usually advised." msgstr "" "Czy chcesz usunąć wszystkie dane w %s i zastąpić je danymi lokalnymi? Nie " "jest to zwykle zalecane." #: ../src/gtk-ui/sync-ui.c:491 msgid "Trying to cancel sync" msgstr "Trwa próba anulowania synchronizacji" #: ../src/gtk-ui/sync-ui.c:533 msgid "No service or device selected" msgstr "Nie wybrano usługi lub urządzenia" #. TRANSLATORS: This is the title on main view. Placeholder is #. * the service name. Example: "Google - synced just now" #: ../src/gtk-ui/sync-ui.c:541 #, c-format msgid "%s - synced just now" msgstr "%s - właśnie zsynchronizowano" #: ../src/gtk-ui/sync-ui.c:545 #, c-format msgid "%s - synced a minute ago" msgstr "%s - zsynchronizowano minutę temu" #: ../src/gtk-ui/sync-ui.c:549 #, c-format msgid "%s - synced %ld minutes ago" msgstr "%s - zsynchronizowano %ld minut temu" #: ../src/gtk-ui/sync-ui.c:554 #, c-format msgid "%s - synced an hour ago" msgstr "%s - zsynchronizowano godzinę temu" #: ../src/gtk-ui/sync-ui.c:558 #, c-format msgid "%s - synced %ld hours ago" msgstr "%s - zsynchronizowano %ld godzin temu" #: ../src/gtk-ui/sync-ui.c:563 #, c-format msgid "%s - synced a day ago" msgstr "%s - zsynchronizowano wczoraj" #: ../src/gtk-ui/sync-ui.c:567 #, c-format msgid "%s - synced %ld days ago" msgstr "%s - zsynchronizowano %ld dni temu" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "You've just restored a backup. The changes have not been " #. * "synced with %s yet" #: ../src/gtk-ui/sync-ui.c:616 ../src/gtk-ui/sync-ui.c:701 msgid "Sync now" msgstr "Synchronizuj teraz" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "A normal sync is not possible at this time..." message. #. * "Other options" will open Emergency view #: ../src/gtk-ui/sync-ui.c:622 ../src/gtk-ui/ui.glade.h:37 msgid "Slow sync" msgstr "Wolna synchronizacja" #: ../src/gtk-ui/sync-ui.c:623 msgid "Other options..." msgstr "Inne opcje..." #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * when no service is selected. Will open configuration view #: ../src/gtk-ui/sync-ui.c:628 msgid "Select sync service" msgstr "Wybierz usługę synchronizacji" #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * login to service fails. Will open configuration view for this service #: ../src/gtk-ui/sync-ui.c:633 msgid "Edit service settings" msgstr "Edycja ustawień usługi" #: ../src/gtk-ui/sync-ui.c:709 msgid "" "You haven't selected a sync service or device yet. Sync services let you " "synchronize your data between your netbook and a web service. You can also " "sync directly with some devices." msgstr "" "Nie wybrałeś jeszcze usługi synchronizacji lub urządzenia. Usługi " "synchronizacji pozwalają na synchronizowanie danych pomiędzy netbookiem a " "usługą internetową. Można również synchronizować bezpośrednio z niektórymi " "urządzeniami." #: ../src/gtk-ui/sync-ui.c:729 msgid "Sync again" msgstr "Synchronizuj jeszcze raz" #: ../src/gtk-ui/sync-ui.c:748 msgid "Restoring" msgstr "Przywracanie" #: ../src/gtk-ui/sync-ui.c:750 msgid "Syncing" msgstr "Synchronizowanie" #. TRANSLATORS: This is for the button in main view, right side. #. Keep line length below ~20 characters, use two lines if needed #: ../src/gtk-ui/sync-ui.c:762 ../src/gtk-ui/sync-ui.c:3407 msgid "Cancel sync" msgstr "Anuluj synchronizację" #: ../src/gtk-ui/sync-ui.c:927 msgid "Back to sync" msgstr "Powrót do synchronizacji" #. TRANSLATORS: label for checkbutton/toggle in main view. #. * Please stick to similar length strings or break the line with #. * "\n" if absolutely needed #: ../src/gtk-ui/sync-ui.c:1229 msgid "Automatic sync" msgstr "" "Automatyczna\n" "synchronizacja" #. This is the expander label in emergency view. It summarizes the #. * currently selected data sources. First placeholder is service/device #. * name, second a comma separeted list of sources. #. * E.g. "Affected data: Google Contacts, Appointments" #: ../src/gtk-ui/sync-ui.c:1524 #, c-format msgid "Affected data: %s %s" msgstr "Dane, na które ma to wpływ: %s %s" #: ../src/gtk-ui/sync-ui.c:1529 #, c-format msgid "Affected data: none" msgstr "Dane, na które ma to wpływ: brak" #. TRANSLATORS: confirmation for restoring a backup. placeholder is the #. * backup time string defined below #: ../src/gtk-ui/sync-ui.c:1607 #, c-format msgid "" "Do you want to restore the backup from %s? All changes you have made since " "then will be lost." msgstr "" "Czy chcesz przywrócić kopię zapasową z %s? Wszystkie wprowadzone od tamtego " "czasu zmiany zostaną utracone." #: ../src/gtk-ui/sync-ui.c:1610 msgid "Yes, restore" msgstr "Tak, przywróć" #. TRANSLATORS: date/time for strftime(), used in emergency view backup #. * label. Any time format that shows date and time is good. #: ../src/gtk-ui/sync-ui.c:1642 #, c-format msgid "%x %X" msgstr "%x %X" #. TRANSLATORS: label for a backup in emergency view. Placeholder is #. * service or device name #: ../src/gtk-ui/sync-ui.c:1661 #, c-format msgid "Backed up before syncing with %s" msgstr "Wykonano kopie zapasową przed synchronizacją z %s" #: ../src/gtk-ui/sync-ui.c:1678 msgid "Restore" msgstr "Przywróć" #. TRANSLATORS: this is an explanation in Emergency view. #. * Placeholder is a service/device name #: ../src/gtk-ui/sync-ui.c:1785 #, c-format msgid "" "A normal sync with %s is not possible at this time. You can do a slow two-" "way sync or start from scratch. You can also restore a backup, but a slow " "sync or starting from scratch will still be required before normal sync is " "possible." msgstr "" "Normalna synchronizacja z %s nie jest teraz możliwa. Można wykonać wolną, " "dwukierunkową synchronizację, lub zacząć od początku. Można również " "przywrócić kopię zapasową, ale wykonanie wolnej synchronizacji lub " "rozpoczęcie od początku wciąż będzie wymagane, zanim normalna synchronizacja" " będzie możliwa." #: ../src/gtk-ui/sync-ui.c:1795 #, c-format msgid "" "If something has gone horribly wrong, you can try a slow sync, start from " "scratch or restore from backup." msgstr "" "W przypadku poważnych problemów można spróbować wykonać wolną " "synchronizację, zacząć od początku lub przywrócić z kopii zapasowej." #. TRANSLATORS: These are a buttons in Emergency view. Placeholder is a #. * service/device name. Please don't use too long lines, but feel free to #. * use several lines. #: ../src/gtk-ui/sync-ui.c:1804 #, c-format msgid "" "Delete all your local\n" "data and replace with\n" "data from %s" msgstr "" "Skasuj wszystkie lokalne\n" "dane i zastąp je\n" "zdalnymi z %s" #: ../src/gtk-ui/sync-ui.c:1810 #, c-format msgid "" "Delete all data on\n" "%s and replace\n" "with your local data" msgstr "" "Skasuj wszystkie dane\n" "na %s i zastąp je\n" "lokalnymi" #: ../src/gtk-ui/sync-ui.c:2275 msgid "Failed to get list of supported services from SyncEvolution" msgstr "Błąd przy pobieraniu listy kompatybilnych usług z SyncEvolution" #: ../src/gtk-ui/sync-ui.c:2329 msgid "" "There was a problem communicating with the sync process. Please try again " "later." msgstr "" "Wystąpił problem podczas komunikacji z procesem synchronizacji. Spróbuj " "ponownie później." #: ../src/gtk-ui/sync-ui.c:2388 msgid "Restore failed" msgstr "Przywracanie nie powiodło się" #: ../src/gtk-ui/sync-ui.c:2391 ../src/gtk-ui/sync-ui.c:3276 msgid "Sync failed" msgstr "Synchronizacja nie powiodła się" #: ../src/gtk-ui/sync-ui.c:2397 msgid "Restore complete" msgstr "Przywracanie zakończone" #: ../src/gtk-ui/sync-ui.c:2400 msgid "Sync complete" msgstr "Synchronizacja zakończona" #: ../src/gtk-ui/sync-ui.c:2492 #, c-format msgid "Preparing '%s'" msgstr "Przygotowywanie '%s'" #: ../src/gtk-ui/sync-ui.c:2495 #, c-format msgid "Receiving '%s'" msgstr "Odbieranie '%s'" #: ../src/gtk-ui/sync-ui.c:2498 #, c-format msgid "Sending '%s'" msgstr "Wysyłanie '%s'" #: ../src/gtk-ui/sync-ui.c:2619 #, c-format msgid "There was one remote rejection." msgid_plural "There were %ld remote rejections." msgstr[0] "Druga strona odrzuciła jeden z elementów." msgstr[1] "Druga strona odrzuciła %ld elementy." msgstr[2] "Druga strona odrzuciła %ld elementów." #: ../src/gtk-ui/sync-ui.c:2624 #, c-format msgid "There was one local rejection." msgid_plural "There were %ld local rejections." msgstr[0] "Odrzucono lokalnie jeden element." msgstr[1] "Odrzucono lokalnie %ld elementy." msgstr[2] "Odrzucono lokalnie %ld elementów." #: ../src/gtk-ui/sync-ui.c:2629 #, c-format msgid "There were %ld local rejections and %ld remote rejections." msgstr "" "Odrzucono lokalnie %ld elementów podczas gdy druga strona odrzuciła %ld " "elementów." #: ../src/gtk-ui/sync-ui.c:2634 #, c-format msgid "Last time: No changes." msgstr "Ostatnio: brak zmian." #: ../src/gtk-ui/sync-ui.c:2636 #, c-format msgid "Last time: Sent one change." msgid_plural "Last time: Sent %ld changes." msgstr[0] "Ostatnio: wysłano jedną zmianę." msgstr[1] "Ostatnio: wysłano %ld zmiany." msgstr[2] "Ostatnio: wysłano %ld zmian." #. This is about changes made to the local data. Not all of these #. changes were requested by the remote server, so "applied" #. is a better word than "received" (bug #5185). #: ../src/gtk-ui/sync-ui.c:2644 #, c-format msgid "Last time: Applied one change." msgid_plural "Last time: Applied %ld changes." msgstr[0] "Ostatnio: zastosowano jedną zmianę." msgstr[1] "Ostatnio: zastosowano %ld zmiany." msgstr[2] "Ostatnio: zastosowano %ld zmian." #: ../src/gtk-ui/sync-ui.c:2649 #, c-format msgid "Last time: Applied %ld changes and sent %ld changes." msgstr "Ostatnio: zastosowano %ld zmian i wysłano %ld zmian." #. TRANSLATORS: the placeholder is a error message (hopefully) #. * explaining the problem #: ../src/gtk-ui/sync-ui.c:2856 #, c-format msgid "" "There was a problem with last sync:\n" "%s" msgstr "" "Wystąpił problem z ostatnią synchronizacją:\n" "%s" #: ../src/gtk-ui/sync-ui.c:2866 #, c-format msgid "" "You've just restored a backup. The changes have not been synced with %s yet" msgstr "" "Właśnie przywrócono kopię zapasową. Zmiany nie zostały jeszcze " "zsynchronizowane z %s" #: ../src/gtk-ui/sync-ui.c:3154 msgid "Waiting for current operation to finish..." msgstr "Oczekiwanie na zakończenie bieżącej operacji..." #. TRANSLATORS: next strings are error messages. #: ../src/gtk-ui/sync-ui.c:3188 msgid "" "A normal sync is not possible at this time. The server suggests a slow sync," " but this might not always be what you want if both ends already have data." msgstr "" "Wykonanie normalnej synchronizacji nie jest teraz możliwe. Serwer sugeruje " "wykonanie wolnej synchronizacji, ale może to nie być pożądana czynność w " "przypadku, gdy po obu stronach znajdują się już dane." #: ../src/gtk-ui/sync-ui.c:3192 msgid "The sync process died unexpectedly." msgstr "Proces synchronizacji został nieoczekiwanie zakończony." #: ../src/gtk-ui/sync-ui.c:3197 msgid "" "Password request was not answered. You can save the password in the settings" " to prevent the request." msgstr "" "Nie uzyskano odpowiedzi na żądanie o hasło. Hasło można zapisać w " "ustawieniach, aby uniknąć wysyłania żądania." #. TODO use the service device name here, this is a remote problem #: ../src/gtk-ui/sync-ui.c:3201 msgid "There was a problem processing sync request. Trying again may help." msgstr "" "Wystąpił problem z przetwarzaniem żądania synchronizacji. Ponowna próba może" " pomóc." #: ../src/gtk-ui/sync-ui.c:3207 msgid "" "Failed to login. Could there be a problem with your username or password?" msgstr "" "Logowanie nie powiodło się. Czy może to być problem z nazwą użytkownika lub " "hasłem?" #: ../src/gtk-ui/sync-ui.c:3210 msgid "Forbidden" msgstr "Nie dozwolone" #. TRANSLATORS: data source means e.g. calendar or addressbook #: ../src/gtk-ui/sync-ui.c:3216 msgid "" "A data source could not be found. Could there be a problem with the " "settings?" msgstr "" "Nie można odnaleźć źródła danych. Czy może być to problem z ustawieniami?" #: ../src/gtk-ui/sync-ui.c:3220 msgid "Remote database error" msgstr "Błąd zdalnej bazy danych" #. This can happen when EDS is borked, restart it may help... #: ../src/gtk-ui/sync-ui.c:3223 msgid "" "There is a problem with the local database. Syncing again or rebooting may " "help." msgstr "" "Wystąpił problem z lokalną bazą danych. Ponowna synchronizacja lub " "uruchomienie komputera mogą pomóc." #: ../src/gtk-ui/sync-ui.c:3226 msgid "No space on disk" msgstr "Brak miejsca na dysku" #: ../src/gtk-ui/sync-ui.c:3228 msgid "Failed to process SyncML" msgstr "Błąd przetwarzania SyncML" #: ../src/gtk-ui/sync-ui.c:3230 msgid "Server authorization failed" msgstr "Serwer nie zaakceptował autoryzacji" #: ../src/gtk-ui/sync-ui.c:3232 msgid "Failed to parse configuration file" msgstr "Błąd składniowy w pliku konfiguracyjnym" #: ../src/gtk-ui/sync-ui.c:3234 msgid "Failed to read configuration file" msgstr "Błąd odczytu pliku konfiguracyjnego" #: ../src/gtk-ui/sync-ui.c:3236 msgid "No configuration found" msgstr "Nie znaleziono konfiguracji" #: ../src/gtk-ui/sync-ui.c:3238 msgid "No configuration file found" msgstr "Nie znaleziono pliku konfiguracyjnego" #: ../src/gtk-ui/sync-ui.c:3240 msgid "Server sent bad content" msgstr "Serwer przysłał nie prawidłowe dane" #: ../src/gtk-ui/sync-ui.c:3242 msgid "Connection certificate has expired" msgstr "Wygasł certyfikat połączenia" #: ../src/gtk-ui/sync-ui.c:3244 msgid "Connection certificate is invalid" msgstr "Niewłaściwy certyfikat połączenia" #: ../src/gtk-ui/sync-ui.c:3252 msgid "" "We were unable to connect to the server. The problem could be temporary or " "there could be something wrong with the settings." msgstr "" "Nie można połączyć się z serwerem. Problem może być tymczasowy, lub " "ustawienia mogą być błędne." #: ../src/gtk-ui/sync-ui.c:3259 msgid "The server URL is bad" msgstr "Nieprawidłowy adres URL serwera" #: ../src/gtk-ui/sync-ui.c:3264 msgid "The server was not found" msgstr "Nie znaleziono serwera" #: ../src/gtk-ui/sync-ui.c:3266 #, c-format msgid "Error %d" msgstr "Błąd %d" #. TRANSLATORS: password request dialog contents: title, cancel button #. * and ok button #: ../src/gtk-ui/sync-ui.c:3404 msgid "Password is required for sync" msgstr "Synchronizacja wymaga hasła" #: ../src/gtk-ui/sync-ui.c:3408 msgid "Sync with password" msgstr "Synchronizacja z hasłem" #. TRANSLATORS: password request dialog message, placeholder is service name #: ../src/gtk-ui/sync-ui.c:3418 #, c-format msgid "Please enter password for syncing with %s:" msgstr "Podaj hasło do synchronizacji z %s:" #. title for the buttons on the right side of main view #: ../src/gtk-ui/ui.glade.h:2 msgid "Actions" msgstr "Czynności" #. text between the two "start from scratch" buttons in emergency view #: ../src/gtk-ui/ui.glade.h:4 msgid "or" msgstr "lub" #: ../src/gtk-ui/ui.glade.h:5 msgid "Direct sync" msgstr "Synchronizacja bezpośrednia" #: ../src/gtk-ui/ui.glade.h:6 msgid "Network sync" msgstr "Synchronizacja sieciowa" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:8 msgid "Restore from backup" msgstr "Przywracanie z kopii zapasowej" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:10 msgid "Slow sync" msgstr "Wolna synchronizacja" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:12 msgid "Start from scratch" msgstr "Zacznij od początku" #: ../src/gtk-ui/ui.glade.h:13 msgid "" "A slow sync compares items from both sides and tries to merge them. \n" "This may fail in some cases, leading to duplicates or lost information." msgstr "" "Powolna synchronizacja porównuje elementu po obu stronach i próbuje je połączyć. \n" "W niektórych przypadkach może się to nie powieść, prowadząc do zduplikowania lub utraty informacji." #: ../src/gtk-ui/ui.glade.h:15 msgid "Add new device" msgstr "Dodaj nowe urządzenie" #: ../src/gtk-ui/ui.glade.h:16 msgid "Add new service" msgstr "Dodaj nową usługę" #. explanation of "Restore backup" function #: ../src/gtk-ui/ui.glade.h:18 msgid "" "Backups are made before every time we Sync. Choose a backup to restore. Any " "changes you have made since then will be lost." msgstr "" "Kopie zapasowe są wykonywane przy każdej synchronizacji. Wybierz kopię " "zapasową, z której przywrócić. Wszelkie zmiany wprowadzone od tamtego czasu " "zostaną utracone." #: ../src/gtk-ui/ui.glade.h:19 msgid "Calendar" msgstr "Kalendarz" #. Button in main view, right side. Keep to below 20 chars per line, feel free #. to use two lines #: ../src/gtk-ui/ui.glade.h:21 msgid "" "Change or edit\n" "sync service" msgstr "" "Zmień lub edytuj\n" "usługę synchronizacji" #. close button for settings window #: ../src/gtk-ui/ui.glade.h:24 msgid "Close" msgstr "Zamknij" #: ../src/gtk-ui/ui.glade.h:25 msgid "" "Delete all data on Zyb \n" "and replace with your\n" "local information" msgstr "" "Skasuj wszystkie dane\n" "z usługi Zyb i zastąp\n" "je lokalnymi" #: ../src/gtk-ui/ui.glade.h:28 msgid "" "Delete all your local\n" "information and replace\n" "with data from Zyb" msgstr "" "Skasuj wszystkie lokalne\n" "dane i zastąp je zdalnymi\n" "z usługi Zyb" #. button in main view, right side. Keep length to 20 characters or so, use #. two lines if needed #: ../src/gtk-ui/ui.glade.h:32 msgid "" "Fix a sync\n" "emergency" msgstr "" "Napraw awarię\n" "synchronizacji" #: ../src/gtk-ui/ui.glade.h:34 msgid "" "If you don't see your service above but know that your sync provider uses SyncML\n" "you can setup a service manually." msgstr "" "Jeśli Twoja usługa nie jest widoczna powyżej a wiesz że dostawca używa SyncML\n" "możesz ustawić usługę ręcznie." #: ../src/gtk-ui/ui.glade.h:36 msgid "Settings" msgstr "Ustawienia" #: ../src/gtk-ui/ui.glade.h:39 msgid "Sync Emergency" msgstr "Awaria synchronizacji" #: ../src/gtk-ui/ui.glade.h:41 msgid "" "To sync you'll need a network connection and an account with a sync service.\n" "We support the following services: " msgstr "" "Aby móc synchronizować potrzebne są połączenie z siecią oraz konto dla usługi\n" "synchronizacji. Następujące usługi są kompatybilne:" #: ../src/gtk-ui/ui.glade.h:43 msgid "Use Bluetooth to Sync your data from one device to another." msgstr "" "Użyj interfejsu Bluetooth lub zsynchronizuj dane z jednego urządzenia z " "innym." #: ../src/gtk-ui/ui.glade.h:44 msgid "You will need to add Bluetooth devices before they can be synced." msgstr "" "Konieczne będzie dodanie urządzeń Bluetooth, zanim będą one synchronizowane." #: ../src/gtk-ui/sync.desktop.in.h:2 msgid "Up to date" msgstr "Aktualny" #: ../src/gtk-ui/sync-gtk.desktop.in.h:1 msgid "SyncEvolution (GTK)" msgstr "SyncEvolution (GTK)" #: ../src/gtk-ui/sync-gtk.desktop.in.h:2 msgid "Synchronize PIM data" msgstr "Synchronizuj dane PIM" #: ../src/gtk-ui/sync-config-widget.c:88 msgid "" "ScheduleWorld enables you to keep your contacts, events, tasks, and notes in" " sync." msgstr "" "ScheduleWorld pozwala na synchronizowanie kontaktów, spotkań, zadań i " "notatek." #: ../src/gtk-ui/sync-config-widget.c:91 msgid "" "Google Sync can back up and synchronize your contacts with your Gmail " "contacts." msgstr "" "Google Sync może zapisywać i synchronizować kopię twojej książki adresowej z" " kontaktami Gmail." #. TRANSLATORS: Please include the word "demo" (or the equivalent in #. your language): Funambol is going to be a 90 day demo service #. in the future #: ../src/gtk-ui/sync-config-widget.c:97 msgid "" "Back up your contacts and calendar. Sync with a single click, anytime, " "anywhere (DEMO)." msgstr "" "Zrób kopię zapasową swoich kontaktów i kalendarza. Synchronizuj jednym " "kliknięciem, kiedykolwiek i gdziekolwiek (DEMO)." #: ../src/gtk-ui/sync-config-widget.c:100 msgid "" "Mobical Backup and Restore service allows you to securely back up your " "personal mobile data for free." msgstr "" "Usługa kopii zapasowej i przywracania Mobical umożliwia za darmo wykonanie " "bezpiecznej kopii zapasowej osobistych danych mobilnych." #: ../src/gtk-ui/sync-config-widget.c:103 msgid "" "ZYB is a simple way for people to store and share mobile information online." msgstr "" "ZYB to prosty sposób na przechowywanie i udostępnianie informacji mobilnych " "w trybie online." #: ../src/gtk-ui/sync-config-widget.c:106 msgid "" "Memotoo lets you access your personal data from any computer connected to " "the Internet." msgstr "" "Memotoo umożliwia dostęp do informacji osobistych z dowolnego komputera " "podłączonego do sieci Internet." #: ../src/gtk-ui/sync-config-widget.c:195 msgid "Sorry, failed to save the configuration" msgstr "Przepraszamy, zapis pliku konfiguracyjnego nie powiódł się" #: ../src/gtk-ui/sync-config-widget.c:445 msgid "Service must have a name and server URL" msgstr "Usługa musi mieć nazwę i URL serwera" #. TRANSLATORS: error dialog when creating a new sync configuration #: ../src/gtk-ui/sync-config-widget.c:451 msgid "A username is required for this service" msgstr "Ta usługa wymaga podania nazwy użytkownika" #: ../src/gtk-ui/sync-config-widget.c:493 #, c-format msgid "" "Do you want to reset the settings for %s? This will not remove any synced " "information on either end." msgstr "" "Czy chcesz zresetować ustawienia dla %s? Nie spowoduje to usunięcia " "jakichkolwiek informacji zsynchronizowanych na żadnym z urządzeń." #. TRANSLATORS: buttons in reset-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:497 msgid "Yes, reset" msgstr "Tak, zresetuj" #: ../src/gtk-ui/sync-config-widget.c:498 #: ../src/gtk-ui/sync-config-widget.c:509 msgid "No, keep settings" msgstr "Nie, zachowaj ustawienia" #: ../src/gtk-ui/sync-config-widget.c:503 #, c-format msgid "" "Do you want to delete the settings for %s? This will not remove any synced " "information on either end but it will remove these settings." msgstr "" "Czy chcesz usunąć ustawienia dla %s? Nie spowoduje to usunięcia " "jakichkolwiek informacji zsynchronizowanych na żadnym z urządzeń, ale " "spowoduje usunięcie ustawień." #. TRANSLATORS: buttons in delete-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:508 msgid "Yes, delete" msgstr "Tak, usuń" #: ../src/gtk-ui/sync-config-widget.c:539 msgid "Reset settings" msgstr "Resetuj ustawienia" #: ../src/gtk-ui/sync-config-widget.c:542 msgid "Delete settings" msgstr "Usuń ustawienia" #: ../src/gtk-ui/sync-config-widget.c:552 msgid "Save and use" msgstr "Zapisz i użyj" #: ../src/gtk-ui/sync-config-widget.c:555 msgid "" "Save and replace\n" "current service" msgstr "" "Zapisz i zastąp\n" "bieżącą usługę" #: ../src/gtk-ui/sync-config-widget.c:563 msgid "Stop using device" msgstr "Zaprzestań używania urządzenia" #: ../src/gtk-ui/sync-config-widget.c:566 msgid "Stop using service" msgstr "Zaprzestań używania usługi" #. TRANSLATORS: label for an entry in service configuration form. #. * Placeholder is a source name. #. * Example: "Appointments URI" #: ../src/gtk-ui/sync-config-widget.c:749 #, c-format msgid "%s URI" msgstr "URI %s" #. TRANSLATORS: toggles in service configuration form, placeholder is service #. * or device name #: ../src/gtk-ui/sync-config-widget.c:936 #, c-format msgid "Send changes to %s" msgstr "Wyślij zmiany do %s" #: ../src/gtk-ui/sync-config-widget.c:941 #, c-format msgid "Receive changes from %s" msgstr "Odbierz zmiany od %s" #: ../src/gtk-ui/sync-config-widget.c:957 msgid "Sync" msgstr "Synchronizacja" #. TRANSLATORS: label of a entry in service configuration #: ../src/gtk-ui/sync-config-widget.c:973 msgid "Server address" msgstr "Adres serwera" #. TRANSLATORS: explanation before a device template combobox. #. * Placeholder is a device name like 'Nokia N85' or 'Syncevolution #. * Client' #: ../src/gtk-ui/sync-config-widget.c:1049 #, c-format msgid "" "This device looks like it might be a '%s'. If this is not correct, please " "take a look at the list of supported devices and pick yours if it is listed" msgstr "" "Najprawdopodobniej do urządzenie to „%s”. Jeśli tak nie jest, przejrzyj " "listę obsługiwanych urządzeń i wybierz posiadane, jeśli się na niej " "znajduje." #: ../src/gtk-ui/sync-config-widget.c:1055 #: ../src/gtk-ui/sync-config-widget.c:1915 msgid "" "We don't know what this device is exactly. Please take a look at the list of" " supported devices and pick yours if it is listed" msgstr "" "Nie można dokładnie zidentyfikować urządzenia. Przejrzyj listę obsługiwanych" " urządzeń i wybierz posiadane, jeśli się na niej znajduje." #: ../src/gtk-ui/sync-config-widget.c:1207 #, c-format msgid "%s - Bluetooth device" msgstr "%s - Urządzenie Bluetooth" #. TRANSLATORS: service title for services that are not based on a #. * template in service list, the placeholder is the name of the service #: ../src/gtk-ui/sync-config-widget.c:1213 #, c-format msgid "%s - manually setup" msgstr "%s - konfiguracja ręczna" #. TRANSLATORS: link button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1886 msgid "Launch website" msgstr "Otworzyć stronę www" #. TRANSLATORS: button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1895 msgid "Set up now" msgstr "Skonfiguruj teraz" #: ../src/gtk-ui/sync-config-widget.c:1953 msgid "Use these settings" msgstr "Użyj tych ustawień" #. TRANSLATORS: labels of entries in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1991 msgid "Username" msgstr "Nazwa użytkownika" #: ../src/gtk-ui/sync-config-widget.c:2006 msgid "Password" msgstr "Hasło" #. TRANSLATORS: warning in service configuration form for people #. who have modified the configuration via other means. #: ../src/gtk-ui/sync-config-widget.c:2029 msgid "" "Current configuration is more complex than what can be shown here. Changes " "to sync mode or synced data types will overwrite that configuration." msgstr "" "Bieżąca konfiguracja usługi jest bardziej skomplikowana niż to, co można tu " "pokazać. Zmiany trybu synchronizacji lub synchronizowanych typów danych " "nadpiszą tę konfigurację." #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2048 msgid "Hide server settings" msgstr "Ukryj ustawienia serwera" #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2068 msgid "Show server settings" msgstr "Pokaż ustawienia serwera" #: ../src/gnome-bluetooth/syncevolution.c:110 msgid "Sync in the Sync application" msgstr "Synchronizuj w aplikacji do synchronizacji" #: ../src/syncevo-dbus-server.cpp:6653 #, c-format msgid "%s is syncing" msgstr "Synchronizowanie %s" #: ../src/syncevo-dbus-server.cpp:6654 #, c-format msgid "We have just started to sync your computer with the %s sync service." msgstr "" "Właśnie rozpoczęto synchronizację komputera z usługą synchronizacji %s." #. if sync is successfully started and done #: ../src/syncevo-dbus-server.cpp:6670 #, c-format msgid "%s sync complete" msgstr "Synchronizacja %s zakończona" #: ../src/syncevo-dbus-server.cpp:6671 #, c-format msgid "We have just finished syncing your computer with the %s sync service." msgstr "" "Właśnie zakończono synchronizację komputera z usługą synchronizacji %s." #. if sync is successfully started and has errors, or not started successful #. with a fatal problem #: ../src/syncevo-dbus-server.cpp:6676 msgid "Sync problem." msgstr "Problem z synchronizacją." #: ../src/syncevo-dbus-server.cpp:6677 msgid "Sorry, there's a problem with your sync that you need to attend to." msgstr "" "Przepraszamy, wystąpił problem z synchronizacją wymagający interwencji " "użytkownika." #: ../src/syncevo-dbus-server.cpp:6758 msgid "View" msgstr "Pokaż" #. Use "default" as ID because that is what mutter-moblin #. recognizes: it then skips the action instead of adding it #. in addition to its own "Dismiss" button (always added). #: ../src/syncevo-dbus-server.cpp:6762 msgid "Dismiss" msgstr "Odrzuć" syncevolution_1.4/po/pt_BR.po000066400000000000000000000744531230021373600163570ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # # Translators: # , 2011. # , 2011. # , 2011. msgid "" msgstr "" "Project-Id-Version: syncevolution\n" "Report-Msgid-Bugs-To: https://bugs.meego.com/\n" "POT-Creation-Date: 2011-12-05 10:21-0800\n" "PO-Revision-Date: 2011-12-10 00:43+0000\n" "Last-Translator: GLS_PTB \n" "Language-Team: Portuguese (Brazilian) \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: pt_BR\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" #. TRANSLATORS: this is the application name that may be used by e.g. #. the windowmanager #: ../src/gtk-ui/main.c:40 ../src/gtk-ui/ui.glade.h:38 #: ../src/gtk-ui/sync.desktop.in.h:1 #: ../src/gnome-bluetooth/syncevolution.c:112 msgid "Sync" msgstr "Sincronização" #: ../src/gtk-ui/sync-ui.c:266 msgid "Contacts" msgstr "Contatos" #: ../src/gtk-ui/sync-ui.c:268 msgid "Appointments" msgstr "Compromissos" #: ../src/gtk-ui/sync-ui.c:270 ../src/gtk-ui/ui.glade.h:40 msgid "Tasks" msgstr "Tarefas" #: ../src/gtk-ui/sync-ui.c:272 msgid "Notes" msgstr "Notas" #. TRANSLATORS: This is a "combination source" for syncing with devices #. * that combine appointments and tasks. the name should match the ones #. * used for calendar and todo above #: ../src/gtk-ui/sync-ui.c:277 msgid "Appointments & Tasks" msgstr "Compromissos e tarefas" #: ../src/gtk-ui/sync-ui.c:349 msgid "Starting sync" msgstr "Iniciando sincronização" #. TRANSLATORS: slow sync confirmation dialog message. Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:387 #, c-format msgid "Do you want to slow sync with %s?" msgstr "Você quer fazer sincronização lenta com %s?" #: ../src/gtk-ui/sync-ui.c:391 msgid "Yes, do slow sync" msgstr "Sim, faça sincronização lenta" #: ../src/gtk-ui/sync-ui.c:391 msgid "No, cancel sync" msgstr "Não, cancele a sincronização" #. TRANSLATORS: confirmation dialog for "refresh from peer". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:424 #, c-format msgid "" "Do you want to delete all local data and replace it with data from %s? This " "is not usually advised." msgstr "" "Você quer excluir todos os dados locais e substituir por dados de: %s? Isso " "não é aconselhável." #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 msgid "Yes, delete and replace" msgstr "Sim, excluir e substituir" #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 #: ../src/gtk-ui/sync-ui.c:1610 msgid "No" msgstr "Não" #. TRANSLATORS: confirmation dialog for "refresh from local side". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:457 #, c-format msgid "" "Do you want to delete all data in %s and replace it with your local data? " "This is not usually advised." msgstr "" "Você quer excluir todos os dados em: %s e substituir por seus dados locais? " "Isso não é aconselhável." #: ../src/gtk-ui/sync-ui.c:491 msgid "Trying to cancel sync" msgstr "Tentando cancelar sincronização" #: ../src/gtk-ui/sync-ui.c:533 msgid "No service or device selected" msgstr "Nenhum serviço ou dispositivo selecionado" #. TRANSLATORS: This is the title on main view. Placeholder is #. * the service name. Example: "Google - synced just now" #: ../src/gtk-ui/sync-ui.c:541 #, c-format msgid "%s - synced just now" msgstr "%s - fez sincronização agora mesmo" #: ../src/gtk-ui/sync-ui.c:545 #, c-format msgid "%s - synced a minute ago" msgstr "%s - fez sincronização há um minuto atrás" #: ../src/gtk-ui/sync-ui.c:549 #, c-format msgid "%s - synced %ld minutes ago" msgstr "%s - fez sincronização há: %ld minutos atrás" #: ../src/gtk-ui/sync-ui.c:554 #, c-format msgid "%s - synced an hour ago" msgstr "%s - fez sincronização há uma hora atrás" #: ../src/gtk-ui/sync-ui.c:558 #, c-format msgid "%s - synced %ld hours ago" msgstr "%s - fez sincronização há: %ld horas atrás" #: ../src/gtk-ui/sync-ui.c:563 #, c-format msgid "%s - synced a day ago" msgstr "%s - sincronização há um dia atrás" #: ../src/gtk-ui/sync-ui.c:567 #, c-format msgid "%s - synced %ld days ago" msgstr "%s - sincronização há %ld dias atrás" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "You've just restored a backup. The changes have not been " #. * "synced with %s yet" #: ../src/gtk-ui/sync-ui.c:616 ../src/gtk-ui/sync-ui.c:701 msgid "Sync now" msgstr "Sincron. agora" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "A normal sync is not possible at this time..." message. #. * "Other options" will open Emergency view #: ../src/gtk-ui/sync-ui.c:622 ../src/gtk-ui/ui.glade.h:37 msgid "Slow sync" msgstr "Sincronização lenta" #: ../src/gtk-ui/sync-ui.c:623 msgid "Other options..." msgstr "Outras opções" #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * when no service is selected. Will open configuration view #: ../src/gtk-ui/sync-ui.c:628 msgid "Select sync service" msgstr "Selecione serviço de sincronização" #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * login to service fails. Will open configuration view for this service #: ../src/gtk-ui/sync-ui.c:633 msgid "Edit service settings" msgstr "Editar configurações de serviço" #: ../src/gtk-ui/sync-ui.c:709 msgid "" "You haven't selected a sync service or device yet. Sync services let you " "synchronize your data between your netbook and a web service. You can also " "sync directly with some devices." msgstr "" "Você ainda não selecionou um serviço de sincronização ou dispositivo. " "Serviços de sincronização permitem a sincronização de dados entre seu " "netbook e um serviço na web. Você também pode sincronizar diretamente com " "alguns dispositivos." #: ../src/gtk-ui/sync-ui.c:729 msgid "Sync again" msgstr "Sincronize de novo" #: ../src/gtk-ui/sync-ui.c:748 msgid "Restoring" msgstr "Restaurando" #: ../src/gtk-ui/sync-ui.c:750 msgid "Syncing" msgstr "Sincronizando" #. TRANSLATORS: This is for the button in main view, right side. #. Keep line length below ~20 characters, use two lines if needed #: ../src/gtk-ui/sync-ui.c:762 ../src/gtk-ui/sync-ui.c:3407 msgid "Cancel sync" msgstr "Cancelar sincronização" #: ../src/gtk-ui/sync-ui.c:927 msgid "Back to sync" msgstr "Voltar à sincronização" #. TRANSLATORS: label for checkbutton/toggle in main view. #. * Please stick to similar length strings or break the line with #. * "\n" if absolutely needed #: ../src/gtk-ui/sync-ui.c:1229 msgid "Automatic sync" msgstr "Sincronização automática" #. This is the expander label in emergency view. It summarizes the #. * currently selected data sources. First placeholder is service/device #. * name, second a comma separeted list of sources. #. * E.g. "Affected data: Google Contacts, Appointments" #: ../src/gtk-ui/sync-ui.c:1524 #, c-format msgid "Affected data: %s %s" msgstr "Dados afetados: %s %s" #: ../src/gtk-ui/sync-ui.c:1529 #, c-format msgid "Affected data: none" msgstr "Dados afetados: nenhum" #. TRANSLATORS: confirmation for restoring a backup. placeholder is the #. * backup time string defined below #: ../src/gtk-ui/sync-ui.c:1607 #, c-format msgid "" "Do you want to restore the backup from %s? All changes you have made since " "then will be lost." msgstr "" "Você quer restaurar o backup do %s? Todas as modificações que você fez " "depois serão perdidas." #: ../src/gtk-ui/sync-ui.c:1610 msgid "Yes, restore" msgstr "Sim, restaurar" #. TRANSLATORS: date/time for strftime(), used in emergency view backup #. * label. Any time format that shows date and time is good. #: ../src/gtk-ui/sync-ui.c:1642 #, c-format msgid "%x %X" msgstr "%x %X" #. TRANSLATORS: label for a backup in emergency view. Placeholder is #. * service or device name #: ../src/gtk-ui/sync-ui.c:1661 #, c-format msgid "Backed up before syncing with %s" msgstr "Fez backup antes de sincronização com %s" #: ../src/gtk-ui/sync-ui.c:1678 msgid "Restore" msgstr "Restaurar" #. TRANSLATORS: this is an explanation in Emergency view. #. * Placeholder is a service/device name #: ../src/gtk-ui/sync-ui.c:1785 #, c-format msgid "" "A normal sync with %s is not possible at this time. You can do a slow two-" "way sync or start from scratch. You can also restore a backup, but a slow " "sync or starting from scratch will still be required before normal sync is " "possible." msgstr "" "Uma sincronização normal com %s não é possível agora. Você poderá fazer uma " "sincronização lenta bidirecional ou iniciar do começo. Você também poderá " "restaurar um backup, mas uma sincronização lenta ou começando do início " "ainda será necessária antes de uma sincronização normal." #: ../src/gtk-ui/sync-ui.c:1795 #, c-format msgid "" "If something has gone horribly wrong, you can try a slow sync, start from " "scratch or restore from backup." msgstr "" "Se algo está terrivelmente errado, você ainda pode tentar uma sincronização " "lenta, começar tudo de novo ou restaurar do backup." #. TRANSLATORS: These are a buttons in Emergency view. Placeholder is a #. * service/device name. Please don't use too long lines, but feel free to #. * use several lines. #: ../src/gtk-ui/sync-ui.c:1804 #, c-format msgid "" "Delete all your local\n" "data and replace with\n" "data from %s" msgstr "" "Excluir todos os dados\n" "locais e substitua com\n" "dados de %s" #: ../src/gtk-ui/sync-ui.c:1810 #, c-format msgid "" "Delete all data on\n" "%s and replace\n" "with your local data" msgstr "" "Excluir todos os dados no\n" "%s e substituir\n" "com seus dados locais" #: ../src/gtk-ui/sync-ui.c:2275 msgid "Failed to get list of supported services from SyncEvolution" msgstr "Não conseguiu a lista de serviços suportados do SyncEvolution" #: ../src/gtk-ui/sync-ui.c:2329 msgid "" "There was a problem communicating with the sync process. Please try again " "later." msgstr "" "Houve um problema comunicando com o processo de sincronização. Favor tente " "de novo mais tarde." #: ../src/gtk-ui/sync-ui.c:2388 msgid "Restore failed" msgstr "Restauração falhou" #: ../src/gtk-ui/sync-ui.c:2391 ../src/gtk-ui/sync-ui.c:3276 msgid "Sync failed" msgstr "Sincronização falhou" #: ../src/gtk-ui/sync-ui.c:2397 msgid "Restore complete" msgstr "Restauração completada" #: ../src/gtk-ui/sync-ui.c:2400 msgid "Sync complete" msgstr "Sincronização completada" #: ../src/gtk-ui/sync-ui.c:2492 #, c-format msgid "Preparing '%s'" msgstr "Preparando: '%s'" #: ../src/gtk-ui/sync-ui.c:2495 #, c-format msgid "Receiving '%s'" msgstr "Recebendo: '%s'" #: ../src/gtk-ui/sync-ui.c:2498 #, c-format msgid "Sending '%s'" msgstr "Enviando: '%s" #: ../src/gtk-ui/sync-ui.c:2619 #, c-format msgid "There was one remote rejection." msgid_plural "There were %ld remote rejections." msgstr[0] "Houve uma rejeição remota." msgstr[1] "Houve %ld rejeições remotas." #: ../src/gtk-ui/sync-ui.c:2624 #, c-format msgid "There was one local rejection." msgid_plural "There were %ld local rejections." msgstr[0] "Houve uma rejeição local." msgstr[1] "Houve %ld rejeições locais." #: ../src/gtk-ui/sync-ui.c:2629 #, c-format msgid "There were %ld local rejections and %ld remote rejections." msgstr "Houve %ld rejeições locais e %ld rejeições remotas." #: ../src/gtk-ui/sync-ui.c:2634 #, c-format msgid "Last time: No changes." msgstr "Ultima vez: sem mudanças." #: ../src/gtk-ui/sync-ui.c:2636 #, c-format msgid "Last time: Sent one change." msgid_plural "Last time: Sent %ld changes." msgstr[0] "Ultima vez: Enviou uma mudança." msgstr[1] "Ultima vez: Enviou %ld mudanças." #. This is about changes made to the local data. Not all of these #. changes were requested by the remote server, so "applied" #. is a better word than "received" (bug #5185). #: ../src/gtk-ui/sync-ui.c:2644 #, c-format msgid "Last time: Applied one change." msgid_plural "Last time: Applied %ld changes." msgstr[0] "Ultima vez: Efetuou uma mudança." msgstr[1] "Ultima vez: Efetuou %ld mudanças." #: ../src/gtk-ui/sync-ui.c:2649 #, c-format msgid "Last time: Applied %ld changes and sent %ld changes." msgstr "Ultima vez: Efetuou %ld mudanças e enviou %ld mudanças." #. TRANSLATORS: the placeholder is a error message (hopefully) #. * explaining the problem #: ../src/gtk-ui/sync-ui.c:2856 #, c-format msgid "" "There was a problem with last sync:\n" "%s" msgstr "" "Houve um problema com a ultima sincronização:\n" "%s" #: ../src/gtk-ui/sync-ui.c:2866 #, c-format msgid "" "You've just restored a backup. The changes have not been synced with %s yet" msgstr "" "Você acabou de restaurar um backup. As modificações não foram sincronizadas " "com %s ainda" #: ../src/gtk-ui/sync-ui.c:3154 msgid "Waiting for current operation to finish..." msgstr "Aguardando a conclusão da atual operação..." #. TRANSLATORS: next strings are error messages. #: ../src/gtk-ui/sync-ui.c:3188 msgid "" "A normal sync is not possible at this time. The server suggests a slow sync," " but this might not always be what you want if both ends already have data." msgstr "" "Uma sincronização normal não é possível agora. O servidor sugere uma " "sincronização normal, mas isto não é sempre aquilo que você deseja se ambos " "os lados têm dados." #: ../src/gtk-ui/sync-ui.c:3192 msgid "The sync process died unexpectedly." msgstr "Saída inesperada do serviço de sincronização." #: ../src/gtk-ui/sync-ui.c:3197 msgid "" "Password request was not answered. You can save the password in the settings" " to prevent the request." msgstr "" "O pedido de senha não teve resposta. Você pode salvar a senha nas " "configurações para prevenir o pedido." #. TODO use the service device name here, this is a remote problem #: ../src/gtk-ui/sync-ui.c:3201 msgid "There was a problem processing sync request. Trying again may help." msgstr "" "Houve um problema com o processamento do pedido de sincronização. Tentar de " "novo poderá ajudar. " #: ../src/gtk-ui/sync-ui.c:3207 msgid "" "Failed to login. Could there be a problem with your username or password?" msgstr "Falha do login. Existe um problema com seu nome de usuário ou senha?" #: ../src/gtk-ui/sync-ui.c:3210 msgid "Forbidden" msgstr "Proibido" #. TRANSLATORS: data source means e.g. calendar or addressbook #: ../src/gtk-ui/sync-ui.c:3216 msgid "" "A data source could not be found. Could there be a problem with the " "settings?" msgstr "" "Uma fonte de dados não pôde ser encontrada. Existe um problema com as " "configurações?" #: ../src/gtk-ui/sync-ui.c:3220 msgid "Remote database error" msgstr "Erro no banco de dados remoto" #. This can happen when EDS is borked, restart it may help... #: ../src/gtk-ui/sync-ui.c:3223 msgid "" "There is a problem with the local database. Syncing again or rebooting may " "help." msgstr "" "Existe um problema com o banco de dados local. Nova sincronização ou " "reinicialização poderá ajudar. " #: ../src/gtk-ui/sync-ui.c:3226 msgid "No space on disk" msgstr "Não há espaço no disco" #: ../src/gtk-ui/sync-ui.c:3228 msgid "Failed to process SyncML" msgstr "Falhou o processo de SyncML" #: ../src/gtk-ui/sync-ui.c:3230 msgid "Server authorization failed" msgstr "Autorização do servidor falhou" #: ../src/gtk-ui/sync-ui.c:3232 msgid "Failed to parse configuration file" msgstr "Falha na triagem do arquivo de configuração" #: ../src/gtk-ui/sync-ui.c:3234 msgid "Failed to read configuration file" msgstr "Falha na leitura do arquivo de configuração" #: ../src/gtk-ui/sync-ui.c:3236 msgid "No configuration found" msgstr "Nenhuma configuração encontrada" #: ../src/gtk-ui/sync-ui.c:3238 msgid "No configuration file found" msgstr "Nenhum arquivo de configuração encontrado" #: ../src/gtk-ui/sync-ui.c:3240 msgid "Server sent bad content" msgstr "Servidor enviou conteúdo ruim" #: ../src/gtk-ui/sync-ui.c:3242 msgid "Connection certificate has expired" msgstr "Certificado de conexão vencido" #: ../src/gtk-ui/sync-ui.c:3244 msgid "Connection certificate is invalid" msgstr "Certificado de conexão inválido" #: ../src/gtk-ui/sync-ui.c:3252 msgid "" "We were unable to connect to the server. The problem could be temporary or " "there could be something wrong with the settings." msgstr "" "Não foi possível conectar com o servidor. O problema pode ser temporário ou " "talvez exista um problema com as configurações." #: ../src/gtk-ui/sync-ui.c:3259 msgid "The server URL is bad" msgstr "O URL do servidor está ruim" #: ../src/gtk-ui/sync-ui.c:3264 msgid "The server was not found" msgstr "Servidor não localizado" #: ../src/gtk-ui/sync-ui.c:3266 #, c-format msgid "Error %d" msgstr "Erro: %d" #. TRANSLATORS: password request dialog contents: title, cancel button #. * and ok button #: ../src/gtk-ui/sync-ui.c:3404 msgid "Password is required for sync" msgstr "Senha é necessária para sincronização " #: ../src/gtk-ui/sync-ui.c:3408 msgid "Sync with password" msgstr "Sincronização com senha" #. TRANSLATORS: password request dialog message, placeholder is service name #: ../src/gtk-ui/sync-ui.c:3418 #, c-format msgid "Please enter password for syncing with %s:" msgstr "Favor introduzir a senha para sincronização com %s:" #. title for the buttons on the right side of main view #: ../src/gtk-ui/ui.glade.h:2 msgid "Actions" msgstr "Ações" #. text between the two "start from scratch" buttons in emergency view #: ../src/gtk-ui/ui.glade.h:4 msgid "or" msgstr "ou" #: ../src/gtk-ui/ui.glade.h:5 msgid "Direct sync" msgstr "Sincronização direta" #: ../src/gtk-ui/ui.glade.h:6 msgid "Network sync" msgstr "Sincronização de rede" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:8 msgid "Restore from backup" msgstr "Restaurar do backup" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:10 msgid "Slow sync" msgstr "Sincronização lenta" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:12 msgid "Start from scratch" msgstr "Começar do início" #: ../src/gtk-ui/ui.glade.h:13 msgid "" "A slow sync compares items from both sides and tries to merge them. \n" "This may fail in some cases, leading to duplicates or lost information." msgstr "" "Uma sincronização lenta compara itens dos dois lados e tenta intercalá-los.\n" "Isto poderá falhar em alguns casos, resultando em duplicatas ou informações perdidas." #: ../src/gtk-ui/ui.glade.h:15 msgid "Add new device" msgstr "Adicionar novo dispositivo" #: ../src/gtk-ui/ui.glade.h:16 msgid "Add new service" msgstr "Adicionar novo serviço" #. explanation of "Restore backup" function #: ../src/gtk-ui/ui.glade.h:18 msgid "" "Backups are made before every time we Sync. Choose a backup to restore. Any " "changes you have made since then will be lost." msgstr "" "Backups são feitos antes de cada sincronização . Selecione um backup para " "restaurar. Quaisquer modificações que você fez depois serão perdidas." #: ../src/gtk-ui/ui.glade.h:19 msgid "Calendar" msgstr "Calendário" #. Button in main view, right side. Keep to below 20 chars per line, feel free #. to use two lines #: ../src/gtk-ui/ui.glade.h:21 msgid "" "Change or edit\n" "sync service" msgstr "" "Modificar ou editar\n" "serviço de sincron." #. close button for settings window #: ../src/gtk-ui/ui.glade.h:24 msgid "Close" msgstr "Fechar" #: ../src/gtk-ui/ui.glade.h:25 msgid "" "Delete all data on Zyb \n" "and replace with your\n" "local information" msgstr "" "Excluir todos os dados no Zyb \n" "e substituir por suas\n" "informações locais" #: ../src/gtk-ui/ui.glade.h:28 msgid "" "Delete all your local\n" "information and replace\n" "with data from Zyb" msgstr "" "Excluir todos as\n" "informações locais e\n" "substituir por dados de Zyb" #. button in main view, right side. Keep length to 20 characters or so, use #. two lines if needed #: ../src/gtk-ui/ui.glade.h:32 msgid "" "Fix a sync\n" "emergency" msgstr "" "Conserta uma\n" "sincr. emergência" #: ../src/gtk-ui/ui.glade.h:34 msgid "" "If you don't see your service above but know that your sync provider uses SyncML\n" "you can setup a service manually." msgstr "" "Se não ver seu serviço acima, porém sabe que seu provedor de sinc. usa SyncML\n" "você poderá configurá-la manualmente." #: ../src/gtk-ui/ui.glade.h:36 msgid "Settings" msgstr "Configurações " #: ../src/gtk-ui/ui.glade.h:39 msgid "Sync Emergency" msgstr "Emergência sincronização " #: ../src/gtk-ui/ui.glade.h:41 msgid "" "To sync you'll need a network connection and an account with a sync service.\n" "We support the following services: " msgstr "" "Para sincronizar você precisa de conexão de rede e uma conta com serviço\n" "de sincronização. Nós suportamos os seguintes serviços:" #: ../src/gtk-ui/ui.glade.h:43 msgid "Use Bluetooth to Sync your data from one device to another." msgstr "" "Use Bluetooth para sincronização de dados de um dispositivo para outro." #: ../src/gtk-ui/ui.glade.h:44 msgid "You will need to add Bluetooth devices before they can be synced." msgstr "" "Você precisará adicionar dispositivos Bluetooth antes que eles possam fazer " "sincronização." #: ../src/gtk-ui/sync.desktop.in.h:2 msgid "Up to date" msgstr "Atualizado" #: ../src/gtk-ui/sync-gtk.desktop.in.h:1 msgid "SyncEvolution (GTK)" msgstr "SyncEvolution (GTK)" #: ../src/gtk-ui/sync-gtk.desktop.in.h:2 msgid "Synchronize PIM data" msgstr "Sincronizar dados PIM" #: ../src/gtk-ui/sync-config-widget.c:88 msgid "" "ScheduleWorld enables you to keep your contacts, events, tasks, and notes in" " sync." msgstr "" "ScheduleWorld possibilita manter sincronizados seus contatos, eventos, " "tarefas e anotações." #: ../src/gtk-ui/sync-config-widget.c:91 msgid "" "Google Sync can back up and synchronize your contacts with your Gmail " "contacts." msgstr "" "Google Sync pode fazer backup e sincronizar seus contatos com seus contatos " "Gmail." #. TRANSLATORS: Please include the word "demo" (or the equivalent in #. your language): Funambol is going to be a 90 day demo service #. in the future #: ../src/gtk-ui/sync-config-widget.c:97 msgid "" "Back up your contacts and calendar. Sync with a single click, anytime, " "anywhere (DEMO)." msgstr "" "Faça backup de seus contatos e calendário. Sincronize com um só clique, a " "qualquer hora em qualquer lugar (DEMO)." #: ../src/gtk-ui/sync-config-widget.c:100 msgid "" "Mobical Backup and Restore service allows you to securely back up your " "personal mobile data for free." msgstr "" "O serviço Backup e Restauração Mobical permite o backup seguro de seus dados" " móveis pessoais de graça." #: ../src/gtk-ui/sync-config-widget.c:103 msgid "" "ZYB is a simple way for people to store and share mobile information online." msgstr "" "ZYB é uma maneira simples para as pessoas armazenarem e compartilharem " "informações móveis online." #: ../src/gtk-ui/sync-config-widget.c:106 msgid "" "Memotoo lets you access your personal data from any computer connected to " "the Internet." msgstr "" "Memotoo permite a você acessar seus dados pessoais de qualquer computador " "conectado a Internet." #: ../src/gtk-ui/sync-config-widget.c:195 msgid "Sorry, failed to save the configuration" msgstr "Sinto muito, falha ao salvar a configuração" #: ../src/gtk-ui/sync-config-widget.c:445 msgid "Service must have a name and server URL" msgstr "Serviço tem que ter um nome e um URL de servidor" #. TRANSLATORS: error dialog when creating a new sync configuration #: ../src/gtk-ui/sync-config-widget.c:451 msgid "A username is required for this service" msgstr "É preciso fornecer um nome de usuário para este serviço" #: ../src/gtk-ui/sync-config-widget.c:493 #, c-format msgid "" "Do you want to reset the settings for %s? This will not remove any synced " "information on either end." msgstr "" "Você quer restaurar as configurações para %s? Isto não removerá qualquer " "informação sincronizada em cada lado." #. TRANSLATORS: buttons in reset-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:497 msgid "Yes, reset" msgstr "Sim, redefinir" #: ../src/gtk-ui/sync-config-widget.c:498 #: ../src/gtk-ui/sync-config-widget.c:509 msgid "No, keep settings" msgstr "Não, manter configurações " #: ../src/gtk-ui/sync-config-widget.c:503 #, c-format msgid "" "Do you want to delete the settings for %s? This will not remove any synced " "information on either end but it will remove these settings." msgstr "" "Você quer excluir as configurações para %s? Isto não removerá qualquer " "informação sincronizada de cada lado, mas removerá essas configurações." #. TRANSLATORS: buttons in delete-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:508 msgid "Yes, delete" msgstr "Sim, excluir " #: ../src/gtk-ui/sync-config-widget.c:539 msgid "Reset settings" msgstr "Redefinir configurações " #: ../src/gtk-ui/sync-config-widget.c:542 msgid "Delete settings" msgstr "Excluir configurações" #: ../src/gtk-ui/sync-config-widget.c:552 msgid "Save and use" msgstr "Salvar e usar" #: ../src/gtk-ui/sync-config-widget.c:555 msgid "" "Save and replace\n" "current service" msgstr "" "Salvar e substituir\n" "serviço atual" #: ../src/gtk-ui/sync-config-widget.c:563 msgid "Stop using device" msgstr "Pare de usar o dispositivo" #: ../src/gtk-ui/sync-config-widget.c:566 msgid "Stop using service" msgstr "Pare de usar o serviço" #. TRANSLATORS: label for an entry in service configuration form. #. * Placeholder is a source name. #. * Example: "Appointments URI" #: ../src/gtk-ui/sync-config-widget.c:749 #, c-format msgid "%s URI" msgstr "URI: %s" #. TRANSLATORS: toggles in service configuration form, placeholder is service #. * or device name #: ../src/gtk-ui/sync-config-widget.c:936 #, c-format msgid "Send changes to %s" msgstr "Enviar mudanças para %s" #: ../src/gtk-ui/sync-config-widget.c:941 #, c-format msgid "Receive changes from %s" msgstr "Receber modificações de %s" #: ../src/gtk-ui/sync-config-widget.c:957 msgid "Sync" msgstr "Sincronização" #. TRANSLATORS: label of a entry in service configuration #: ../src/gtk-ui/sync-config-widget.c:973 msgid "Server address" msgstr "Endereço de servidor" #. TRANSLATORS: explanation before a device template combobox. #. * Placeholder is a device name like 'Nokia N85' or 'Syncevolution #. * Client' #: ../src/gtk-ui/sync-config-widget.c:1049 #, c-format msgid "" "This device looks like it might be a '%s'. If this is not correct, please " "take a look at the list of supported devices and pick yours if it is listed" msgstr "" "Este dispositivo parece ser um '%s'. Se isto não for o caso, favor verificar" " a lista de dispositivos suportados e selecione o seu se constar da lista" #: ../src/gtk-ui/sync-config-widget.c:1055 #: ../src/gtk-ui/sync-config-widget.c:1915 msgid "" "We don't know what this device is exactly. Please take a look at the list of" " supported devices and pick yours if it is listed" msgstr "" "Não sabemos que dispositivo isto é exatamente. Favor verificar a lista de " "dispositivos suportados e selecione o seu se constar da lista" #: ../src/gtk-ui/sync-config-widget.c:1207 #, c-format msgid "%s - Bluetooth device" msgstr "%s - dispositivo Bluetooth " #. TRANSLATORS: service title for services that are not based on a #. * template in service list, the placeholder is the name of the service #: ../src/gtk-ui/sync-config-widget.c:1213 #, c-format msgid "%s - manually setup" msgstr "%s - configurar manualmente" #. TRANSLATORS: link button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1886 msgid "Launch website" msgstr "Iniciar o site na web" #. TRANSLATORS: button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1895 msgid "Set up now" msgstr "Configure agora" #: ../src/gtk-ui/sync-config-widget.c:1953 msgid "Use these settings" msgstr "Usar essas configurações" #. TRANSLATORS: labels of entries in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1991 msgid "Username" msgstr "Nome do usuário" #: ../src/gtk-ui/sync-config-widget.c:2006 msgid "Password" msgstr "Senha" #. TRANSLATORS: warning in service configuration form for people #. who have modified the configuration via other means. #: ../src/gtk-ui/sync-config-widget.c:2029 msgid "" "Current configuration is more complex than what can be shown here. Changes " "to sync mode or synced data types will overwrite that configuration." msgstr "" "Atual configuração é complexa demais para ser mostrada aqui. As modificações" " ao modo sincronização ou tipos de dados sincronizados substituirão aquela " "configuração," #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2048 msgid "Hide server settings" msgstr "Ocultar configurações de servidor" #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2068 msgid "Show server settings" msgstr "Mostrar configurações de servidor" #: ../src/gnome-bluetooth/syncevolution.c:110 msgid "Sync in the Sync application" msgstr "Sincronização no aplicativo Sync" #: ../src/syncevo-dbus-server.cpp:6653 #, c-format msgid "%s is syncing" msgstr "%s está sincronizando" #: ../src/syncevo-dbus-server.cpp:6654 #, c-format msgid "We have just started to sync your computer with the %s sync service." msgstr "" "Começamos a sincronização de seu computador com o serviço de sincronização " "%s." #. if sync is successfully started and done #: ../src/syncevo-dbus-server.cpp:6670 #, c-format msgid "%s sync complete" msgstr "%s Sincronização completada" #: ../src/syncevo-dbus-server.cpp:6671 #, c-format msgid "We have just finished syncing your computer with the %s sync service." msgstr "" "Completamos a sincronização de seu computador com o serviço de sincronização" " %s." #. if sync is successfully started and has errors, or not started successful #. with a fatal problem #: ../src/syncevo-dbus-server.cpp:6676 msgid "Sync problem." msgstr "Problema com sincronização. " #: ../src/syncevo-dbus-server.cpp:6677 msgid "Sorry, there's a problem with your sync that you need to attend to." msgstr "" "Sinto muito, existe um problema com sua sincronização e necessita a sua " "atenção." #: ../src/syncevo-dbus-server.cpp:6758 msgid "View" msgstr "Visualizar" #. Use "default" as ID because that is what mutter-moblin #. recognizes: it then skips the action instead of adding it #. in addition to its own "Dismiss" button (always added). #: ../src/syncevo-dbus-server.cpp:6762 msgid "Dismiss" msgstr "Ignorar" syncevolution_1.4/po/ro.po000066400000000000000000000414461230021373600157650ustar00rootroot00000000000000# Romanian translations for syncevolution package. # Copyright (C) 2009 The syncevolution copyright holder # This file is distributed under the same license as the syncevolution package. # Cosmin Bordeianu , 2009. # #: ../src/gtk-ui/sync-ui.c:764 msgid "" msgstr "" "Project-Id-Version: syncevolution\n" "Report-Msgid-Bugs-To: http://moblin.org/projects/syncevolution\n" "POT-Creation-Date: 2009-10-01 19:27+0000\n" "PO-Revision-Date: 2009-10-03 20:23+0200\n" "Last-Translator: Cosmin Bordeianu \n" "Language-Team: Moblin Romania \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Poedit-Language: Romanian\n" "X-Poedit-Country: ROMANIA\n" "X-Poedit-SourceCharset: utf-8\n" #. TRANSLATORS: this is the application name that may be used by e.g. #. the windowmanager #: ../src/gtk-ui/main.c:31 ../src/gtk-ui/ui.glade.h:28 #: ../src/gtk-ui/sync.desktop.in.h:1 msgid "Sync" msgstr "Sincronizare" #: ../src/gtk-ui/sync-ui.c:259 msgid "Addressbook" msgstr "Jurnal" #: ../src/gtk-ui/sync-ui.c:261 msgid "Calendar" msgstr "Calendar" #: ../src/gtk-ui/sync-ui.c:263 msgid "Todo" msgstr "De făcut" #: ../src/gtk-ui/sync-ui.c:265 msgid "Memo" msgstr "Momento" #: ../src/gtk-ui/sync-ui.c:320 msgid "Failed to save current service in GConf configuration system" msgstr "" "Nu s-a putut salva serviciul de configurare al sistemului actual în GConf" #: ../src/gtk-ui/sync-ui.c:331 msgid "Failed to save service configuration to SyncEvolution" msgstr "Nu s-a putut salva configurația serviciului pentru SyncEvolution" #: ../src/gtk-ui/sync-ui.c:416 msgid "Failed to get service configuration from SyncEvolution" msgstr "Nu s-a putut obține configurația serviciului pentru SyncEvolution" #: ../src/gtk-ui/sync-ui.c:479 msgid "Failed to remove service configuration from SyncEvolution" msgstr "Nu s-a putut șterge configurația serviciului pentru SyncEvolution" #: ../src/gtk-ui/sync-ui.c:599 msgid "Service must have a name and server URL" msgstr "Serviciul trebuie sa conțină un nume și o adresă de internet" #. sync is no longer in progress for some reason #: ../src/gtk-ui/sync-ui.c:675 msgid "Failed to cancel: sync was no longer in progress" msgstr "Nu s-a putut anula: Sincronizarea nu era în curs de desfășurare" #: ../src/gtk-ui/sync-ui.c:679 msgid "Failed to cancel sync" msgstr "Nu s-a putut anula sincronizarea" #: ../src/gtk-ui/sync-ui.c:683 msgid "Canceling sync" msgstr "Anulează sincronizarea" #: ../src/gtk-ui/sync-ui.c:697 msgid "Trying to cancel sync" msgstr "Încerc să anulez sincronizarea" #: ../src/gtk-ui/sync-ui.c:704 #, c-format msgid "" "Do you want to delete all local data and replace it with data from %s? This " "is not usually advised." msgstr "" "Doriți să ștergeți toate datele locale și să le înlocuiți cu date din %s? " "Acest lucru nu este, de obicei, recomand." #: ../src/gtk-ui/sync-ui.c:709 #, c-format msgid "" "Do you want to delete all data in %s and replace it with your local data? " "This is not usually advised." msgstr "" "Doriți să ștergeți toate datele din %s și să le înlocuiți cu datele locale? " "Acest lucru nu este, de obicei, recomand." #: ../src/gtk-ui/sync-ui.c:726 msgid "No, cancel sync" msgstr "Nu, anulează sincronizarea" #: ../src/gtk-ui/sync-ui.c:727 msgid "Yes, delete and replace" msgstr "Da, șterge și înlocuiește" #: ../src/gtk-ui/sync-ui.c:749 msgid "No sources are enabled, not syncing" msgstr "Nu există o sursă activată, nu se sincronizează" #: ../src/gtk-ui/sync-ui.c:766 msgid "A sync is already in progress" msgstr "O sincronizare este deja în curs de desfășurare" #: ../src/gtk-ui/sync-ui.c:768 msgid "Failed to start sync" msgstr "Nu s-a putut porni sincronizarea" #: ../src/gtk-ui/sync-ui.c:773 msgid "Starting sync" msgstr "Pornește sincronizarea" #: ../src/gtk-ui/sync-ui.c:798 msgid "Last synced just seconds ago" msgstr "Ultima sincronizare a fost făcută acum câteva secunde" #: ../src/gtk-ui/sync-ui.c:801 msgid "Last synced a minute ago" msgstr "Ultima sincronizare a fost făcută acum un minut" #: ../src/gtk-ui/sync-ui.c:804 #, c-format msgid "Last synced %ld minutes ago" msgstr "Ultima sincronizare a fost făcută acum %ld minute" #: ../src/gtk-ui/sync-ui.c:807 msgid "Last synced an hour ago" msgstr "Ultima sincronizare a fost făcută acum o oră" #: ../src/gtk-ui/sync-ui.c:810 #, c-format msgid "Last synced %ld hours ago" msgstr "Ultima sincronizare a fost făcută acum %ld ore" #: ../src/gtk-ui/sync-ui.c:813 msgid "Last synced a day ago" msgstr "Ultima sincronizare a fost făcută acum o zi" #: ../src/gtk-ui/sync-ui.c:816 #, c-format msgid "Last synced %ld days ago" msgstr "Ultima sincronizare a fost făcută acum %ld zile" #: ../src/gtk-ui/sync-ui.c:901 msgid "Sync again" msgstr "Sincronizează din nou" #: ../src/gtk-ui/sync-ui.c:903 ../src/gtk-ui/ui.glade.h:29 msgid "Sync now" msgstr "Sincronizează acum" #: ../src/gtk-ui/sync-ui.c:912 msgid "Syncing" msgstr "Se sincronizează" #: ../src/gtk-ui/sync-ui.c:918 msgid "Cancel sync" msgstr "Anulează sincronizarea" #. TRANSLATORS: placeholder is a source name, shown with checkboxes in main window #: ../src/gtk-ui/sync-ui.c:1265 #, c-format msgid "%s (not supported by this service)" msgstr "%s (serviciul nu suportă)" #: ../src/gtk-ui/sync-ui.c:1298 #, c-format msgid "There was one remote rejection." msgid_plural "There were %d remote rejections." msgstr[0] "A existat o respingere de la distanţă." msgstr[1] "Au existat %d respingeri de la distanţă." #: ../src/gtk-ui/sync-ui.c:1303 #, c-format msgid "There was one local rejection." msgid_plural "There were %d local rejections." msgstr[0] "A existat o respingere locală." msgstr[1] "Au existat %d respingeri locale." #: ../src/gtk-ui/sync-ui.c:1308 #, c-format msgid "There were %d local rejections and %d remote rejections." msgstr "Au existat %d respingeri locale și %d respingeri de la distanță." #: ../src/gtk-ui/sync-ui.c:1313 #, c-format msgid "Last time: No changes." msgstr "Ultima dată: Nici o schimbare" #: ../src/gtk-ui/sync-ui.c:1315 #, c-format msgid "Last time: Sent one change." msgid_plural "Last time: Sent %d changes." msgstr[0] "Ultima dată: A fost trimisă o modificare" msgstr[1] "Ultima dată: Au fost trimise %d modificări." #. This is about changes made to the local data. Not all of these #. changes were requested by the remote server, so "applied" #. is a better word than "received" (bug #5185). #: ../src/gtk-ui/sync-ui.c:1323 #, c-format msgid "Last time: Applied one change." msgid_plural "Last time: Applied %d changes." msgstr[0] "Ultima dată: A fost aplicată o modificare." msgstr[1] "Ultima dată: Au fost aplicate %d modificări." #: ../src/gtk-ui/sync-ui.c:1328 #, c-format msgid "Last time: Applied %d changes and sent %d changes." msgstr "Ultima dată: Au fost aplicate %d modificări și %d au fost trimise." #: ../src/gtk-ui/sync-ui.c:1420 msgid "Failed to get server configuration from SyncEvolution" msgstr "Nu am putut să retrag configurația serverului de la SyncEvolution" #: ../src/gtk-ui/sync-ui.c:1472 msgid "" "ScheduleWorld enables you to keep your contacts, events, tasks, and notes in " "sync." msgstr "" "ScheduleWorld permite păstrarea contactelor, a evenimentelor, sarcinilor și " "a notițelor sincronizate." #: ../src/gtk-ui/sync-ui.c:1475 msgid "" "Google Sync can back up and synchronize your Address Book with your Gmail " "contacts." msgstr "" "Google Sync poate face o copie de rezervă și sincroniza Jurnalul cu " "contactele dvs. Gmail." #. TRANSLATORS: Please include the word "demo" (or the equivalent in #. your language): Funambol is going to be a 90 day demo service #. in the future #: ../src/gtk-ui/sync-ui.c:1481 msgid "" "Back up your contacts and calendar. Sync with a singleclick, anytime, " "anywhere (DEMO)." msgstr "" "Copiază contactele și calendarul. Sincronizează cu un singur clic, oricând, " "oriunde (DEMO)." #: ../src/gtk-ui/sync-ui.c:1509 msgid "New service" msgstr "Serviciu nou" #: ../src/gtk-ui/sync-ui.c:1556 msgid "Server URL" msgstr "Adresa Serverului" #. TRANSLATORS: placeholder is a source name in settings window #: ../src/gtk-ui/sync-ui.c:1578 #, c-format msgid "%s URI" msgstr "%s adrese" #: ../src/gtk-ui/sync-ui.c:1715 ../src/gtk-ui/ui.glade.h:17 msgid "Launch website" msgstr "Lansează site-ul" #: ../src/gtk-ui/sync-ui.c:1719 msgid "Setup and use" msgstr "Configurează și folosește" #: ../src/gtk-ui/sync-ui.c:1765 msgid "Failed to get list of manually setup services from SyncEvolution" msgstr "" "Nu am putut retrage lista de configurări manuale ale serviciilor de la " "SyncEvolution" #: ../src/gtk-ui/sync-ui.c:1806 msgid "Failed to get list of supported services from SyncEvolution" msgstr "Nu am putut retrage lista cu servicii suportate de la SyncEvolution" #. TODO: this is a hack... SyncEnd should be a signal of it's own, #. not just hacked on top of the syncevolution error codes #: ../src/gtk-ui/sync-ui.c:1967 msgid "Service configuration not found" msgstr "Nu a fost găsită configurația serviciului" #: ../src/gtk-ui/sync-ui.c:1973 msgid "Not authorized" msgstr "Nu aveți autorizație" #: ../src/gtk-ui/sync-ui.c:1975 msgid "Forbidden" msgstr "Interzis" #: ../src/gtk-ui/sync-ui.c:1977 msgid "Not found" msgstr "Nu a fost găsit(ă)" #: ../src/gtk-ui/sync-ui.c:1979 msgid "Fatal database error" msgstr "Eroare fatală la baza de date" #: ../src/gtk-ui/sync-ui.c:1981 msgid "Database error" msgstr "Eroare la baza de date" #: ../src/gtk-ui/sync-ui.c:1983 msgid "No space left" msgstr "Nu mai există spațiu" #. TODO identify problem item somehow ? #: ../src/gtk-ui/sync-ui.c:1986 msgid "Failed to process SyncML" msgstr "Nu s-a putut procesa SyncML" #: ../src/gtk-ui/sync-ui.c:1988 msgid "Server authorization failed" msgstr "Autorizaţia serverului nu a reuşit" #: ../src/gtk-ui/sync-ui.c:1990 msgid "Failed to parse configuration file" msgstr "Nu s-a putut interpreta fișierul de configurare" #: ../src/gtk-ui/sync-ui.c:1992 msgid "Failed to read configuration file" msgstr "Nu s-a putut citi fișierul de configurare" #: ../src/gtk-ui/sync-ui.c:1994 msgid "No configuration found" msgstr "Nu s-a găsit nici o configurație" #: ../src/gtk-ui/sync-ui.c:1996 msgid "No configuration file found" msgstr "Nu s-a găsit nici un fișier de configurare" #: ../src/gtk-ui/sync-ui.c:1998 msgid "Server sent bad content" msgstr "Serverul a trimis conținut eronat" #: ../src/gtk-ui/sync-ui.c:2000 msgid "Transport failure (no connection?)" msgstr "Transport eșuat (nici o conexiune?)" #: ../src/gtk-ui/sync-ui.c:2002 msgid "Connection timed out" msgstr "Timpul acordat conectării a expirat" #: ../src/gtk-ui/sync-ui.c:2004 msgid "Connection certificate has expired" msgstr "Certificatul conexiunii a expirat" #: ../src/gtk-ui/sync-ui.c:2006 msgid "Connection certificate is invalid" msgstr "Certificatul conexiunii este invalid" #: ../src/gtk-ui/sync-ui.c:2009 msgid "Connection failed" msgstr "Conexiunea a eșuat" #: ../src/gtk-ui/sync-ui.c:2011 msgid "URL is bad" msgstr "Adresa este greșită" #: ../src/gtk-ui/sync-ui.c:2013 msgid "Server not found" msgstr "Serverul nu a fost găsit" #: ../src/gtk-ui/sync-ui.c:2015 #, c-format msgid "Error %d" msgstr "Eroare %d" #: ../src/gtk-ui/sync-ui.c:2025 msgid "Sync D-Bus service exited unexpectedly" msgstr "" "Procesul de sincronizare a serviciului D-Bus s-a întrerupt pe neașteptate" #: ../src/gtk-ui/sync-ui.c:2028 ../src/gtk-ui/sync-ui.c:2079 msgid "Sync Failed" msgstr "Sincronizarea a eșuat" #: ../src/gtk-ui/sync-ui.c:2071 msgid "Sync complete" msgstr "Sincronizarea este completă" #: ../src/gtk-ui/sync-ui.c:2076 msgid "Sync canceled" msgstr "Sincronizarea a fost amânată" #. NOTE extra1 can be error here #: ../src/gtk-ui/sync-ui.c:2094 msgid "Ending sync" msgstr "Terminare sincronizare" #. TRANSLATORS: placeholder is a source name (e.g. 'Calendar') in a progress text #: ../src/gtk-ui/sync-ui.c:2118 #, c-format msgid "Preparing '%s'" msgstr "Prepară '%s'" #. TRANSLATORS: placeholder is a source name in a progress text #: ../src/gtk-ui/sync-ui.c:2130 #, c-format msgid "Sending '%s'" msgstr "Se trimite '%s'" #. TRANSLATORS: placeholder is a source name in a progress text #: ../src/gtk-ui/sync-ui.c:2142 #, c-format msgid "Receiving '%s'" msgstr "Se primește '%s'" #: ../src/gtk-ui/ui.glade.h:1 msgid "Data" msgstr "Date" #: ../src/gtk-ui/ui.glade.h:2 msgid "No sync service in use" msgstr "Nici un serviciu de sincronizare în curs" #: ../src/gtk-ui/ui.glade.h:3 msgid "Sync failure" msgstr "Sincronizare eșuată" #: ../src/gtk-ui/ui.glade.h:4 msgid "Type of Sync" msgstr "Tipul de sincronizare" #: ../src/gtk-ui/ui.glade.h:5 msgid "Manual setup" msgstr "Configurare manuală" #: ../src/gtk-ui/ui.glade.h:6 msgid "Supported services" msgstr "Servicii suportate" #: ../src/gtk-ui/ui.glade.h:7 msgid "Add new service" msgstr "Adaugă un nou dispozitiv" #: ../src/gtk-ui/ui.glade.h:8 msgid "Back to sync" msgstr "Înapoi la sincronizare" #: ../src/gtk-ui/ui.glade.h:9 msgid "" "Change sync\n" "service" msgstr "" "Schimbă serviciul\n" "de sincronizare" #: ../src/gtk-ui/ui.glade.h:11 msgid "Delete all local data and replace it with remote data" msgstr "Șterge toate datele locale și înlocuiește-le cu date de la distanţă" #: ../src/gtk-ui/ui.glade.h:12 msgid "Delete all remote data and replace it with local data" msgstr "Șterge toate datele de la distanță și înlocuiește-le cu date locale" #: ../src/gtk-ui/ui.glade.h:13 msgid "Delete this service" msgstr "Șterge acest serviciu" #: ../src/gtk-ui/ui.glade.h:14 msgid "Edit service settings" msgstr "Modifică setările serviciului" #: ../src/gtk-ui/ui.glade.h:15 msgid "" "If you don't see your service above but know that your sync provider uses " "SyncML\n" "you can setup a service manually." msgstr "" "Dacă nu vizibil serviciul mai sus, dar se știe că furnizorul de sincronizări " "utilizează tehnologia SyncML\n" "este posibilă configurarea manuală a unui serviciu." #: ../src/gtk-ui/ui.glade.h:18 msgid "Merge local and remote data (recommended)" msgstr "Fuzionează datele locale cu cele la distanţă (recomandat)" #: ../src/gtk-ui/ui.glade.h:19 msgid "Password" msgstr "Parolă" #: ../src/gtk-ui/ui.glade.h:20 msgid "Reset original server settings" msgstr "Restabilește setările implicite ale serverului" #: ../src/gtk-ui/ui.glade.h:21 msgid "Save and use this service" msgstr "Salvează și folosește acest serviciu" #: ../src/gtk-ui/ui.glade.h:22 msgid "Select sync service" msgstr "Selectează serviciul de sincronizare" #: ../src/gtk-ui/ui.glade.h:23 msgid "Server settings" msgstr "Setările serverului" #: ../src/gtk-ui/ui.glade.h:24 msgid "Service name" msgstr "Numele serviciului" #: ../src/gtk-ui/ui.glade.h:25 msgid "" "Sorry, you need an internet\n" "connection to sync." msgstr "" "Ne pare rău, aveți nevoie de o conexiune\n" "la internet pentru sincronizare" #: ../src/gtk-ui/ui.glade.h:27 msgid "Stop using this service" msgstr "Nu mai folosiți acest serviciu" #: ../src/gtk-ui/ui.glade.h:30 msgid "" "Synchronization is not available (D-Bus service does not answer), sorry." msgstr "" "Sincronizarea nu este accesibilă (Serviciul D-Bus nu răspunde), ne pare rău." #: ../src/gtk-ui/ui.glade.h:31 msgid "" "To sync you'll need a network connection and an account with a sync " "service.\n" "We support the following services: " msgstr "" "Pentru a sincroniza, este nevoie de o conexiune la rețea și un cont cu " "servicii de sincronizare.\n" "Noi sprijinim următoarele servicii: " #: ../src/gtk-ui/ui.glade.h:33 msgid "Username" msgstr "Utilizator" #: ../src/gtk-ui/ui.glade.h:34 msgid "" "You haven't selected a sync service yet. Sync services let you \n" "synchronize your data between your netbook and a web service." msgstr "" "Nu este înfiinţat nici un serviciu de sincronizare. Serviciile de " "sincronizare permit \n" "sincronizarea datelor între netbook şi un serviciu web." #: ../src/gtk-ui/sync.desktop.in.h:2 ../src/gtk-ui/sync-gtk.desktop.in.h:2 msgid "Up to date" msgstr "La zi" #: ../src/gtk-ui/sync-gtk.desktop.in.h:1 msgid "Sync (GTK)" msgstr "Sincronizare (GTK)" #~ msgid "Last time: Received one change." #~ msgid_plural "Last time: Received %d changes." #~ msgstr[0] "Ultima dată: A fost primită o modificare." #~ msgstr[1] "Ultima dată: Au fost primite %d modificări." #~ msgid "Last time: Received %d changes and sent %d changes." #~ msgstr "Ultima dată: Au fost primite %d modificări și %d au fost trimise." #~ msgid "Setup sync service" #~ msgstr "Configurează serviciul de sincronizare" #~ msgid "" #~ "You haven't set up a sync service yet. Sync services let you \n" #~ "synchronize your data between your netbook and a web service." #~ msgstr "" #~ "Nu este înfiinţat nici un serviciu de sincronizare. Serviciile de " #~ "sincronizare permit \n" #~ "sincronizarea datelor între netbook şi un serviciu web." syncevolution_1.4/po/ru.po000066400000000000000000001104261230021373600157660ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # # Translators: # , 2011. # , 2011. msgid "" msgstr "" "Project-Id-Version: syncevolution\n" "Report-Msgid-Bugs-To: https://bugs.meego.com/\n" "POT-Creation-Date: 2011-12-05 10:21-0800\n" "PO-Revision-Date: 2011-12-05 23:16+0000\n" "Last-Translator: GLS_Translator_RUS2 \n" "Language-Team: Russian (http://www.transifex.net/projects/p/meego/team/ru/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ru\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n" #. TRANSLATORS: this is the application name that may be used by e.g. #. the windowmanager #: ../src/gtk-ui/main.c:40 ../src/gtk-ui/ui.glade.h:38 #: ../src/gtk-ui/sync.desktop.in.h:1 #: ../src/gnome-bluetooth/syncevolution.c:112 msgid "Sync" msgstr "Синхронизация" #: ../src/gtk-ui/sync-ui.c:266 msgid "Contacts" msgstr "Контакты" #: ../src/gtk-ui/sync-ui.c:268 msgid "Appointments" msgstr "Встречи" #: ../src/gtk-ui/sync-ui.c:270 ../src/gtk-ui/ui.glade.h:40 msgid "Tasks" msgstr "Задания" #: ../src/gtk-ui/sync-ui.c:272 msgid "Notes" msgstr "Примечания" #. TRANSLATORS: This is a "combination source" for syncing with devices #. * that combine appointments and tasks. the name should match the ones #. * used for calendar and todo above #: ../src/gtk-ui/sync-ui.c:277 msgid "Appointments & Tasks" msgstr "Встречи и задания" #: ../src/gtk-ui/sync-ui.c:349 msgid "Starting sync" msgstr "Начало синхронизации" #. TRANSLATORS: slow sync confirmation dialog message. Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:387 #, c-format msgid "Do you want to slow sync with %s?" msgstr "Замедлить синхронизацию с %s?" #: ../src/gtk-ui/sync-ui.c:391 msgid "Yes, do slow sync" msgstr "Да, замедлить синхронизацию" #: ../src/gtk-ui/sync-ui.c:391 msgid "No, cancel sync" msgstr "Нет, отменить синхронизацию" #. TRANSLATORS: confirmation dialog for "refresh from peer". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:424 #, c-format msgid "" "Do you want to delete all local data and replace it with data from %s? This " "is not usually advised." msgstr "" "Удалить все локальные данные и заменить их на %s? Обычно это не " "рекомендуется." #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 msgid "Yes, delete and replace" msgstr "Да, удалить и заменить" #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 #: ../src/gtk-ui/sync-ui.c:1610 msgid "No" msgstr "Нет" #. TRANSLATORS: confirmation dialog for "refresh from local side". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:457 #, c-format msgid "" "Do you want to delete all data in %s and replace it with your local data? " "This is not usually advised." msgstr "" "Хотите удалить все данные в %s и заменить на ваши локальные данные? Обычно " "это не рекомендуется." #: ../src/gtk-ui/sync-ui.c:491 msgid "Trying to cancel sync" msgstr "Попытка отменить синхронизацию" #: ../src/gtk-ui/sync-ui.c:533 msgid "No service or device selected" msgstr "Служба или устройство не выбраны" #. TRANSLATORS: This is the title on main view. Placeholder is #. * the service name. Example: "Google - synced just now" #: ../src/gtk-ui/sync-ui.c:541 #, c-format msgid "%s - synced just now" msgstr "%s - синхронизировано сейчас" #: ../src/gtk-ui/sync-ui.c:545 #, c-format msgid "%s - synced a minute ago" msgstr "%s - синхронизировано минуту назад" #: ../src/gtk-ui/sync-ui.c:549 #, c-format msgid "%s - synced %ld minutes ago" msgstr "%s - синхронизировано %ld минут назад" #: ../src/gtk-ui/sync-ui.c:554 #, c-format msgid "%s - synced an hour ago" msgstr "%s - синхронизировано час назад" #: ../src/gtk-ui/sync-ui.c:558 #, c-format msgid "%s - synced %ld hours ago" msgstr "%s - синхронизировано %ld часов назад" #: ../src/gtk-ui/sync-ui.c:563 #, c-format msgid "%s - synced a day ago" msgstr "%s - синхронизировано день назад" #: ../src/gtk-ui/sync-ui.c:567 #, c-format msgid "%s - synced %ld days ago" msgstr "%s - синхронизировано %ld дней назад" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "You've just restored a backup. The changes have not been " #. * "synced with %s yet" #: ../src/gtk-ui/sync-ui.c:616 ../src/gtk-ui/sync-ui.c:701 msgid "Sync now" msgstr "Синхронизировать" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "A normal sync is not possible at this time..." message. #. * "Other options" will open Emergency view #: ../src/gtk-ui/sync-ui.c:622 ../src/gtk-ui/ui.glade.h:37 msgid "Slow sync" msgstr "Медленная/nсинхронизация" #: ../src/gtk-ui/sync-ui.c:623 msgid "Other options..." msgstr "Другие варианты..." #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * when no service is selected. Will open configuration view #: ../src/gtk-ui/sync-ui.c:628 msgid "Select sync service" msgstr "Выбрать службу синхронизации" #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * login to service fails. Will open configuration view for this service #: ../src/gtk-ui/sync-ui.c:633 msgid "Edit service settings" msgstr "Редактировать настройки службы" #: ../src/gtk-ui/sync-ui.c:709 msgid "" "You haven't selected a sync service or device yet. Sync services let you " "synchronize your data between your netbook and a web service. You can also " "sync directly with some devices." msgstr "" "Служба синхронизации или устройство еще не выбраны. Службы синхронизации " "позволяют синхронизировать данные между нетбуком и веб-службой. Кроме того, " "вы можете выполнить синхронизацию непосредственно с некоторыми устройствами." #: ../src/gtk-ui/sync-ui.c:729 msgid "Sync again" msgstr "Синхронизировать повторно" #: ../src/gtk-ui/sync-ui.c:748 msgid "Restoring" msgstr "Восстановление" #: ../src/gtk-ui/sync-ui.c:750 msgid "Syncing" msgstr "Синхронизация" #. TRANSLATORS: This is for the button in main view, right side. #. Keep line length below ~20 characters, use two lines if needed #: ../src/gtk-ui/sync-ui.c:762 ../src/gtk-ui/sync-ui.c:3407 msgid "Cancel sync" msgstr "Отменить синхронизацию" #: ../src/gtk-ui/sync-ui.c:927 msgid "Back to sync" msgstr "Вернуться к синхронизации" #. TRANSLATORS: label for checkbutton/toggle in main view. #. * Please stick to similar length strings or break the line with #. * "\n" if absolutely needed #: ../src/gtk-ui/sync-ui.c:1229 msgid "Automatic sync" msgstr "Автоматическая синхронизация" #. This is the expander label in emergency view. It summarizes the #. * currently selected data sources. First placeholder is service/device #. * name, second a comma separeted list of sources. #. * E.g. "Affected data: Google Contacts, Appointments" #: ../src/gtk-ui/sync-ui.c:1524 #, c-format msgid "Affected data: %s %s" msgstr "Поврежденные данные: %s %s" #: ../src/gtk-ui/sync-ui.c:1529 #, c-format msgid "Affected data: none" msgstr "Поврежденные данные: нет" #. TRANSLATORS: confirmation for restoring a backup. placeholder is the #. * backup time string defined below #: ../src/gtk-ui/sync-ui.c:1607 #, c-format msgid "" "Do you want to restore the backup from %s? All changes you have made since " "then will be lost." msgstr "" "Восстановить резервные файлы из %s? Все внесенные изменения будут утрачены." #: ../src/gtk-ui/sync-ui.c:1610 msgid "Yes, restore" msgstr "Да, восстановить" #. TRANSLATORS: date/time for strftime(), used in emergency view backup #. * label. Any time format that shows date and time is good. #: ../src/gtk-ui/sync-ui.c:1642 #, c-format msgid "%x %X" msgstr "%x %X" #. TRANSLATORS: label for a backup in emergency view. Placeholder is #. * service or device name #: ../src/gtk-ui/sync-ui.c:1661 #, c-format msgid "Backed up before syncing with %s" msgstr "Создана резервная копия перед синхронизацией с %s" #: ../src/gtk-ui/sync-ui.c:1678 msgid "Restore" msgstr "Восстановить" #. TRANSLATORS: this is an explanation in Emergency view. #. * Placeholder is a service/device name #: ../src/gtk-ui/sync-ui.c:1785 #, c-format msgid "" "A normal sync with %s is not possible at this time. You can do a slow two-" "way sync or start from scratch. You can also restore a backup, but a slow " "sync or starting from scratch will still be required before normal sync is " "possible." msgstr "" "В настоящий момент стандартная синхронизация с %s невозможна. Вы можете " "выполнить медленную двустороннюю синхронизацию или начать с черновой версии." " Вы также можете восстановить резервную копию, но медленная синхронизация " "или черновая вресия все еще будут необходимы до того, как стандартная " "синхронизация станет возможной." #: ../src/gtk-ui/sync-ui.c:1795 #, c-format msgid "" "If something has gone horribly wrong, you can try a slow sync, start from " "scratch or restore from backup." msgstr "" "При наличии серьезных сбоев можно попробовать выполнить медленную " "синхронизацию, запуск с черновой версии или восстановление резервной копии." #. TRANSLATORS: These are a buttons in Emergency view. Placeholder is a #. * service/device name. Please don't use too long lines, but feel free to #. * use several lines. #: ../src/gtk-ui/sync-ui.c:1804 #, c-format msgid "" "Delete all your local\n" "data and replace with\n" "data from %s" msgstr "" "Удалите все свои локальные\n" "данные и замените на\n" "данные из %s" #: ../src/gtk-ui/sync-ui.c:1810 #, c-format msgid "" "Delete all data on\n" "%s and replace\n" "with your local data" msgstr "" "Удалите все данные на\n" "%s и замените\n" "на свои локальные данные" #: ../src/gtk-ui/sync-ui.c:2275 msgid "Failed to get list of supported services from SyncEvolution" msgstr "Не удалось получить список поддерживаемых служб от SyncEvolution" #: ../src/gtk-ui/sync-ui.c:2329 msgid "" "There was a problem communicating with the sync process. Please try again " "later." msgstr "" "проблема установки связи с процессом синхронизации. Повторите попытку позже." #: ../src/gtk-ui/sync-ui.c:2388 msgid "Restore failed" msgstr "Восстановление не удалось" #: ../src/gtk-ui/sync-ui.c:2391 ../src/gtk-ui/sync-ui.c:3276 msgid "Sync failed" msgstr "Синхронизация не удалась" #: ../src/gtk-ui/sync-ui.c:2397 msgid "Restore complete" msgstr "Восстановление завершено" #: ../src/gtk-ui/sync-ui.c:2400 msgid "Sync complete" msgstr "Синхронизация завершена" #: ../src/gtk-ui/sync-ui.c:2492 #, c-format msgid "Preparing '%s'" msgstr "Подготовка '%s'" #: ../src/gtk-ui/sync-ui.c:2495 #, c-format msgid "Receiving '%s'" msgstr "Получение '%s'" #: ../src/gtk-ui/sync-ui.c:2498 #, c-format msgid "Sending '%s'" msgstr "Отправка '%s'" #: ../src/gtk-ui/sync-ui.c:2619 #, c-format msgid "There was one remote rejection." msgid_plural "There were %ld remote rejections." msgstr[0] "Удаленное отклонение: одно." msgstr[1] "Удаленные отклонения: %ld." msgstr[2] "Удаленные отклонения: %ld." #: ../src/gtk-ui/sync-ui.c:2624 #, c-format msgid "There was one local rejection." msgid_plural "There were %ld local rejections." msgstr[0] "Локальное отклонение: одно." msgstr[1] "Локальные отклонения: %ld." msgstr[2] "Локальные отклонения: %ld." #: ../src/gtk-ui/sync-ui.c:2629 #, c-format msgid "There were %ld local rejections and %ld remote rejections." msgstr "Было %ld локальных и %ld удаленных отказов." #: ../src/gtk-ui/sync-ui.c:2634 #, c-format msgid "Last time: No changes." msgstr "Прошлый раз: без изменений." #: ../src/gtk-ui/sync-ui.c:2636 #, c-format msgid "Last time: Sent one change." msgid_plural "Last time: Sent %ld changes." msgstr[0] "Прошлый раз: отправлено одно изменение." msgstr[1] "Прошлый раз: отправлено изменений: %ld." msgstr[2] "Прошлый раз: отправлено изменений: %ld." #. This is about changes made to the local data. Not all of these #. changes were requested by the remote server, so "applied" #. is a better word than "received" (bug #5185). #: ../src/gtk-ui/sync-ui.c:2644 #, c-format msgid "Last time: Applied one change." msgid_plural "Last time: Applied %ld changes." msgstr[0] "Прошлый раз: применено одно изменение." msgstr[1] "Прошлый раз: применено изменений: %ld." msgstr[2] "Прошлый раз: применено изменений: %ld." #: ../src/gtk-ui/sync-ui.c:2649 #, c-format msgid "Last time: Applied %ld changes and sent %ld changes." msgstr "Прошлый раз: применено %ld и отправлено %ld изменений." #. TRANSLATORS: the placeholder is a error message (hopefully) #. * explaining the problem #: ../src/gtk-ui/sync-ui.c:2856 #, c-format msgid "" "There was a problem with last sync:\n" "%s" msgstr "" "Ошибка во время последней синхронизации:\n" "%s" #: ../src/gtk-ui/sync-ui.c:2866 #, c-format msgid "" "You've just restored a backup. The changes have not been synced with %s yet" msgstr "" "Вы только что восстановили резервную копию. Изменения еще не " "синхронизированы с %s" #: ../src/gtk-ui/sync-ui.c:3154 msgid "Waiting for current operation to finish..." msgstr "Ожидание окончания текущей операции..." #. TRANSLATORS: next strings are error messages. #: ../src/gtk-ui/sync-ui.c:3188 msgid "" "A normal sync is not possible at this time. The server suggests a slow sync," " but this might not always be what you want if both ends already have data." msgstr "" "В настоящий момент стандартная синхронизация невозможна. Сервер предлагает " "медленную синхронизацию, но это не всегда приводит к желаемому результату, " "если на обеих сторонах уже имеются данные." #: ../src/gtk-ui/sync-ui.c:3192 msgid "The sync process died unexpectedly." msgstr "Неожиданный сбой процесса синхронизации." #: ../src/gtk-ui/sync-ui.c:3197 msgid "" "Password request was not answered. You can save the password in the settings" " to prevent the request." msgstr "" "Запрос пароля остался без ответа. Вы можете сохранить пароль в настройках " "для предупреждения запроса." #. TODO use the service device name here, this is a remote problem #: ../src/gtk-ui/sync-ui.c:3201 msgid "There was a problem processing sync request. Trying again may help." msgstr "" "Проблема обработки запроса синхронизации. Возможно, повторная попытка может " "помочь." #: ../src/gtk-ui/sync-ui.c:3207 msgid "" "Failed to login. Could there be a problem with your username or password?" msgstr "" "Не удалось войти в систему. Возможно, неправильное имя пользователя или " "пароль?" #: ../src/gtk-ui/sync-ui.c:3210 msgid "Forbidden" msgstr "Запрещено" #. TRANSLATORS: data source means e.g. calendar or addressbook #: ../src/gtk-ui/sync-ui.c:3216 msgid "" "A data source could not be found. Could there be a problem with the " "settings?" msgstr "Источник найти не удалось. Возможно, неправильные настройки." #: ../src/gtk-ui/sync-ui.c:3220 msgid "Remote database error" msgstr "Ошибка удаленной базы данных" #. This can happen when EDS is borked, restart it may help... #: ../src/gtk-ui/sync-ui.c:3223 msgid "" "There is a problem with the local database. Syncing again or rebooting may " "help." msgstr "" "Проблема локальной базы данных. Возможно, повторная синхронизация или " "перезагрузка могут помочь." #: ../src/gtk-ui/sync-ui.c:3226 msgid "No space on disk" msgstr "Недостаточно свободного места на диске" #: ../src/gtk-ui/sync-ui.c:3228 msgid "Failed to process SyncML" msgstr "Не удалось обработать SyncML" #: ../src/gtk-ui/sync-ui.c:3230 msgid "Server authorization failed" msgstr "Авторизация сервера не удалась" #: ../src/gtk-ui/sync-ui.c:3232 msgid "Failed to parse configuration file" msgstr "Преобразование файла конфигурации не удалось" #: ../src/gtk-ui/sync-ui.c:3234 msgid "Failed to read configuration file" msgstr "Не удалось прочитать файл конфигурации" #: ../src/gtk-ui/sync-ui.c:3236 msgid "No configuration found" msgstr "Конфигурация не найдена" #: ../src/gtk-ui/sync-ui.c:3238 msgid "No configuration file found" msgstr "Файл конфигурации не найден" #: ../src/gtk-ui/sync-ui.c:3240 msgid "Server sent bad content" msgstr "Сервер отправил недействительную информацию" #: ../src/gtk-ui/sync-ui.c:3242 msgid "Connection certificate has expired" msgstr "Срок действия сертификата подключения истек" #: ../src/gtk-ui/sync-ui.c:3244 msgid "Connection certificate is invalid" msgstr "Сертификат подключения недействителен" #: ../src/gtk-ui/sync-ui.c:3252 msgid "" "We were unable to connect to the server. The problem could be temporary or " "there could be something wrong with the settings." msgstr "" "Не удалось подключиться к серверу. Проблема может быть временной, либо " "неправильно указаны настройки." #: ../src/gtk-ui/sync-ui.c:3259 msgid "The server URL is bad" msgstr "Неверный URL-адрес сервера" #: ../src/gtk-ui/sync-ui.c:3264 msgid "The server was not found" msgstr "Сервер найти не удалось" #: ../src/gtk-ui/sync-ui.c:3266 #, c-format msgid "Error %d" msgstr "Ошибка %d" #. TRANSLATORS: password request dialog contents: title, cancel button #. * and ok button #: ../src/gtk-ui/sync-ui.c:3404 msgid "Password is required for sync" msgstr "Для синхронизации требуется пароль" #: ../src/gtk-ui/sync-ui.c:3408 msgid "Sync with password" msgstr "Синхронизация с паролем" #. TRANSLATORS: password request dialog message, placeholder is service name #: ../src/gtk-ui/sync-ui.c:3418 #, c-format msgid "Please enter password for syncing with %s:" msgstr "Введите пароль для синхронизации с %s:" #. title for the buttons on the right side of main view #: ../src/gtk-ui/ui.glade.h:2 msgid "Actions" msgstr "Действия" #. text between the two "start from scratch" buttons in emergency view #: ../src/gtk-ui/ui.glade.h:4 msgid "or" msgstr "или" #: ../src/gtk-ui/ui.glade.h:5 msgid "Direct sync" msgstr "Прямая синхронизация" #: ../src/gtk-ui/ui.glade.h:6 msgid "Network sync" msgstr "Сетевая синхронизация" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:8 msgid "Restore from backup" msgstr "Восстановить из резервной копии" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:10 msgid "Slow sync" msgstr "Медленная синхронизация" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:12 msgid "Start from scratch" msgstr "Запуск из черновой версии" #: ../src/gtk-ui/ui.glade.h:13 msgid "" "A slow sync compares items from both sides and tries to merge them. \n" "This may fail in some cases, leading to duplicates or lost information." msgstr "" "Медленная синхронизация сравнивает элементы с обеих сторон и выполняет их объединение. \n" "В некоторых случаях это может вызвать появление копий данных или их утрату. " #: ../src/gtk-ui/ui.glade.h:15 msgid "Add new device" msgstr "Новое устройство" #: ../src/gtk-ui/ui.glade.h:16 msgid "Add new service" msgstr "Новая служба" #. explanation of "Restore backup" function #: ../src/gtk-ui/ui.glade.h:18 msgid "" "Backups are made before every time we Sync. Choose a backup to restore. Any " "changes you have made since then will be lost." msgstr "" "Резервные копии создаются перед каждой синхронизацией. Выберите одну из них " "для восстановления. Все изменения, внесенные после этого, будут утрачены." #: ../src/gtk-ui/ui.glade.h:19 msgid "Calendar" msgstr "Календарь" #. Button in main view, right side. Keep to below 20 chars per line, feel free #. to use two lines #: ../src/gtk-ui/ui.glade.h:21 msgid "" "Change or edit\n" "sync service" msgstr "" "Изменить\n" "службу синхронизации" #. close button for settings window #: ../src/gtk-ui/ui.glade.h:24 msgid "Close" msgstr "Закрыть" #: ../src/gtk-ui/ui.glade.h:25 msgid "" "Delete all data on Zyb \n" "and replace with your\n" "local information" msgstr "" "Удалите все данные на Zyb \n" "и замените на свою\n" "локальную информацию" #: ../src/gtk-ui/ui.glade.h:28 msgid "" "Delete all your local\n" "information and replace\n" "with data from Zyb" msgstr "" "Удалите всю свою локальную\n" "информацию и замените\n" "на данные из Zyb" #. button in main view, right side. Keep length to 20 characters or so, use #. two lines if needed #: ../src/gtk-ui/ui.glade.h:32 msgid "" "Fix a sync\n" "emergency" msgstr "" "Изменить экстренную\n" "синхронизацию" #: ../src/gtk-ui/ui.glade.h:34 msgid "" "If you don't see your service above but know that your sync provider uses SyncML\n" "you can setup a service manually." msgstr "" "Если служба не отображается вверху, но известно, что провайдер использует SyncML,\n" "службу можно настроить вручную." #: ../src/gtk-ui/ui.glade.h:36 msgid "Settings" msgstr "Настройки" #: ../src/gtk-ui/ui.glade.h:39 msgid "Sync Emergency" msgstr "Экстренная синхронизация" #: ../src/gtk-ui/ui.glade.h:41 msgid "" "To sync you'll need a network connection and an account with a sync service.\n" "We support the following services: " msgstr "" "Для синхронизации вам потребуется сетевое подключение и учетная запись со службой синхронизации.\n" "Поддерживаются следующие службы: " #: ../src/gtk-ui/ui.glade.h:43 msgid "Use Bluetooth to Sync your data from one device to another." msgstr "" "Используйте bluetooth для синхронизации своих данных между устройствами." #: ../src/gtk-ui/ui.glade.h:44 msgid "You will need to add Bluetooth devices before they can be synced." msgstr "" "Перед синхронизацией вам необходимо добавить устройства с поддержкой " "Bluetooth." #: ../src/gtk-ui/sync.desktop.in.h:2 msgid "Up to date" msgstr "Обновлено" #: ../src/gtk-ui/sync-gtk.desktop.in.h:1 msgid "SyncEvolution (GTK)" msgstr "SyncEvolution (GTK)" #: ../src/gtk-ui/sync-gtk.desktop.in.h:2 msgid "Synchronize PIM data" msgstr "Синхронизировать данные PIM" #: ../src/gtk-ui/sync-config-widget.c:88 msgid "" "ScheduleWorld enables you to keep your contacts, events, tasks, and notes in" " sync." msgstr "" "ScheduleWorld позволяет синхронизировать ваши контакты, информацию о " "событиях, задания и заметки." #: ../src/gtk-ui/sync-config-widget.c:91 msgid "" "Google Sync can back up and synchronize your contacts with your Gmail " "contacts." msgstr "" "Google Sync может создавать резервные копии и синхронизировать ваши контакты" " с контактами в Gmail." #. TRANSLATORS: Please include the word "demo" (or the equivalent in #. your language): Funambol is going to be a 90 day demo service #. in the future #: ../src/gtk-ui/sync-config-widget.c:97 msgid "" "Back up your contacts and calendar. Sync with a single click, anytime, " "anywhere (DEMO)." msgstr "" "Создайте резервные копии своих контактов и календаря. Синхронизируйтесь " "одним щелчком мыши в любое время и в любом месте (ДЕМО)." #: ../src/gtk-ui/sync-config-widget.c:100 msgid "" "Mobical Backup and Restore service allows you to securely back up your " "personal mobile data for free." msgstr "" "Служба Mobical позволяет вам безопасно и бесплатно создавать копии ваших " "персональных мобильных данных." #: ../src/gtk-ui/sync-config-widget.c:103 msgid "" "ZYB is a simple way for people to store and share mobile information online." msgstr "" "ZYB - это простой способ хранения и передачи мобильной информации в режиме " "онлайн." #: ../src/gtk-ui/sync-config-widget.c:106 msgid "" "Memotoo lets you access your personal data from any computer connected to " "the Internet." msgstr "" "Memotoo позволяет вам получить доступ к своим данным с любого компьютера, " "подключенного к Интернету." #: ../src/gtk-ui/sync-config-widget.c:195 msgid "Sorry, failed to save the configuration" msgstr "Не удалось сохранить файл конфигурации" #: ../src/gtk-ui/sync-config-widget.c:445 msgid "Service must have a name and server URL" msgstr "У службы должно быть имя и URL-адрес сервера" #. TRANSLATORS: error dialog when creating a new sync configuration #: ../src/gtk-ui/sync-config-widget.c:451 msgid "A username is required for this service" msgstr "Необходимо указать имя пользователя этой службы" #: ../src/gtk-ui/sync-config-widget.c:493 #, c-format msgid "" "Do you want to reset the settings for %s? This will not remove any synced " "information on either end." msgstr "" "Сбросить настройки для %s? Это не приведет к удалению какой-либо " "синхронизированной информации на любой из сторон." #. TRANSLATORS: buttons in reset-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:497 msgid "Yes, reset" msgstr "Да, сбросить" #: ../src/gtk-ui/sync-config-widget.c:498 #: ../src/gtk-ui/sync-config-widget.c:509 msgid "No, keep settings" msgstr "Нет, сохранить настройки" #: ../src/gtk-ui/sync-config-widget.c:503 #, c-format msgid "" "Do you want to delete the settings for %s? This will not remove any synced " "information on either end but it will remove these settings." msgstr "" "Удалить настройки для %s? Это не приведет к удалению какой-либо " "синхронизированной информации на любой стороне, но при этом эти настройки " "будут удалены." #. TRANSLATORS: buttons in delete-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:508 msgid "Yes, delete" msgstr "Да, удалить" #: ../src/gtk-ui/sync-config-widget.c:539 msgid "Reset settings" msgstr "Сбросить настройки" #: ../src/gtk-ui/sync-config-widget.c:542 msgid "Delete settings" msgstr "Удалить настройки" #: ../src/gtk-ui/sync-config-widget.c:552 msgid "Save and use" msgstr "Сохранить и использовать" #: ../src/gtk-ui/sync-config-widget.c:555 msgid "" "Save and replace\n" "current service" msgstr "" "Сохранить и заменить\n" "текущую службу" #: ../src/gtk-ui/sync-config-widget.c:563 msgid "Stop using device" msgstr "Прекратить использование устройства" #: ../src/gtk-ui/sync-config-widget.c:566 msgid "Stop using service" msgstr "Прекратить использование сервера" #. TRANSLATORS: label for an entry in service configuration form. #. * Placeholder is a source name. #. * Example: "Appointments URI" #: ../src/gtk-ui/sync-config-widget.c:749 #, c-format msgid "%s URI" msgstr "URL-адрес %s" #. TRANSLATORS: toggles in service configuration form, placeholder is service #. * or device name #: ../src/gtk-ui/sync-config-widget.c:936 #, c-format msgid "Send changes to %s" msgstr "Отправить изменения в %s" #: ../src/gtk-ui/sync-config-widget.c:941 #, c-format msgid "Receive changes from %s" msgstr "Получить изменения от %s" #: ../src/gtk-ui/sync-config-widget.c:957 msgid "Sync" msgstr "Sync" #. TRANSLATORS: label of a entry in service configuration #: ../src/gtk-ui/sync-config-widget.c:973 msgid "Server address" msgstr "Адрес сервера" #. TRANSLATORS: explanation before a device template combobox. #. * Placeholder is a device name like 'Nokia N85' or 'Syncevolution #. * Client' #: ../src/gtk-ui/sync-config-widget.c:1049 #, c-format msgid "" "This device looks like it might be a '%s'. If this is not correct, please " "take a look at the list of supported devices and pick yours if it is listed" msgstr "" "Возможно, это устройство - '%s'. Если это неверно, просмотрите список " "поддерживаемых устройств и выберите свое, если оно указано" #: ../src/gtk-ui/sync-config-widget.c:1055 #: ../src/gtk-ui/sync-config-widget.c:1915 msgid "" "We don't know what this device is exactly. Please take a look at the list of" " supported devices and pick yours if it is listed" msgstr "" "Невозможно точно установить тип устройства. Просмотрите список " "поддерживаемых устройств и выберите свое, если оно указано" #: ../src/gtk-ui/sync-config-widget.c:1207 #, c-format msgid "%s - Bluetooth device" msgstr "%s - Bluetooth-устройство" #. TRANSLATORS: service title for services that are not based on a #. * template in service list, the placeholder is the name of the service #: ../src/gtk-ui/sync-config-widget.c:1213 #, c-format msgid "%s - manually setup" msgstr "%s - ручная настройка" #. TRANSLATORS: link button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1886 msgid "Launch website" msgstr "Открыть веб-сайт" #. TRANSLATORS: button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1895 msgid "Set up now" msgstr "Настроить сейчас" #: ../src/gtk-ui/sync-config-widget.c:1953 msgid "Use these settings" msgstr "Использовать эти настройки" #. TRANSLATORS: labels of entries in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1991 msgid "Username" msgstr "Имя пользователя" #: ../src/gtk-ui/sync-config-widget.c:2006 msgid "Password" msgstr "Пароль" #. TRANSLATORS: warning in service configuration form for people #. who have modified the configuration via other means. #: ../src/gtk-ui/sync-config-widget.c:2029 msgid "" "Current configuration is more complex than what can be shown here. Changes " "to sync mode or synced data types will overwrite that configuration." msgstr "" "Текущая конфигурация сервера более сложная, чем та, которую можно отобразить" " здесь. Изменения режима синхронизации или синхронизированных типов данных " "перезапишут эту конфигурацию." #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2048 msgid "Hide server settings" msgstr "Скрыть настройки сервера" #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2068 msgid "Show server settings" msgstr "Отобразить настройки сервера" #: ../src/gnome-bluetooth/syncevolution.c:110 msgid "Sync in the Sync application" msgstr "Синхронизировать в приложении Sync" #: ../src/syncevo-dbus-server.cpp:6653 #, c-format msgid "%s is syncing" msgstr "Синхронизация %s" #: ../src/syncevo-dbus-server.cpp:6654 #, c-format msgid "We have just started to sync your computer with the %s sync service." msgstr "" "Синхронизация вашего компьютера со службой синхронизации %s только что " "началась." #. if sync is successfully started and done #: ../src/syncevo-dbus-server.cpp:6670 #, c-format msgid "%s sync complete" msgstr "Синхронизация %s завершена" #: ../src/syncevo-dbus-server.cpp:6671 #, c-format msgid "We have just finished syncing your computer with the %s sync service." msgstr "" "Синхронизация вашего компьютера со службой синхронизации %s только что " "завершилась." #. if sync is successfully started and has errors, or not started successful #. with a fatal problem #: ../src/syncevo-dbus-server.cpp:6676 msgid "Sync problem." msgstr "Ошибка синхронизации" #: ../src/syncevo-dbus-server.cpp:6677 msgid "Sorry, there's a problem with your sync that you need to attend to." msgstr "" "Во время синхронизации возникла проблема, на которую вам следует обратить " "внимание." #: ../src/syncevo-dbus-server.cpp:6758 msgid "View" msgstr "Просмотреть" #. Use "default" as ID because that is what mutter-moblin #. recognizes: it then skips the action instead of adding it #. in addition to its own "Dismiss" button (always added). #: ../src/syncevo-dbus-server.cpp:6762 msgid "Dismiss" msgstr "Отклонить" syncevolution_1.4/po/sk.po000066400000000000000000000602511230021373600157550ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Tomáš Virgl , 2010. # msgid "" msgstr "" "Project-Id-Version: syncevolution.master\n" "Report-Msgid-Bugs-To: http://moblin.org/projects/syncevolution\n" "POT-Creation-Date: 2010-04-21 09:23+0000\n" "PO-Revision-Date: 2010-04-20 23:45+0200\n" "Last-Translator: Tomáš Virgl \n" "Language-Team: slovak \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 1 : (n>=2 && n<=4) ? 2 : 0;\n" #. TRANSLATORS: this is the application name that may be used by e.g. #. the windowmanager #: ../src/gtk-ui/main.c:40 ../src/gtk-ui/ui.glade.h:38 #: ../src/gtk-ui/sync.desktop.in.h:1 #: ../src/gnome-bluetooth/syncevolution.c:112 msgid "Sync" msgstr "Synchronizovať" #: ../src/gtk-ui/sync-ui.c:266 msgid "Contacts" msgstr "Kontakty" #: ../src/gtk-ui/sync-ui.c:268 msgid "Appointments" msgstr "Stretnutia" #: ../src/gtk-ui/sync-ui.c:270 ../src/gtk-ui/ui.glade.h:40 msgid "Tasks" msgstr "Úlohy" #: ../src/gtk-ui/sync-ui.c:272 msgid "Notes" msgstr "Poznámky" #. TRANSLATORS: This is a "combination source" for syncing with devices #. * that combine appointments and tasks. the name should match the ones #. * used for calendar and todo above #: ../src/gtk-ui/sync-ui.c:277 msgid "Appointments & Tasks" msgstr "Stretnutia a úlohy" #: ../src/gtk-ui/sync-ui.c:349 msgid "Starting sync" msgstr "Začína sa synchronizácia" #. TRANSLATORS: slow sync confirmation dialog message. Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:387 #, c-format msgid "Do you want to slow sync with %s?" msgstr "Chcete zahájiť pomalú synchronizáciu s %s?" #: ../src/gtk-ui/sync-ui.c:391 msgid "Yes, do slow sync" msgstr "Áno, zahájiť pomalú synchronizáciu" #: ../src/gtk-ui/sync-ui.c:391 msgid "No, cancel sync" msgstr "Nie, zrušiť synchronizovanie" #. TRANSLATORS: confirmation dialog for refresh-from-server. Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:424 #, c-format msgid "" "Do you want to delete all local data and replace it with data from %s? This " "is not usually advised." msgstr "" "Chcete vymazať všetky lokálne dáta a nahradiť ich dátami z %s? Toto sa " "zvyčajne neodporúča." #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:460 msgid "Yes, delete and replace" msgstr "Áno, vymazať a nahradiť" #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:460 #: ../src/gtk-ui/sync-ui.c:1580 msgid "No" msgstr "Nie" #. TRANSLATORS: confirmation dialog for refresh-from-client. Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:455 #, c-format msgid "" "Do you want to delete all data in %s and replace it with your local data? " "This is not usually advised." msgstr "" "Chcete vymazať všetky dáta v %s a nahradiť ich vašimi lokálnymi dátami? Toto " "sa zvyčajne neodporúča." #: ../src/gtk-ui/sync-ui.c:487 msgid "Trying to cancel sync" msgstr "Pokúša sa zrušiť synchronizácia" #: ../src/gtk-ui/sync-ui.c:529 msgid "No service or device selected" msgstr "Nie je vybraná žiadna služba alebo zariadenie" #. TRANSLATORS: This is the title on main view. Placeholder is #. * the service name. Example: "Google - synced just now" #: ../src/gtk-ui/sync-ui.c:537 #, c-format msgid "%s - synced just now" msgstr "%s - synchronizované práve teraz" #: ../src/gtk-ui/sync-ui.c:541 #, c-format msgid "%s - synced a minute ago" msgstr "%s - synchronizované pred minútou" #: ../src/gtk-ui/sync-ui.c:545 #, c-format msgid "%s - synced %ld minutes ago" msgstr "%s - synchronizované pred %ld minútami" #: ../src/gtk-ui/sync-ui.c:550 #, c-format msgid "%s - synced an hour ago" msgstr "%s - synchronizované pred hodinou" #: ../src/gtk-ui/sync-ui.c:554 #, c-format msgid "%s - synced %ld hours ago" msgstr "%s - synchronizované pred %ld hodinami" #: ../src/gtk-ui/sync-ui.c:559 #, c-format msgid "%s - synced a day ago" msgstr "%s - synchronizované pred jedným dňom" #: ../src/gtk-ui/sync-ui.c:563 #, c-format msgid "%s - synced %ld days ago" msgstr "%s - synchronizované pred %ld dňami" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "You've just restored a backup. The changes have not been " #. * "synced with %s yet" #: ../src/gtk-ui/sync-ui.c:612 ../src/gtk-ui/sync-ui.c:726 msgid "Sync now" msgstr "Synchronizovať teraz" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "A normal sync is not possible at this time..." message. #. * "Other options" will open Emergency view #: ../src/gtk-ui/sync-ui.c:618 ../src/gtk-ui/ui.glade.h:37 msgid "Slow sync" msgstr "Pomalá synchronizácia" #: ../src/gtk-ui/sync-ui.c:619 msgid "Other options..." msgstr "Iné možnosti..." #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * when no service is selected. Will open configuration view #: ../src/gtk-ui/sync-ui.c:624 msgid "Select sync service" msgstr "Vyberte si synchronizačnú službu" #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * login to service fails. Will open configuration view for this service #: ../src/gtk-ui/sync-ui.c:629 msgid "Edit service settings" msgstr "Upraviť nastavenia služby" #: ../src/gtk-ui/sync-ui.c:700 msgid "" "You haven't selected a sync service or device yet. Sync services let you " "synchronize your data between your netbook and a web service. You can also " "sync directly with some devices." msgstr "" "Nevybrali ste si žiadnu synchronizačnú službu alebo zariadenie. " "Synchronizačná služba vám umožní synchronizovať si vaše dáta medzi netbookom " "a webovou službou. Taktiež môžete dáta synchronizovať priamo s niektorými " "zariadeniami." #: ../src/gtk-ui/sync-ui.c:722 msgid "Sync again" msgstr "Synchronizovať znovu" #: ../src/gtk-ui/sync-ui.c:743 msgid "Restoring" msgstr "Obnovovanie" #: ../src/gtk-ui/sync-ui.c:745 msgid "Syncing" msgstr "Synchronizovanie" #. TRANSLATORS: This is for the button in main view, right side. #. Keep line length below ~20 characters, use two lines if needed #: ../src/gtk-ui/sync-ui.c:757 ../src/gtk-ui/sync-ui.c:3373 msgid "Cancel sync" msgstr "" "Zrušiť\n" "synchronizáciu" #: ../src/gtk-ui/sync-ui.c:922 msgid "Back to sync" msgstr "Späť na synchronizáciu" #. TRANSLATORS: label for checkbutton/toggle in main view. #. * Please stick to similar length strings or break the line with #. * "\n" if absolutely needed #: ../src/gtk-ui/sync-ui.c:1223 msgid "Automatic sync" msgstr "" "Automatická\n" "synchronizácia" #. This is the expander label in emergency view. It summarizes the #. * currently selected data sources. First placeholder is service/device #. * name, second a comma separeted list of sources. #. * E.g. "Affected data: Google Contacts, Appointments" #: ../src/gtk-ui/sync-ui.c:1494 #, c-format msgid "Affected data: %s %s" msgstr "Dotknuté dáta: %s %s" #: ../src/gtk-ui/sync-ui.c:1499 #, c-format msgid "Affected data: none" msgstr "Dotknuté dáta: žiadne" #. TRANSLATORS: confirmation for restoring a backup. placeholder is the #. * backup time string defined below #: ../src/gtk-ui/sync-ui.c:1577 #, c-format msgid "" "Do you want to restore the backup from %s? All changes you have made since " "then will be lost." msgstr "" "Chcete obnoviť zálohu z %s? Všetky zmeny, ktoré ste urobili doteraz budú " "stratené." #: ../src/gtk-ui/sync-ui.c:1580 msgid "Yes, restore" msgstr "Áno, obnoviť" #. TRANSLATORS: date/time for strftime(), used in emergency view backup #. * label. Any time format that shows date and time is good. #: ../src/gtk-ui/sync-ui.c:1612 #, c-format msgid "%x %X" msgstr "%x %X" #. TRANSLATORS: label for a backup in emergency view. Placeholder is #. * service or device name #: ../src/gtk-ui/sync-ui.c:1631 #, c-format msgid "Backed up before syncing with %s" msgstr "Pred synchronizovaním s %s bola vytvorená záložná kópia." #: ../src/gtk-ui/sync-ui.c:1648 msgid "Restore" msgstr "Obnoviť" #. TRANSLATORS: this is an explanation in Emergency view. #. * Placeholder is a service/device name #: ../src/gtk-ui/sync-ui.c:1755 #, c-format msgid "" "A normal sync with %s is not possible at this time. You can do a slow two-" "way sync or start from scratch. You can also restore a backup, but a slow " "sync or starting from scratch will still be required before normal sync is " "possible." msgstr "" "Momentálne nie je možné vykonať obyčajnú synchronizáciu s %s. Môžete vykonať " "pomalú synchronizáciu alebo začať úplne od začiatku. Taktiež môžete obnoviť " "zálohu, ale pomalá synchronizácia alebo začiatok budú požadované pred tým " "než bude možná obyčajná synchronizácia." #: ../src/gtk-ui/sync-ui.c:1765 #, c-format msgid "" "If something has gone horribly wrong, you can try a slow sync, start from " "scratch or restore from backup." msgstr "" "Keď sa stane niečo veľmi zlé môžete skúsiť pomalú synchronizáciu, začať " "úplne od začiatku alebo obnoviť zálohu." #. TRANSLATORS: These are a buttons in Emergency view. Placeholder is a #. * service/device name. Please don't use too long lines, but feel free to #. * use several lines. #: ../src/gtk-ui/sync-ui.c:1774 #, c-format msgid "" "Delete all your local\n" "data and replace with\n" "data from %s" msgstr "" "Vymazať všetky\n" "lokálne dáta a nahradiť\n" "ich s dátami z %s" #: ../src/gtk-ui/sync-ui.c:1780 #, c-format msgid "" "Delete all data on\n" "%s and replace\n" "with your local data" msgstr "" "Vymazať všetky dáta\n" "na %s a nahradiť ich\n" "lokálnymi dátami" #: ../src/gtk-ui/sync-ui.c:2241 msgid "Failed to get list of supported services from SyncEvolution" msgstr "Zlyhalo získavanie zoznamu podporovaných služieb z SyncEvolution" #: ../src/gtk-ui/sync-ui.c:2295 msgid "" "There was a problem communicating with the sync process. Please try again " "later." msgstr "" "Nastal problém pri komunikovaní so synchronizačným procesom. Pokus zopakujte " "neskôr." #: ../src/gtk-ui/sync-ui.c:2354 msgid "Restore failed" msgstr "Obnova zlyhala" #: ../src/gtk-ui/sync-ui.c:2357 ../src/gtk-ui/sync-ui.c:3242 msgid "Sync failed" msgstr "Synchronizácia zlyhala" #: ../src/gtk-ui/sync-ui.c:2363 msgid "Restore complete" msgstr "Obnova hotová" #: ../src/gtk-ui/sync-ui.c:2366 msgid "Sync complete" msgstr "Synchronizácia hotová" #: ../src/gtk-ui/sync-ui.c:2458 #, c-format msgid "Preparing '%s'" msgstr "Pripravuje sa '%s'" #: ../src/gtk-ui/sync-ui.c:2461 #, c-format msgid "Receiving '%s'" msgstr "Prijíma sa '%s'" #: ../src/gtk-ui/sync-ui.c:2464 #, c-format msgid "Sending '%s'" msgstr "Posiela sa '%s'" #: ../src/gtk-ui/sync-ui.c:2585 #, c-format msgid "There was one remote rejection." msgid_plural "There were %ld remote rejections." msgstr[0] "" msgstr[1] "" #: ../src/gtk-ui/sync-ui.c:2590 #, c-format msgid "There was one local rejection." msgid_plural "There were %ld local rejections." msgstr[0] "" msgstr[1] "" #: ../src/gtk-ui/sync-ui.c:2595 #, c-format msgid "There were %ld local rejections and %ld remote rejections." msgstr "" #: ../src/gtk-ui/sync-ui.c:2600 #, c-format msgid "Last time: No changes." msgstr "" #: ../src/gtk-ui/sync-ui.c:2602 #, c-format msgid "Last time: Sent one change." msgid_plural "Last time: Sent %ld changes." msgstr[0] "" msgstr[1] "" #. This is about changes made to the local data. Not all of these #. changes were requested by the remote server, so "applied" #. is a better word than "received" (bug #5185). #: ../src/gtk-ui/sync-ui.c:2610 #, c-format msgid "Last time: Applied one change." msgid_plural "Last time: Applied %ld changes." msgstr[0] "" msgstr[1] "" #: ../src/gtk-ui/sync-ui.c:2615 #, c-format msgid "Last time: Applied %ld changes and sent %ld changes." msgstr "" #. TRANSLATORS: the placeholder is a error message (hopefully) #. * explaining the problem #: ../src/gtk-ui/sync-ui.c:2822 #, c-format msgid "" "There was a problem with last sync:\n" "%s" msgstr "" #: ../src/gtk-ui/sync-ui.c:2832 #, c-format msgid "" "You've just restored a backup. The changes have not been synced with %s yet" msgstr "" #: ../src/gtk-ui/sync-ui.c:3120 msgid "Waiting for current operation to finish..." msgstr "" #. TRANSLATORS: next strings are error messages. #: ../src/gtk-ui/sync-ui.c:3154 msgid "" "A normal sync is not possible at this time. The server suggests a slow sync, " "but this might not always be what you want if both ends already have data." msgstr "" #: ../src/gtk-ui/sync-ui.c:3158 msgid "The sync process died unexpectedly." msgstr "" #: ../src/gtk-ui/sync-ui.c:3163 msgid "" "Password request was not answered. You can save the password in the settings " "to prevent the request." msgstr "" #. TODO use the service device name here, this is a remote problem #: ../src/gtk-ui/sync-ui.c:3167 msgid "There was a problem processing sync request. Trying again may help." msgstr "" #: ../src/gtk-ui/sync-ui.c:3173 msgid "" "Failed to login. Could there be a problem with your username or password?" msgstr "" #: ../src/gtk-ui/sync-ui.c:3176 msgid "Forbidden" msgstr "" #. TRANSLATORS: data source means e.g. calendar or addressbook #: ../src/gtk-ui/sync-ui.c:3182 msgid "" "A data source could not be found. Could there be a problem with the settings?" msgstr "" #: ../src/gtk-ui/sync-ui.c:3186 msgid "Remote database error" msgstr "" #. This can happen when EDS is borked, restart it may help... #: ../src/gtk-ui/sync-ui.c:3189 msgid "" "There is a problem with the local database. Syncing again or rebooting may " "help." msgstr "" #: ../src/gtk-ui/sync-ui.c:3192 msgid "No space on disk" msgstr "" #: ../src/gtk-ui/sync-ui.c:3194 msgid "Failed to process SyncML" msgstr "" #: ../src/gtk-ui/sync-ui.c:3196 msgid "Server authorization failed" msgstr "" #: ../src/gtk-ui/sync-ui.c:3198 msgid "Failed to parse configuration file" msgstr "" #: ../src/gtk-ui/sync-ui.c:3200 msgid "Failed to read configuration file" msgstr "" #: ../src/gtk-ui/sync-ui.c:3202 msgid "No configuration found" msgstr "" #: ../src/gtk-ui/sync-ui.c:3204 msgid "No configuration file found" msgstr "" #: ../src/gtk-ui/sync-ui.c:3206 msgid "Server sent bad content" msgstr "" #: ../src/gtk-ui/sync-ui.c:3208 msgid "Connection certificate has expired" msgstr "" #: ../src/gtk-ui/sync-ui.c:3210 msgid "Connection certificate is invalid" msgstr "" #: ../src/gtk-ui/sync-ui.c:3218 msgid "" "We were unable to connect to the server. The problem could be temporary or " "there could be something wrong with the settings." msgstr "" #: ../src/gtk-ui/sync-ui.c:3225 msgid "The server URL is bad" msgstr "" #: ../src/gtk-ui/sync-ui.c:3230 msgid "The server was not found" msgstr "" #: ../src/gtk-ui/sync-ui.c:3232 #, c-format msgid "Error %d" msgstr "" #. TRANSLATORS: password request dialog contents: title, cancel button #. * and ok button #: ../src/gtk-ui/sync-ui.c:3370 msgid "Password is required for sync" msgstr "" #: ../src/gtk-ui/sync-ui.c:3374 msgid "Sync with password" msgstr "" #. TRANSLATORS: password request dialog message, placeholder is service name #: ../src/gtk-ui/sync-ui.c:3384 #, c-format msgid "Please enter password for syncing with %s:" msgstr "" #. title for the buttons on the right side of main view #: ../src/gtk-ui/ui.glade.h:2 msgid "Actions" msgstr "" #. text between the two "start from scratch" buttons in emergency view #: ../src/gtk-ui/ui.glade.h:4 msgid "or" msgstr "" #: ../src/gtk-ui/ui.glade.h:5 msgid "Direct sync" msgstr "" #: ../src/gtk-ui/ui.glade.h:6 msgid "Network sync" msgstr "" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:8 msgid "Restore from backup" msgstr "" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:10 msgid "Slow sync" msgstr "" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:12 msgid "Start from scratch" msgstr "" #: ../src/gtk-ui/ui.glade.h:13 msgid "" "A slow sync compares items from both sides and tries to merge them. \n" "This may fail in some cases, leading to duplicates or lost information." msgstr "" #: ../src/gtk-ui/ui.glade.h:15 msgid "Add new device" msgstr "" #: ../src/gtk-ui/ui.glade.h:16 msgid "Add new service" msgstr "" #. explanation of "Restore backup" function #: ../src/gtk-ui/ui.glade.h:18 msgid "" "Backups are made before every time we Sync. Choose a backup to restore. Any " "changes you have made since then will be lost." msgstr "" #: ../src/gtk-ui/ui.glade.h:19 msgid "Calendar" msgstr "" #. Button in main view, right side. Keep to below 20 chars per line, feel free to use two lines #: ../src/gtk-ui/ui.glade.h:21 msgid "" "Change or edit\n" "sync service" msgstr "" #. close button for settings window #: ../src/gtk-ui/ui.glade.h:24 msgid "Close" msgstr "" #: ../src/gtk-ui/ui.glade.h:25 msgid "" "Delete all data on Zyb \n" "and replace with your\n" "local information" msgstr "" #: ../src/gtk-ui/ui.glade.h:28 msgid "" "Delete all your local\n" "information and replace\n" "with data from Zyb" msgstr "" #. button in main view, right side. Keep length to 20 characters or so, use two lines if needed #: ../src/gtk-ui/ui.glade.h:32 msgid "" "Fix a sync\n" "emergency" msgstr "" #: ../src/gtk-ui/ui.glade.h:34 msgid "" "If you don't see your service above but know that your sync provider uses " "SyncML\n" "you can setup a service manually." msgstr "" #: ../src/gtk-ui/ui.glade.h:36 msgid "Settings" msgstr "" #: ../src/gtk-ui/ui.glade.h:39 msgid "Sync Emergency" msgstr "" #: ../src/gtk-ui/ui.glade.h:41 msgid "" "To sync you'll need a network connection and an account with a sync " "service.\n" "We support the following services: " msgstr "" #: ../src/gtk-ui/ui.glade.h:43 msgid "Use Bluetooth to Sync your data from one device to another." msgstr "" #: ../src/gtk-ui/ui.glade.h:44 msgid "You will need to add Bluetooth devices before they can be synced." msgstr "" #: ../src/gtk-ui/sync.desktop.in.h:2 ../src/gtk-ui/sync-gtk.desktop.in.h:2 msgid "Up to date" msgstr "" #: ../src/gtk-ui/sync-gtk.desktop.in.h:1 msgid "Sync (GTK)" msgstr "" #: ../src/gtk-ui/sync-config-widget.c:78 msgid "" "ScheduleWorld enables you to keep your contacts, events, tasks, and notes in " "sync." msgstr "" #: ../src/gtk-ui/sync-config-widget.c:81 msgid "" "Google Sync can back up and synchronize your contacts with your Gmail " "contacts." msgstr "" #. TRANSLATORS: Please include the word "demo" (or the equivalent in #. your language): Funambol is going to be a 90 day demo service #. in the future #: ../src/gtk-ui/sync-config-widget.c:87 msgid "" "Back up your contacts and calendar. Sync with a single click, anytime, " "anywhere (DEMO)." msgstr "" #: ../src/gtk-ui/sync-config-widget.c:90 msgid "" "Mobical Backup and Restore service allows you to securely back up your " "personal mobile data for free." msgstr "" #: ../src/gtk-ui/sync-config-widget.c:93 msgid "" "ZYB is a simple way for people to store and share mobile information online." msgstr "" #: ../src/gtk-ui/sync-config-widget.c:96 msgid "" "Memotoo lets you access your personal data from any computer connected to " "the Internet." msgstr "" #: ../src/gtk-ui/sync-config-widget.c:192 msgid "Sorry, failed to save the configuration" msgstr "" #: ../src/gtk-ui/sync-config-widget.c:381 msgid "Service must have a name and server URL" msgstr "" #: ../src/gtk-ui/sync-config-widget.c:422 #, c-format msgid "" "Do you want to reset the settings for %s? This will not remove any synced " "information on either end." msgstr "" #. TRANSLATORS: buttons in reset-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:426 msgid "Yes, reset" msgstr "" #: ../src/gtk-ui/sync-config-widget.c:427 #: ../src/gtk-ui/sync-config-widget.c:438 msgid "No, keep settings" msgstr "" #: ../src/gtk-ui/sync-config-widget.c:432 #, c-format msgid "" "Do you want to delete the settings for %s? This will not remove any synced " "information on either end but it will remove these settings." msgstr "" #. TRANSLATORS: buttons in delete-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:437 msgid "Yes, delete" msgstr "" #: ../src/gtk-ui/sync-config-widget.c:467 msgid "Reset settings" msgstr "" #: ../src/gtk-ui/sync-config-widget.c:470 msgid "Delete settings" msgstr "" #: ../src/gtk-ui/sync-config-widget.c:480 msgid "Save and use" msgstr "" #: ../src/gtk-ui/sync-config-widget.c:483 msgid "" "Save and replace\n" "current service" msgstr "" #: ../src/gtk-ui/sync-config-widget.c:493 msgid "Stop using device" msgstr "" #: ../src/gtk-ui/sync-config-widget.c:496 msgid "Stop using service" msgstr "" #. TRANSLATORS: label for an entry in service configuration form. #. * Placeholder is a source name. #. * Example: "Appointments URI" #: ../src/gtk-ui/sync-config-widget.c:679 #, c-format msgid "%s URI" msgstr "" #. TRANSLATORS: toggles in service configuration form, placeholder is service #. * or device name #: ../src/gtk-ui/sync-config-widget.c:854 #, c-format msgid "Send changes to %s" msgstr "" #: ../src/gtk-ui/sync-config-widget.c:859 #, c-format msgid "Receive changes from %s" msgstr "" #: ../src/gtk-ui/sync-config-widget.c:875 msgid "Sync" msgstr "" #. TRANSLATORS: label of a entry in service configuration #: ../src/gtk-ui/sync-config-widget.c:891 msgid "Server address" msgstr "" #. TRANSLATORS: explanation before a device template combobox. #. * Placeholder is a device name like 'Nokia N85' or 'Syncevolution #. * Client' #: ../src/gtk-ui/sync-config-widget.c:967 #, c-format msgid "" "This device looks like it might be a '%s'. If this is not correct, please " "take a look at the list of supported devices and pick yours if it is listed" msgstr "" #: ../src/gtk-ui/sync-config-widget.c:973 msgid "" "We don't know what this device is exactly. Please take a look at the list of " "supported devices and pick yours if it is listed" msgstr "" #: ../src/gtk-ui/sync-config-widget.c:1126 #, c-format msgid "%s - Bluetooth device" msgstr "" #. TRANSLATORS: service title for services that are not based on a #. * template in service list, the placeholder is the name of the service #: ../src/gtk-ui/sync-config-widget.c:1132 #, c-format msgid "%s - manually setup" msgstr "" #. TRANSLATORS: link button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1807 msgid "Launch website" msgstr "" #. TRANSLATORS: button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1816 msgid "Set up now" msgstr "" #. TRANSLATORS: labels of entries in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1912 msgid "Username" msgstr "" #: ../src/gtk-ui/sync-config-widget.c:1927 msgid "Password" msgstr "" #. TRANSLATORS: warning in service configuration form for people #. who have modified the configuration via other means. #: ../src/gtk-ui/sync-config-widget.c:1950 msgid "" "Current configuration is more complex than what can be shown here. Changes " "to sync mode or synced data types will overwrite that configuration." msgstr "" #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1969 msgid "Hide server settings" msgstr "" #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1989 msgid "Show server settings" msgstr "" #: ../src/gnome-bluetooth/syncevolution.c:110 msgid "Sync in the Sync application" msgstr "" #: ../src/syncevo-dbus-server.cpp:5664 #, c-format msgid "%s is syncing" msgstr "" #: ../src/syncevo-dbus-server.cpp:5665 #, c-format msgid "We have just started to sync your computer with the %s sync service." msgstr "" #. if sync is successfully started and done #: ../src/syncevo-dbus-server.cpp:5679 #, c-format msgid "%s sync complete" msgstr "" #: ../src/syncevo-dbus-server.cpp:5680 #, c-format msgid "We have just finished syncing your computer with the %s sync service." msgstr "" #. if sync is successfully started and has errors, or not started successful with a fatal problem #: ../src/syncevo-dbus-server.cpp:5685 msgid "Sync problem." msgstr "" #: ../src/syncevo-dbus-server.cpp:5686 msgid "Sorry, there's a problem with your sync that you need to attend to." msgstr "" #: ../src/syncevo-dbus-server.cpp:5759 msgid "View" msgstr "" #. Use "default" as ID because that is what mutter-moblin #. recognizes: it then skips the action instead of adding it #. in addition to its own "Dismiss" button (always added). #: ../src/syncevo-dbus-server.cpp:5763 msgid "Dismiss" msgstr "" syncevolution_1.4/po/sv.po000066400000000000000000000732661230021373600160020ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # # Translators: # Annika Olsson , 2011. # Mathilda , 2011. # , 2011. msgid "" msgstr "" "Project-Id-Version: syncevolution\n" "Report-Msgid-Bugs-To: https://bugs.meego.com/\n" "POT-Creation-Date: 2011-12-05 10:21-0800\n" "PO-Revision-Date: 2011-12-11 17:36+0000\n" "Last-Translator: GLS_SVE1 \n" "Language-Team: Swedish (http://www.transifex.net/projects/p/meego/team/sv/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: sv\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" #. TRANSLATORS: this is the application name that may be used by e.g. #. the windowmanager #: ../src/gtk-ui/main.c:40 ../src/gtk-ui/ui.glade.h:38 #: ../src/gtk-ui/sync.desktop.in.h:1 #: ../src/gnome-bluetooth/syncevolution.c:112 msgid "Sync" msgstr "Synkronisering " #: ../src/gtk-ui/sync-ui.c:266 msgid "Contacts" msgstr "Kontakter" #: ../src/gtk-ui/sync-ui.c:268 msgid "Appointments" msgstr "Möten" #: ../src/gtk-ui/sync-ui.c:270 ../src/gtk-ui/ui.glade.h:40 msgid "Tasks" msgstr "Uppgifter" #: ../src/gtk-ui/sync-ui.c:272 msgid "Notes" msgstr "Anteckningar" #. TRANSLATORS: This is a "combination source" for syncing with devices #. * that combine appointments and tasks. the name should match the ones #. * used for calendar and todo above #: ../src/gtk-ui/sync-ui.c:277 msgid "Appointments & Tasks" msgstr "Möten och uppgifter" #: ../src/gtk-ui/sync-ui.c:349 msgid "Starting sync" msgstr "Startar synkronisering" #. TRANSLATORS: slow sync confirmation dialog message. Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:387 #, c-format msgid "Do you want to slow sync with %s?" msgstr "Vill du sakta ner synkronisering med %s?" #: ../src/gtk-ui/sync-ui.c:391 msgid "Yes, do slow sync" msgstr "Ja, sakta ner synk" #: ../src/gtk-ui/sync-ui.c:391 msgid "No, cancel sync" msgstr "Nej, avbryt synkronisering " #. TRANSLATORS: confirmation dialog for "refresh from peer". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:424 #, c-format msgid "" "Do you want to delete all local data and replace it with data from %s? This " "is not usually advised." msgstr "" "Vill du radera alla lokala data och ersätta dem med data från %s? Detta " "rekommenderas vanligtvis inte." #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 msgid "Yes, delete and replace" msgstr "Ja, radera och ersätt filer" #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 #: ../src/gtk-ui/sync-ui.c:1610 msgid "No" msgstr "Nej" #. TRANSLATORS: confirmation dialog for "refresh from local side". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:457 #, c-format msgid "" "Do you want to delete all data in %s and replace it with your local data? " "This is not usually advised." msgstr "" "Vill du radera alla data på %s och ersätta dem med lokala data? Detta " "rekommenderas vanligtvis inte." #: ../src/gtk-ui/sync-ui.c:491 msgid "Trying to cancel sync" msgstr "Försöker att avbryta synkronisering" #: ../src/gtk-ui/sync-ui.c:533 msgid "No service or device selected" msgstr "Ingen markerad tjänst eller enhet" #. TRANSLATORS: This is the title on main view. Placeholder is #. * the service name. Example: "Google - synced just now" #: ../src/gtk-ui/sync-ui.c:541 #, c-format msgid "%s - synced just now" msgstr "%s - synkroniserade nyss" #: ../src/gtk-ui/sync-ui.c:545 #, c-format msgid "%s - synced a minute ago" msgstr "%s - synkroniserat en minut sedan" #: ../src/gtk-ui/sync-ui.c:549 #, c-format msgid "%s - synced %ld minutes ago" msgstr "%s - synkroniserat %ld minuter sedan" #: ../src/gtk-ui/sync-ui.c:554 #, c-format msgid "%s - synced an hour ago" msgstr "%s - synkroniserat en timme sedan" #: ../src/gtk-ui/sync-ui.c:558 #, c-format msgid "%s - synced %ld hours ago" msgstr "%s - synkroniserat %ld timmar sedan" #: ../src/gtk-ui/sync-ui.c:563 #, c-format msgid "%s - synced a day ago" msgstr "%s - synkroniserat en dag sedan" #: ../src/gtk-ui/sync-ui.c:567 #, c-format msgid "%s - synced %ld days ago" msgstr "%s - synkroniserat %ld dagar sedan" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "You've just restored a backup. The changes have not been " #. * "synced with %s yet" #: ../src/gtk-ui/sync-ui.c:616 ../src/gtk-ui/sync-ui.c:701 msgid "Sync now" msgstr "Synkronisera nu" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "A normal sync is not possible at this time..." message. #. * "Other options" will open Emergency view #: ../src/gtk-ui/sync-ui.c:622 ../src/gtk-ui/ui.glade.h:37 msgid "Slow sync" msgstr "Långsam synkronisering " #: ../src/gtk-ui/sync-ui.c:623 msgid "Other options..." msgstr "Andra alternativ" #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * when no service is selected. Will open configuration view #: ../src/gtk-ui/sync-ui.c:628 msgid "Select sync service" msgstr "Välj synkroniseringstjänst" #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * login to service fails. Will open configuration view for this service #: ../src/gtk-ui/sync-ui.c:633 msgid "Edit service settings" msgstr "Ändra inställningar för tjänst" #: ../src/gtk-ui/sync-ui.c:709 msgid "" "You haven't selected a sync service or device yet. Sync services let you " "synchronize your data between your netbook and a web service. You can also " "sync directly with some devices." msgstr "" "Du har inte valt synktjänst eller -enhet än. Med synktjänster kan du synka " "dina data mellan din netbook och en webbtjänst. Du kan också synka direkt " "med vissa enheter." #: ../src/gtk-ui/sync-ui.c:729 msgid "Sync again" msgstr "Synkronisera igen" #: ../src/gtk-ui/sync-ui.c:748 msgid "Restoring" msgstr "Återställer" #: ../src/gtk-ui/sync-ui.c:750 msgid "Syncing" msgstr "Synkroniserar " #. TRANSLATORS: This is for the button in main view, right side. #. Keep line length below ~20 characters, use two lines if needed #: ../src/gtk-ui/sync-ui.c:762 ../src/gtk-ui/sync-ui.c:3407 msgid "Cancel sync" msgstr "Avbryt synkronisering" #: ../src/gtk-ui/sync-ui.c:927 msgid "Back to sync" msgstr "Tillbaka till synkronisering" #. TRANSLATORS: label for checkbutton/toggle in main view. #. * Please stick to similar length strings or break the line with #. * "\n" if absolutely needed #: ../src/gtk-ui/sync-ui.c:1229 msgid "Automatic sync" msgstr "Automatisk synkronisering" #. This is the expander label in emergency view. It summarizes the #. * currently selected data sources. First placeholder is service/device #. * name, second a comma separeted list of sources. #. * E.g. "Affected data: Google Contacts, Appointments" #: ../src/gtk-ui/sync-ui.c:1524 #, c-format msgid "Affected data: %s %s" msgstr "Data som påverkas: %s %s" #: ../src/gtk-ui/sync-ui.c:1529 #, c-format msgid "Affected data: none" msgstr "Data som påverkas: inga" #. TRANSLATORS: confirmation for restoring a backup. placeholder is the #. * backup time string defined below #: ../src/gtk-ui/sync-ui.c:1607 #, c-format msgid "" "Do you want to restore the backup from %s? All changes you have made since " "then will be lost." msgstr "" "Vill du återställa säkerhetskopian från %s? Då förlorar du alla ändringar du" " har gjort sedan dess." #: ../src/gtk-ui/sync-ui.c:1610 msgid "Yes, restore" msgstr "Ja, återställ" #. TRANSLATORS: date/time for strftime(), used in emergency view backup #. * label. Any time format that shows date and time is good. #: ../src/gtk-ui/sync-ui.c:1642 #, c-format msgid "%x %X" msgstr "%x %X" #. TRANSLATORS: label for a backup in emergency view. Placeholder is #. * service or device name #: ../src/gtk-ui/sync-ui.c:1661 #, c-format msgid "Backed up before syncing with %s" msgstr "Säkerhetskopierades före synkronisering med %s" #: ../src/gtk-ui/sync-ui.c:1678 msgid "Restore" msgstr "Återställ" #. TRANSLATORS: this is an explanation in Emergency view. #. * Placeholder is a service/device name #: ../src/gtk-ui/sync-ui.c:1785 #, c-format msgid "" "A normal sync with %s is not possible at this time. You can do a slow two-" "way sync or start from scratch. You can also restore a backup, but a slow " "sync or starting from scratch will still be required before normal sync is " "possible." msgstr "" "En normal synkronisering med %s är inte möjlig just nu. Du kan göra en " "långsam tvåvägssynk eller börja om från början. Du kan också återställa en " "säkerhetskopia, men en långsam synk eller att börja om från början krävs " "ändå innan normal synk blir möjlig." #: ../src/gtk-ui/sync-ui.c:1795 #, c-format msgid "" "If something has gone horribly wrong, you can try a slow sync, start from " "scratch or restore from backup." msgstr "" "Om något har gått verkligt fel kan du prova en långsam synk, börja från " "början eller återställa från säkerhetskopian." #. TRANSLATORS: These are a buttons in Emergency view. Placeholder is a #. * service/device name. Please don't use too long lines, but feel free to #. * use several lines. #: ../src/gtk-ui/sync-ui.c:1804 #, c-format msgid "" "Delete all your local\n" "data and replace with\n" "data from %s" msgstr "" "Radera alla lokala\n" "data och ersätt med\n" "data från %s" #: ../src/gtk-ui/sync-ui.c:1810 #, c-format msgid "" "Delete all data on\n" "%s and replace\n" "with your local data" msgstr "" "Radera alla data på\n" "%s och ersätt med\n" "dina lokala data" #: ../src/gtk-ui/sync-ui.c:2275 msgid "Failed to get list of supported services from SyncEvolution" msgstr "Kunde inte få en lista med tjänster som stöds från SyncEvolution" #: ../src/gtk-ui/sync-ui.c:2329 msgid "" "There was a problem communicating with the sync process. Please try again " "later." msgstr "" "Det uppstod problem vid kommunikation med synkprocessen. Försök igen senare." #: ../src/gtk-ui/sync-ui.c:2388 msgid "Restore failed" msgstr "Återställning misslyckades" #: ../src/gtk-ui/sync-ui.c:2391 ../src/gtk-ui/sync-ui.c:3276 msgid "Sync failed" msgstr "Synk misslyckades" #: ../src/gtk-ui/sync-ui.c:2397 msgid "Restore complete" msgstr "Återställning färdig" #: ../src/gtk-ui/sync-ui.c:2400 msgid "Sync complete" msgstr "Synk färdig" #: ../src/gtk-ui/sync-ui.c:2492 #, c-format msgid "Preparing '%s'" msgstr "Förbereder '%s'" #: ../src/gtk-ui/sync-ui.c:2495 #, c-format msgid "Receiving '%s'" msgstr "Tas emot från '%s'" #: ../src/gtk-ui/sync-ui.c:2498 #, c-format msgid "Sending '%s'" msgstr "Skickas till '%s'" #: ../src/gtk-ui/sync-ui.c:2619 #, c-format msgid "There was one remote rejection." msgid_plural "There were %ld remote rejections." msgstr[0] "Det finns en fjärr-refusering." msgstr[1] "Det finns %ld fjärr-refuseringar." #: ../src/gtk-ui/sync-ui.c:2624 #, c-format msgid "There was one local rejection." msgid_plural "There were %ld local rejections." msgstr[0] "Det finns en lokal refusering." msgstr[1] "Det finns %ld lokala refuseringar." #: ../src/gtk-ui/sync-ui.c:2629 #, c-format msgid "There were %ld local rejections and %ld remote rejections." msgstr "Det fanns %ld lokala refuseringar och %ld fjärr-refuseringar." #: ../src/gtk-ui/sync-ui.c:2634 #, c-format msgid "Last time: No changes." msgstr "Senast: Inga ändringar." #: ../src/gtk-ui/sync-ui.c:2636 #, c-format msgid "Last time: Sent one change." msgid_plural "Last time: Sent %ld changes." msgstr[0] "Senast: Skickade en ändring." msgstr[1] "Senast: Skickade %ld ändringar." #. This is about changes made to the local data. Not all of these #. changes were requested by the remote server, so "applied" #. is a better word than "received" (bug #5185). #: ../src/gtk-ui/sync-ui.c:2644 #, c-format msgid "Last time: Applied one change." msgid_plural "Last time: Applied %ld changes." msgstr[0] "Senast: Utförde en ändring." msgstr[1] "Senast: Utförde %ld ändringar." #: ../src/gtk-ui/sync-ui.c:2649 #, c-format msgid "Last time: Applied %ld changes and sent %ld changes." msgstr "Senast: Utförde %ld ändringar och skickade %ld ändringar." #. TRANSLATORS: the placeholder is a error message (hopefully) #. * explaining the problem #: ../src/gtk-ui/sync-ui.c:2856 #, c-format msgid "" "There was a problem with last sync:\n" "%s" msgstr "" "Problem med senaste synkronisering:\n" "%s" #: ../src/gtk-ui/sync-ui.c:2866 #, c-format msgid "" "You've just restored a backup. The changes have not been synced with %s yet" msgstr "" "Du har just återställt en säkerhetskopia. Ändringarna har ännu inte " "synkroniserats med %s" #: ../src/gtk-ui/sync-ui.c:3154 msgid "Waiting for current operation to finish..." msgstr "Väntar på att nuvarande åtgärd ska bli klar..." #. TRANSLATORS: next strings are error messages. #: ../src/gtk-ui/sync-ui.c:3188 msgid "" "A normal sync is not possible at this time. The server suggests a slow sync," " but this might not always be what you want if both ends already have data." msgstr "" "En normal synk är inte möjlig just nu. Servern föreslår en långsam synk men " "detta kanke inte alltid är vad du vill ha om båda ändar redan har data." #: ../src/gtk-ui/sync-ui.c:3192 msgid "The sync process died unexpectedly." msgstr "Synkprocessen avslutades oväntat." #: ../src/gtk-ui/sync-ui.c:3197 msgid "" "Password request was not answered. You can save the password in the settings" " to prevent the request." msgstr "" "Lösenordsförfrågan besvarades inte. Förhindra förfrågan genom att spara " "lösenordet i inställningarna." #. TODO use the service device name here, this is a remote problem #: ../src/gtk-ui/sync-ui.c:3201 msgid "There was a problem processing sync request. Trying again may help." msgstr "" "Problem med behandling av synkbegäran. Det kan vara en bra idé att försöka " "igen." #: ../src/gtk-ui/sync-ui.c:3207 msgid "" "Failed to login. Could there be a problem with your username or password?" msgstr "" "Kunde inte logg in. Kan det finnas problem med ditt användarnamn eller " "lösenord?" #: ../src/gtk-ui/sync-ui.c:3210 msgid "Forbidden" msgstr "Förbjuden" #. TRANSLATORS: data source means e.g. calendar or addressbook #: ../src/gtk-ui/sync-ui.c:3216 msgid "" "A data source could not be found. Could there be a problem with the " "settings?" msgstr "Hittade inte en datakälla. Kan det finnas problem i inställningarna?" #: ../src/gtk-ui/sync-ui.c:3220 msgid "Remote database error" msgstr "Fjärrdatabasfel" #. This can happen when EDS is borked, restart it may help... #: ../src/gtk-ui/sync-ui.c:3223 msgid "" "There is a problem with the local database. Syncing again or rebooting may " "help." msgstr "" "Det finns problem i den lokala databasen. Det kan vara en bra idé att " "synkronisera eller starta upp igen." #: ../src/gtk-ui/sync-ui.c:3226 msgid "No space on disk" msgstr "Inget diskutrymme" #: ../src/gtk-ui/sync-ui.c:3228 msgid "Failed to process SyncML" msgstr "Kunde inte hantera SyncML" #: ../src/gtk-ui/sync-ui.c:3230 msgid "Server authorization failed" msgstr "Kunde inte ansluta till server" #: ../src/gtk-ui/sync-ui.c:3232 msgid "Failed to parse configuration file" msgstr "Kunde inte analysera konfigurationsfilen " #: ../src/gtk-ui/sync-ui.c:3234 msgid "Failed to read configuration file" msgstr "Kunde inte öppna konfigurationsfilen" #: ../src/gtk-ui/sync-ui.c:3236 msgid "No configuration found" msgstr "Konfiurationen hittades inte" #: ../src/gtk-ui/sync-ui.c:3238 msgid "No configuration file found" msgstr "Konfigurationsfilen hittades inte" #: ../src/gtk-ui/sync-ui.c:3240 msgid "Server sent bad content" msgstr "Server skickade felaktigt innehåll" #: ../src/gtk-ui/sync-ui.c:3242 msgid "Connection certificate has expired" msgstr "Utfärdat certifikat har gått ut" #: ../src/gtk-ui/sync-ui.c:3244 msgid "Connection certificate is invalid" msgstr "Anslutningscertifikatet är ogiltig." #: ../src/gtk-ui/sync-ui.c:3252 msgid "" "We were unable to connect to the server. The problem could be temporary or " "there could be something wrong with the settings." msgstr "" "Vi kunde inte ansluta till servern. Problemet kan vara temporärt eller så " "kan det finnas fel i inställningarna." #: ../src/gtk-ui/sync-ui.c:3259 msgid "The server URL is bad" msgstr "Ogiltig server-URL" #: ../src/gtk-ui/sync-ui.c:3264 msgid "The server was not found" msgstr "Hittade inte servern" #: ../src/gtk-ui/sync-ui.c:3266 #, c-format msgid "Error %d" msgstr "Fel %d" #. TRANSLATORS: password request dialog contents: title, cancel button #. * and ok button #: ../src/gtk-ui/sync-ui.c:3404 msgid "Password is required for sync" msgstr "Lösenord krävs för synkronisering" #: ../src/gtk-ui/sync-ui.c:3408 msgid "Sync with password" msgstr "Synkronisera med lösenord" #. TRANSLATORS: password request dialog message, placeholder is service name #: ../src/gtk-ui/sync-ui.c:3418 #, c-format msgid "Please enter password for syncing with %s:" msgstr "Ange lösenord för synkronisering med %s:" #. title for the buttons on the right side of main view #: ../src/gtk-ui/ui.glade.h:2 msgid "Actions" msgstr "Åtgärder" #. text between the two "start from scratch" buttons in emergency view #: ../src/gtk-ui/ui.glade.h:4 msgid "or" msgstr "eller" #: ../src/gtk-ui/ui.glade.h:5 msgid "Direct sync" msgstr "Direktsynk" #: ../src/gtk-ui/ui.glade.h:6 msgid "Network sync" msgstr "Nätverkssynk" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:8 msgid "Restore from backup" msgstr "Återställ från säkerhetskopia" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:10 msgid "Slow sync" msgstr "Långsam synk" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:12 msgid "Start from scratch" msgstr "Börja från början" #: ../src/gtk-ui/ui.glade.h:13 msgid "" "A slow sync compares items from both sides and tries to merge them. \n" "This may fail in some cases, leading to duplicates or lost information." msgstr "" "Långsam synkronisering jämför objekt från båda sidor och försöker kombinera dem. \n" "Detta kan misslyckas i vissa fall och leda till duplikat eller förlorad information." #: ../src/gtk-ui/ui.glade.h:15 msgid "Add new device" msgstr "Lägg till ny enhet" #: ../src/gtk-ui/ui.glade.h:16 msgid "Add new service" msgstr "Lägg till ny tjänst" #. explanation of "Restore backup" function #: ../src/gtk-ui/ui.glade.h:18 msgid "" "Backups are made before every time we Sync. Choose a backup to restore. Any " "changes you have made since then will be lost." msgstr "" "Säkerhetskopieringar görs före varje synkronisering. Välj en säkerhetskopia " "att återställa. Alla ändringar som du gjort sedan dess förloras." #: ../src/gtk-ui/ui.glade.h:19 msgid "Calendar" msgstr "Kalender" #. Button in main view, right side. Keep to below 20 chars per line, feel free #. to use two lines #: ../src/gtk-ui/ui.glade.h:21 msgid "" "Change or edit\n" "sync service" msgstr "" "Ändra eller redigera\n" "synktjänst" #. close button for settings window #: ../src/gtk-ui/ui.glade.h:24 msgid "Close" msgstr "Stäng" #: ../src/gtk-ui/ui.glade.h:25 msgid "" "Delete all data on Zyb \n" "and replace with your\n" "local information" msgstr "" "Radera alla data på Zyb \n" "och ersätt med din\n" "lokala information" #: ../src/gtk-ui/ui.glade.h:28 msgid "" "Delete all your local\n" "information and replace\n" "with data from Zyb" msgstr "" "Radera all lokal\n" "information och ersätt\n" "med data från Zyb" #. button in main view, right side. Keep length to 20 characters or so, use #. two lines if needed #: ../src/gtk-ui/ui.glade.h:32 msgid "" "Fix a sync\n" "emergency" msgstr "" "Fixa ett akut\n" "synkproblem" #: ../src/gtk-ui/ui.glade.h:34 msgid "" "If you don't see your service above but know that your sync provider uses SyncML\n" "you can setup a service manually." msgstr "" "Om du inte ser din tjänst ovan men vet att ditt synkföretag använder SyncML\n" "kan du manuellt göra inställningarna." #: ../src/gtk-ui/ui.glade.h:36 msgid "Settings" msgstr "Inställningar" #: ../src/gtk-ui/ui.glade.h:39 msgid "Sync Emergency" msgstr "Akut synkproblem" #: ../src/gtk-ui/ui.glade.h:41 msgid "" "To sync you'll need a network connection and an account with a sync service.\n" "We support the following services: " msgstr "" "Innan du kan synkronisera behöver du nätverksanslutning och ett konto med\n" "synktjänst. Vi stöder följande tjänster:" #: ../src/gtk-ui/ui.glade.h:43 msgid "Use Bluetooth to Sync your data from one device to another." msgstr "" "Använd Bluetooth för att synkroisera dina data från en enhet till en annan." #: ../src/gtk-ui/ui.glade.h:44 msgid "You will need to add Bluetooth devices before they can be synced." msgstr "Du måste lägga till Bluetooth-enheter innan du kan synkronisera dem." #: ../src/gtk-ui/sync.desktop.in.h:2 msgid "Up to date" msgstr "Uppdaterad" #: ../src/gtk-ui/sync-gtk.desktop.in.h:1 msgid "SyncEvolution (GTK)" msgstr "SyncEvolution (GTK)" #: ../src/gtk-ui/sync-gtk.desktop.in.h:2 msgid "Synchronize PIM data" msgstr "Synkronisera PIM-data" #: ../src/gtk-ui/sync-config-widget.c:88 msgid "" "ScheduleWorld enables you to keep your contacts, events, tasks, and notes in" " sync." msgstr "" "Med ScheduleWorld kan du hålla kontakter, aktiviteter, uppgifter och " "meddelanden uppdaterade." #: ../src/gtk-ui/sync-config-widget.c:91 msgid "" "Google Sync can back up and synchronize your contacts with your Gmail " "contacts." msgstr "" "Google Sync kan säkerhetskopiera och synka dina kontakter med dina Gmail-" "kontakter." #. TRANSLATORS: Please include the word "demo" (or the equivalent in #. your language): Funambol is going to be a 90 day demo service #. in the future #: ../src/gtk-ui/sync-config-widget.c:97 msgid "" "Back up your contacts and calendar. Sync with a single click, anytime, " "anywhere (DEMO)." msgstr "" "Säkerhetskopiera kontakter och kalender. Synka med ett enkelklick, när som " "helst, var som helst (DEMO)." #: ../src/gtk-ui/sync-config-widget.c:100 msgid "" "Mobical Backup and Restore service allows you to securely back up your " "personal mobile data for free." msgstr "" "Med Mobical-tjänsten för säkerhetskopiering och återställning kan du säkert " "säkerhetskopia dina personliga mobila data gratis." #: ../src/gtk-ui/sync-config-widget.c:103 msgid "" "ZYB is a simple way for people to store and share mobile information online." msgstr "" "ZYB är ett enkelt sätt för människor att lagra och dela mobilinformation " "online." #: ../src/gtk-ui/sync-config-widget.c:106 msgid "" "Memotoo lets you access your personal data from any computer connected to " "the Internet." msgstr "" "Med Memotoo kan du nå dina personliga data från valfri dator ansluten till " "Internet." #: ../src/gtk-ui/sync-config-widget.c:195 msgid "Sorry, failed to save the configuration" msgstr "Kunde inte spara konfigurationen " #: ../src/gtk-ui/sync-config-widget.c:445 msgid "Service must have a name and server URL" msgstr "Du måste ange ett namn på tjänsten och URL till servern" #. TRANSLATORS: error dialog when creating a new sync configuration #: ../src/gtk-ui/sync-config-widget.c:451 msgid "A username is required for this service" msgstr "Det behövs ett användarnamn för denna tjänst" #: ../src/gtk-ui/sync-config-widget.c:493 #, c-format msgid "" "Do you want to reset the settings for %s? This will not remove any synced " "information on either end." msgstr "" "Vill du återställa inställningarna för %s? Detta tar inte bort någon " "synkroniserad information på någon ände." #. TRANSLATORS: buttons in reset-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:497 msgid "Yes, reset" msgstr "Ja, återställ" #: ../src/gtk-ui/sync-config-widget.c:498 #: ../src/gtk-ui/sync-config-widget.c:509 msgid "No, keep settings" msgstr "Nej, behåll inställningar" #: ../src/gtk-ui/sync-config-widget.c:503 #, c-format msgid "" "Do you want to delete the settings for %s? This will not remove any synced " "information on either end but it will remove these settings." msgstr "" "Vill du ta bort inställningarna för %s? Detta tar inte bort någon " "synkroniserad information på någon ände men det tar bort dessa " "inställningar." #. TRANSLATORS: buttons in delete-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:508 msgid "Yes, delete" msgstr "Ja, radera" #: ../src/gtk-ui/sync-config-widget.c:539 msgid "Reset settings" msgstr "Återställ inställningar" #: ../src/gtk-ui/sync-config-widget.c:542 msgid "Delete settings" msgstr "Radera inställningar" #: ../src/gtk-ui/sync-config-widget.c:552 msgid "Save and use" msgstr "Spara och använd" #: ../src/gtk-ui/sync-config-widget.c:555 msgid "" "Save and replace\n" "current service" msgstr "" "Spara och ersätt\n" "nuvarande tjänst" #: ../src/gtk-ui/sync-config-widget.c:563 msgid "Stop using device" msgstr "Sluta använda enhet" #: ../src/gtk-ui/sync-config-widget.c:566 msgid "Stop using service" msgstr "Sluta använda tjänst" #. TRANSLATORS: label for an entry in service configuration form. #. * Placeholder is a source name. #. * Example: "Appointments URI" #: ../src/gtk-ui/sync-config-widget.c:749 #, c-format msgid "%s URI" msgstr "%s URI" #. TRANSLATORS: toggles in service configuration form, placeholder is service #. * or device name #: ../src/gtk-ui/sync-config-widget.c:936 #, c-format msgid "Send changes to %s" msgstr "Skicka ändringar till %s" #: ../src/gtk-ui/sync-config-widget.c:941 #, c-format msgid "Receive changes from %s" msgstr "Ta emot ändringar från %s" #: ../src/gtk-ui/sync-config-widget.c:957 msgid "Sync" msgstr "Synkronisera" #. TRANSLATORS: label of a entry in service configuration #: ../src/gtk-ui/sync-config-widget.c:973 msgid "Server address" msgstr "Serveradress" #. TRANSLATORS: explanation before a device template combobox. #. * Placeholder is a device name like 'Nokia N85' or 'Syncevolution #. * Client' #: ../src/gtk-ui/sync-config-widget.c:1049 #, c-format msgid "" "This device looks like it might be a '%s'. If this is not correct, please " "take a look at the list of supported devices and pick yours if it is listed" msgstr "" "Den här enheten ser ut som om den kan vara en %s. Om detta inte är korrekt, " "ta en titt på listan med enheter som stöds och välj din om den finns med på " "listan" #: ../src/gtk-ui/sync-config-widget.c:1055 #: ../src/gtk-ui/sync-config-widget.c:1915 msgid "" "We don't know what this device is exactly. Please take a look at the list of" " supported devices and pick yours if it is listed" msgstr "" "Vi kan inte identifiera den här enheten. Ta en titt på listan med enheter " "som stöds och välj din om den finns med på listan" #: ../src/gtk-ui/sync-config-widget.c:1207 #, c-format msgid "%s - Bluetooth device" msgstr "%s - Bluetooth-enhet" #. TRANSLATORS: service title for services that are not based on a #. * template in service list, the placeholder is the name of the service #: ../src/gtk-ui/sync-config-widget.c:1213 #, c-format msgid "%s - manually setup" msgstr "%s - konfigurera manuellt" #. TRANSLATORS: link button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1886 msgid "Launch website" msgstr "Öppna webbplatsen " #. TRANSLATORS: button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1895 msgid "Set up now" msgstr "Konfigurera nu" #: ../src/gtk-ui/sync-config-widget.c:1953 msgid "Use these settings" msgstr "Använd dessa inställningar" #. TRANSLATORS: labels of entries in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1991 msgid "Username" msgstr "Användarnamn" #: ../src/gtk-ui/sync-config-widget.c:2006 msgid "Password" msgstr "Lösenord" #. TRANSLATORS: warning in service configuration form for people #. who have modified the configuration via other means. #: ../src/gtk-ui/sync-config-widget.c:2029 msgid "" "Current configuration is more complex than what can be shown here. Changes " "to sync mode or synced data types will overwrite that configuration." msgstr "" "Nuvarande konfiguration är mer komplex än vad som kan visas här. Ändringar i" " synkläge eller synkroniserade datatyper skriver över den konfigurationen." #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2048 msgid "Hide server settings" msgstr "Dölj serverinställningar" #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2068 msgid "Show server settings" msgstr "Visa serverinställningar" #: ../src/gnome-bluetooth/syncevolution.c:110 msgid "Sync in the Sync application" msgstr "Synkronisera i programmet Sync" #: ../src/syncevo-dbus-server.cpp:6653 #, c-format msgid "%s is syncing" msgstr "%s synkroniserar " #: ../src/syncevo-dbus-server.cpp:6654 #, c-format msgid "We have just started to sync your computer with the %s sync service." msgstr "Vi har nyss börjat synkronisera din dator med synktjänsten %s." #. if sync is successfully started and done #: ../src/syncevo-dbus-server.cpp:6670 #, c-format msgid "%s sync complete" msgstr "%s synk färdig" #: ../src/syncevo-dbus-server.cpp:6671 #, c-format msgid "We have just finished syncing your computer with the %s sync service." msgstr "Vi har precis synkroniserat din dator med synktjänsten %s." #. if sync is successfully started and has errors, or not started successful #. with a fatal problem #: ../src/syncevo-dbus-server.cpp:6676 msgid "Sync problem." msgstr "Synkproblem." #: ../src/syncevo-dbus-server.cpp:6677 msgid "Sorry, there's a problem with your sync that you need to attend to." msgstr "" "Det finns ett problem i din synkronisering som du måste ta en titt på." #: ../src/syncevo-dbus-server.cpp:6758 msgid "View" msgstr "Visa" #. Use "default" as ID because that is what mutter-moblin #. recognizes: it then skips the action instead of adding it #. in addition to its own "Dismiss" button (always added). #: ../src/syncevo-dbus-server.cpp:6762 msgid "Dismiss" msgstr "Ignorera" syncevolution_1.4/po/th.po000066400000000000000000000257061230021373600157610ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: syncevolution\n" "Report-Msgid-Bugs-To: http://moblin.org/projects/syncevolution\n" "POT-Creation-Date: 2009-12-21 18:45+0000\n" "PO-Revision-Date: 2010-01-19 22:07+0700\n" "Last-Translator: Anuchit Chalothorn \n" "Language-Team: Thai \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n!=1;\n" "X-Poedit-Language: Thai\n" "X-Poedit-Country: THAILAND\n" "X-Poedit-SourceCharset: utf-8\n" #. TRANSLATORS: this is the application name that may be used by e.g. #. the windowmanager #: ../src/gtk-ui/main.c:31 #: ../src/gtk-ui/ui.glade.h:20 #: ../src/gtk-ui/sync.desktop.in.h:1 msgid "Sync" msgstr "" #. TRANSLATORS: The name was changed from 'Addressbook' to #. 'Contacts' to match naming in rest of moblin. Please make sure the #. name you use matches the name in the panel and Contacts application. #: ../src/gtk-ui/sync-ui.c:192 msgid "Contacts" msgstr "" #: ../src/gtk-ui/sync-ui.c:194 msgid "Calendar" msgstr "" #: ../src/gtk-ui/sync-ui.c:196 msgid "Todo" msgstr "" #: ../src/gtk-ui/sync-ui.c:198 msgid "Memo" msgstr "" #. TODO show in UI: failed to abort sync (while syncing) #: ../src/gtk-ui/sync-ui.c:273 msgid "Failed to abort sync" msgstr "" #. TODO show in UI: sync failed (failed to even start) #: ../src/gtk-ui/sync-ui.c:287 msgid "Failed to start sync" msgstr "" #: ../src/gtk-ui/sync-ui.c:293 msgid "Starting sync" msgstr "" #: ../src/gtk-ui/sync-ui.c:309 msgid "Trying to cancel sync" msgstr "" #: ../src/gtk-ui/sync-ui.c:316 #, c-format msgid "Do you want to delete all local data and replace it with data from %s? This is not usually advised." msgstr "" #: ../src/gtk-ui/sync-ui.c:321 #, c-format msgid "Do you want to delete all data in %s and replace it with your local data? This is not usually advised." msgstr "" #: ../src/gtk-ui/sync-ui.c:338 msgid "No, cancel sync" msgstr "" #: ../src/gtk-ui/sync-ui.c:340 msgid "Yes, delete and replace" msgstr "" #: ../src/gtk-ui/sync-ui.c:377 msgid "Last synced just seconds ago" msgstr "" #: ../src/gtk-ui/sync-ui.c:380 msgid "Last synced a minute ago" msgstr "" #: ../src/gtk-ui/sync-ui.c:383 #, c-format msgid "Last synced %ld minutes ago" msgstr "" #: ../src/gtk-ui/sync-ui.c:386 msgid "Last synced an hour ago" msgstr "" #: ../src/gtk-ui/sync-ui.c:389 #, c-format msgid "Last synced %ld hours ago" msgstr "" #: ../src/gtk-ui/sync-ui.c:392 msgid "Last synced a day ago" msgstr "" #: ../src/gtk-ui/sync-ui.c:395 #, c-format msgid "Last synced %ld days ago" msgstr "" #: ../src/gtk-ui/sync-ui.c:480 msgid "Sync again" msgstr "" #: ../src/gtk-ui/sync-ui.c:482 #: ../src/gtk-ui/ui.glade.h:21 msgid "Sync now" msgstr "" #: ../src/gtk-ui/sync-ui.c:493 msgid "Syncing" msgstr "" #: ../src/gtk-ui/sync-ui.c:499 msgid "Cancel sync" msgstr "" #. TRANSLATORS: placeholder is a source name, shown with checkboxes in main window #: ../src/gtk-ui/sync-ui.c:849 #, c-format msgid "%s (not supported by this service)" msgstr "" #: ../src/gtk-ui/sync-ui.c:1046 msgid "Failed to get list of configured services from SyncEvolution" msgstr "" #: ../src/gtk-ui/sync-ui.c:1117 msgid "Failed to get list of supported services from SyncEvolution" msgstr "" #: ../src/gtk-ui/sync-ui.c:1221 msgid "Sync complete" msgstr "" #: ../src/gtk-ui/sync-ui.c:1310 #, c-format msgid "Preparing '%s'" msgstr "" #: ../src/gtk-ui/sync-ui.c:1313 #, c-format msgid "Receiving '%s'" msgstr "" #: ../src/gtk-ui/sync-ui.c:1316 #, c-format msgid "Sending '%s'" msgstr "" #: ../src/gtk-ui/sync-ui.c:1705 msgid "Waiting for current operation to finish..." msgstr "" #: ../src/gtk-ui/sync-ui.c:1750 msgid "Not authorized" msgstr "" #: ../src/gtk-ui/sync-ui.c:1752 msgid "Forbidden" msgstr "" #: ../src/gtk-ui/sync-ui.c:1754 msgid "Not found" msgstr "" #: ../src/gtk-ui/sync-ui.c:1756 msgid "Fatal database error" msgstr "" #: ../src/gtk-ui/sync-ui.c:1758 msgid "Database error" msgstr "" #: ../src/gtk-ui/sync-ui.c:1760 msgid "No space left" msgstr "" #. TODO identify problem item somehow ? #: ../src/gtk-ui/sync-ui.c:1763 msgid "Failed to process SyncML" msgstr "" #: ../src/gtk-ui/sync-ui.c:1765 msgid "Server authorization failed" msgstr "" #: ../src/gtk-ui/sync-ui.c:1767 msgid "Failed to parse configuration file" msgstr "" #: ../src/gtk-ui/sync-ui.c:1769 msgid "Failed to read configuration file" msgstr "" #: ../src/gtk-ui/sync-ui.c:1771 msgid "No configuration found" msgstr "" #: ../src/gtk-ui/sync-ui.c:1773 msgid "No configuration file found" msgstr "" #: ../src/gtk-ui/sync-ui.c:1775 msgid "Server sent bad content" msgstr "" #: ../src/gtk-ui/sync-ui.c:1777 msgid "Transport failure (no connection?)" msgstr "" #: ../src/gtk-ui/sync-ui.c:1779 msgid "Connection timed out" msgstr "" #: ../src/gtk-ui/sync-ui.c:1781 msgid "Connection certificate has expired" msgstr "" #: ../src/gtk-ui/sync-ui.c:1783 msgid "Connection certificate is invalid" msgstr "" #: ../src/gtk-ui/sync-ui.c:1786 msgid "Connection failed" msgstr "" #: ../src/gtk-ui/sync-ui.c:1788 msgid "URL is bad" msgstr "" #: ../src/gtk-ui/sync-ui.c:1790 msgid "Server not found" msgstr "" #: ../src/gtk-ui/sync-ui.c:1792 #, c-format msgid "Error %d" msgstr "" #. TODO show in UI: server disappeared #: ../src/gtk-ui/sync-ui.c:1803 msgid "Syncevolution.Server D-Bus service exited unexpectedly" msgstr "" #: ../src/gtk-ui/sync-ui.c:1806 msgid "Sync Failed" msgstr "" #: ../src/gtk-ui/sync-ui-config.c:95 #, c-format msgid "There was one remote rejection." msgid_plural "There were %d remote rejections." msgstr[0] "" msgstr[1] "" #: ../src/gtk-ui/sync-ui-config.c:100 #, c-format msgid "There was one local rejection." msgid_plural "There were %d local rejections." msgstr[0] "" msgstr[1] "" #: ../src/gtk-ui/sync-ui-config.c:105 #, c-format msgid "There were %d local rejections and %d remote rejections." msgstr "" #: ../src/gtk-ui/sync-ui-config.c:110 #, c-format msgid "Last time: No changes." msgstr "" #: ../src/gtk-ui/sync-ui-config.c:112 #, c-format msgid "Last time: Sent one change." msgid_plural "Last time: Sent %d changes." msgstr[0] "" msgstr[1] "" #. This is about changes made to the local data. Not all of these #. changes were requested by the remote server, so "applied" #. is a better word than "received" (bug #5185). #: ../src/gtk-ui/sync-ui-config.c:120 #, c-format msgid "Last time: Applied one change." msgid_plural "Last time: Applied %d changes." msgstr[0] "" msgstr[1] "" #: ../src/gtk-ui/sync-ui-config.c:125 #, c-format msgid "Last time: Applied %d changes and sent %d changes." msgstr "" #: ../src/gtk-ui/ui.glade.h:1 msgid "Data" msgstr "" #: ../src/gtk-ui/ui.glade.h:2 msgid "No sync service in use" msgstr "" #: ../src/gtk-ui/ui.glade.h:3 msgid "Sync failure" msgstr "" #: ../src/gtk-ui/ui.glade.h:4 msgid "Type of Sync" msgstr "" #: ../src/gtk-ui/ui.glade.h:5 msgid "Manual setup" msgstr "" #: ../src/gtk-ui/ui.glade.h:6 msgid "Supported services" msgstr "" #: ../src/gtk-ui/ui.glade.h:7 msgid "Add new service" msgstr "" #: ../src/gtk-ui/ui.glade.h:8 msgid "Back to sync" msgstr "" #: ../src/gtk-ui/ui.glade.h:9 msgid "" "Change sync\n" "service" msgstr "" #: ../src/gtk-ui/ui.glade.h:11 msgid "Delete all local data and replace it with remote data" msgstr "" #: ../src/gtk-ui/ui.glade.h:12 msgid "Delete all remote data and replace it with local data" msgstr "" #: ../src/gtk-ui/ui.glade.h:13 msgid "Edit service settings" msgstr "" #: ../src/gtk-ui/ui.glade.h:14 msgid "" "If you don't see your service above but know that your sync provider uses SyncML\n" "you can setup a service manually." msgstr "" #: ../src/gtk-ui/ui.glade.h:16 msgid "Merge local and remote data (recommended)" msgstr "" #: ../src/gtk-ui/ui.glade.h:17 msgid "Select sync service" msgstr "" #: ../src/gtk-ui/ui.glade.h:18 msgid "" "Sorry, you need an internet\n" "connection to sync." msgstr "" #: ../src/gtk-ui/ui.glade.h:22 msgid "Sync settings" msgstr "" #: ../src/gtk-ui/ui.glade.h:23 msgid "Synchronization is not available (D-Bus service does not answer), sorry." msgstr "" #: ../src/gtk-ui/ui.glade.h:24 msgid "" "To sync you'll need a network connection and an account with a sync service.\n" "We support the following services: " msgstr "" #: ../src/gtk-ui/ui.glade.h:26 msgid "" "You haven't selected a sync service yet. Sync services let you \n" "synchronize your data between your netbook and a web service." msgstr "" #: ../src/gtk-ui/sync.desktop.in.h:2 #: ../src/gtk-ui/sync-gtk.desktop.in.h:2 msgid "Up to date" msgstr "" #: ../src/gtk-ui/sync-gtk.desktop.in.h:1 msgid "Sync (GTK)" msgstr "" #: ../src/gtk-ui/sync-config-widget.c:48 msgid "ScheduleWorld enables you to keep your contacts, events, tasks, and notes in sync." msgstr "" #: ../src/gtk-ui/sync-config-widget.c:51 msgid "Google Sync can back up and synchronize your Address Book with your Gmail contacts." msgstr "" #. TRANSLATORS: Please include the word "demo" (or the equivalent in #. your language): Funambol is going to be a 90 day demo service #. in the future #: ../src/gtk-ui/sync-config-widget.c:57 msgid "Back up your contacts and calendar. Sync with a singleclick, anytime, anywhere (DEMO)." msgstr "" #: ../src/gtk-ui/sync-config-widget.c:227 msgid "Service must have a name and server URL" msgstr "" #: ../src/gtk-ui/sync-config-widget.c:271 msgid "Reset service" msgstr "" #: ../src/gtk-ui/sync-config-widget.c:274 msgid "Delete service" msgstr "" #: ../src/gtk-ui/sync-config-widget.c:284 msgid "Save and use" msgstr "" #: ../src/gtk-ui/sync-config-widget.c:287 msgid "" "Save and replace\n" "current service" msgstr "" #. TRANSLATORS: label for an entry in service configuration. #. * Placeholder is a source name in settings window. #. * Example: "Addressbook URI" #: ../src/gtk-ui/sync-config-widget.c:355 #, c-format msgid "%s URI" msgstr "" #. TRANSLATORS: label of a entry in service configuration #: ../src/gtk-ui/sync-config-widget.c:419 msgid "Server URL" msgstr "" #. TRANSLATORS: this is the epander label for server settings #: ../src/gtk-ui/sync-config-widget.c:459 msgid "Hide server settings" msgstr "" #: ../src/gtk-ui/sync-config-widget.c:461 msgid "Show server settings" msgstr "" #. TRANSLATORS: service title for services that are not based on a #. * template in service list, the placeholder is the name of the service #: ../src/gtk-ui/sync-config-widget.c:512 #, c-format msgid "%s - manually setup" msgstr "" #. TRANSLATORS: linkbutton label #: ../src/gtk-ui/sync-config-widget.c:1189 msgid "Launch website" msgstr "" #. TRANSLATORS: button label #: ../src/gtk-ui/sync-config-widget.c:1198 msgid "Set up now" msgstr "" #. TRANSLATORS: labels of entries #: ../src/gtk-ui/sync-config-widget.c:1236 msgid "Username" msgstr "" #: ../src/gtk-ui/sync-config-widget.c:1251 msgid "Password" msgstr "" #: ../src/gtk-ui/sync-config-widget.c:1295 msgid "Stop using service" msgstr "" syncevolution_1.4/po/tr.po000066400000000000000000000572171230021373600157750ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: http://moblin.org/projects/syncevolution\n" "POT-Creation-Date: 2010-05-19 09:19+0000\n" "PO-Revision-Date: 2010-05-19 10:57-0500\n" "Last-Translator: Ahmet Özgür Erdemli \n" "Language-Team: MeeGo Türkçe\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Poedit-Language: Turkish\n" "X-Poedit-Country: TURKEY\n" "X-Poedit-SourceCharset: utf-8\n" #. TRANSLATORS: this is the application name that may be used by e.g. #. the windowmanager #: ../src/gtk-ui/main.c:40 #: ../src/gtk-ui/ui.glade.h:38 #: ../src/gtk-ui/sync.desktop.in.h:1 #: ../src/gnome-bluetooth/syncevolution.c:112 msgid "Sync" msgstr "" #: ../src/gtk-ui/sync-ui.c:266 msgid "Contacts" msgstr "" #: ../src/gtk-ui/sync-ui.c:268 msgid "Appointments" msgstr "Randevular" #: ../src/gtk-ui/sync-ui.c:270 #: ../src/gtk-ui/ui.glade.h:40 #, fuzzy msgid "Tasks" msgstr "Sık Kullanılan Görevler" #: ../src/gtk-ui/sync-ui.c:272 msgid "Notes" msgstr "" #. TRANSLATORS: This is a "combination source" for syncing with devices #. * that combine appointments and tasks. the name should match the ones #. * used for calendar and todo above #: ../src/gtk-ui/sync-ui.c:277 #, fuzzy msgid "Appointments & Tasks" msgstr "Sık Kullanılan Görevler" #: ../src/gtk-ui/sync-ui.c:349 msgid "Starting sync" msgstr "Senkronizasyonu başlat" #. TRANSLATORS: slow sync confirmation dialog message. Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:387 #, c-format msgid "Do you want to slow sync with %s?" msgstr "" #: ../src/gtk-ui/sync-ui.c:391 msgid "Yes, do slow sync" msgstr "" #: ../src/gtk-ui/sync-ui.c:391 msgid "No, cancel sync" msgstr "Hayır, senkronizasyonu iptal et" #. TRANSLATORS: confirmation dialog for refresh-from-server. Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:424 #, c-format msgid "Do you want to delete all local data and replace it with data from %s? This is not usually advised." msgstr "" #: ../src/gtk-ui/sync-ui.c:429 #: ../src/gtk-ui/sync-ui.c:460 msgid "Yes, delete and replace" msgstr "" #: ../src/gtk-ui/sync-ui.c:429 #: ../src/gtk-ui/sync-ui.c:460 #: ../src/gtk-ui/sync-ui.c:1605 msgid "No" msgstr "Hayır" #. TRANSLATORS: confirmation dialog for refresh-from-client. Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:455 #, c-format msgid "Do you want to delete all data in %s and replace it with your local data? This is not usually advised." msgstr "" #: ../src/gtk-ui/sync-ui.c:487 msgid "Trying to cancel sync" msgstr "Senkronizasyon iptal edilmeye çalışılıyor" #: ../src/gtk-ui/sync-ui.c:529 msgid "No service or device selected" msgstr "" #. TRANSLATORS: This is the title on main view. Placeholder is #. * the service name. Example: "Google - synced just now" #: ../src/gtk-ui/sync-ui.c:537 #, c-format msgid "%s - synced just now" msgstr "%s - yeni senkronize edildi" #: ../src/gtk-ui/sync-ui.c:541 #, c-format msgid "%s - synced a minute ago" msgstr "%s - bir dakika önce senkronize edildi" #: ../src/gtk-ui/sync-ui.c:545 #, c-format msgid "%s - synced %ld minutes ago" msgstr "" #: ../src/gtk-ui/sync-ui.c:550 #, c-format msgid "%s - synced an hour ago" msgstr "%s - bir saat önce senkronize edildi" #: ../src/gtk-ui/sync-ui.c:554 #, c-format msgid "%s - synced %ld hours ago" msgstr "" #: ../src/gtk-ui/sync-ui.c:559 #, c-format msgid "%s - synced a day ago" msgstr "%s - bir gün önce senkronize edildi" #: ../src/gtk-ui/sync-ui.c:563 #, c-format msgid "%s - synced %ld days ago" msgstr "" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "You've just restored a backup. The changes have not been " #. * "synced with %s yet" #: ../src/gtk-ui/sync-ui.c:612 #: ../src/gtk-ui/sync-ui.c:726 msgid "Sync now" msgstr "Şimdi senkronize et" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "A normal sync is not possible at this time..." message. #. * "Other options" will open Emergency view #: ../src/gtk-ui/sync-ui.c:618 #: ../src/gtk-ui/ui.glade.h:37 msgid "Slow sync" msgstr "Yavaş senkronizasyon" #: ../src/gtk-ui/sync-ui.c:619 msgid "Other options..." msgstr "Diğer seçenekler..." #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * when no service is selected. Will open configuration view #: ../src/gtk-ui/sync-ui.c:624 msgid "Select sync service" msgstr "Senkronizasyon servisi seçin" #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * login to service fails. Will open configuration view for this service #: ../src/gtk-ui/sync-ui.c:629 msgid "Edit service settings" msgstr "Servis ayarlarını değiştirin" #: ../src/gtk-ui/sync-ui.c:700 msgid "You haven't selected a sync service or device yet. Sync services let you synchronize your data between your netbook and a web service. You can also sync directly with some devices." msgstr "" #: ../src/gtk-ui/sync-ui.c:722 msgid "Sync again" msgstr "Tekrar senkronize edin" #: ../src/gtk-ui/sync-ui.c:743 msgid "Restoring" msgstr "" #: ../src/gtk-ui/sync-ui.c:745 msgid "Syncing" msgstr "" #. TRANSLATORS: This is for the button in main view, right side. #. Keep line length below ~20 characters, use two lines if needed #: ../src/gtk-ui/sync-ui.c:757 #: ../src/gtk-ui/sync-ui.c:3399 msgid "Cancel sync" msgstr "Senkronizasyonu iptal et" #: ../src/gtk-ui/sync-ui.c:922 msgid "Back to sync" msgstr "Senkronizasyona geri dönün" #. TRANSLATORS: label for checkbutton/toggle in main view. #. * Please stick to similar length strings or break the line with #. * "\n" if absolutely needed #: ../src/gtk-ui/sync-ui.c:1224 msgid "Automatic sync" msgstr "Otomatik senkronizasyon" #. This is the expander label in emergency view. It summarizes the #. * currently selected data sources. First placeholder is service/device #. * name, second a comma separeted list of sources. #. * E.g. "Affected data: Google Contacts, Appointments" #: ../src/gtk-ui/sync-ui.c:1519 #, fuzzy, c-format msgid "Affected data: %s %s" msgstr "Veri isteniyor" #: ../src/gtk-ui/sync-ui.c:1524 #, fuzzy, c-format msgid "Affected data: none" msgstr "Etkilenen paketler:Hiçbiri" #. TRANSLATORS: confirmation for restoring a backup. placeholder is the #. * backup time string defined below #: ../src/gtk-ui/sync-ui.c:1602 #, c-format msgid "Do you want to restore the backup from %s? All changes you have made since then will be lost." msgstr "" #: ../src/gtk-ui/sync-ui.c:1605 #, fuzzy msgid "Yes, restore" msgstr "Evet, sil" #. TRANSLATORS: date/time for strftime(), used in emergency view backup #. * label. Any time format that shows date and time is good. #: ../src/gtk-ui/sync-ui.c:1637 #, c-format msgid "%x %X" msgstr "" #. TRANSLATORS: label for a backup in emergency view. Placeholder is #. * service or device name #: ../src/gtk-ui/sync-ui.c:1656 #, c-format msgid "Backed up before syncing with %s" msgstr "" #: ../src/gtk-ui/sync-ui.c:1673 msgid "Restore" msgstr "" #. TRANSLATORS: this is an explanation in Emergency view. #. * Placeholder is a service/device name #: ../src/gtk-ui/sync-ui.c:1780 #, c-format msgid "A normal sync with %s is not possible at this time. You can do a slow two-way sync or start from scratch. You can also restore a backup, but a slow sync or starting from scratch will still be required before normal sync is possible." msgstr "" #: ../src/gtk-ui/sync-ui.c:1790 #, c-format msgid "If something has gone horribly wrong, you can try a slow sync, start from scratch or restore from backup." msgstr "" #. TRANSLATORS: These are a buttons in Emergency view. Placeholder is a #. * service/device name. Please don't use too long lines, but feel free to #. * use several lines. #: ../src/gtk-ui/sync-ui.c:1799 #, c-format msgid "" "Delete all your local\n" "data and replace with\n" "data from %s" msgstr "" #: ../src/gtk-ui/sync-ui.c:1805 #, c-format msgid "" "Delete all data on\n" "%s and replace\n" "with your local data" msgstr "" #: ../src/gtk-ui/sync-ui.c:2267 msgid "Failed to get list of supported services from SyncEvolution" msgstr "" #: ../src/gtk-ui/sync-ui.c:2321 msgid "There was a problem communicating with the sync process. Please try again later." msgstr "" #: ../src/gtk-ui/sync-ui.c:2380 #, fuzzy msgid "Restore failed" msgstr "Bağlantı başarısız" #: ../src/gtk-ui/sync-ui.c:2383 #: ../src/gtk-ui/sync-ui.c:3268 msgid "Sync failed" msgstr "Senkronizasyon başarısız" #: ../src/gtk-ui/sync-ui.c:2389 #, fuzzy msgid "Restore complete" msgstr "Tamam, ancak tek değil" #: ../src/gtk-ui/sync-ui.c:2392 msgid "Sync complete" msgstr "Senkronizasyon tamamlandı" #: ../src/gtk-ui/sync-ui.c:2484 #, c-format msgid "Preparing '%s'" msgstr "'%s' hazırlanıyor" #: ../src/gtk-ui/sync-ui.c:2487 #, c-format msgid "Receiving '%s'" msgstr "" #: ../src/gtk-ui/sync-ui.c:2490 #, c-format msgid "Sending '%s'" msgstr "'%s' gönderiliyor" #: ../src/gtk-ui/sync-ui.c:2611 #, c-format msgid "There was one remote rejection." msgid_plural "There were %ld remote rejections." msgstr[0] "" msgstr[1] "" #: ../src/gtk-ui/sync-ui.c:2616 #, c-format msgid "There was one local rejection." msgid_plural "There were %ld local rejections." msgstr[0] "" msgstr[1] "" #: ../src/gtk-ui/sync-ui.c:2621 #, c-format msgid "There were %ld local rejections and %ld remote rejections." msgstr "" #: ../src/gtk-ui/sync-ui.c:2626 #, c-format msgid "Last time: No changes." msgstr "" #: ../src/gtk-ui/sync-ui.c:2628 #, c-format msgid "Last time: Sent one change." msgid_plural "Last time: Sent %ld changes." msgstr[0] "" msgstr[1] "" #. This is about changes made to the local data. Not all of these #. changes were requested by the remote server, so "applied" #. is a better word than "received" (bug #5185). #: ../src/gtk-ui/sync-ui.c:2636 #, c-format msgid "Last time: Applied one change." msgid_plural "Last time: Applied %ld changes." msgstr[0] "" msgstr[1] "" #: ../src/gtk-ui/sync-ui.c:2641 #, c-format msgid "Last time: Applied %ld changes and sent %ld changes." msgstr "" #. TRANSLATORS: the placeholder is a error message (hopefully) #. * explaining the problem #: ../src/gtk-ui/sync-ui.c:2848 #, c-format msgid "" "There was a problem with last sync:\n" "%s" msgstr "" #: ../src/gtk-ui/sync-ui.c:2858 #, c-format msgid "You've just restored a backup. The changes have not been synced with %s yet" msgstr "" #: ../src/gtk-ui/sync-ui.c:3146 msgid "Waiting for current operation to finish..." msgstr "" #. TRANSLATORS: next strings are error messages. #: ../src/gtk-ui/sync-ui.c:3180 msgid "A normal sync is not possible at this time. The server suggests a slow sync, but this might not always be what you want if both ends already have data." msgstr "" #: ../src/gtk-ui/sync-ui.c:3184 msgid "The sync process died unexpectedly." msgstr "" #: ../src/gtk-ui/sync-ui.c:3189 msgid "Password request was not answered. You can save the password in the settings to prevent the request." msgstr "" #. TODO use the service device name here, this is a remote problem #: ../src/gtk-ui/sync-ui.c:3193 msgid "There was a problem processing sync request. Trying again may help." msgstr "" #: ../src/gtk-ui/sync-ui.c:3199 msgid "Failed to login. Could there be a problem with your username or password?" msgstr "" #: ../src/gtk-ui/sync-ui.c:3202 msgid "Forbidden" msgstr "" #. TRANSLATORS: data source means e.g. calendar or addressbook #: ../src/gtk-ui/sync-ui.c:3208 msgid "A data source could not be found. Could there be a problem with the settings?" msgstr "" #: ../src/gtk-ui/sync-ui.c:3212 #, fuzzy msgid "Remote database error" msgstr "Dahili hata oluştu" #. This can happen when EDS is borked, restart it may help... #: ../src/gtk-ui/sync-ui.c:3215 msgid "There is a problem with the local database. Syncing again or rebooting may help." msgstr "" #: ../src/gtk-ui/sync-ui.c:3218 msgid "No space on disk" msgstr "Diskte yer kalmadı" #: ../src/gtk-ui/sync-ui.c:3220 #, fuzzy msgid "Failed to process SyncML" msgstr "Dizin okuma başarısız %s: %m" #: ../src/gtk-ui/sync-ui.c:3222 #, fuzzy msgid "Server authorization failed" msgstr "Paket indirme başarısız" #: ../src/gtk-ui/sync-ui.c:3224 #, fuzzy msgid "Failed to parse configuration file" msgstr "Geçici dosya açılamadı" #: ../src/gtk-ui/sync-ui.c:3226 #, fuzzy msgid "Failed to read configuration file" msgstr "Geçici dosya okumak için açılamadı" #: ../src/gtk-ui/sync-ui.c:3228 #, fuzzy msgid "No configuration found" msgstr "Hiçbir eşleşme bulunamadı." #: ../src/gtk-ui/sync-ui.c:3230 #, fuzzy msgid "No configuration file found" msgstr "XPM başlığı bulunamadı" #: ../src/gtk-ui/sync-ui.c:3232 msgid "Server sent bad content" msgstr "" #: ../src/gtk-ui/sync-ui.c:3234 msgid "Connection certificate has expired" msgstr "" #: ../src/gtk-ui/sync-ui.c:3236 #, fuzzy msgid "Connection certificate is invalid" msgstr "Temel PNM resim türü geçersiz" #: ../src/gtk-ui/sync-ui.c:3244 msgid "We were unable to connect to the server. The problem could be temporary or there could be something wrong with the settings." msgstr "" #: ../src/gtk-ui/sync-ui.c:3251 #, fuzzy msgid "The server URL is bad" msgstr "Adres bir _yansı listesi" #: ../src/gtk-ui/sync-ui.c:3256 #, fuzzy msgid "The server was not found" msgstr "Paket bulunamadı" #: ../src/gtk-ui/sync-ui.c:3258 #, c-format msgid "Error %d" msgstr "Hata: %d" #. TRANSLATORS: password request dialog contents: title, cancel button #. * and ok button #: ../src/gtk-ui/sync-ui.c:3396 msgid "Password is required for sync" msgstr "" #: ../src/gtk-ui/sync-ui.c:3400 #, fuzzy msgid "Sync with password" msgstr "Şifreyi girin" #. TRANSLATORS: password request dialog message, placeholder is service name #: ../src/gtk-ui/sync-ui.c:3410 #, c-format msgid "Please enter password for syncing with %s:" msgstr "" #. title for the buttons on the right side of main view #: ../src/gtk-ui/ui.glade.h:2 msgid "Actions" msgstr "" #. text between the two "start from scratch" buttons in emergency view #: ../src/gtk-ui/ui.glade.h:4 msgid "or" msgstr "ya da" #: ../src/gtk-ui/ui.glade.h:5 msgid "Direct sync" msgstr "" #: ../src/gtk-ui/ui.glade.h:6 msgid "Network sync" msgstr "Ağ senkronizasyonu" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:8 msgid "Restore from backup" msgstr "" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:10 msgid "Slow sync" msgstr "" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:12 msgid "Start from scratch" msgstr "" #: ../src/gtk-ui/ui.glade.h:13 msgid "" "A slow sync compares items from both sides and tries to merge them. \n" "This may fail in some cases, leading to duplicates or lost information." msgstr "" #: ../src/gtk-ui/ui.glade.h:15 msgid "Add new device" msgstr "Yeni cihaz ekle" #: ../src/gtk-ui/ui.glade.h:16 msgid "Add new service" msgstr "Yeni servis ekle" #. explanation of "Restore backup" function #: ../src/gtk-ui/ui.glade.h:18 msgid "Backups are made before every time we Sync. Choose a backup to restore. Any changes you have made since then will be lost." msgstr "" #: ../src/gtk-ui/ui.glade.h:19 msgid "Calendar" msgstr "Takvim" #. Button in main view, right side. Keep to below 20 chars per line, feel free to use two lines #: ../src/gtk-ui/ui.glade.h:21 msgid "" "Change or edit\n" "sync service" msgstr "" #. close button for settings window #: ../src/gtk-ui/ui.glade.h:24 msgid "Close" msgstr "Kapat" #: ../src/gtk-ui/ui.glade.h:25 msgid "" "Delete all data on Zyb \n" "and replace with your\n" "local information" msgstr "" #: ../src/gtk-ui/ui.glade.h:28 msgid "" "Delete all your local\n" "information and replace\n" "with data from Zyb" msgstr "" #. button in main view, right side. Keep length to 20 characters or so, use two lines if needed #: ../src/gtk-ui/ui.glade.h:32 msgid "" "Fix a sync\n" "emergency" msgstr "" #: ../src/gtk-ui/ui.glade.h:34 msgid "" "If you don't see your service above but know that your sync provider uses SyncML\n" "you can setup a service manually." msgstr "" #: ../src/gtk-ui/ui.glade.h:36 msgid "Settings" msgstr "Ayarlar" #: ../src/gtk-ui/ui.glade.h:39 msgid "Sync Emergency" msgstr "" #: ../src/gtk-ui/ui.glade.h:41 msgid "" "To sync you'll need a network connection and an account with a sync service.\n" "We support the following services: " msgstr "" #: ../src/gtk-ui/ui.glade.h:43 msgid "Use Bluetooth to Sync your data from one device to another." msgstr "" #: ../src/gtk-ui/ui.glade.h:44 msgid "You will need to add Bluetooth devices before they can be synced." msgstr "" #: ../src/gtk-ui/sync.desktop.in.h:2 #: ../src/gtk-ui/sync-gtk.desktop.in.h:2 msgid "Up to date" msgstr "Güncel" #: ../src/gtk-ui/sync-gtk.desktop.in.h:1 msgid "Sync (GTK)" msgstr "Seknronize et (GTK)" #: ../src/gtk-ui/sync-config-widget.c:78 msgid "ScheduleWorld enables you to keep your contacts, events, tasks, and notes in sync." msgstr "" #: ../src/gtk-ui/sync-config-widget.c:81 msgid "Google Sync can back up and synchronize your contacts with your Gmail contacts." msgstr "" #. TRANSLATORS: Please include the word "demo" (or the equivalent in #. your language): Funambol is going to be a 90 day demo service #. in the future #: ../src/gtk-ui/sync-config-widget.c:87 msgid "Back up your contacts and calendar. Sync with a single click, anytime, anywhere (DEMO)." msgstr "" #: ../src/gtk-ui/sync-config-widget.c:90 msgid "Mobical Backup and Restore service allows you to securely back up your personal mobile data for free." msgstr "" #: ../src/gtk-ui/sync-config-widget.c:93 msgid "ZYB is a simple way for people to store and share mobile information online." msgstr "" #: ../src/gtk-ui/sync-config-widget.c:96 msgid "Memotoo lets you access your personal data from any computer connected to the Internet." msgstr "" #: ../src/gtk-ui/sync-config-widget.c:192 #, fuzzy msgid "Sorry, failed to save the configuration" msgstr "TIFF resmi kaydedilemedi" #: ../src/gtk-ui/sync-config-widget.c:381 msgid "Service must have a name and server URL" msgstr "" #: ../src/gtk-ui/sync-config-widget.c:422 #, c-format msgid "Do you want to reset the settings for %s? This will not remove any synced information on either end." msgstr "" #. TRANSLATORS: buttons in reset-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:426 #, fuzzy msgid "Yes, reset" msgstr "Evet, sil" #: ../src/gtk-ui/sync-config-widget.c:427 #: ../src/gtk-ui/sync-config-widget.c:438 #, fuzzy msgid "No, keep settings" msgstr "Yeni bağlantı ayarları" #: ../src/gtk-ui/sync-config-widget.c:432 #, c-format msgid "Do you want to delete the settings for %s? This will not remove any synced information on either end but it will remove these settings." msgstr "" #. TRANSLATORS: buttons in delete-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:437 msgid "Yes, delete" msgstr "Evet, sil" #: ../src/gtk-ui/sync-config-widget.c:467 #, fuzzy msgid "Reset settings" msgstr "Ağ ayarları" #: ../src/gtk-ui/sync-config-widget.c:470 #, fuzzy msgid "Delete settings" msgstr "Ağ ayarları" #: ../src/gtk-ui/sync-config-widget.c:480 #, fuzzy msgid "Save and use" msgstr "Önceki yazıtipini kullan" #: ../src/gtk-ui/sync-config-widget.c:483 msgid "" "Save and replace\n" "current service" msgstr "" #: ../src/gtk-ui/sync-config-widget.c:493 #, fuzzy msgid "Stop using device" msgstr "FCP cihazı ekle" #: ../src/gtk-ui/sync-config-widget.c:496 #, fuzzy msgid "Stop using service" msgstr "3G servisi seçin" #. TRANSLATORS: label for an entry in service configuration form. #. * Placeholder is a source name. #. * Example: "Appointments URI" #: ../src/gtk-ui/sync-config-widget.c:679 #, fuzzy, c-format msgid "%s URI" msgstr "Kaynak URI" #. TRANSLATORS: toggles in service configuration form, placeholder is service #. * or device name #: ../src/gtk-ui/sync-config-widget.c:854 #, fuzzy, c-format msgid "Send changes to %s" msgstr "%s çalıştırılamadı: %s" #: ../src/gtk-ui/sync-config-widget.c:859 #, fuzzy, c-format msgid "Receive changes from %s" msgstr "Sık Kullanılanlardan Çıkart" #: ../src/gtk-ui/sync-config-widget.c:875 msgid "Sync" msgstr "Senkronizasyon" #. TRANSLATORS: label of a entry in service configuration #: ../src/gtk-ui/sync-config-widget.c:891 #, fuzzy msgid "Server address" msgstr "IPv6 adresi:" #. TRANSLATORS: explanation before a device template combobox. #. * Placeholder is a device name like 'Nokia N85' or 'Syncevolution #. * Client' #: ../src/gtk-ui/sync-config-widget.c:967 #, c-format msgid "This device looks like it might be a '%s'. If this is not correct, please take a look at the list of supported devices and pick yours if it is listed" msgstr "" #: ../src/gtk-ui/sync-config-widget.c:973 msgid "We don't know what this device is exactly. Please take a look at the list of supported devices and pick yours if it is listed" msgstr "" #: ../src/gtk-ui/sync-config-widget.c:1126 #, fuzzy, c-format msgid "%s - Bluetooth device" msgstr "RAID Cihazı" #. TRANSLATORS: service title for services that are not based on a #. * template in service list, the placeholder is the name of the service #: ../src/gtk-ui/sync-config-widget.c:1132 #, fuzzy, c-format msgid "%s - manually setup" msgstr "Adres kurulumu" #. TRANSLATORS: link button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1807 #, fuzzy msgid "Launch website" msgstr "PackageKit Sitesi" #. TRANSLATORS: button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1816 #, fuzzy msgid "Set up now" msgstr "Şimdi Bul" #. TRANSLATORS: labels of entries in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1912 msgid "Username" msgstr "Kullanıcı Adı" #: ../src/gtk-ui/sync-config-widget.c:1927 msgid "Password" msgstr "Şifre" #. TRANSLATORS: warning in service configuration form for people #. who have modified the configuration via other means. #: ../src/gtk-ui/sync-config-widget.c:1950 msgid "Current configuration is more complex than what can be shown here. Changes to sync mode or synced data types will overwrite that configuration." msgstr "" #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1969 #, fuzzy msgid "Hide server settings" msgstr "Yeni bağlantı ayarları" #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1989 #, fuzzy msgid "Show server settings" msgstr "Yeni bağlantı ayarları" #: ../src/gnome-bluetooth/syncevolution.c:110 #, fuzzy msgid "Sync in the Sync application" msgstr "Oturumu Aç" #: ../src/syncevo-dbus-server.cpp:5667 #, fuzzy, c-format msgid "%s is syncing" msgstr "Üzgünüz, %s çalışmıyor" #: ../src/syncevo-dbus-server.cpp:5668 #, c-format msgid "We have just started to sync your computer with the %s sync service." msgstr "" #. if sync is successfully started and done #: ../src/syncevo-dbus-server.cpp:5682 #, fuzzy, c-format msgid "%s sync complete" msgstr "Tamam, ancak tek değil" #: ../src/syncevo-dbus-server.cpp:5683 #, c-format msgid "We have just finished syncing your computer with the %s sync service." msgstr "" #. if sync is successfully started and has errors, or not started successful with a fatal problem #: ../src/syncevo-dbus-server.cpp:5688 msgid "Sync problem." msgstr "Senkronizasyon problemi." #: ../src/syncevo-dbus-server.cpp:5689 msgid "Sorry, there's a problem with your sync that you need to attend to." msgstr "" #: ../src/syncevo-dbus-server.cpp:5762 #, fuzzy msgid "View" msgstr "Dokümanları görüntüle" #. Use "default" as ID because that is what mutter-moblin #. recognizes: it then skips the action instead of adding it #. in addition to its own "Dismiss" button (always added). #: ../src/syncevo-dbus-server.cpp:5766 #, fuzzy msgid "Dismiss" msgstr "Bırak" syncevolution_1.4/po/zh_CN.po000066400000000000000000000700761230021373600163470ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # # Translators: # , 2011. # , 2011. # Zhu Yanhai , 2009. msgid "" msgstr "" "Project-Id-Version: syncevolution\n" "Report-Msgid-Bugs-To: https://bugs.meego.com/\n" "POT-Creation-Date: 2011-12-05 10:21-0800\n" "PO-Revision-Date: 2011-12-06 00:09+0000\n" "Last-Translator: GLS_Translator_CHS2 \n" "Language-Team: Chinese (China) (http://www.transifex.net/projects/p/meego/team/zh_CN/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: zh_CN\n" "Plural-Forms: nplurals=1; plural=0\n" #. TRANSLATORS: this is the application name that may be used by e.g. #. the windowmanager #: ../src/gtk-ui/main.c:40 ../src/gtk-ui/ui.glade.h:38 #: ../src/gtk-ui/sync.desktop.in.h:1 #: ../src/gnome-bluetooth/syncevolution.c:112 msgid "Sync" msgstr "同步" #: ../src/gtk-ui/sync-ui.c:266 msgid "Contacts" msgstr "联系人" #: ../src/gtk-ui/sync-ui.c:268 msgid "Appointments" msgstr "约会" #: ../src/gtk-ui/sync-ui.c:270 ../src/gtk-ui/ui.glade.h:40 msgid "Tasks" msgstr "任务" #: ../src/gtk-ui/sync-ui.c:272 msgid "Notes" msgstr "便条" #. TRANSLATORS: This is a "combination source" for syncing with devices #. * that combine appointments and tasks. the name should match the ones #. * used for calendar and todo above #: ../src/gtk-ui/sync-ui.c:277 msgid "Appointments & Tasks" msgstr "约会和任务" #: ../src/gtk-ui/sync-ui.c:349 msgid "Starting sync" msgstr "正在开始同步" #. TRANSLATORS: slow sync confirmation dialog message. Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:387 #, c-format msgid "Do you want to slow sync with %s?" msgstr "您想要和 %s 进行慢速同步吗?" #: ../src/gtk-ui/sync-ui.c:391 msgid "Yes, do slow sync" msgstr "好的,做慢速同步吧" #: ../src/gtk-ui/sync-ui.c:391 msgid "No, cancel sync" msgstr "不,取消同步" #. TRANSLATORS: confirmation dialog for "refresh from peer". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:424 #, c-format msgid "" "Do you want to delete all local data and replace it with data from %s? This " "is not usually advised." msgstr "您想删除所有本地数据并用来自%s的数据替代它们吗?通常我们不建议您这样做." #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 msgid "Yes, delete and replace" msgstr "是的,删除然后替代" #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 #: ../src/gtk-ui/sync-ui.c:1610 msgid "No" msgstr "我不要做慢速同步" #. TRANSLATORS: confirmation dialog for "refresh from local side". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:457 #, c-format msgid "" "Do you want to delete all data in %s and replace it with your local data? " "This is not usually advised." msgstr "您想删除 %s 中的所有数据并用您的本地数据来替代它们吗?通常我们不建议您这样做" #: ../src/gtk-ui/sync-ui.c:491 msgid "Trying to cancel sync" msgstr "尝试取消同步" #: ../src/gtk-ui/sync-ui.c:533 msgid "No service or device selected" msgstr "没有选择服务或设备" #. TRANSLATORS: This is the title on main view. Placeholder is #. * the service name. Example: "Google - synced just now" #: ../src/gtk-ui/sync-ui.c:541 #, c-format msgid "%s - synced just now" msgstr "%s - 刚刚同步完" #: ../src/gtk-ui/sync-ui.c:545 #, c-format msgid "%s - synced a minute ago" msgstr "%s - 一分钟前已同步" #: ../src/gtk-ui/sync-ui.c:549 #, c-format msgid "%s - synced %ld minutes ago" msgstr "%s - %ld 分钟前已同步" #: ../src/gtk-ui/sync-ui.c:554 #, c-format msgid "%s - synced an hour ago" msgstr "%s - 一小时前已同步" #: ../src/gtk-ui/sync-ui.c:558 #, c-format msgid "%s - synced %ld hours ago" msgstr "%s - %ld 小时前已同步" #: ../src/gtk-ui/sync-ui.c:563 #, c-format msgid "%s - synced a day ago" msgstr "%s - 一天之前已同步" #: ../src/gtk-ui/sync-ui.c:567 #, c-format msgid "%s - synced %ld days ago" msgstr "%s - %ld 天之前已同步" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "You've just restored a backup. The changes have not been " #. * "synced with %s yet" #: ../src/gtk-ui/sync-ui.c:616 ../src/gtk-ui/sync-ui.c:701 msgid "Sync now" msgstr "现在同步" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "A normal sync is not possible at this time..." message. #. * "Other options" will open Emergency view #: ../src/gtk-ui/sync-ui.c:622 ../src/gtk-ui/ui.glade.h:37 msgid "Slow sync" msgstr "慢速同步" #: ../src/gtk-ui/sync-ui.c:623 msgid "Other options..." msgstr "其它选项" #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * when no service is selected. Will open configuration view #: ../src/gtk-ui/sync-ui.c:628 msgid "Select sync service" msgstr "选择同步服务" #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * login to service fails. Will open configuration view for this service #: ../src/gtk-ui/sync-ui.c:633 msgid "Edit service settings" msgstr "编辑服务设置" #: ../src/gtk-ui/sync-ui.c:709 msgid "" "You haven't selected a sync service or device yet. Sync services let you " "synchronize your data between your netbook and a web service. You can also " "sync directly with some devices." msgstr "您尚未选择同步服务或设备。同步服务允许您在您的上网本和网络服务之间同步数据。您也可以直接与一些设备同步。" #: ../src/gtk-ui/sync-ui.c:729 msgid "Sync again" msgstr "再次同步" #: ../src/gtk-ui/sync-ui.c:748 msgid "Restoring" msgstr "恢复" #: ../src/gtk-ui/sync-ui.c:750 msgid "Syncing" msgstr "正在同步" #. TRANSLATORS: This is for the button in main view, right side. #. Keep line length below ~20 characters, use two lines if needed #: ../src/gtk-ui/sync-ui.c:762 ../src/gtk-ui/sync-ui.c:3407 msgid "Cancel sync" msgstr "取消同步" #: ../src/gtk-ui/sync-ui.c:927 msgid "Back to sync" msgstr "返回同步" #. TRANSLATORS: label for checkbutton/toggle in main view. #. * Please stick to similar length strings or break the line with #. * "\n" if absolutely needed #: ../src/gtk-ui/sync-ui.c:1229 msgid "Automatic sync" msgstr "自动同步" #. This is the expander label in emergency view. It summarizes the #. * currently selected data sources. First placeholder is service/device #. * name, second a comma separeted list of sources. #. * E.g. "Affected data: Google Contacts, Appointments" #: ../src/gtk-ui/sync-ui.c:1524 #, c-format msgid "Affected data: %s %s" msgstr "受影响的资料:%s %s" #: ../src/gtk-ui/sync-ui.c:1529 #, c-format msgid "Affected data: none" msgstr "受影响的资料:没有" #. TRANSLATORS: confirmation for restoring a backup. placeholder is the #. * backup time string defined below #: ../src/gtk-ui/sync-ui.c:1607 #, c-format msgid "" "Do you want to restore the backup from %s? All changes you have made since " "then will be lost." msgstr "您想要从 %s 恢复备份吗?您从那个时间" #: ../src/gtk-ui/sync-ui.c:1610 msgid "Yes, restore" msgstr "是的,恢复备份" #. TRANSLATORS: date/time for strftime(), used in emergency view backup #. * label. Any time format that shows date and time is good. #: ../src/gtk-ui/sync-ui.c:1642 #, c-format msgid "%x %X" msgstr "%x %X" #. TRANSLATORS: label for a backup in emergency view. Placeholder is #. * service or device name #: ../src/gtk-ui/sync-ui.c:1661 #, c-format msgid "Backed up before syncing with %s" msgstr "在和 %s 同步前已备份" #: ../src/gtk-ui/sync-ui.c:1678 msgid "Restore" msgstr "恢复" #. TRANSLATORS: this is an explanation in Emergency view. #. * Placeholder is a service/device name #: ../src/gtk-ui/sync-ui.c:1785 #, c-format msgid "" "A normal sync with %s is not possible at this time. You can do a slow two-" "way sync or start from scratch. You can also restore a backup, but a slow " "sync or starting from scratch will still be required before normal sync is " "possible." msgstr "与 %s 的正常同步现在无法进行。您可以做慢速双向同步或从头开始。您也可以恢复一个备份,但是正常同步之前还是会需要慢速同步或从头开始。" #: ../src/gtk-ui/sync-ui.c:1795 #, c-format msgid "" "If something has gone horribly wrong, you can try a slow sync, start from " "scratch or restore from backup." msgstr "如果发生严重错误,您可以尝试慢速同步,从头开始或者从备份恢复。" #. TRANSLATORS: These are a buttons in Emergency view. Placeholder is a #. * service/device name. Please don't use too long lines, but feel free to #. * use several lines. #: ../src/gtk-ui/sync-ui.c:1804 #, c-format msgid "" "Delete all your local\n" "data and replace with\n" "data from %s" msgstr "" "删除所有本地\n" "数据并用 %s \n" "数据替代它们" #: ../src/gtk-ui/sync-ui.c:1810 #, c-format msgid "" "Delete all data on\n" "%s and replace\n" "with your local data" msgstr "" "删除所有 %s 上的\n" "数据并用本地数据\n" "替代它们" #: ../src/gtk-ui/sync-ui.c:2275 msgid "Failed to get list of supported services from SyncEvolution" msgstr "未能从 SyncEvolution 得到支持的服务列表" #: ../src/gtk-ui/sync-ui.c:2329 msgid "" "There was a problem communicating with the sync process. Please try again " "later." msgstr "与同步进程通信时发生问题。请稍后再试。" #: ../src/gtk-ui/sync-ui.c:2388 msgid "Restore failed" msgstr "恢复失败" #: ../src/gtk-ui/sync-ui.c:2391 ../src/gtk-ui/sync-ui.c:3276 msgid "Sync failed" msgstr "同步失败" #: ../src/gtk-ui/sync-ui.c:2397 msgid "Restore complete" msgstr "恢复完成" #: ../src/gtk-ui/sync-ui.c:2400 msgid "Sync complete" msgstr "同步完成" #: ../src/gtk-ui/sync-ui.c:2492 #, c-format msgid "Preparing '%s'" msgstr "正在准备 '%s'" #: ../src/gtk-ui/sync-ui.c:2495 #, c-format msgid "Receiving '%s'" msgstr "正在接收 '%s'" #: ../src/gtk-ui/sync-ui.c:2498 #, c-format msgid "Sending '%s'" msgstr "正在发送 '%s'" #: ../src/gtk-ui/sync-ui.c:2619 #, c-format msgid "There was one remote rejection." msgid_plural "There were %ld remote rejections." msgstr[0] "有 %ld 次远程拒绝。" #: ../src/gtk-ui/sync-ui.c:2624 #, c-format msgid "There was one local rejection." msgid_plural "There were %ld local rejections." msgstr[0] "有 %ld 次本地拒绝。" #: ../src/gtk-ui/sync-ui.c:2629 #, c-format msgid "There were %ld local rejections and %ld remote rejections." msgstr "一共出现了 %ld 个本地拒绝和 %ld 个远端拒绝。" #: ../src/gtk-ui/sync-ui.c:2634 #, c-format msgid "Last time: No changes." msgstr "最近一次:没有更改" #: ../src/gtk-ui/sync-ui.c:2636 #, c-format msgid "Last time: Sent one change." msgid_plural "Last time: Sent %ld changes." msgstr[0] "最近一次:发送了 %ld 个更改。" #. This is about changes made to the local data. Not all of these #. changes were requested by the remote server, so "applied" #. is a better word than "received" (bug #5185). #: ../src/gtk-ui/sync-ui.c:2644 #, c-format msgid "Last time: Applied one change." msgid_plural "Last time: Applied %ld changes." msgstr[0] "最近一次:应用了 %ld 个更改。" #: ../src/gtk-ui/sync-ui.c:2649 #, c-format msgid "Last time: Applied %ld changes and sent %ld changes." msgstr "最近一次: 应用了 %ld 个更改并发送了 %ld 个更改。" #. TRANSLATORS: the placeholder is a error message (hopefully) #. * explaining the problem #: ../src/gtk-ui/sync-ui.c:2856 #, c-format msgid "" "There was a problem with last sync:\n" "%s" msgstr "" "上一个同步发生问题:\n" "%s" #: ../src/gtk-ui/sync-ui.c:2866 #, c-format msgid "" "You've just restored a backup. The changes have not been synced with %s yet" msgstr "您刚刚恢复了一个备份。这些修改还没有和 %s 同步" #: ../src/gtk-ui/sync-ui.c:3154 msgid "Waiting for current operation to finish..." msgstr "正在等待当前的操作完成。。。" #. TRANSLATORS: next strings are error messages. #: ../src/gtk-ui/sync-ui.c:3188 msgid "" "A normal sync is not possible at this time. The server suggests a slow sync," " but this might not always be what you want if both ends already have data." msgstr "现在无法进行正常同步。服务器建议您做慢速同步,但是如果同步双方已经都有数据,您可能并不想使用慢速同步。" #: ../src/gtk-ui/sync-ui.c:3192 msgid "The sync process died unexpectedly." msgstr "同步服务异常退出。" #: ../src/gtk-ui/sync-ui.c:3197 msgid "" "Password request was not answered. You can save the password in the settings" " to prevent the request." msgstr "密码请求没有回复。您可以在设置中保存密码,以防止密码请求。" #. TODO use the service device name here, this is a remote problem #: ../src/gtk-ui/sync-ui.c:3201 msgid "There was a problem processing sync request. Trying again may help." msgstr "处理同步请求时发生问题。请再试一次。" #: ../src/gtk-ui/sync-ui.c:3207 msgid "" "Failed to login. Could there be a problem with your username or password?" msgstr "登录失败。会不会是您的用户名或密码有问题?" #: ../src/gtk-ui/sync-ui.c:3210 msgid "Forbidden" msgstr "被禁止" #. TRANSLATORS: data source means e.g. calendar or addressbook #: ../src/gtk-ui/sync-ui.c:3216 msgid "" "A data source could not be found. Could there be a problem with the " "settings?" msgstr "无法找到数据来源。会不会是设置有问题?" #: ../src/gtk-ui/sync-ui.c:3220 msgid "Remote database error" msgstr "远程数据库错误" #. This can happen when EDS is borked, restart it may help... #: ../src/gtk-ui/sync-ui.c:3223 msgid "" "There is a problem with the local database. Syncing again or rebooting may " "help." msgstr "本地资料库有问题。请重新同步或者重新启动可能会有帮助。" #: ../src/gtk-ui/sync-ui.c:3226 msgid "No space on disk" msgstr "磁盘上没有剩余空间" #: ../src/gtk-ui/sync-ui.c:3228 msgid "Failed to process SyncML" msgstr "处理 SyncML 失败" #: ../src/gtk-ui/sync-ui.c:3230 msgid "Server authorization failed" msgstr "服务器认证失败" #: ../src/gtk-ui/sync-ui.c:3232 msgid "Failed to parse configuration file" msgstr "解析配置文件出错" #: ../src/gtk-ui/sync-ui.c:3234 msgid "Failed to read configuration file" msgstr "读配置文件时出错" #: ../src/gtk-ui/sync-ui.c:3236 msgid "No configuration found" msgstr "未找到配置" #: ../src/gtk-ui/sync-ui.c:3238 msgid "No configuration file found" msgstr "未找到配置文件" #: ../src/gtk-ui/sync-ui.c:3240 msgid "Server sent bad content" msgstr "服务器发送的内容错误" #: ../src/gtk-ui/sync-ui.c:3242 msgid "Connection certificate has expired" msgstr "该连接的认证已过期" #: ../src/gtk-ui/sync-ui.c:3244 msgid "Connection certificate is invalid" msgstr "连接认证不合法" #: ../src/gtk-ui/sync-ui.c:3252 msgid "" "We were unable to connect to the server. The problem could be temporary or " "there could be something wrong with the settings." msgstr "我们无法连接到服务器。问题可能是暂时的,也可能是设置有问题。" #: ../src/gtk-ui/sync-ui.c:3259 msgid "The server URL is bad" msgstr "服务器的 URL有错误" #: ../src/gtk-ui/sync-ui.c:3264 msgid "The server was not found" msgstr "没有找到服务器" #: ../src/gtk-ui/sync-ui.c:3266 #, c-format msgid "Error %d" msgstr "错误 %d" #. TRANSLATORS: password request dialog contents: title, cancel button #. * and ok button #: ../src/gtk-ui/sync-ui.c:3404 msgid "Password is required for sync" msgstr "同步需要密码" #: ../src/gtk-ui/sync-ui.c:3408 msgid "Sync with password" msgstr "用密码同步" #. TRANSLATORS: password request dialog message, placeholder is service name #: ../src/gtk-ui/sync-ui.c:3418 #, c-format msgid "Please enter password for syncing with %s:" msgstr "请输入与 %s 同步的密码:" #. title for the buttons on the right side of main view #: ../src/gtk-ui/ui.glade.h:2 msgid "Actions" msgstr "动作" #. text between the two "start from scratch" buttons in emergency view #: ../src/gtk-ui/ui.glade.h:4 msgid "or" msgstr "或者" #: ../src/gtk-ui/ui.glade.h:5 msgid "Direct sync" msgstr "直接同步" #: ../src/gtk-ui/ui.glade.h:6 msgid "Network sync" msgstr "网络同步" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:8 msgid "Restore from backup" msgstr "从备份恢复" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:10 msgid "Slow sync" msgstr "慢速同步" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:12 msgid "Start from scratch" msgstr "从头开始" #: ../src/gtk-ui/ui.glade.h:13 msgid "" "A slow sync compares items from both sides and tries to merge them. \n" "This may fail in some cases, leading to duplicates or lost information." msgstr "" "慢速同步会比较同步双方的数据,并尝试合并它们。\n" "在某些情况下可能会失败,导致数据被重复或者丢失数据。" #: ../src/gtk-ui/ui.glade.h:15 msgid "Add new device" msgstr "添加新设备" #: ../src/gtk-ui/ui.glade.h:16 msgid "Add new service" msgstr "添加新服务" #. explanation of "Restore backup" function #: ../src/gtk-ui/ui.glade.h:18 msgid "" "Backups are made before every time we Sync. Choose a backup to restore. Any " "changes you have made since then will be lost." msgstr "在我们每次同步前都会进行备份。请选择要恢复的备份。所有您从备份时间后做的更改都将丢失。" #: ../src/gtk-ui/ui.glade.h:19 msgid "Calendar" msgstr "日历" #. Button in main view, right side. Keep to below 20 chars per line, feel free #. to use two lines #: ../src/gtk-ui/ui.glade.h:21 msgid "" "Change or edit\n" "sync service" msgstr "" "改变或编辑\n" "同步服务" #. close button for settings window #: ../src/gtk-ui/ui.glade.h:24 msgid "Close" msgstr "关闭" #: ../src/gtk-ui/ui.glade.h:25 msgid "" "Delete all data on Zyb \n" "and replace with your\n" "local information" msgstr "" "删除所有 Zyb 上的数据 \n" "并用本地数据 \n" "替代它们" #: ../src/gtk-ui/ui.glade.h:28 msgid "" "Delete all your local\n" "information and replace\n" "with data from Zyb" msgstr "" "删除所有本地数据\n" "并用 Zyb 上的数据\n" "替代它们" #. button in main view, right side. Keep length to 20 characters or so, use #. two lines if needed #: ../src/gtk-ui/ui.glade.h:32 msgid "" "Fix a sync\n" "emergency" msgstr "" "修复同步\n" "紧急状况" #: ../src/gtk-ui/ui.glade.h:34 msgid "" "If you don't see your service above but know that your sync provider uses SyncML\n" "you can setup a service manually." msgstr "" "如果您没有在上边看见您的服务,但知道您的服务提供者使用了SyncML接口\n" "那么您可以手动设置服务" #: ../src/gtk-ui/ui.glade.h:36 msgid "Settings" msgstr "设置" #: ../src/gtk-ui/ui.glade.h:39 msgid "Sync Emergency" msgstr "同步紧急状况" #: ../src/gtk-ui/ui.glade.h:41 msgid "" "To sync you'll need a network connection and an account with a sync service.\n" "We support the following services: " msgstr "" "要进行同步您需要一个网络连接和一个同步服务的账号.\n" "我们目前支持以下服务" #: ../src/gtk-ui/ui.glade.h:43 msgid "Use Bluetooth to Sync your data from one device to another." msgstr "使用蓝牙将您的数据从一个设备同步到另一个设备。" #: ../src/gtk-ui/ui.glade.h:44 msgid "You will need to add Bluetooth devices before they can be synced." msgstr "同步前你需要添加蓝牙设备。" #: ../src/gtk-ui/sync.desktop.in.h:2 msgid "Up to date" msgstr "同步数据" #: ../src/gtk-ui/sync-gtk.desktop.in.h:1 msgid "SyncEvolution (GTK)" msgstr "SyncEvolution (GTK)" #: ../src/gtk-ui/sync-gtk.desktop.in.h:2 msgid "Synchronize PIM data" msgstr "同步 PIM(个人信息管理器)数据" #: ../src/gtk-ui/sync-config-widget.c:88 msgid "" "ScheduleWorld enables you to keep your contacts, events, tasks, and notes in" " sync." msgstr "ScheduleWorld 使您的联系人、事件、任务和便条保持同步。" #: ../src/gtk-ui/sync-config-widget.c:91 msgid "" "Google Sync can back up and synchronize your contacts with your Gmail " "contacts." msgstr "Google Sync 可以把您的联系人和 Gmail 联系人备份和同步。" #. TRANSLATORS: Please include the word "demo" (or the equivalent in #. your language): Funambol is going to be a 90 day demo service #. in the future #: ../src/gtk-ui/sync-config-widget.c:97 msgid "" "Back up your contacts and calendar. Sync with a single click, anytime, " "anywhere (DEMO)." msgstr "备份您的联系人和日历。只需一键即可同步,随时、随地(演示)。" #: ../src/gtk-ui/sync-config-widget.c:100 msgid "" "Mobical Backup and Restore service allows you to securely back up your " "personal mobile data for free." msgstr "Mobical 备份和恢复服务可以让您免费而且安全地备份您的个人行动数据。" #: ../src/gtk-ui/sync-config-widget.c:103 msgid "" "ZYB is a simple way for people to store and share mobile information online." msgstr "ZYB 是让您网上存储和分享行动咨询的简单方法。" #: ../src/gtk-ui/sync-config-widget.c:106 msgid "" "Memotoo lets you access your personal data from any computer connected to " "the Internet." msgstr "Memotoo 让您从连接到互联网的任何电脑中存取您的个人数据。" #: ../src/gtk-ui/sync-config-widget.c:195 msgid "Sorry, failed to save the configuration" msgstr "对不起,保存配置失败" #: ../src/gtk-ui/sync-config-widget.c:445 msgid "Service must have a name and server URL" msgstr "需要给服务指定一个名字和服务器地址" #. TRANSLATORS: error dialog when creating a new sync configuration #: ../src/gtk-ui/sync-config-widget.c:451 msgid "A username is required for this service" msgstr "此服务要求用户名" #: ../src/gtk-ui/sync-config-widget.c:493 #, c-format msgid "" "Do you want to reset the settings for %s? This will not remove any synced " "information on either end." msgstr "您想要重置 %s 的设置吗?这不会移除任何同步双方已经同步的数据。" #. TRANSLATORS: buttons in reset-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:497 msgid "Yes, reset" msgstr "是,重置。" #: ../src/gtk-ui/sync-config-widget.c:498 #: ../src/gtk-ui/sync-config-widget.c:509 msgid "No, keep settings" msgstr "不,保留设置" #: ../src/gtk-ui/sync-config-widget.c:503 #, c-format msgid "" "Do you want to delete the settings for %s? This will not remove any synced " "information on either end but it will remove these settings." msgstr "您想要删除 %s 的设置吗?这不会移除任何双方已同步的资料,但是会移除这些设置。" #. TRANSLATORS: buttons in delete-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:508 msgid "Yes, delete" msgstr "是的,删除" #: ../src/gtk-ui/sync-config-widget.c:539 msgid "Reset settings" msgstr "重置设置" #: ../src/gtk-ui/sync-config-widget.c:542 msgid "Delete settings" msgstr "删除设置" #: ../src/gtk-ui/sync-config-widget.c:552 msgid "Save and use" msgstr "保存并使用" #: ../src/gtk-ui/sync-config-widget.c:555 msgid "" "Save and replace\n" "current service" msgstr "" "保存并替换\n" "现有服务" #: ../src/gtk-ui/sync-config-widget.c:563 msgid "Stop using device" msgstr "停止使用此设备" #: ../src/gtk-ui/sync-config-widget.c:566 msgid "Stop using service" msgstr "停止使用此服务" #. TRANSLATORS: label for an entry in service configuration form. #. * Placeholder is a source name. #. * Example: "Appointments URI" #: ../src/gtk-ui/sync-config-widget.c:749 #, c-format msgid "%s URI" msgstr "%s URI" #. TRANSLATORS: toggles in service configuration form, placeholder is service #. * or device name #: ../src/gtk-ui/sync-config-widget.c:936 #, c-format msgid "Send changes to %s" msgstr "发送更改至%s" #: ../src/gtk-ui/sync-config-widget.c:941 #, c-format msgid "Receive changes from %s" msgstr "接收改动自%s " #: ../src/gtk-ui/sync-config-widget.c:957 msgid "Sync" msgstr "同步" #. TRANSLATORS: label of a entry in service configuration #: ../src/gtk-ui/sync-config-widget.c:973 msgid "Server address" msgstr "服务器地址" #. TRANSLATORS: explanation before a device template combobox. #. * Placeholder is a device name like 'Nokia N85' or 'Syncevolution #. * Client' #: ../src/gtk-ui/sync-config-widget.c:1049 #, c-format msgid "" "This device looks like it might be a '%s'. If this is not correct, please " "take a look at the list of supported devices and pick yours if it is listed" msgstr "设备看起来像“%s”。如果不是,请看一下列表中支持的设备,并从中选择您的设备。" #: ../src/gtk-ui/sync-config-widget.c:1055 #: ../src/gtk-ui/sync-config-widget.c:1915 msgid "" "We don't know what this device is exactly. Please take a look at the list of" " supported devices and pick yours if it is listed" msgstr "我们不知道这到底是什么设备。请看一下列表中支持的设备,并从中选择您的设备。" #: ../src/gtk-ui/sync-config-widget.c:1207 #, c-format msgid "%s - Bluetooth device" msgstr "%s - 蓝牙设备" #. TRANSLATORS: service title for services that are not based on a #. * template in service list, the placeholder is the name of the service #: ../src/gtk-ui/sync-config-widget.c:1213 #, c-format msgid "%s - manually setup" msgstr "%s - 手动设置" #. TRANSLATORS: link button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1886 msgid "Launch website" msgstr "打开站点" #. TRANSLATORS: button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1895 msgid "Set up now" msgstr "现在设置" #: ../src/gtk-ui/sync-config-widget.c:1953 msgid "Use these settings" msgstr "使用这些设置" #. TRANSLATORS: labels of entries in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1991 msgid "Username" msgstr "用户名" #: ../src/gtk-ui/sync-config-widget.c:2006 msgid "Password" msgstr "密码" #. TRANSLATORS: warning in service configuration form for people #. who have modified the configuration via other means. #: ../src/gtk-ui/sync-config-widget.c:2029 msgid "" "Current configuration is more complex than what can be shown here. Changes " "to sync mode or synced data types will overwrite that configuration." msgstr "目前的配置比当前显示的更复杂。同步模式或已同步数据类型的更改将会覆盖此配置。" #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2048 msgid "Hide server settings" msgstr "隐藏服务器设置" #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2068 msgid "Show server settings" msgstr "显示服务器设置" #: ../src/gnome-bluetooth/syncevolution.c:110 msgid "Sync in the Sync application" msgstr "在同步应用中同步" #: ../src/syncevo-dbus-server.cpp:6653 #, c-format msgid "%s is syncing" msgstr "%s 正在进行同步" #: ../src/syncevo-dbus-server.cpp:6654 #, c-format msgid "We have just started to sync your computer with the %s sync service." msgstr "我们已经将您的电脑与 %s 开始同步。" #. if sync is successfully started and done #: ../src/syncevo-dbus-server.cpp:6670 #, c-format msgid "%s sync complete" msgstr "%s 同步完成" #: ../src/syncevo-dbus-server.cpp:6671 #, c-format msgid "We have just finished syncing your computer with the %s sync service." msgstr "我们已经完成您的电脑与 %s 的同步。" #. if sync is successfully started and has errors, or not started successful #. with a fatal problem #: ../src/syncevo-dbus-server.cpp:6676 msgid "Sync problem." msgstr "同步出现问题。" #: ../src/syncevo-dbus-server.cpp:6677 msgid "Sorry, there's a problem with your sync that you need to attend to." msgstr "抱歉,有一个同步需要您看一下。" #: ../src/syncevo-dbus-server.cpp:6758 msgid "View" msgstr "查看" #. Use "default" as ID because that is what mutter-moblin #. recognizes: it then skips the action instead of adding it #. in addition to its own "Dismiss" button (always added). #: ../src/syncevo-dbus-server.cpp:6762 msgid "Dismiss" msgstr "解除" syncevolution_1.4/po/zh_TW.po000066400000000000000000000705701230021373600164000ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # # Translators: # Cheng-Chia Tseng , 2010. # Chung-Ying Huang , 2011. # , 2011. # Wen-Hsin Moh , 2011. msgid "" msgstr "" "Project-Id-Version: syncevolution\n" "Report-Msgid-Bugs-To: https://bugs.meego.com/\n" "POT-Creation-Date: 2011-12-05 10:21-0800\n" "PO-Revision-Date: 2011-12-09 19:35+0000\n" "Last-Translator: GLS_CHT \n" "Language-Team: Chinese (Taiwan) (http://www.transifex.net/projects/p/meego/team/zh_TW/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: zh_TW\n" "Plural-Forms: nplurals=1; plural=0\n" #. TRANSLATORS: this is the application name that may be used by e.g. #. the windowmanager #: ../src/gtk-ui/main.c:40 ../src/gtk-ui/ui.glade.h:38 #: ../src/gtk-ui/sync.desktop.in.h:1 #: ../src/gnome-bluetooth/syncevolution.c:112 msgid "Sync" msgstr "同步" #: ../src/gtk-ui/sync-ui.c:266 msgid "Contacts" msgstr "聯絡人" #: ../src/gtk-ui/sync-ui.c:268 msgid "Appointments" msgstr "約會" #: ../src/gtk-ui/sync-ui.c:270 ../src/gtk-ui/ui.glade.h:40 msgid "Tasks" msgstr "工作" #: ../src/gtk-ui/sync-ui.c:272 msgid "Notes" msgstr "備註" #. TRANSLATORS: This is a "combination source" for syncing with devices #. * that combine appointments and tasks. the name should match the ones #. * used for calendar and todo above #: ../src/gtk-ui/sync-ui.c:277 msgid "Appointments & Tasks" msgstr "約會與工作" #: ../src/gtk-ui/sync-ui.c:349 msgid "Starting sync" msgstr "正在開始同步" #. TRANSLATORS: slow sync confirmation dialog message. Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:387 #, c-format msgid "Do you want to slow sync with %s?" msgstr "您想要與 %s 進行慢速同步嗎?" #: ../src/gtk-ui/sync-ui.c:391 msgid "Yes, do slow sync" msgstr "是,執行慢速同步" #: ../src/gtk-ui/sync-ui.c:391 msgid "No, cancel sync" msgstr "不,取消同步" #. TRANSLATORS: confirmation dialog for "refresh from peer". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:424 #, c-format msgid "" "Do you want to delete all local data and replace it with data from %s? This " "is not usually advised." msgstr "您要刪除所有本機資料,並使用 %s 上的資料取代它嗎?我們通常不建議這麼做。" #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 msgid "Yes, delete and replace" msgstr "是,刪除並取代" #: ../src/gtk-ui/sync-ui.c:429 ../src/gtk-ui/sync-ui.c:462 #: ../src/gtk-ui/sync-ui.c:1610 msgid "No" msgstr "否" #. TRANSLATORS: confirmation dialog for "refresh from local side". Placeholder #. * is service/device name #: ../src/gtk-ui/sync-ui.c:457 #, c-format msgid "" "Do you want to delete all data in %s and replace it with your local data? " "This is not usually advised." msgstr "您要刪除所有在 %s 上的資料,並且使用本機資料取代它嗎?我們通常不建議這麼做。" #: ../src/gtk-ui/sync-ui.c:491 msgid "Trying to cancel sync" msgstr "正在嘗試取消同步" #: ../src/gtk-ui/sync-ui.c:533 msgid "No service or device selected" msgstr "未選取服務或裝置" #. TRANSLATORS: This is the title on main view. Placeholder is #. * the service name. Example: "Google - synced just now" #: ../src/gtk-ui/sync-ui.c:541 #, c-format msgid "%s - synced just now" msgstr "%s - 剛才同步過" #: ../src/gtk-ui/sync-ui.c:545 #, c-format msgid "%s - synced a minute ago" msgstr "%s - 一分鐘前同步過" #: ../src/gtk-ui/sync-ui.c:549 #, c-format msgid "%s - synced %ld minutes ago" msgstr "%s - %ld 分鐘前同步過" #: ../src/gtk-ui/sync-ui.c:554 #, c-format msgid "%s - synced an hour ago" msgstr "%s - 一小時前同步過" #: ../src/gtk-ui/sync-ui.c:558 #, c-format msgid "%s - synced %ld hours ago" msgstr "%s - %ld 小時前同步過" #: ../src/gtk-ui/sync-ui.c:563 #, c-format msgid "%s - synced a day ago" msgstr "%s - 一天前同步過" #: ../src/gtk-ui/sync-ui.c:567 #, c-format msgid "%s - synced %ld days ago" msgstr "%s - %ld 天前同步過" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "You've just restored a backup. The changes have not been " #. * "synced with %s yet" #: ../src/gtk-ui/sync-ui.c:616 ../src/gtk-ui/sync-ui.c:701 msgid "Sync now" msgstr "現在同步" #. TRANSLATORS: Action button in info bar in main view. Shown with e.g. #. * "A normal sync is not possible at this time..." message. #. * "Other options" will open Emergency view #: ../src/gtk-ui/sync-ui.c:622 ../src/gtk-ui/ui.glade.h:37 msgid "Slow sync" msgstr "慢速同步" #: ../src/gtk-ui/sync-ui.c:623 msgid "Other options..." msgstr "其他選項..." #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * when no service is selected. Will open configuration view #: ../src/gtk-ui/sync-ui.c:628 msgid "Select sync service" msgstr "選取同步服務" #. TRANSLATORS: Action button in info bar in main view. Shown e.g. #. * login to service fails. Will open configuration view for this service #: ../src/gtk-ui/sync-ui.c:633 msgid "Edit service settings" msgstr "編輯服務設定" #: ../src/gtk-ui/sync-ui.c:709 msgid "" "You haven't selected a sync service or device yet. Sync services let you " "synchronize your data between your netbook and a web service. You can also " "sync directly with some devices." msgstr "您尚未選取同步服務或裝置。同步服務讓您在您的網路筆電與網路服務間同步您的資料。您也可以直接和一些裝置進行同步。" #: ../src/gtk-ui/sync-ui.c:729 msgid "Sync again" msgstr "再次同步" #: ../src/gtk-ui/sync-ui.c:748 msgid "Restoring" msgstr "正在還原" #: ../src/gtk-ui/sync-ui.c:750 msgid "Syncing" msgstr "正在同步中" #. TRANSLATORS: This is for the button in main view, right side. #. Keep line length below ~20 characters, use two lines if needed #: ../src/gtk-ui/sync-ui.c:762 ../src/gtk-ui/sync-ui.c:3407 msgid "Cancel sync" msgstr "取消同步" #: ../src/gtk-ui/sync-ui.c:927 msgid "Back to sync" msgstr "返回同步" #. TRANSLATORS: label for checkbutton/toggle in main view. #. * Please stick to similar length strings or break the line with #. * "\n" if absolutely needed #: ../src/gtk-ui/sync-ui.c:1229 msgid "Automatic sync" msgstr "自動同步" #. This is the expander label in emergency view. It summarizes the #. * currently selected data sources. First placeholder is service/device #. * name, second a comma separeted list of sources. #. * E.g. "Affected data: Google Contacts, Appointments" #: ../src/gtk-ui/sync-ui.c:1524 #, c-format msgid "Affected data: %s %s" msgstr "受影響的資料:%s %s" #: ../src/gtk-ui/sync-ui.c:1529 #, c-format msgid "Affected data: none" msgstr "受影響的資料:無" #. TRANSLATORS: confirmation for restoring a backup. placeholder is the #. * backup time string defined below #: ../src/gtk-ui/sync-ui.c:1607 #, c-format msgid "" "Do you want to restore the backup from %s? All changes you have made since " "then will be lost." msgstr "您想要還原來自 %s 的備份嗎?所有您從那時起的變更都會喪失。" #: ../src/gtk-ui/sync-ui.c:1610 msgid "Yes, restore" msgstr "是,還原" #. TRANSLATORS: date/time for strftime(), used in emergency view backup #. * label. Any time format that shows date and time is good. #: ../src/gtk-ui/sync-ui.c:1642 #, c-format msgid "%x %X" msgstr "%x %X" #. TRANSLATORS: label for a backup in emergency view. Placeholder is #. * service or device name #: ../src/gtk-ui/sync-ui.c:1661 #, c-format msgid "Backed up before syncing with %s" msgstr "在與 %s 同步前已備份" #: ../src/gtk-ui/sync-ui.c:1678 msgid "Restore" msgstr "還原" #. TRANSLATORS: this is an explanation in Emergency view. #. * Placeholder is a service/device name #: ../src/gtk-ui/sync-ui.c:1785 #, c-format msgid "" "A normal sync with %s is not possible at this time. You can do a slow two-" "way sync or start from scratch. You can also restore a backup, but a slow " "sync or starting from scratch will still be required before normal sync is " "possible." msgstr "" "與 %s 的一般同步目前無法使用。您可以執行慢速雙方同步,或是從零開始。您也可以從備份中還原,但是在一般同步可以使用之前,會需要慢速同步或從零開始。" #: ../src/gtk-ui/sync-ui.c:1795 #, c-format msgid "" "If something has gone horribly wrong, you can try a slow sync, start from " "scratch or restore from backup." msgstr "如果有東西發生可怕錯誤,您可以嘗試慢速同步、從零開始或是從備份還原。" #. TRANSLATORS: These are a buttons in Emergency view. Placeholder is a #. * service/device name. Please don't use too long lines, but feel free to #. * use several lines. #: ../src/gtk-ui/sync-ui.c:1804 #, c-format msgid "" "Delete all your local\n" "data and replace with\n" "data from %s" msgstr "" "刪除所有本機\n" "資料並使用來自\n" "%s 的資料取代" #: ../src/gtk-ui/sync-ui.c:1810 #, c-format msgid "" "Delete all data on\n" "%s and replace\n" "with your local data" msgstr "" "刪除所有 %s 上的\n" "資料並使用您的本機\n" "資料取代" #: ../src/gtk-ui/sync-ui.c:2275 msgid "Failed to get list of supported services from SyncEvolution" msgstr "從 SyncEvolution 取得支援的服務清單時失敗" #: ../src/gtk-ui/sync-ui.c:2329 msgid "" "There was a problem communicating with the sync process. Please try again " "later." msgstr "與同步程序溝通成有錯誤發生。請稍候重試。" #: ../src/gtk-ui/sync-ui.c:2388 msgid "Restore failed" msgstr "還原失敗" #: ../src/gtk-ui/sync-ui.c:2391 ../src/gtk-ui/sync-ui.c:3276 msgid "Sync failed" msgstr "同步失敗" #: ../src/gtk-ui/sync-ui.c:2397 msgid "Restore complete" msgstr "還原完成" #: ../src/gtk-ui/sync-ui.c:2400 msgid "Sync complete" msgstr "同步完成" #: ../src/gtk-ui/sync-ui.c:2492 #, c-format msgid "Preparing '%s'" msgstr "正在準備「%s」" #: ../src/gtk-ui/sync-ui.c:2495 #, c-format msgid "Receiving '%s'" msgstr "正在接收「%s」" #: ../src/gtk-ui/sync-ui.c:2498 #, c-format msgid "Sending '%s'" msgstr "正在傳送「%s」" #: ../src/gtk-ui/sync-ui.c:2619 #, c-format msgid "There was one remote rejection." msgid_plural "There were %ld remote rejections." msgstr[0] "有 %ld 個遠端拒絕。" #: ../src/gtk-ui/sync-ui.c:2624 #, c-format msgid "There was one local rejection." msgid_plural "There were %ld local rejections." msgstr[0] "有 %ld 個本機拒絕。" #: ../src/gtk-ui/sync-ui.c:2629 #, c-format msgid "There were %ld local rejections and %ld remote rejections." msgstr "有過 %ld 次本機拒絕和 %ld 次遠端拒絕。" #: ../src/gtk-ui/sync-ui.c:2634 #, c-format msgid "Last time: No changes." msgstr "上次:沒有變更。" #: ../src/gtk-ui/sync-ui.c:2636 #, c-format msgid "Last time: Sent one change." msgid_plural "Last time: Sent %ld changes." msgstr[0] "上次:已傳送 %ld 個變更。" #. This is about changes made to the local data. Not all of these #. changes were requested by the remote server, so "applied" #. is a better word than "received" (bug #5185). #: ../src/gtk-ui/sync-ui.c:2644 #, c-format msgid "Last time: Applied one change." msgid_plural "Last time: Applied %ld changes." msgstr[0] "上次:已套用 %ld 個變更。" #: ../src/gtk-ui/sync-ui.c:2649 #, c-format msgid "Last time: Applied %ld changes and sent %ld changes." msgstr "上次:套用了 %ld 個變更並傳送了 %ld 個變更。" #. TRANSLATORS: the placeholder is a error message (hopefully) #. * explaining the problem #: ../src/gtk-ui/sync-ui.c:2856 #, c-format msgid "" "There was a problem with last sync:\n" "%s" msgstr "" "上次同步有錯誤發生:\n" "%s" #: ../src/gtk-ui/sync-ui.c:2866 #, c-format msgid "" "You've just restored a backup. The changes have not been synced with %s yet" msgstr "您剛才已經還原一份備份。這些變更尚未與 %s 同步" #: ../src/gtk-ui/sync-ui.c:3154 msgid "Waiting for current operation to finish..." msgstr "正在等候目前的操作完成..." #. TRANSLATORS: next strings are error messages. #: ../src/gtk-ui/sync-ui.c:3188 msgid "" "A normal sync is not possible at this time. The server suggests a slow sync," " but this might not always be what you want if both ends already have data." msgstr "一般同步目前無法使用。這台伺服器建議使用慢速同步,但這可能不總是您想要的,因為雙方可能都已經有資料。" #: ../src/gtk-ui/sync-ui.c:3192 msgid "The sync process died unexpectedly." msgstr "同步程序在沒有預期的情況下終止了。" #: ../src/gtk-ui/sync-ui.c:3197 msgid "" "Password request was not answered. You can save the password in the settings" " to prevent the request." msgstr "密碼請求沒有回應。您可以在設定值內儲存密碼來略過請求。" #. TODO use the service device name here, this is a remote problem #: ../src/gtk-ui/sync-ui.c:3201 msgid "There was a problem processing sync request. Trying again may help." msgstr "同步請發生問題。重試一次可能會有幫助。" #: ../src/gtk-ui/sync-ui.c:3207 msgid "" "Failed to login. Could there be a problem with your username or password?" msgstr "登入失敗。您的使用者名稱或密碼是否有問題?" #: ../src/gtk-ui/sync-ui.c:3210 msgid "Forbidden" msgstr "禁止" #. TRANSLATORS: data source means e.g. calendar or addressbook #: ../src/gtk-ui/sync-ui.c:3216 msgid "" "A data source could not be found. Could there be a problem with the " "settings?" msgstr "無法找到資料來源。設定值是否有問題?" #: ../src/gtk-ui/sync-ui.c:3220 msgid "Remote database error" msgstr "遠端資料庫錯誤" #. This can happen when EDS is borked, restart it may help... #: ../src/gtk-ui/sync-ui.c:3223 msgid "" "There is a problem with the local database. Syncing again or rebooting may " "help." msgstr "本機資料庫有問題。再次同步或是重新開機可能會有所幫助。" #: ../src/gtk-ui/sync-ui.c:3226 msgid "No space on disk" msgstr "磁碟沒有空間" #: ../src/gtk-ui/sync-ui.c:3228 msgid "Failed to process SyncML" msgstr "進行 SyncML 動作失敗" #: ../src/gtk-ui/sync-ui.c:3230 msgid "Server authorization failed" msgstr "伺服器授權失敗" #: ../src/gtk-ui/sync-ui.c:3232 msgid "Failed to parse configuration file" msgstr "解析設置檔案動作失敗" #: ../src/gtk-ui/sync-ui.c:3234 msgid "Failed to read configuration file" msgstr "讀取設置檔案動作失敗" #: ../src/gtk-ui/sync-ui.c:3236 msgid "No configuration found" msgstr "未找到設置" #: ../src/gtk-ui/sync-ui.c:3238 msgid "No configuration file found" msgstr "未找到設置檔案" #: ../src/gtk-ui/sync-ui.c:3240 msgid "Server sent bad content" msgstr "伺服器傳送出壞的內容" #: ../src/gtk-ui/sync-ui.c:3242 msgid "Connection certificate has expired" msgstr "連線憑證己過期" #: ../src/gtk-ui/sync-ui.c:3244 msgid "Connection certificate is invalid" msgstr "連線憑證無效" #: ../src/gtk-ui/sync-ui.c:3252 msgid "" "We were unable to connect to the server. The problem could be temporary or " "there could be something wrong with the settings." msgstr "我們無法連接到伺服器。問題可能是暫時的,或是伺服器的設定值可能有問題。" #: ../src/gtk-ui/sync-ui.c:3259 msgid "The server URL is bad" msgstr "伺服器的 URL 不對" #: ../src/gtk-ui/sync-ui.c:3264 msgid "The server was not found" msgstr "找不到伺服器" #: ../src/gtk-ui/sync-ui.c:3266 #, c-format msgid "Error %d" msgstr "錯誤 %d" #. TRANSLATORS: password request dialog contents: title, cancel button #. * and ok button #: ../src/gtk-ui/sync-ui.c:3404 msgid "Password is required for sync" msgstr "同步需要密碼" #: ../src/gtk-ui/sync-ui.c:3408 msgid "Sync with password" msgstr "使用密碼進行同步" #. TRANSLATORS: password request dialog message, placeholder is service name #: ../src/gtk-ui/sync-ui.c:3418 #, c-format msgid "Please enter password for syncing with %s:" msgstr "請輸入使用 %s 進行同步的密碼:" #. title for the buttons on the right side of main view #: ../src/gtk-ui/ui.glade.h:2 msgid "Actions" msgstr "動作" #. text between the two "start from scratch" buttons in emergency view #: ../src/gtk-ui/ui.glade.h:4 msgid "or" msgstr "" #: ../src/gtk-ui/ui.glade.h:5 msgid "Direct sync" msgstr "直接同步" #: ../src/gtk-ui/ui.glade.h:6 msgid "Network sync" msgstr "網路同步" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:8 msgid "Restore from backup" msgstr "從備份還原" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:10 msgid "Slow sync" msgstr "慢速同步" #. a title in emergency view #: ../src/gtk-ui/ui.glade.h:12 msgid "Start from scratch" msgstr "從零開始" #: ../src/gtk-ui/ui.glade.h:13 msgid "" "A slow sync compares items from both sides and tries to merge them. \n" "This may fail in some cases, leading to duplicates or lost information." msgstr "" "慢速同步會比較兩邊的項目,並嘗試合併它們。\n" "這在某些情況下可能會失敗,並導致副本產生或是遺失資訊。" #: ../src/gtk-ui/ui.glade.h:15 msgid "Add new device" msgstr "加入新裝置" #: ../src/gtk-ui/ui.glade.h:16 msgid "Add new service" msgstr "新增服務" #. explanation of "Restore backup" function #: ../src/gtk-ui/ui.glade.h:18 msgid "" "Backups are made before every time we Sync. Choose a backup to restore. Any " "changes you have made since then will be lost." msgstr "在我們每次同步前都會進行備份。選擇要還原的備份。所有您從那時起的變更都會喪失。" #: ../src/gtk-ui/ui.glade.h:19 msgid "Calendar" msgstr "行事曆" #. Button in main view, right side. Keep to below 20 chars per line, feel free #. to use two lines #: ../src/gtk-ui/ui.glade.h:21 msgid "" "Change or edit\n" "sync service" msgstr "" "變更或編輯\n" "同步服務" #. close button for settings window #: ../src/gtk-ui/ui.glade.h:24 msgid "Close" msgstr "關閉" #: ../src/gtk-ui/ui.glade.h:25 msgid "" "Delete all data on Zyb \n" "and replace with your\n" "local information" msgstr "刪除所有遠端資料,並使用本機資料取代" #: ../src/gtk-ui/ui.glade.h:28 msgid "" "Delete all your local\n" "information and replace\n" "with data from Zyb" msgstr "刪除所有本機資料,並使用遠端資料取代" #. button in main view, right side. Keep length to 20 characters or so, use #. two lines if needed #: ../src/gtk-ui/ui.glade.h:32 msgid "" "Fix a sync\n" "emergency" msgstr "" "修正同步\n" "緊急狀況" #: ../src/gtk-ui/ui.glade.h:34 msgid "" "If you don't see your service above but know that your sync provider uses SyncML\n" "you can setup a service manually." msgstr "" "如果您在上方沒有看到您的服務,但是您知道您的同步功能供應廠商使用 SyncML 的話,\n" "您可以手動設定服務。" #: ../src/gtk-ui/ui.glade.h:36 msgid "Settings" msgstr "設定" #: ../src/gtk-ui/ui.glade.h:39 msgid "Sync Emergency" msgstr "同步緊急狀況" #: ../src/gtk-ui/ui.glade.h:41 msgid "" "To sync you'll need a network connection and an account with a sync service.\n" "We support the following services: " msgstr "" "若要同步,您需要有網路連線以及使用同步服務的帳號。\n" "我們支援下列服務:" #: ../src/gtk-ui/ui.glade.h:43 msgid "Use Bluetooth to Sync your data from one device to another." msgstr "使用藍牙從某裝置同步您的資料到另一個裝置去。" #: ../src/gtk-ui/ui.glade.h:44 msgid "You will need to add Bluetooth devices before they can be synced." msgstr "在它們能被同步前,您需要先加入藍牙裝置。" #: ../src/gtk-ui/sync.desktop.in.h:2 msgid "Up to date" msgstr "已是最新" #: ../src/gtk-ui/sync-gtk.desktop.in.h:1 msgid "SyncEvolution (GTK)" msgstr "SyncEvolution (GTK)" #: ../src/gtk-ui/sync-gtk.desktop.in.h:2 msgid "Synchronize PIM data" msgstr "將個人資訊管理系統 (PIM) 的資料同步" #: ../src/gtk-ui/sync-config-widget.c:88 msgid "" "ScheduleWorld enables you to keep your contacts, events, tasks, and notes in" " sync." msgstr "ScheduleWorld 可以讓您的連絡人、行事曆事項、工作、以及註記保持同步。" #: ../src/gtk-ui/sync-config-widget.c:91 msgid "" "Google Sync can back up and synchronize your contacts with your Gmail " "contacts." msgstr "Google Sync 可以將您的聯絡人和 Gmail 連絡人進行備份以及同步。" #. TRANSLATORS: Please include the word "demo" (or the equivalent in #. your language): Funambol is going to be a 90 day demo service #. in the future #: ../src/gtk-ui/sync-config-widget.c:97 msgid "" "Back up your contacts and calendar. Sync with a single click, anytime, " "anywhere (DEMO)." msgstr "備份您的連絡人與行事曆。一個按鍵就同步,任何時間、任何地點 (樣本)。" #: ../src/gtk-ui/sync-config-widget.c:100 msgid "" "Mobical Backup and Restore service allows you to securely back up your " "personal mobile data for free." msgstr "Mobical 備份與還原服務讓您可以安全地備份個人行動資料,而且免費。" #: ../src/gtk-ui/sync-config-widget.c:103 msgid "" "ZYB is a simple way for people to store and share mobile information online." msgstr "ZYB 是人們儲存與線上分享行動資訊的一種簡單方法。" #: ../src/gtk-ui/sync-config-widget.c:106 msgid "" "Memotoo lets you access your personal data from any computer connected to " "the Internet." msgstr "Memotoo 能讓您從任何連接到網路的電腦存取您的個人資料。" #: ../src/gtk-ui/sync-config-widget.c:195 msgid "Sorry, failed to save the configuration" msgstr "很抱歉,儲存設定檔失敗" #: ../src/gtk-ui/sync-config-widget.c:445 msgid "Service must have a name and server URL" msgstr "服務項目必須要有一個名稱和伺服器網址" #. TRANSLATORS: error dialog when creating a new sync configuration #: ../src/gtk-ui/sync-config-widget.c:451 msgid "A username is required for this service" msgstr "此服務需要使用者名稱" #: ../src/gtk-ui/sync-config-widget.c:493 #, c-format msgid "" "Do you want to reset the settings for %s? This will not remove any synced " "information on either end." msgstr "您想要為 %s 重設設定嗎?這不會移除任何兩方同步過的資訊。" #. TRANSLATORS: buttons in reset-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:497 msgid "Yes, reset" msgstr "是,重設" #: ../src/gtk-ui/sync-config-widget.c:498 #: ../src/gtk-ui/sync-config-widget.c:509 msgid "No, keep settings" msgstr "不,維持設定" #: ../src/gtk-ui/sync-config-widget.c:503 #, c-format msgid "" "Do you want to delete the settings for %s? This will not remove any synced " "information on either end but it will remove these settings." msgstr "您想要為 %s 刪除設定值嗎?這不會移除任何兩端同步過的資訊,但是它會移除這些設定值。" #. TRANSLATORS: buttons in delete-service warning dialog #: ../src/gtk-ui/sync-config-widget.c:508 msgid "Yes, delete" msgstr "是,刪除" #: ../src/gtk-ui/sync-config-widget.c:539 msgid "Reset settings" msgstr "重設設定值" #: ../src/gtk-ui/sync-config-widget.c:542 msgid "Delete settings" msgstr "刪除設定值" #: ../src/gtk-ui/sync-config-widget.c:552 msgid "Save and use" msgstr "儲存並使用" #: ../src/gtk-ui/sync-config-widget.c:555 msgid "" "Save and replace\n" "current service" msgstr "" "儲存並取代\n" "目前的服務" #: ../src/gtk-ui/sync-config-widget.c:563 msgid "Stop using device" msgstr "停止使用裝置" #: ../src/gtk-ui/sync-config-widget.c:566 msgid "Stop using service" msgstr "停止使用服務" #. TRANSLATORS: label for an entry in service configuration form. #. * Placeholder is a source name. #. * Example: "Appointments URI" #: ../src/gtk-ui/sync-config-widget.c:749 #, c-format msgid "%s URI" msgstr "%s URI" #. TRANSLATORS: toggles in service configuration form, placeholder is service #. * or device name #: ../src/gtk-ui/sync-config-widget.c:936 #, c-format msgid "Send changes to %s" msgstr "傳送變更到 %s" #: ../src/gtk-ui/sync-config-widget.c:941 #, c-format msgid "Receive changes from %s" msgstr "接收來自 %s 的變更" #: ../src/gtk-ui/sync-config-widget.c:957 msgid "Sync" msgstr "同步" #. TRANSLATORS: label of a entry in service configuration #: ../src/gtk-ui/sync-config-widget.c:973 msgid "Server address" msgstr "伺服器位址" #. TRANSLATORS: explanation before a device template combobox. #. * Placeholder is a device name like 'Nokia N85' or 'Syncevolution #. * Client' #: ../src/gtk-ui/sync-config-widget.c:1049 #, c-format msgid "" "This device looks like it might be a '%s'. If this is not correct, please " "take a look at the list of supported devices and pick yours if it is listed" msgstr "此裝置看起來可能是「%s」。如果不正確的話,請查看支援裝置的清單,如果它列於其中的話請選取您的裝置。" #: ../src/gtk-ui/sync-config-widget.c:1055 #: ../src/gtk-ui/sync-config-widget.c:1915 msgid "" "We don't know what this device is exactly. Please take a look at the list of" " supported devices and pick yours if it is listed" msgstr "我們不知道此裝置到底是什麼。請查看支援裝置的清單,如果它列於其中的話請選取您的裝置。" #: ../src/gtk-ui/sync-config-widget.c:1207 #, c-format msgid "%s - Bluetooth device" msgstr "%s - 藍牙裝置" #. TRANSLATORS: service title for services that are not based on a #. * template in service list, the placeholder is the name of the service #: ../src/gtk-ui/sync-config-widget.c:1213 #, c-format msgid "%s - manually setup" msgstr "%s - 手動設置" #. TRANSLATORS: link button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1886 msgid "Launch website" msgstr "開啟網站" #. TRANSLATORS: button in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1895 msgid "Set up now" msgstr "立刻設置" #: ../src/gtk-ui/sync-config-widget.c:1953 msgid "Use these settings" msgstr "使用這些設定" #. TRANSLATORS: labels of entries in service configuration form #: ../src/gtk-ui/sync-config-widget.c:1991 msgid "Username" msgstr "使用者名稱" #: ../src/gtk-ui/sync-config-widget.c:2006 msgid "Password" msgstr "密碼" #. TRANSLATORS: warning in service configuration form for people #. who have modified the configuration via other means. #: ../src/gtk-ui/sync-config-widget.c:2029 msgid "" "Current configuration is more complex than what can be shown here. Changes " "to sync mode or synced data types will overwrite that configuration." msgstr "目前的設定檔比這裡能夠顯示的還要複雜許多。變更為同步模式或是同步過的資料類型將會覆蓋寫入那份設定檔。" #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2048 msgid "Hide server settings" msgstr "隱藏伺服器設定" #. TRANSLATORS: this is the epander label for server settings #. in service configuration form #: ../src/gtk-ui/sync-config-widget.c:2068 msgid "Show server settings" msgstr "顯示伺服器設定" #: ../src/gnome-bluetooth/syncevolution.c:110 msgid "Sync in the Sync application" msgstr "在「同步」應用程式內同步" #: ../src/syncevo-dbus-server.cpp:6653 #, c-format msgid "%s is syncing" msgstr "%s 正在同步中" #: ../src/syncevo-dbus-server.cpp:6654 #, c-format msgid "We have just started to sync your computer with the %s sync service." msgstr "我們剛開始將您的電腦同步到 %s 同步服務。" #. if sync is successfully started and done #: ../src/syncevo-dbus-server.cpp:6670 #, c-format msgid "%s sync complete" msgstr "%s 同步完成" #: ../src/syncevo-dbus-server.cpp:6671 #, c-format msgid "We have just finished syncing your computer with the %s sync service." msgstr "我們剛完成將您的電腦同步到 %s 同步服務。" #. if sync is successfully started and has errors, or not started successful #. with a fatal problem #: ../src/syncevo-dbus-server.cpp:6676 msgid "Sync problem." msgstr "同步發生問題。" #: ../src/syncevo-dbus-server.cpp:6677 msgid "Sorry, there's a problem with your sync that you need to attend to." msgstr "抱歉,您的同步有個問題是您需要關心的。" #: ../src/syncevo-dbus-server.cpp:6758 msgid "View" msgstr "檢視" #. Use "default" as ID because that is what mutter-moblin #. recognizes: it then skips the action instead of adding it #. in addition to its own "Dismiss" button (always added). #: ../src/syncevo-dbus-server.cpp:6762 msgid "Dismiss" msgstr "摒棄" syncevolution_1.4/setup-variables.am000066400000000000000000000025351230021373600200100ustar00rootroot00000000000000## This file just specifies all variables used in entire project. ## If any of them was specified before this file is included then those ones ## won't change the value. ## ## The reason for it is that many files are using BUILT_SOURCES, CLEANFILES and ## other variables. This file allows them to harmlessly append stuff ## to them without worrying whether the variable was defined earlier. # clean variables CLEANFILES = DISTCLEANFILES = MAINTAINERCLEANFILES = MOSTLYCLEANFILES = # standard arch dependent pkg-config dir pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = # Install test suite (client-test, test-dbus.py, testpim.py, etc.). testparentdir = $(libdir)/syncevolution # Must end in "/test" so that we can use nobase_testparent_DATA=test/... testdir = $(testparentdir)/test test_DATA = nobase_test_DATA = test_PROGRAMS = test_SCRIPTS = nobase_testparent_DATA = # standard variables with standard prefixes dist_doc_DATA = dist_noinst_DATA = dist_pkgdata_DATA = doc_DATA = nodist_noinst_DATA = lib_LTLIBRARIES = noinst_LTLIBRARIES = bin_PROGRAMS = libexec_PROGRAMS = noinst_PROGRAMS = bin_SCRIPTS = dist_noinst_SCRIPTS = libexec_SCRIPTS = nodist_bin_SCRIPTS = # other all_dist_hooks = all_install_exec_hooks = all_uninstall_hooks = all_local_installchecks = all_phonies = BUILT_SOURCES = DIST_SUBDIRS = EXTRA_DIST = EXTRA_PROGRAMS = SUBDIRS = TESTS = syncevolution_1.4/src/000077500000000000000000000000001230021373600151455ustar00rootroot00000000000000syncevolution_1.4/src/README.h000066400000000000000000000033211230021373600162520ustar00rootroot00000000000000/* * Copyright (C) 2008 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ /** * @mainpage Getting Started * * This documentation for SyncEvolution and the Funambol C++ Client * API was generated automatically from the source code. * * While most of the classes in SyncEvolution are documented, very * little effort was spent on organizing this information in a coherent * way. If you are a developer who wants to write a SyncML client based * on the SyncEvolution framework, then you should have a look at * the following classes: * - TrackingSyncSource is the most convenient class to derive from. * - EvolutionSyncSource is a bit more general. * - RegisterSyncSource adds additional sources to the framework. * * The following classes help with testing your derived classes: * - RegisterSyncSourceTest is what you have to use. * - TestEvolution uses that information. * - ClientTest, LocalTests, SyncTests are used by TestEvolution. * * The FileSyncSource is a good example to get started. */ syncevolution_1.4/src/README.templates000066400000000000000000000013161230021373600200230ustar00rootroot00000000000000The configuration templates in "templates" get installed into $(datadir)/syncevolution/templates. When adding/changing a new server, then only enter the properties which need to be changed here so that the default values can be used for the remaining properties. An icon can be added here for servers. The file name must start with "icon". Server configurations must be kept in sync in three different places: - here (if a server is installed as files) - in SyncEvolutionConfig.cpp's EvolutionSyncConfig::createServerTemplate() - in SyncEvolutionCmdline.cpp's test server configs - in test/test-dbus.py testGetConfigsTemplates() Note that server icons must come with a suitable license that allows redistribution. syncevolution_1.4/src/async.patch000066400000000000000000000433531230021373600173130ustar00rootroot00000000000000This patch demonstrates how switching from the current, synchronous to the corresponding asynchronous API could work. This helps to avoid some timeouts with EDS-DBus, but not all: the e_book_async_get_changes() call still times out. Index: src/EvolutionContactSource.cpp =================================================================== RCS file: /cvsroot/sync4jevolution/sync4jevolution/src/EvolutionContactSource.cpp,v retrieving revision 1.50 diff -c -r1.50 EvolutionContactSource.cpp *** src/EvolutionContactSource.cpp 3 Jan 2007 20:58:41 -0000 1.50 --- src/EvolutionContactSource.cpp 22 Feb 2007 19:27:50 -0000 *************** *** 27,32 **** --- 27,33 ---- #ifdef ENABLE_EBOOK #include "EvolutionContactSource.h" + #include #include #include "vocl/VConverter.h" *************** *** 154,159 **** --- 155,207 ---- } } + void EvolutionContactSource::addContacts(void *custom, GList *nextItem) + { + EvolutionContactSource *source = (EvolutionContactSource *)custom; + + while (nextItem) { + const char *uid = (const char *)e_contact_get_const(E_CONTACT(nextItem->data), + E_CONTACT_UID); + source->m_allItems.addItem(uid); + nextItem = nextItem->next; + } + } + + void EvolutionContactSource::addChanges(EBook *book, + EBookStatus status, + GList *nextItem, + gpointer custom) + { + EvolutionContactSource *source = (EvolutionContactSource *)custom; + source->m_status = status; + + while (nextItem) { + EBookChange *ebc = (EBookChange *)nextItem->data; + + if (ebc->contact) { + const char *uid = (const char *)e_contact_get_const( ebc->contact, E_CONTACT_UID ); + + if (uid) { + switch (ebc->change_type) { + case E_BOOK_CHANGE_CARD_ADDED: + source->m_newItems.addItem(uid); + break; + case E_BOOK_CHANGE_CARD_MODIFIED: + source->m_updatedItems.addItem(uid); + break; + case E_BOOK_CHANGE_CARD_DELETED: + source->m_deletedItems.addItem(uid); + break; + } + } + } + nextItem = nextItem->next; + } + + source->m_loop.quit(); + } + + void EvolutionContactSource::beginSyncThrow(bool needAll, bool needPartial, bool deleteLocal) *************** *** 161,234 **** GError *gerror = NULL; if (deleteLocal) { ! eptr allItemsQuery( e_book_query_any_field_contains(""), "query" ); ! GList *nextItem; ! if (!e_book_get_contacts( m_addressbook, allItemsQuery, &nextItem, &gerror )) { ! throwError( "reading all items", gerror ); ! } ! while (nextItem) { ! const char *uid = (const char *)e_contact_get_const(E_CONTACT(nextItem->data), ! E_CONTACT_UID); ! if (!e_book_remove_contact( m_addressbook, uid, &gerror ) ) { ! throwError( string( "deleting contact " ) + uid, gerror ); } - nextItem = nextItem->next; } } if (needAll) { ! eptr allItemsQuery( e_book_query_any_field_contains(""), "query" ); ! GList *nextItem; ! if (!e_book_get_contacts( m_addressbook, allItemsQuery, &nextItem, &gerror )) { ! throwError( "reading all items", gerror ); ! } ! while (nextItem) { ! const char *uid = (const char *)e_contact_get_const(E_CONTACT(nextItem->data), ! E_CONTACT_UID); ! m_allItems.addItem(uid); ! nextItem = nextItem->next; ! } } if (needPartial) { GList *nextItem; if (!e_book_get_changes( m_addressbook, (char *)m_changeId.c_str(), &nextItem, &gerror )) { throwError( "reading changes", gerror ); } ! while (nextItem) { ! EBookChange *ebc = (EBookChange *)nextItem->data; ! ! if (ebc->contact) { ! const char *uid = (const char *)e_contact_get_const( ebc->contact, E_CONTACT_UID ); ! ! if (uid) { ! switch (ebc->change_type) { ! case E_BOOK_CHANGE_CARD_ADDED: ! m_newItems.addItem(uid); ! break; ! case E_BOOK_CHANGE_CARD_MODIFIED: ! m_updatedItems.addItem(uid); ! break; ! case E_BOOK_CHANGE_CARD_DELETED: ! m_deletedItems.addItem(uid); ! break; ! } ! } ! } ! nextItem = nextItem->next; } } } void EvolutionContactSource::endSyncThrow() { if (m_isModified) { - GError *gerror = NULL; - GList *nextItem; // move change_id forward so that our own changes are not listed the next time ! if (!e_book_get_changes( m_addressbook, (char *)m_changeId.c_str(), &nextItem, &gerror )) { ! throwError( "reading changes", gerror ); } } resetItems(); --- 209,264 ---- GError *gerror = NULL; if (deleteLocal) { ! m_allItems.clear(); ! listAllContacts(addContacts, this); ! ! EvolutionSyncSource::itemList::const_iterator it; ! for (it = m_allItems.begin(); ! it != m_allItems.end(); ! ++it) { ! if (!e_book_remove_contact( m_addressbook, it->c_str(), &gerror ) ) { ! throwError( string( "deleting contact " ) + *it, gerror ); } } + m_allItems.clear(); } if (needAll) { ! listAllContacts(addContacts, this); } if (needPartial) { + #if 0 + // times out on N770 + GError *gerror = NULL; GList *nextItem; if (!e_book_get_changes( m_addressbook, (char *)m_changeId.c_str(), &nextItem, &gerror )) { throwError( "reading changes", gerror ); } ! addChanges(m_addressbook, E_BOOK_ERROR_OK, nextItem, (gpointer)this); ! #else ! if (e_book_async_get_changes(m_addressbook, (char *)m_changeId.c_str(), addChanges, this)) { ! throwError( "reading changes", gerror ); } + m_loop.run(); + if (m_status != E_BOOK_ERROR_OK) { + throw runtime_error("reading changes stopped with an error"); + } + #endif } } void EvolutionContactSource::endSyncThrow() { if (m_isModified) { // move change_id forward so that our own changes are not listed the next time ! if (e_book_async_get_changes(m_addressbook, (char *)m_changeId.c_str(), addChanges, this)) { ! throw runtime_error("reading changes"); ! } ! m_loop.run(); ! if (m_status != E_BOOK_ERROR_OK) { ! throw runtime_error("reading changes stopped with an error"); } } resetItems(); *************** *** 241,263 **** m_addressbook = NULL; } ! void EvolutionContactSource::exportData(ostream &out) { ! eptr allItemsQuery( e_book_query_any_field_contains(""), "query" ); ! GList *nextItem; ! GError *gerror = NULL; ! if (!e_book_get_contacts( m_addressbook, allItemsQuery, &nextItem, &gerror )) { ! throwError( "reading all items", gerror ); ! } while (nextItem) { eptr vcardstr(e_vcard_to_string(&E_CONTACT(nextItem->data)->parent, EVC_FORMAT_VCARD_30)); ! out << (const char *)vcardstr << "\r\n\r\n"; nextItem = nextItem->next; } } SyncItem *EvolutionContactSource::createItem( const string &uid, SyncState state ) { logItem( uid, "extracting from EV" ); --- 271,296 ---- m_addressbook = NULL; } ! ! static void dumpContacts(void *custom, GList *nextItem) { ! ostream *out = (ostream *)custom; ! while (nextItem) { eptr vcardstr(e_vcard_to_string(&E_CONTACT(nextItem->data)->parent, EVC_FORMAT_VCARD_30)); ! *out << (const char *)vcardstr << "\r\n\r\n"; ! nextItem = nextItem->next; } } + void EvolutionContactSource::exportData(ostream &out) + { + listAllContacts(dumpContacts, (void *)&out); + } + SyncItem *EvolutionContactSource::createItem( const string &uid, SyncState state ) { logItem( uid, "extracting from EV" ); *************** *** 773,776 **** --- 806,876 ---- } } + class EvolutionContactListAll { + public: + EvolutionContactListAll(void (*processList)(void *custom, GList *list), void *custom, EvolutionContactSource &source): + m_processList(processList), + m_custom(custom), + m_source(source), + m_status(E_BOOK_VIEW_STATUS_OK) + {} + + static void contactsAdded(EBookView *ebookview, + gpointer arg1, + gpointer user_data) { + EvolutionContactListAll *listAll = (EvolutionContactListAll *)user_data; + listAll->m_processList(listAll->m_custom, (GList *)arg1); + } + + static void sequenceDone(EBookView *ebookview, + gint arg1, + gpointer user_data) { + EvolutionContactListAll *listAll = (EvolutionContactListAll *)user_data; + listAll->m_status = (EBookViewStatus)arg1; + listAll->m_source.m_loop.quit(); + } + + /** throw an error exception if an error occurred */ + void checkStatus() { + if (m_status != E_BOOK_VIEW_STATUS_OK) { + throw runtime_error("iterating over all contacts failed"); + } + } + + private: + void (*m_processList)(void *custom, GList *list); + void *m_custom; + EvolutionContactSource &m_source; + EBookViewStatus m_status; + }; + + + + + + void EvolutionContactSource::listAllContacts(void (*processList)(void *custom, GList *list), void *custom) + { + eptr allItemsQuery( e_book_query_any_field_contains(""), "query" ); + GError *gerror = NULL; + EBookView *viewptr; + + if (e_book_get_book_view(m_addressbook, allItemsQuery, NULL, -1, &viewptr, &gerror)) { + eptr view(viewptr); + EvolutionContactListAll listAll(processList, custom, *this); + + g_signal_connect(viewptr, "contacts-added", G_CALLBACK(listAll.contactsAdded), &listAll); + g_signal_connect(viewptr, "sequence-complete", G_CALLBACK(listAll.sequenceDone), &listAll); + e_book_view_start(view); + m_loop.run(); + e_book_view_stop(view); + // workaround for http://bugzilla.gnome.org/show_bug.cgi?id=399011 + // Without the sleep() EDS often (but not always) crashes in e_book_backend_get_book_views() + // during one of the following calls. + sleep(1); + listAll.checkStatus(); + } else { + throwError( "getting view on addressbook", gerror ); + } + } + #endif /* ENABLE_EBOOK */ Index: src/EvolutionContactSource.h =================================================================== RCS file: /cvsroot/sync4jevolution/sync4jevolution/src/EvolutionContactSource.h,v retrieving revision 1.22 diff -c -r1.22 EvolutionContactSource.h *** src/EvolutionContactSource.h 10 Dec 2006 17:35:18 -0000 1.22 --- src/EvolutionContactSource.h 22 Feb 2007 19:27:50 -0000 *************** *** 32,37 **** --- 32,49 ---- #include /** + * callback used by EvolutionContactSource::listAll() and + */ + class EvolutionCallback + { + public: + /** + * Called to iterate over data. Content of list depends on context. + */ + virtual void processList(GList *list) = 0; + }; + + /** * Implements access to Evolution address books. */ class EvolutionContactSource : public EvolutionSyncSource *************** *** 81,87 **** // implementation of SyncSource // virtual ArrayElement *clone() { return new EvolutionContactSource(*this); } ! protected: // // implementation of EvolutionSyncSource callbacks --- 93,102 ---- // implementation of SyncSource // virtual ArrayElement *clone() { return new EvolutionContactSource(*this); } ! ! /** start and stop event processing on this source */ ! EvolutionAsync m_loop; ! protected: // // implementation of EvolutionSyncSource callbacks *************** *** 142,149 **** --- 157,191 ---- insert("CALURI"); } } m_uniqueProperties; + + /** + * extracts all contacts + * + * @param processList is fed the contacts, possibly in multiple chunks + * @param custom pointer passed through to processList + */ + void listAllContacts(void (*processList)(void *custom, GList *list), void *custom); + + /** + * callback for listAllContacts() which adds all contacts to m_allItems + */ + static void addContacts(void *custom, GList *nextItem); + + /** + * EBookListCallback for beginSyncThrow()'s e_book_async_get_changes () + */ + static void addChanges(EBook *book, + EBookStatus status, + GList *nextItem, + gpointer custom); + /** + * status passed to addChanges() + */ + EBookStatus m_status; }; + + #endif // ENABLE_EBOOK #endif // INCL_EVOLUTIONCONTACTSOURCE Index: src/EvolutionSmartPtr.h =================================================================== RCS file: /cvsroot/sync4jevolution/sync4jevolution/src/EvolutionSmartPtr.h,v retrieving revision 1.8 diff -c -r1.8 EvolutionSmartPtr.h *** src/EvolutionSmartPtr.h 10 Dec 2006 17:35:19 -0000 1.8 --- src/EvolutionSmartPtr.h 22 Feb 2007 19:27:50 -0000 *************** *** 34,39 **** --- 34,40 ---- void inline unref( char *pointer ) { free( pointer ); } void inline unref( GObject *pointer ) { g_object_unref( pointer ); } + void inline unref( GMainLoop *pointer ) { g_main_loop_unref( pointer ); } #ifdef ENABLE_EBOOK void inline unref( EBookQuery *pointer ) { e_book_query_unref( pointer ); } #endif Index: src/EvolutionSyncClient.cpp =================================================================== RCS file: /cvsroot/sync4jevolution/sync4jevolution/src/EvolutionSyncClient.cpp,v retrieving revision 1.24 diff -c -r1.24 EvolutionSyncClient.cpp *** src/EvolutionSyncClient.cpp 17 Dec 2006 16:33:45 -0000 1.24 --- src/EvolutionSyncClient.cpp 22 Feb 2007 19:27:50 -0000 *************** *** 432,438 **** int res = DMTClientConfig::readDevInfoConfig(syncMLNode, syncMLNode); // always read device ID from the traditional property "deviceId" ! eptr tmp(syncMLNode.readPropertyValue("deviceId")); deviceConfig.setDevID(tmp); return res; --- 432,438 ---- int res = DMTClientConfig::readDevInfoConfig(syncMLNode, syncMLNode); // always read device ID from the traditional property "deviceId" ! arrayptr tmp(syncMLNode.readPropertyValue("deviceId")); deviceConfig.setDevID(tmp); return res; *************** *** 482,489 **** // redirect logging as soon as possible SourceList sourceList(m_server, m_doLogging); ! eptr logdir(config.getSyncMLNode()->readPropertyValue("logdir")); ! eptr maxlogdirs(config.getSyncMLNode()->readPropertyValue("maxlogdirs")); sourceList.setLogdir(logdir, atoi(maxlogdirs)); SyncSourceConfig *sourceconfigs = config.getSyncSourceConfigs(); --- 482,489 ---- // redirect logging as soon as possible SourceList sourceList(m_server, m_doLogging); ! arrayptr logdir(config.getSyncMLNode()->readPropertyValue("logdir")); ! arrayptr maxlogdirs(config.getSyncMLNode()->readPropertyValue("maxlogdirs")); sourceList.setLogdir(logdir, atoi(maxlogdirs)); SyncSourceConfig *sourceconfigs = config.getSyncSourceConfigs(); Index: src/EvolutionSyncSource.h =================================================================== RCS file: /cvsroot/sync4jevolution/sync4jevolution/src/EvolutionSyncSource.h,v retrieving revision 1.24 diff -c -r1.24 EvolutionSyncSource.h *** src/EvolutionSyncSource.h 10 Dec 2006 17:35:19 -0000 1.24 --- src/EvolutionSyncSource.h 22 Feb 2007 19:27:51 -0000 *************** *** 33,38 **** --- 33,40 ---- #include #include + #include + /** * This class implements the functionality shared by * both EvolutionCalenderSource and EvolutionContactSource: *************** *** 371,374 **** --- 373,400 ---- string m_user, m_passwd; }; + /** + * Utility class which hides the mechanisms needed to handle events + * during asynchronous calls. + */ + class EvolutionAsync { + public: + EvolutionAsync() : + m_loop(g_main_loop_new(NULL, FALSE), "main loop") + {} + + /** start processing events */ + void run() { + g_main_loop_run(m_loop); + } + + /** stop processing events, to be called inside run() by callback */ + void quit() { + g_main_loop_quit(m_loop); + } + + private: + eptr m_loop; + }; + #endif // INCL_EVOLUTIONSYNCSOURCE syncevolution_1.4/src/backends/000077500000000000000000000000001230021373600167175ustar00rootroot00000000000000syncevolution_1.4/src/backends/activesync/000077500000000000000000000000001230021373600210675ustar00rootroot00000000000000syncevolution_1.4/src/backends/activesync/ActiveSyncCalendarSource.cpp000066400000000000000000000701711230021373600264640ustar00rootroot00000000000000/* * Copyright (C) 2010,2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "config.h" #ifdef ENABLE_ACTIVESYNC // include first, it sets HANDLE_LIBICAL_MEMORY for us #include #include "ActiveSyncCalendarSource.h" #include #include SE_BEGIN_CXX ActiveSyncCalendarSource::ActiveSyncCalendarSource(const SyncSourceParams ¶ms, EasItemType type) : ActiveSyncCalFormatSource(params, type) { } void ActiveSyncCalendarSource::beginSync(const std::string &lastToken, const std::string &resumeToken) { // erase content which might have been set in a previous call reset(); // claim item node for our change tracking, if not done already if (m_itemNode && !m_trackingNode) { m_trackingNode.swap(m_itemNode); } // incremental sync (non-empty token) or start from scratch setStartSyncKey(lastToken); if (lastToken.empty()) { // slow sync: wipe out cached list of IDs, will be filled anew below SE_LOG_DEBUG(getDisplayName(), "sync key empty, starting slow sync"); m_trackingNode->clear(); } else { SE_LOG_DEBUG(getDisplayName(), "sync key %s, starting incremental sync", lastToken.c_str()); // re-populate cache from storage, without any item data ConfigProps props; m_trackingNode->readProperties(props); BOOST_FOREACH(const StringPair &prop, props) { const std::string &easid = prop.first; const std::string &value = prop.second; size_t pos = value.find('/'); bool okay = false; if (pos == 0) { pos = value.find('/', 1); if (pos != value.npos) { std::string revision = m_escape.unescape(value.substr(1, pos - 1)); size_t nextpos = value.find('/', pos + 1); if (nextpos != value.npos) { std::string uid = m_escape.unescape(value.substr(pos + 1, nextpos - pos - 1)); boost::shared_ptr &eventptr = m_cache[easid]; if (!eventptr) { eventptr = boost::shared_ptr(new Event); } eventptr->m_easid = easid; eventptr->m_uid = uid; pos = nextpos; while ((nextpos = value.find('/', pos + 1)) != value.npos) { std::string subid = m_escape.unescape(value.substr(pos + 1, nextpos - pos - 1)); eventptr->m_subids.insert(subid); pos = nextpos; } okay = true; } } } if (!okay) { SE_LOG_DEBUG(getDisplayName(), "unsupported or corrupt revision entry: %s = %s", easid.c_str(), value.c_str()); } } } gboolean moreAvailable = TRUE; setCurrentSyncKey(getStartSyncKey()); // same logic as in ActiveSyncSource::beginSync() // TODO: use slow sync? bool slowSync = false; for (bool firstIteration = true; moreAvailable; firstIteration = false) { gchar *buffer = NULL; GErrorCXX gerror; EASItemsCXX created, updated; EASIdsCXX deleted; bool wasSlowSync = getCurrentSyncKey().empty(); if (!eas_sync_handler_get_items(getHandler(), getCurrentSyncKey().c_str(), &buffer, getEasType(), getFolder().c_str(), created, updated, deleted, &moreAvailable, gerror)) { if (gerror.m_gerror && /* gerror.m_gerror->domain == EAS_TYPE_CONECTION_ERROR && gerror.m_gerror->code == EAS_CONNECTION_SYNC_ERROR_INVALIDSYNCKEY && */ gerror.m_gerror->message && !strstr(gerror.m_gerror->message, "Sync error: Invalid synchronization key") && firstIteration) { // fall back to slow sync // slowSync = true; setCurrentSyncKey(""); m_trackingNode->clear(); continue; } gerror.throwError("reading ActiveSync changes"); } GStringPtr bufferOwner(buffer, "reading changes: empty sync key returned"); // TODO: Test that we really get an empty token here for an unexpected slow // sync. If not, we'll start an incremental sync here and later the engine // will ask us for older, unmodified item content which we won't have. // populate ID lists and content cache BOOST_FOREACH(EasItemInfo *item, created) { if (!item->server_id) { throwError("no server ID for new eas item"); } string easid(item->server_id); if (easid.empty()) { throwError("empty server ID for new eas item"); } SE_LOG_DEBUG(getDisplayName(), "new eas item %s", easid.c_str()); if (!item->data) { throwError(StringPrintf("no body returned for new eas item %s", easid.c_str())); } Event &event = setItemData(easid, item->data); BOOST_FOREACH(const std::string &subid, event.m_subids) { SE_LOG_DEBUG(getDisplayName(), "new eas item %s = uid %s + rid %s", easid.c_str(), event.m_uid.c_str(), subid.c_str()); addItem(createLUID(easid, subid), NEW); } } BOOST_FOREACH(EasItemInfo *item, updated) { if (!item->server_id) { throwError("no server ID for updated eas item"); } string easid(item->server_id); if (easid.empty()) { throwError("empty server ID for updated eas item"); } SE_LOG_DEBUG(getDisplayName(), "updated eas item %s", easid.c_str()); if (!item->data) { throwError(StringPrintf("no body returned for updated eas item %s", easid.c_str())); } Event &event = setItemData(easid, item->data); BOOST_FOREACH(const std::string &subid, event.m_subids) { SE_LOG_DEBUG(getDisplayName(), "deleted eas item %s = uid %s + rid %s", easid.c_str(), event.m_uid.c_str(), subid.c_str()); addItem(createLUID(easid, subid), UPDATED); } } BOOST_FOREACH(const char *serverID, deleted) { if (!serverID) { throwError("no server ID for deleted eas item"); } string easid(serverID); if (easid.empty()) { throwError("empty server ID for deleted eas item"); } Event &event = findItem(easid); if (event.m_subids.empty()) { SE_LOG_DEBUG(getDisplayName(), "deleted eas item %s empty?!", easid.c_str()); } else { BOOST_FOREACH(const std::string &subid, event.m_subids) { SE_LOG_DEBUG(getDisplayName(), "deleted eas item %s = uid %s + rid %s", easid.c_str(), event.m_uid.c_str(), subid.c_str()); addItem(createLUID(easid, subid), DELETED); } } m_cache.erase(easid); } // update key setCurrentSyncKey(buffer); // Google hack: if we started with an empty sync key (= slow sync) // and got no results (= existing items), then try one more time, // because Google only seems to report results when asked with // a valid sync key. As an additional sanity check make sure that // we have a valid sync key now. if (wasSlowSync && created.empty() && !getCurrentSyncKey().empty()) { moreAvailable = true; } } // now also generate full list of all current items: // old items + new (added to m_events above) - deleted (removed above) BOOST_FOREACH(const EventCache::value_type &entry, m_cache) { const std::string &easid = entry.first; const boost::shared_ptr &eventptr = entry.second; BOOST_FOREACH(const std::string &subid, eventptr->m_subids) { SE_LOG_DEBUG(getDisplayName(), "existing eas item %s = uid %s + rid %s", easid.c_str(), eventptr->m_uid.c_str(), subid.c_str()); addItem(createLUID(easid, subid), ANY); } } } std::string ActiveSyncCalendarSource::endSync(bool success) { m_trackingNode->clear(); if (success) { BOOST_FOREACH(const EventCache::value_type &entry, m_cache) { const std::string &easid = entry.first; const boost::shared_ptr &eventptr = entry.second; std::stringstream buffer; buffer << "//"; // use same format as in MapSyncSource, just in case - was '/' << m_escape.escape(ids.m_revision) << '/'; buffer << m_escape.escape(eventptr->m_uid) << '/'; BOOST_FOREACH(const std::string &subid, eventptr->m_subids) { buffer << m_escape.escape(subid) << '/'; } m_trackingNode->setProperty(easid, buffer.str()); } } else { setCurrentSyncKey(""); } // flush both nodes, just in case; in practice, the properties // end up in the same file and only get flushed once m_trackingNode->flush(); std::string newSyncKey = getCurrentSyncKey(); SE_LOG_DEBUG(getDisplayName(), "next sync key %s", newSyncKey.empty() ? "empty" : newSyncKey.c_str()); return newSyncKey; } std::string ActiveSyncCalendarSource::getDescription(const string &luid) { StringPair ids = MapSyncSource::splitLUID(luid); const std::string &easid = ids.first; const std::string &subid = ids.second; Event &event = findItem(easid); if (!event.m_calendar) { // Don't load (expensive!) only to provide the description. // Returning an empty string will trigger the fallback (logging the ID). return ""; } for (icalcomponent *comp = icalcomponent_get_first_component(event.m_calendar, ICAL_VEVENT_COMPONENT); comp; comp = icalcomponent_get_next_component(event.m_calendar, ICAL_VEVENT_COMPONENT)) { if (Event::getSubID(comp) == subid) { std::string descr; const char *summary = icalcomponent_get_summary(comp); if (summary && summary[0]) { descr += summary; } if (true /* is event */) { const char *location = icalcomponent_get_location(comp); if (location && location[0]) { if (!descr.empty()) { descr += ", "; } descr += location; } } // TODO: other item types return descr; } } return ""; } ActiveSyncCalendarSource::Event &ActiveSyncCalendarSource::findItem(const std::string &easid) { EventCache::iterator it = m_cache.find(easid); if (it == m_cache.end()) { throwError(STATUS_NOT_FOUND, "merged event not found: " + easid); } return *it->second; } ActiveSyncCalendarSource::Event &ActiveSyncCalendarSource::loadItem(const std::string &easid) { Event &event = findItem(easid); return loadItem(event); } ActiveSyncCalendarSource::Event &ActiveSyncCalendarSource::loadItem(Event &event) { if (!event.m_calendar) { std::string item; ActiveSyncSource::readItem(event.m_easid, item); event.m_calendar.set(icalcomponent_new_from_string((char *)item.c_str()), // hack for old libical "parsing iCalendar 2.0"); } return event; } ActiveSyncCalendarSource::Event &ActiveSyncCalendarSource::setItemData(const std::string &easid, const std::string &data) { boost::shared_ptr &eventptr = m_cache[easid]; if (eventptr) { eventptr->m_uid.clear(); eventptr->m_subids.clear(); } else { eventptr = boost::shared_ptr(new Event); } Event &event = *eventptr; event.m_easid = easid; event.m_calendar.set(icalcomponent_new_from_string((char *)data.c_str()), // hack for old libical "parsing iCalendar 2.0"); for (icalcomponent *comp = icalcomponent_get_first_component(event.m_calendar, ICAL_VEVENT_COMPONENT); comp; comp = icalcomponent_get_next_component(event.m_calendar, ICAL_VEVENT_COMPONENT)) { if (event.m_uid.empty()) { event.m_uid = Event::getUID(comp); } std::string subid = Event::getSubID(comp); event.m_subids.insert(subid); } return event; } std::string ActiveSyncCalendarSource::Event::icalTime2Str(const icaltimetype &tt) { static const struct icaltimetype null = { 0 }; if (!memcmp(&tt, &null, sizeof(null))) { return ""; } else { eptr timestr(ical_strdup(icaltime_as_ical_string(tt))); if (!timestr) { SE_THROW("cannot convert to time string"); } return timestr.get(); } } std::string ActiveSyncCalendarSource::Event::getSubID(icalcomponent *comp) { struct icaltimetype rid = icalcomponent_get_recurrenceid(comp); return icalTime2Str(rid); } std::string ActiveSyncCalendarSource::Event::getUID(icalcomponent *comp) { std::string uid; icalproperty *prop = icalcomponent_get_first_property(comp, ICAL_UID_PROPERTY); if (prop) { uid = icalproperty_get_uid(prop); } return uid; } void ActiveSyncCalendarSource::Event::setUID(icalcomponent *comp, const std::string &uid) { icalproperty *prop = icalcomponent_get_first_property(comp, ICAL_UID_PROPERTY); if (prop) { icalproperty_set_uid(prop, uid.c_str()); } else { icalcomponent_add_property(comp, icalproperty_new_uid(uid.c_str())); } } ActiveSyncCalendarSource::EventCache::iterator ActiveSyncCalendarSource::EventCache::findByUID(const std::string &uid) { for (iterator it = begin(); it != end(); ++it) { if (it->second->m_uid == uid) { return it; } } return end(); } SyncSourceRaw::InsertItemResult ActiveSyncCalendarSource::insertItem(const std::string &luid, const std::string &item) { StringPair ids = splitLUID(luid); const std::string &callerEasID = ids.first; std::string easid = ids.first; const std::string &callerSubID = ids.second; // parse new event boost::shared_ptr newEvent(new Event); newEvent->m_calendar.set(icalcomponent_new_from_string((char *)item.c_str()), // hack for old libical "parsing iCalendar 2.0"); icalcomponent *firstcomp = NULL; for (icalcomponent *comp = firstcomp = icalcomponent_get_first_component(newEvent->m_calendar, ICAL_VEVENT_COMPONENT); comp; comp = icalcomponent_get_next_component(newEvent->m_calendar, ICAL_VEVENT_COMPONENT)) { std::string subid = Event::getSubID(comp); EventCache::iterator it; if (!luid.empty() && (it = m_cache.find(luid)) != m_cache.end()) { // Additional sanity check: ensure that the expected UID is set. // Necessary if the peer we synchronize with (aka the local // data storage) doesn't support foreign UIDs. Maemo 5 calendar // backend is one example. Event::setUID(comp, it->second->m_uid); newEvent->m_uid = it->second->m_uid; } else { newEvent->m_uid = Event::getUID(comp); if (newEvent->m_uid.empty()) { // create new UID newEvent->m_uid = UUID(); Event::setUID(comp, newEvent->m_uid); } } newEvent->m_subids.insert(subid); } if (newEvent->m_subids.size() != 1) { SE_THROW("new CalDAV item did not contain exactly one VEVENT"); } std::string subid = *newEvent->m_subids.begin(); // Determine whether we already know the merged item even though // our caller didn't. std::string knownSubID = callerSubID; if (easid.empty()) { EventCache::iterator it = m_cache.findByUID(newEvent->m_uid); if (it != m_cache.end()) { easid = it->first; knownSubID = subid; } } InsertItemResultState state = ITEM_OKAY; if (easid.empty()) { // New VEVENT; should not be part of an existing merged item // ("meeting series"). InsertItemResult res = ActiveSyncSource::insertItem("", item); easid = res.m_luid; EventCache::iterator it = m_cache.find(res.m_luid); if (it != m_cache.end()) { // merge into existing Event Event &event = loadItem(*it->second); if (event.m_subids.find(subid) != event.m_subids.end()) { // was already in that item but caller didn't seem to know state = ITEM_MERGED; } else { // add to merged item event.m_subids.insert(subid); } icalcomponent_merge_component(event.m_calendar, newEvent->m_calendar.release()); // function destroys merged calendar } else { // add to cache without further changes newEvent->m_easid = res.m_luid; m_cache[newEvent->m_easid] = newEvent; } } else { if (subid != knownSubID && !subid.empty()) { SE_THROW(StringPrintf("update for eas item %s rid %s has wrong rid %s", easid.c_str(), knownSubID.c_str(), subid.c_str())); } Event &event = findItem(easid); if (subid.empty() && subid != knownSubID) { // fix incomplete iCalendar 2.0 item: should have had a RECURRENCE-ID icalcomponent *newcomp = icalcomponent_get_first_component(newEvent->m_calendar, ICAL_VEVENT_COMPONENT); icalproperty *prop = icalcomponent_get_first_property(newcomp, ICAL_RECURRENCEID_PROPERTY); if (prop) { icalcomponent_remove_property(newcomp, prop); icalproperty_free(prop); } // reconstruct RECURRENCE-ID with known value and TZID from start time of // the parent event or (if not found) the current event eptr rid(icalproperty_new_recurrenceid(icaltime_from_string(knownSubID.c_str())), "new rid"); icalproperty *dtstart = NULL; icalcomponent *comp; // look for parent first for (comp = icalcomponent_get_first_component(event.m_calendar, ICAL_VEVENT_COMPONENT); comp && !dtstart; comp = icalcomponent_get_next_component(event.m_calendar, ICAL_VEVENT_COMPONENT)) { if (!icalcomponent_get_first_property(comp, ICAL_RECURRENCEID_PROPERTY)) { dtstart = icalcomponent_get_first_property(comp, ICAL_DTSTART_PROPERTY); } } // fall back to current event if (!dtstart) { dtstart = icalcomponent_get_first_property(newcomp, ICAL_DTSTART_PROPERTY); } // ignore missing TZID if (dtstart) { icalparameter *tzid = icalproperty_get_first_parameter(dtstart, ICAL_TZID_PARAMETER); if (tzid) { icalproperty_set_parameter(rid, icalparameter_new_clone(tzid)); } } // finally add RECURRENCE-ID and fix newEvent's meta information icalcomponent_add_property(newcomp, rid.release()); subid = knownSubID; newEvent->m_subids.erase(""); newEvent->m_subids.insert(subid); } if (event.m_subids.size() == 1 && *event.m_subids.begin() == subid) { // special case: no need to load old data, replace or request merge immediately event.m_calendar = newEvent->m_calendar; if (easid != callerEasID) { state = ITEM_NEEDS_MERGE; goto done; } } else { // populate event loadItem(event); // update cache: find old VEVENT and remove it before adding new one icalcomponent *removeme = NULL; for (icalcomponent *comp = icalcomponent_get_first_component(event.m_calendar, ICAL_VEVENT_COMPONENT); comp; comp = icalcomponent_get_next_component(event.m_calendar, ICAL_VEVENT_COMPONENT)) { if (Event::getSubID(comp) == subid) { removeme = comp; break; } } if (easid != callerEasID) { // caller didn't know final UID: if found, then tell him to // merge the items, if not, then don't complain about // it not being found (like we do when the item should exist // but doesn't) if (removeme) { state = ITEM_NEEDS_MERGE; goto done; } else { event.m_subids.insert(subid); } } else { if (removeme) { // this is what we expect when the caller mentions the ActiveSync ID icalcomponent_remove_component(event.m_calendar, removeme); icalcomponent_free(removeme); } else { // caller confused?! SE_THROW("event not found"); } } icalcomponent_merge_component(event.m_calendar, newEvent->m_calendar.release()); // function destroys merged calendar } eptr icalstr(ical_strdup(icalcomponent_as_ical_string(event.m_calendar))); std::string data = icalstr.get(); // TODO: avoid updating item on server immediately? InsertItemResult res = ActiveSyncSource::insertItem(event.m_easid, data); if (res.m_state == ITEM_MERGED || res.m_luid != event.m_easid) { // should not merge with anything, if so, our cache was invalid SE_THROW("CalDAV item not updated as expected"); } } done: return SyncSourceRaw::InsertItemResult(createLUID(easid, subid), "", state); } void ActiveSyncCalendarSource::readItem(const std::string &luid, std::string &item) { StringPair ids = splitLUID(luid); const std::string &easid = ids.first; const std::string &subid = ids.second; Event &event = loadItem(easid); if (event.m_subids.size() == 1) { // simple case: convert existing VCALENDAR if (*event.m_subids.begin() == subid) { eptr icalstr(ical_strdup(icalcomponent_as_ical_string(event.m_calendar))); item = icalstr.get(); } else { throwError(STATUS_NOT_FOUND, "sub event not found: " + subid + " in " + easid); } } else { // complex case: create VCALENDAR with just the VTIMEZONE definition(s) // and the one event, then convert that eptr calendar(icalcomponent_new(ICAL_VCALENDAR_COMPONENT), "VCALENDAR"); for (icalcomponent *tz = icalcomponent_get_first_component(event.m_calendar, ICAL_VTIMEZONE_COMPONENT); tz; tz = icalcomponent_get_next_component(event.m_calendar, ICAL_VTIMEZONE_COMPONENT)) { eptr clone(icalcomponent_new_clone(tz), "VTIMEZONE"); icalcomponent_add_component(calendar, clone.release()); } bool found = false; for (icalcomponent *comp = icalcomponent_get_first_component(event.m_calendar, ICAL_VEVENT_COMPONENT); comp; comp = icalcomponent_get_next_component(event.m_calendar, ICAL_VEVENT_COMPONENT)) { if (Event::getSubID(comp) == subid) { eptr clone(icalcomponent_new_clone(comp), "VEVENT"); icalcomponent_add_component(calendar, clone.release()); found = true; break; } } if (!found) { throwError(STATUS_NOT_FOUND, "sub event not found: " + subid + " in " + easid); } eptr icalstr(ical_strdup(icalcomponent_as_ical_string(calendar))); item = icalstr.get(); } } void ActiveSyncCalendarSource::deleteItem(const string &luid) { StringPair ids = splitLUID(luid); const std::string &easid = ids.first; const std::string &subid = ids.second; // find item in cache first, load only if it is not going to be // removed entirely Event &event = findItem(easid); if (event.m_subids.size() == 1) { // remove entire merged item, nothing will be left after removal if (*event.m_subids.begin() != subid) { throwError(STATUS_NOT_FOUND, "sub event not found: " + subid + " in " + easid); } else { event.m_subids.clear(); event.m_calendar = NULL; ActiveSyncSource::deleteItem(ids.first); } m_cache.erase(easid); } else { loadItem(event); bool found = false; // bool parentRemoved = false; for (icalcomponent *comp = icalcomponent_get_first_component(event.m_calendar, ICAL_VEVENT_COMPONENT); comp; comp = icalcomponent_get_next_component(event.m_calendar, ICAL_VEVENT_COMPONENT)) { if (Event::getSubID(comp) == subid) { icalcomponent_remove_component(event.m_calendar, comp); icalcomponent_free(comp); found = true; // if (subid.empty()) { // parentRemoved = true; // } } } if (!found) { throwError(STATUS_NOT_FOUND, "sub event not found: " + subid + " in " + easid); } event.m_subids.erase(subid); // TODO: avoid updating the item immediately eptr icalstr(ical_strdup(icalcomponent_as_ical_string(event.m_calendar))); InsertItemResult res = ActiveSyncSource::insertItem(easid, icalstr.get()); if (res.m_state != ITEM_OKAY || res.m_luid != easid) { SE_THROW("unexpected result of removing sub event"); } } } void ActiveSyncCalendarSource::removeAllItems() { BOOST_FOREACH(const EventCache::value_type &entry, m_cache) { ActiveSyncSource::deleteItem(entry.first); } m_cache.clear(); } std::string ActiveSyncCalendarSource::createLUID(const std::string &uid, const std::string &subid) { std::string luid = m_escape.escape(uid); if (!subid.empty()) { luid += '/'; luid += m_escape.escape(subid); } return luid; } std::pair ActiveSyncCalendarSource::splitLUID(const std::string &luid) { size_t index = luid.find('/'); if (index != luid.npos) { return make_pair(m_escape.unescape(luid.substr(0, index)), m_escape.unescape(luid.substr(index + 1))); } else { return make_pair(m_escape.unescape(luid), ""); } } StringEscape ActiveSyncCalendarSource::m_escape('%', "/"); SE_END_CXX #endif // ENABLE_ACTIVESYNC syncevolution_1.4/src/backends/activesync/ActiveSyncCalendarSource.h000066400000000000000000000121301230021373600261200ustar00rootroot00000000000000/* * Copyright (C) 2010,2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_ACTIVESYNCCALENDARSOURCE #define INCL_ACTIVESYNCCALENDARSOURCE #include #ifdef ENABLE_ACTIVESYNC #include "ActiveSyncSource.h" #include #include #include #include #include #include SE_BEGIN_CXX /** * Similar to CalDAVSource (and partly copied from it): implements * operations with one VEVENT per item in terms of operations * on items which bundle all VEVENTs with the same UID in one item. * * It works by keeping all active items as VCALENDAR icalcomponent in * memory and updating that when asked to add/update/remove VEVENTs. * The update VCALENDAR items are then stored via the base class. * * Terminology: * - easid = davLUID in CalDAVSource = ActiveSync item ID used in base ActiveSyncSource and by ActiveSync peer, * 1:1 mapping to uid * - uid = mainid in MapSyncSource = the UID shared by multiple VEVENTs * - rid = subid in MapSyncSource = RECURRENCE-ID, turned into a simple string by Event::getRid() * - luid = easid/rid concatenated by createLUID() and split with splitLUID() */ class ActiveSyncCalendarSource : public ActiveSyncCalFormatSource { public: ActiveSyncCalendarSource(const SyncSourceParams ¶ms, EasItemType type); // override operations in base class to work with luid == easid/rid instead // of just easid virtual std::string getDescription(const string &luid); virtual void beginSync(const std::string &lastToken, const std::string &resumeToken); virtual std::string endSync(bool success); virtual void deleteItem(const string &luid); virtual InsertItemResult insertItem(const std::string &luid, const std::string &item); virtual void readItem(const std::string &luid, std::string &item); /** split luid into uid (first) and rid (second) */ static StringPair splitLUID(const std::string &luid); // more efficient implementation than in TestingSyncSource virtual void removeAllItems(); private: /** compose luid from mainid and subid */ static std::string createLUID(const std::string &uid, const std::string &rid); /** escape / in uid with %2F, so that splitMainIDValue() and splitLUID() can use / as separator */ static StringEscape m_escape; /** * Information about each merged item. */ class Event : boost::noncopyable { public: /** the ActiveSync ID */ std::string m_easid; /** the iCalendar 2.0 UID */ std::string m_uid; /** * the list of simplified RECURRENCE-IDs (without time zone, * see icalTime2Str()), empty string for VEVENT without * RECURRENCE-ID */ std::set m_subids; /** * parsed VCALENDAR component representing the current * state of the item as it exists on the WebDAV server, * must be kept up-to-date as we make changes, may be NULL */ eptr m_calendar; /** date-time as string, without time zone */ static std::string icalTime2Str(const icaltimetype &tt); /** RECURRENCE-ID, empty if none */ static std::string getSubID(icalcomponent *icomp); /** UID, empty if none */ static std::string getUID(icalcomponent *icomp); static void setUID(icalcomponent *icomp, const std::string &uid); }; /** * A cache of information about each merged item. Maps from * easid to Event. */ class EventCache : public std::map > { public: EventCache() : m_initialized(false) {} bool m_initialized; iterator findByUID(const std::string &uid); } m_cache; Event &findItem(const std::string &easid); Event &loadItem(const std::string &easid); Event &loadItem(Event &event); /** * create event (if necessary) and populate based on given iCalendar 2.0 VCALENDAR */ Event &setItemData(const std::string &easid, const std::string &data); /** * On-disk representation of m_cache (without the item data). * Format same as in MapSyncSource (code copied, refactor!). */ boost::shared_ptr m_trackingNode; }; SE_END_CXX #endif // ENABLE_ACTIVESYNC #endif // INCL_ACTIVESYNCCALENDARSOURCE syncevolution_1.4/src/backends/activesync/ActiveSyncSource.cpp000066400000000000000000000502441230021373600250310ustar00rootroot00000000000000/* * Copyright (C) 2007-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifdef ENABLE_ACTIVESYNC #include "ActiveSyncSource.h" #include #include #include #include #include #include SE_GOBJECT_TYPE(EasSyncHandler) /* #include */ #include SE_BEGIN_CXX void EASItemUnref(EasItemInfo *info) { g_object_unref(&info->parent_instance); } void GStringUnref(char *str) { g_free(str); } void EASFolderUnref(EasFolder *f) { g_object_unref(&f->parent_instance); } void ActiveSyncSource::enableServerMode() { SyncSourceAdmin::init(m_operations, this); SyncSourceBlob::init(m_operations, getCacheDir()); } bool ActiveSyncSource::serverModeEnabled() const { return m_operations.m_loadAdminData; } /* Recursively work out full path name */ std::string ActiveSyncSource::Collection::fullPath() { if (!pathFound) { if (parentId == "0") { pathName = name; } else { pathName = source->m_collections[parentId].fullPath() + "/" + name; } pathFound = true; } return pathName; } void ActiveSyncSource::findCollections(const std::string &account, const bool force_update) { GErrorCXX gerror; EasSyncHandlerCXX handler; EASFoldersCXX folders; if (!m_collections.empty()) { if (!force_update) return; m_collections.clear(); m_folderPaths.clear(); } /* Fetch the folders */ handler = EasSyncHandlerCXX::steal(eas_sync_handler_new(account.c_str())); if (!handler) throwError("findCollections cannot allocate sync handler"); if (!eas_sync_handler_get_folder_list (handler, force_update, folders, NULL, gerror)) { gerror.throwError("fetching folder list"); } /* Save the Collections */ BOOST_FOREACH(EasFolder *folder, folders) { m_collections[folder->folder_id].collectionId = folder->folder_id; m_collections[folder->folder_id].name = folder->display_name; m_collections[folder->folder_id].parentId = folder->parent_id; m_collections[folder->folder_id].type = folder->type; m_collections[folder->folder_id].source = this; } /* Save the full paths */ BOOST_FOREACH(std::string id, m_collections | boost::adaptors::map_keys) { m_folderPaths[m_collections[id].fullPath()] = id; } } int ActiveSyncSource::Collection::getFolderType () { switch (type) { case EAS_FOLDER_TYPE_DEFAULT_INBOX: case EAS_FOLDER_TYPE_DEFAULT_DRAFTS: case EAS_FOLDER_TYPE_DEFAULT_DELETED_ITEMS: case EAS_FOLDER_TYPE_DEFAULT_SENT_ITEMS: case EAS_FOLDER_TYPE_DEFAULT_OUTBOX: case EAS_FOLDER_TYPE_USER_CREATED_MAIL: return EAS_ITEM_MAIL; case EAS_FOLDER_TYPE_DEFAULT_TASKS: case EAS_FOLDER_TYPE_USER_CREATED_TASKS: return EAS_ITEM_TODO; case EAS_FOLDER_TYPE_DEFAULT_CALENDAR: case EAS_FOLDER_TYPE_USER_CREATED_CALENDAR: return EAS_ITEM_CALENDAR; case EAS_FOLDER_TYPE_DEFAULT_CONTACTS: case EAS_FOLDER_TYPE_USER_CREATED_CONTACTS: return EAS_ITEM_CONTACT; case EAS_FOLDER_TYPE_DEFAULT_NOTES: case EAS_FOLDER_TYPE_USER_CREATED_NOTES: //TODO: implement memos case EAS_FOLDER_TYPE_DEFAULT_JOURNAL: case EAS_FOLDER_TYPE_USER_CREATED_JOURNAL: case EAS_FOLDER_TYPE_UNKNOWN: case EAS_FOLDER_TYPE_RECIPIENT_CACHE: default: return -1; } } bool ActiveSyncSource::Collection::collectionIsDefault () { return type == EAS_FOLDER_TYPE_DEFAULT_INBOX || type == EAS_FOLDER_TYPE_DEFAULT_DRAFTS || type == EAS_FOLDER_TYPE_DEFAULT_DELETED_ITEMS || type == EAS_FOLDER_TYPE_DEFAULT_SENT_ITEMS || type == EAS_FOLDER_TYPE_DEFAULT_OUTBOX || type == EAS_FOLDER_TYPE_DEFAULT_TASKS || type == EAS_FOLDER_TYPE_DEFAULT_CALENDAR || type == EAS_FOLDER_TYPE_DEFAULT_CONTACTS || type == EAS_FOLDER_TYPE_DEFAULT_NOTES || type == EAS_FOLDER_TYPE_DEFAULT_JOURNAL; } ActiveSyncSource::Databases ActiveSyncSource::getDatabases() { Databases result; // do a scan if username is set UserIdentity identity = m_context->getSyncUser(); if (identity.m_provider != USER_IDENTITY_PLAIN_TEXT) { throwError(StringPrintf("%s: only the 'user:' format is supported by ActiveSync", identity.toString().c_str())); } const std::string &account = identity.m_identity; if (!account.empty()) { findCollections(account, true); BOOST_FOREACH(Collection coll, m_collections | boost::adaptors::map_values) { if (coll.getFolderType() == getEasType()) { result.push_back(Database(coll.pathName, coll.collectionId, coll.collectionIsDefault())); } } } else { result.push_back(Database("to scan, specify --print-databases username= backend=\""+getSourceType().m_backend+"\"", "")); } return result; } std::string ActiveSyncSource::lookupFolder(const std::string &folder) { // If folder matches a collectionId, use that if (m_collections.find(folder) != m_collections.end()) return folder; // If folder begins with /, drop it std::string key; if (!folder.empty() && folder[0] == '/') { key = folder.substr(1); } else { key = folder; } // Lookup folder name FolderPaths::const_iterator entry = m_folderPaths.find(key); if (entry != m_folderPaths.end()) { return entry->second; } // Not found return ""; } void ActiveSyncSource::open() { // extract account ID and throw error if missing UserIdentity identity = m_context->getSyncUser(); if (identity.m_provider != USER_IDENTITY_PLAIN_TEXT) { throwError(StringPrintf("%s: only the 'user:' format is supported by ActiveSync", identity.toString().c_str())); } const std::string &username = identity.m_identity; std::string folder = getDatabaseID(); SE_LOG_DEBUG(NULL, "using eas sync account %s from config %s with folder %s", username.c_str(), m_context->getConfigName().c_str(), folder.c_str()); if (folder.empty()) { // Most common case is empty string m_folder = folder; } else { // Lookup folder name // Try using cached folder list findCollections(username, false); m_folder = lookupFolder(folder); if (m_folder.empty()) { // Fetch latest folder list and try again findCollections(username, true); m_folder = lookupFolder(folder); } if (m_folder.empty()) { throwError("could not find folder: "+folder); } } m_account = username; // create handler m_handler.set(eas_sync_handler_new(m_account.c_str()), "EAS handler"); } void ActiveSyncSource::close() { // free handler if not done already m_handler.set(NULL); } void ActiveSyncSource::beginSync(const std::string &lastToken, const std::string &resumeToken) { // erase content which might have been set in a previous call reset(); // claim item node for ids, if not done yet if (m_itemNode && !m_ids) { m_ids.swap(m_itemNode); } // incremental sync (non-empty token) or start from scratch m_startSyncKey = lastToken; if (lastToken.empty()) { // slow sync: wipe out cached list of IDs, will be filled anew below SE_LOG_DEBUG(getDisplayName(), "sync key empty, starting slow sync"); m_ids->clear(); } else { SE_LOG_DEBUG(getDisplayName(), "sync key %s for account '%s' folder '%s', starting incremental sync", lastToken.c_str(), m_account.c_str(), m_folder.c_str()); } gboolean moreAvailable = TRUE; m_currentSyncKey = m_startSyncKey; // same logic as in ActiveSyncCalendarSource::beginSync() bool slowSync = false; for (bool firstIteration = true; moreAvailable; firstIteration = false) { gchar *buffer = NULL; GErrorCXX gerror; EASItemsCXX created, updated; EASIdsCXX deleted; bool wasSlowSync = m_currentSyncKey.empty(); if (!eas_sync_handler_get_items(m_handler, m_currentSyncKey.c_str(), &buffer, getEasType(), m_folder.c_str(), created, updated, deleted, &moreAvailable, gerror)) { if (gerror.m_gerror && /* gerror.m_gerror->domain == EAS_TYPE_CONNECTION_ERROR && gerror.m_gerror->code == EAS_CONNECTION_SYNC_ERROR_INVALIDSYNCKEY && */ gerror.m_gerror->message && strstr(gerror.m_gerror->message, "Sync error: Invalid synchronization key") && firstIteration) { // fall back to slow sync slowSync = true; m_currentSyncKey = ""; m_ids->clear(); continue; } gerror.throwError("reading ActiveSync changes"); } GStringPtr bufferOwner(buffer, "reading changes: empty sync key returned"); // TODO: Test that we really get an empty token here for an unexpected slow // sync. If not, we'll start an incremental sync here and later the engine // will ask us for older, unmodified item content which we won't have. // populate ID lists and content cache BOOST_FOREACH(EasItemInfo *item, created) { if (!item->server_id) { throwError("no server ID for new eas item"); } string luid(item->server_id); if (luid.empty()) { throwError("empty server ID for new eas item"); } SE_LOG_DEBUG(getDisplayName(), "new item %s", luid.c_str()); addItem(luid, NEW); m_ids->setProperty(luid, "1"); if (!item->data) { throwError(StringPrintf("no body returned for new eas item %s", luid.c_str())); } m_items[luid] = item->data; } BOOST_FOREACH(EasItemInfo *item, updated) { if (!item->server_id) { throwError("no server ID for updated eas item"); } string luid(item->server_id); if (luid.empty()) { throwError("empty server ID for updated eas item"); } SE_LOG_DEBUG(getDisplayName(), "updated item %s", luid.c_str()); addItem(luid, UPDATED); // m_ids.setProperty(luid, "1"); not necessary, should already exist (TODO: check?!) if (!item->data) { throwError(StringPrintf("no body returned for updated eas item %s", luid.c_str())); } m_items[luid] = item->data; } BOOST_FOREACH(const char *serverID, deleted) { if (!serverID) { throwError("no server ID for deleted eas item"); } string luid(serverID); if (luid.empty()) { throwError("empty server ID for deleted eas item"); } SE_LOG_DEBUG(getDisplayName(), "deleted item %s", luid.c_str()); addItem(luid, DELETED); m_ids->removeProperty(luid); } // update key m_currentSyncKey = buffer; // Google hack: if we started with an empty sync key (= slow sync) // and got no results (= existing items), then try one more time, // because Google only seems to report results when asked with // a valid sync key. As an additional sanity check make sure that // we have a valid sync key now. if (wasSlowSync && created.empty() && !m_currentSyncKey.empty()) { moreAvailable = true; } } // now also generate full list of all current items: // old items + new (added to m_ids above) - deleted (removed above) ConfigProps props; m_ids->readProperties(props); BOOST_FOREACH(const StringPair &entry, props) { const std::string &luid = entry.first; SE_LOG_DEBUG(getDisplayName(), "existing item %s", luid.c_str()); addItem(luid, ANY); } if (slowSync) { // tell engine that we need a slow sync, if it didn't know already SE_THROW_EXCEPTION_STATUS(StatusException, "ActiveSync error: Invalid synchronization key", STATUS_SLOW_SYNC_508); } } std::string ActiveSyncSource::endSync(bool success) { // store current set of items if (!success) { m_ids->clear(); } m_ids->flush(); // let engine do incremental sync next time or start from scratch // in case of failure std::string newSyncKey = success ? m_currentSyncKey : ""; SE_LOG_DEBUG(getDisplayName(), "next sync key %s", newSyncKey.empty() ? "empty" : newSyncKey.c_str()); return newSyncKey; } void ActiveSyncSource::deleteItem(const string &luid) { // asking to delete a non-existent item via ActiveSync does not // trigger an error; this is expected by the caller, so detect // the problem by looking up the item in our list (and keep the // list up-to-date elsewhere) if (m_ids && m_ids->readProperty(luid).empty()) { throwError(STATUS_NOT_FOUND, "item not found: " + luid); } // send delete request // TODO (?): batch delete requests GListCXX items; items.push_back((char *)luid.c_str()); GErrorCXX gerror; char *buffer; if (!eas_sync_handler_delete_items(m_handler, m_currentSyncKey.c_str(), &buffer, getEasType(), m_folder.c_str(), items, gerror)) { gerror.throwError("deleting eas item"); } GStringPtr bufferOwner(buffer, "delete items: empty sync key returned"); // remove from item list if (m_ids) { m_items.erase(luid); m_ids->removeProperty(luid); } // update key m_currentSyncKey = buffer; } SyncSourceSerialize::InsertItemResult ActiveSyncSource::insertItem(const std::string &luid, const std::string &data) { SyncSourceSerialize::InsertItemResult res; EASItemPtr tmp(eas_item_info_new(), "EasItem"); EasItemInfo *item = tmp.get(); if (!luid.empty()) { // update item->server_id = g_strdup(luid.c_str()); } else { // add // TODO: is a local id needed? We don't have one. } item->data = g_strdup(data.c_str()); EASItemsCXX items; items.push_front(tmp.release()); GErrorCXX gerror; char *buffer; // distinguish between update (existing luid) // or creation (empty luid) if (luid.empty()) { // send item to server if (!eas_sync_handler_add_items(m_handler, m_currentSyncKey.c_str(), &buffer, getEasType(), m_folder.c_str(), items, gerror)) { gerror.throwError("adding eas item"); } if (!item->server_id) { throwError("no server ID for new eas item"); } // get new ID from updated item res.m_luid = item->server_id; if (res.m_luid.empty()) { throwError("empty server ID for new eas item"); } // TODO: if someone else has inserted a new calendar item // with the same UID as the one we are trying to insert here, // what will happen? Does the ActiveSync server prevent // adding our own version of the item or does it merge? // res.m_merged = ??? } else { // update item on server if (!eas_sync_handler_update_items(m_handler, m_currentSyncKey.c_str(), &buffer, getEasType(), m_folder.c_str(), items, gerror)) { gerror.throwError("updating eas item"); } res.m_luid = luid; } GStringPtr bufferOwner(buffer, "insert item: empty sync key returned"); // add/update in cache if (m_ids) { m_items[res.m_luid] = data; m_ids->setProperty(res.m_luid, "1"); } // update key m_currentSyncKey = buffer; return res; } void ActiveSyncSource::readItem(const std::string &luid, std::string &item) { // return straight from cache? std::map::iterator it = m_items.find(luid); if (it == m_items.end()) { // no, must fetch EASItemPtr tmp(eas_item_info_new(), "EasItem"); GErrorCXX gerror; if (!eas_sync_handler_fetch_item(m_handler, m_folder.c_str(), luid.c_str(), tmp, getEasType(), gerror)) { if (gerror.m_gerror->message && strstr(gerror.m_gerror->message, "ObjectNotFound") /* gerror.matches(EAS_CONNECTION_ERROR, EAS_CONNECTION_ITEMOPERATIONS_ERROR_OBJECTNOTFOUND) (gdb) p *m_gerror $7 = {domain = 156, code = 36, message = 0xda2940 "GDBus.Error:org.meego.activesyncd.ItemOperationsError.ObjectNotFound: Document library - The object was not found or access denied."} */) { throwError(STATUS_NOT_FOUND, "item not found: " + luid); } else { gerror.throwError(StringPrintf("reading eas item %s", luid.c_str())); } } if (!tmp->data) { throwError(StringPrintf("no body returned for eas item %s", luid.c_str())); } item = tmp->data; } else { item = it->second; } } void ActiveSyncSource::getSynthesisInfo(SynthesisInfo &info, XMLConfigFragments &fragments) { TestingSyncSource::getSynthesisInfo(info, fragments); /** * Disable reading of existing item by engine before updating * it by pretending to do the merging ourselves. This works * as long as the local side is able to store all data that * activesyncd gives to us and updates on the ActiveSync * server. * * Probably some Exchange-specific extensions currently get * lost because activesyncd does not know how to represent * them as vCard and does not tell the ActiveSync server that * it cannot handle them. */ boost::replace_first(info.m_datastoreOptions, "true", ""); /** * no ActiveSync specific rules yet, use condensed format as * if we were storing locally, with all extensions enabled */ info.m_backendRule = "LOCALSTORAGE"; /** * access to data must be done early so that a slow sync can be * enforced when the ActiveSync sync key turns out to be * invalid */ info.m_earlyStartDataRead = true; } SE_END_CXX #endif /* ENABLE_ACTIVESYNC */ #ifdef ENABLE_MODULES # include "ActiveSyncSourceRegister.cpp" #endif syncevolution_1.4/src/backends/activesync/ActiveSyncSource.h000066400000000000000000000260301230021373600244720ustar00rootroot00000000000000/* * Copyright (C) 2007-2009 Patrick Ohly * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_ACTIVESYNCSOURCE #define INCL_ACTIVESYNCSOURCE #include #include #ifdef ENABLE_ACTIVESYNC #include #include #include #include #include #include #include #include "libeassync.h" #include #include #include SE_BEGIN_CXX /** * Synchronizes contacts, events, tasks and journals with an * ActiveSync server. Sub-classes provide the necessary specialization * for different data formats. * * Data is exchanged between ActiveSyncSource, ActiveSync library, and * Synthesis engine as vCard 3.0 and iCalendar 2.0 format. The * standard contact and calendar profile is used, with ACTIVESYNC set * as sub-rule. This influences how the Synthesis engine converts to * and from its internal format. See KDE in * src/syncevo/profiles/datatypes/01vcard-profile.xml for an extensive * example how that works. * * Each ActiveSync calendar item is a VCALENDAR which contains all * VEVENTs with the same UID. The SyncEvolution/Synthesis engine works * with individual VEVENTs per item. This implies that someone has to * map between the two concepts. This is done in the derived * ActiveSyncCalendarSource, similar to the existing MapSyncSource. * * Reusing that class was considered, but discarded because * MapSyncSource assumes that the class that it wraps uses id/revision * string pairs for change tracking, which is not the case for * ActiveSync. * * A sync session is done like this: * - The Synthesis sync anchor directly maps to the * ActiveSync sync key. * - In SyncSourceSession::beginSync() (inherited from TestingSyncSource), * the ActiveSync library is asked for changes (for a non-empty key) * or all items (empty key). * - The returned item IDs are stored in SyncSourceChanges. * Only the server's IDs are used. They map 1:1 with the "luid" in * the SyncSource API and the Synthesis engine. * Because a full list of all existing items is expected, * ActiveSyncSource maintains a list of all known items * in the params.m_nodes.getTrackingNode() that it gets * for that purpose. * - The returned item content is cached in a local content cache * and returns items from that when asked to via * SyncSourceSerialize. * - As items are added/remove/updated, the content cache, the list * of IDs, and the sync key are updated. The expectation is * that any changes made by other ActiveSync server clients * will be reported when asking for changes based on that updated * key without including changes made by our own client, even * when these changes happen concurrently. This is true for * Exchange 2010 + ActiveSync 12.1. * TODO: write a test program to verify that assumption. * Google + ActiveSync 12.0 do not support this. * TODO: deal with server-side changes reported to us at the * time when we make changes ourselves. * - When multiple events are in one item, it can happen that * the event series has to be retrieved individually from the * server. Example: * - nothing changed on server => nothing sent at start of sync, * cache empty * - ActiveSyncCalendarSource must delete a detached recurrence * inside a series * - retrieve series * - remove recurrence * - sent back the updated series * - At the end of the sync, the updated ID list is stored and * the updated sync key is returned to the Synthesis engine. * - If anything goes wrong, a fatal error is returned to the * Synthesis engine, which then invalidates the sync key and * thus forces a slow sync in the next session. * TODO: investigate more intelligent ways of recovering. * The problem will be that trying again with the original * sync key will return changes made by the client itself * as part of the incomplete sync session. * * The command line item manipulation operations * (--import/export/update/print-items/delete-items) always start a * session without a sync key and thus (with the current API) have to * download all items before doing anything. * TODO: optimize that */ class ActiveSyncSource : public TestingSyncSource, // == SyncSourceSession, SyncSourceChanges, SyncSourceDelete, SyncSourceSerialize // TODO: implement SyncSourceLogging to get nicer debug and command line output // virtual public SyncSourceLogging, virtual public SyncSourceAdmin, virtual public SyncSourceBlob { public: ActiveSyncSource(const SyncSourceParams ¶ms) : TestingSyncSource(params), // Ensure that arbitrary keys can be stored (SafeConfigNode) and // that we use a common prefix, so that we can use the key/value store // also for other keys if the need ever arises). m_itemNode(new PrefixConfigNode("item-", boost::shared_ptr(new SafeConfigNode(params.m_nodes.getTrackingNode())))), m_context(params.m_context) { if (!m_context) { m_context.reset(new SyncConfig()); } } /** sync config used by this instance, never NULL */ SyncConfig &getSyncConfig() { return *m_context; } /* partial implementation of SyncSource */ virtual void enableServerMode(); virtual bool serverModeEnabled() const; virtual Databases getDatabases(); virtual void open(); virtual void close(); virtual std::string getPeerMimeType() const { return getMimeType(); } /* implementation of SyncSourceSession */ virtual void beginSync(const std::string &lastToken, const std::string &resumeToken); virtual std::string endSync(bool success); /* implementation of SyncSourceDelete */ virtual void deleteItem(const string &luid); /* partial implementation of SyncSourceSerialize */ virtual std::string getMimeType() const = 0; virtual std::string getMimeVersion() const = 0; virtual InsertItemResult insertItem(const std::string &luid, const std::string &item); virtual void readItem(const std::string &luid, std::string &item); /** to be provided by derived class */ virtual EasItemType getEasType() const = 0; protected: virtual void getSynthesisInfo(SynthesisInfo &info, XMLConfigFragments &fragments); EasSyncHandler *getHandler() { return m_handler.get(); } std::string getFolder() { return m_folder; } std::string getStartSyncKey() { return m_startSyncKey; } void setStartSyncKey(const std::string &startSyncKey) { m_startSyncKey = startSyncKey; } std::string getCurrentSyncKey() { return m_currentSyncKey; } void setCurrentSyncKey(const std::string ¤tSyncKey) { m_currentSyncKey = currentSyncKey; } void findCollections(const std::string &account, bool force_update); std::string lookupFolder(const std::string &folder); boost::shared_ptr m_itemNode; private: /** "source-config@" instance which holds our username == ActiveSync account ID */ boost::shared_ptr m_context; /** account ID for libeas, must be set in "username" config property */ std::string m_account; /** folder ID for libeas, optionally set in "database" config property */ std::string m_folder; /** smart pointer holding reference to EasSyncHandler during session */ eptr m_handler; /** original sync key, set when session starts */ std::string m_startSyncKey; /** current sync key, set when session starts and updated as changes are made */ std::string m_currentSyncKey; /** * server-side IDs of all items, updated as changes are reported and/or are made; * NULL if not using change tracking */ boost::shared_ptr m_ids; /** * cache of all items, filled at begin of session and updated as * changes are made (if doing change tracking) */ std::map m_items; /** * list of folders */ typedef struct Collection { std::string collectionId; std::string name; std::string parentId; std::string pathName; unsigned type; bool pathFound; ActiveSyncSource *source; Collection() : type(0), pathFound(false), source(NULL) {} int getFolderType(); bool collectionIsDefault(); std::string fullPath(); } collection; std::map m_collections; // Indexed by collectionID typedef std::map FolderPaths; FolderPaths m_folderPaths; // Maps pathName to collectionId }; class ActiveSyncContactSource : public ActiveSyncSource { public: ActiveSyncContactSource(const SyncSourceParams ¶ms) : ActiveSyncSource(params) {} protected: /* partial implementation of SyncSourceSerialize */ virtual std::string getMimeType() const { return "text/vcard"; } virtual std::string getMimeVersion() const { return "3.0"; } EasItemType getEasType() const { return EAS_ITEM_CONTACT; } }; /** * used for all iCalendar 2.0 items (events, todos, journals) */ class ActiveSyncCalFormatSource : public ActiveSyncSource { EasItemType m_type; public: ActiveSyncCalFormatSource(const SyncSourceParams ¶ms, EasItemType type) : ActiveSyncSource(params), m_type(type) {} protected: /* partial implementation of SyncSourceSerialize */ virtual std::string getMimeType() const { return "text/calendar"; } virtual std::string getMimeVersion() const { return "2.0"; } EasItemType getEasType() const { return m_type; } }; void EASItemUnref(EasItemInfo *info); /** non-copyable list of EasItemInfo pointers, owned by list */ typedef GListCXX EASItemsCXX; void GStringUnref(char *str); /** non-copyable list of strings, owned by list */ typedef GListCXX EASIdsCXX; /** non-copyable smart pointer to an EasItemInfo, unrefs when going out of scope */ typedef eptr EASItemPtr; void EASFolderUnref(EasFolder *f); /** non-copyable list of EasFolder pointers, owned by list */ typedef GListCXX EASFoldersCXX; SE_END_CXX #endif // ENABLE_ACTIVESYNC #endif // INCL_ACTIVESYNCSOURCE syncevolution_1.4/src/backends/activesync/ActiveSyncSourceRegister.cpp000066400000000000000000000250011230021373600265270ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "ActiveSyncSource.h" #include "ActiveSyncCalendarSource.h" #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifdef ENABLE_UNIT_TESTS # include # include #endif #include #include SE_BEGIN_CXX static SyncSource *createSource(const SyncSourceParams ¶ms) { SourceType sourceType = SyncSource::getSourceType(params.m_nodes); bool isMe; isMe = sourceType.m_backend == "ActiveSync Address Book"; if (isMe) { return #ifdef ENABLE_ACTIVESYNC new ActiveSyncContactSource(params) #else RegisterSyncSource::InactiveSource(params) #endif ; } isMe = sourceType.m_backend == "ActiveSync Events"; if (isMe) { return #ifdef ENABLE_ACTIVESYNC new ActiveSyncCalendarSource(params, EAS_ITEM_CALENDAR) #else RegisterSyncSource::InactiveSource(params) #endif ; } isMe = sourceType.m_backend == "ActiveSync Todos"; if (isMe) { return #ifdef ENABLE_ACTIVESYNC new ActiveSyncCalFormatSource(params, EAS_ITEM_TODO) #else RegisterSyncSource::InactiveSource(params) #endif ; } isMe = sourceType.m_backend == "ActiveSync Memos"; if (isMe) { return #ifdef ENABLE_ACTIVESYNC new ActiveSyncCalFormatSource(params, EAS_ITEM_JOURNAL) #else RegisterSyncSource::InactiveSource(params) #endif ; } return NULL; } static RegisterSyncSource registerMe("ActiveSync", #ifdef ENABLE_ACTIVESYNC true, #else false, #endif createSource, "ActiveSync Address Book = eas-contacts\n" "ActiveSync Events = eas-events\n" "ActiveSync Todos = eas-todos\n" "ActiveSync Memos = eas-memos", Values() + (Aliases("ActiveSync Address Book") + "eas-contacts") + (Aliases("ActiveSync Events") + "eas-events") + (Aliases("ActiveSync Todos") + "eas-todos") + (Aliases("ActiveSync Memos") + "eas-memos")); #ifdef ENABLE_ACTIVESYNC #ifdef ENABLE_UNIT_TESTS class ActiveSyncsTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(ActiveSyncsTest); CPPUNIT_TEST(testInstantiate); CPPUNIT_TEST_SUITE_END(); protected: void testInstantiate() { boost::shared_ptr source; source.reset(SyncSource::createTestingSource("contacts", "ActiveSync Address Book", true)); source.reset(SyncSource::createTestingSource("events", "ActiveSync Events", true)); source.reset(SyncSource::createTestingSource("todos", "ActiveSync Todos", true)); source.reset(SyncSource::createTestingSource("memos", "ActiveSync Memos", true)); } }; SYNCEVOLUTION_TEST_SUITE_REGISTRATION(ActiveSyncsTest); #endif // ENABLE_UNIT_TESTS namespace { #if 0 } #endif /** * Takes all existing items in the source and writes them into the file, * separated by a blank line. beginSync() with the previous sync key was * already called. * * Used for testing and thus should better not rely on cached information, * but ActiveSync doesn't offer an independent "list and/or retrieve all items" * operation. Using the cached information implies that we won't find bugs in * the handling of that information. */ static int DumpItems(ClientTest &client, TestingSyncSource &source, const std::string &file, bool forceBaseReadItem) { ActiveSyncSource &eassource = static_cast(source); ofstream out(file.c_str()); // find all ActiveSync server IDs: in ActiveSyncCalendarSource, // each server ID might appear multiple times, once for each // recurrence associated with it std::set easids; BOOST_FOREACH (const std::string &luid, eassource.getAllItems()) { // slight hack: we know that luids in ActiveSyncSource base // class pass through this method unmodified, so no need to // avoid it StringPair ids = ActiveSyncCalendarSource::splitLUID(luid); easids.insert(ids.first); } BOOST_FOREACH(const std::string &easid, easids) { std::string item; if (forceBaseReadItem) { // This bypasses the more specialized // ActiveSyncCalendarSource::readItem(), which helps // reveal potential bugs in it. However, it depends on a // working Fetch operation in the ActiveSync server, which // Google doesn't seem to provide (404 error). eassource.ActiveSyncSource::readItem(easid, item); } else { // Normal readItem() works with Google by using the cached // item. However, the source must have done a beginSync() // with empty sync key, because otherwise the cache is // not guaranteed to be complete. eassource.readItem(easid, item); } out << item << '\n'; if (!boost::ends_with(item, "\n")) { out << '\n'; } } return 0; } static TestingSyncSource *createEASSource(const ClientTestConfig::createsource_t &create, ClientTest &client, const std::string &clientID, int source, bool isSourceA) { std::auto_ptr res(create(client, clientID, source, isSourceA)); // Mangle username: if the base username in the config is account // "foo", then source B uses "foo_B", because otherwise it'll end // up sharing change tracking with source A. if (!isSourceA) { ActiveSyncSource *eassource = static_cast(res.get()); std::string account = eassource->getSyncConfig().getSyncUser().toString(); account += "_B"; eassource->getSyncConfig().setSyncUsername(account, true); } if (res->getDatabaseID().empty()) { return res.release(); } else { // sorry, no database SE_LOG_ERROR(NULL, "cannot create EAS source for database %s, check config", res->getDatabaseID().c_str()); return NULL; } } // common settings for all kinds of data static void updateConfigEAS(const RegisterSyncSourceTest */* me */, ClientTestConfig &config, EasItemType type) { // cannot run tests involving a second database: // wrap orginal source creation, set default database for // database #0 and refuse to return a source for database #1 config.m_createSourceA = boost::bind(createEASSource, config.m_createSourceA, _1, _2, _3, _4); config.m_createSourceB = boost::bind(createEASSource, config.m_createSourceB, _1, _2, _3, _4); config.m_dump = boost::bind(DumpItems, _1, _2, _3, type == EAS_ITEM_CONTACT || // need to read from our cache for Google Calendar, // because it does not support Fetch strcmp(getEnv("CLIENT_TEST_SERVER", ""), "googleeas") ); config.m_sourceLUIDsAreVolatile = true; // TODO: find out how ActiveSync/Exchange handle children without parent; // at the moment, the child is stored as if it was a stand-alone event // and the RECURRENCE-ID is lost (BMC #22831). config.m_linkedItemsRelaxedSemantic = false; } static class ActiveSyncContactTest : public RegisterSyncSourceTest { public: ActiveSyncContactTest() : RegisterSyncSourceTest("eas_contact", // name of test => Client::Source::eas_contact" "eds_contact" // name of test cases: inherit from EDS, override below ) {} virtual void updateConfig(ClientTestConfig &config) const { // override default eds_contact test config config.m_type = "eas-contacts"; // TODO: provide comprehensive set of vCard 3.0 contacts as they are understood by the ActiveSync library // config.testcases = "testcases/eas_contact.vcf"; updateConfigEAS(this, config, EAS_ITEM_CONTACT); } } ActiveSyncContactTest; static class ActiveSyncEventTest : public RegisterSyncSourceTest { public: ActiveSyncEventTest() : RegisterSyncSourceTest("eas_event", "eds_event") {} virtual void updateConfig(ClientTestConfig &config) const { config.m_type = "eas-events"; updateConfigEAS(this, config, EAS_ITEM_CALENDAR); } } ActiveSyncEventTest; static class ActiveSyncTodoTest : public RegisterSyncSourceTest { public: ActiveSyncTodoTest() : RegisterSyncSourceTest("eas_task", "eds_task") {} virtual void updateConfig(ClientTestConfig &config) const { config.m_type = "eas-todos"; updateConfigEAS(this, config, EAS_ITEM_TODO); } } ActiveSyncTodoTest; static class ActiveSyncMemoTest : public RegisterSyncSourceTest { public: ActiveSyncMemoTest() : RegisterSyncSourceTest("eas_memo", "eds_memo") {} virtual void updateConfig(ClientTestConfig &config) const { config.m_type = "eas-memos"; updateConfigEAS(this, config, EAS_ITEM_JOURNAL); } } ActiveSyncMemoTest; } // anonymous namespace #endif // ENABLE_ACTIVESYNC SE_END_CXX syncevolution_1.4/src/backends/activesync/README000066400000000000000000000132751230021373600217570ustar00rootroot00000000000000Compilation =========== activesyncd development files must be installed so that pkg-config finds libeassync.pc. --enable-activesync then enables the ActiveSync backend. It is off by default, even if development files are found. Quickstart ========== Check that it is enabled: $ syncevolution backend=? | grep -i activesync ActiveSync Address Book = eas-contacts ActiveSync Events = eas-events ActiveSync Todos = eas-todos ActiveSync Memos = eas-memos Configure access to the server: $ syncevolution --configure \ syncURL= \ username= \ addressbook/backend=eas-contacts \ calendar/backend=eas-events \ todo/backend=eas-todos \ memo/backend=eas-memos \ target-config@exchange addressbook calendar todo memo List items: $ syncevolution --print-items target-config@exchange [calendar|addressbook|todo|memo] Debugging: - run with --daemon=no (otherwise syncevo-dbus-server will execute the operation) - set SYNCEVOLUTION_DEBUG (otherwise SyncEvolution will suppress stdout/stderr from libs) Configure synchronization: $ syncevolution --configure \ --template SyncEvolution_Client \ syncURL=local://@exchange \ username= \ password= \ exchange Run a sync (involving all four data types!): $ syncevolution exchange Sync only events: $ syncevolution exchange calendar Note that it is not possible to share the same username in the target config between different sync configs, because change tracking is tied to the account, not the config using it. For the same reason, listing items will affect syncing if the same username (= account) is used in both. As a workaround it is possible to set up different accounts for the same server, see below. Backend Testing =============== In addition to an activesyncd account called (usually an email adddress), also create an activesyncd account called _B ("_B" appended) with the same settings *plus* "device_id" set explicitly. The value must be different than the one chosen automatically (see apps/activesyncd/device_id key). Because Exchange might not accept arbitrary strings, stick to the scheme of "32 digit hexadecimal number". This second accounts is required for Client::Source::*::testChanges, which must simulate two independent ActiveSync clients. Configure "local" testing (backend is covered, no syncing involved): ./syncevolution --configure username= \ eas_event/backend=eas-events \ eas_event/database= \ eas_contact/backend=eas-contacts \ eas_contact/database= \ --template SyncEvolution target-config@client-test-exchange On MeeGo: - install syncevolution-test and syncevolution-synccompare - create an empty directory, enter it - copy /usr/share/doc/syncevolution/testcases - CLIENT_TEST_UNIQUE_UID=1 \ CLIENT_TEST_SERVER=exchange \ client-test Client::Source::eas_event When compiling SyncEvolution: - enter the src directory - CLIENT_TEST_UNIQUE_UID=1 \ CLIENT_TEST_SERVER=exchange \ PATH=.:$PATH \ ./client-test Client::Source::eas_event CLIENT_TEST_UNIQUE_UID is necessary to avoid a UID conflict in a simple-minded insertion test. The proper solution is to add an "item exists" error to activesyncd and handle that in ActiveSyncSource::insertItem(). The test data that is used for testImport is in: testcases/eds_event.ics This data was meant for Evolution. Because it is standard iCalendar 2.0, it is a suitable starting point for Exchange. If it turns out that this data doesn't work with Exchange, then a simplified/adapter version of the file can be put into: testcases/eds_event.ics.exchange.tem Sync Testing ============ - Configure target-config@exchange with source 'calendar' and 'addressbook'. - Create two sync configs: syncevolution --configure \ syncURL=local://@exchange \ username= \ eds_event/backend=evolution-calendar \ eds_event/uri=eas_event \ eds_event/database=SyncEvolution_Test_eds_event_1 \ eds_contact/backend=evolution-contacts \ eds_contact/uri=eas_contact \ eds_contact/database=SyncEvolution_Test_eds_contact_1 \ --template SyncEvolution_Client exchange_1@client-test-1 eds_event eds_contact syncevolution --configure \ syncURL=local://@exchange \ username=_B \ eds_event/backend=evolution-calendar \ eds_event/uri=eas_event \ eds_event/database=SyncEvolution_Test_eds_event_2 \ eds_contact/backend=evolution-contacts \ eds_contact/uri=eas_contact \ eds_contact/database=SyncEvolution_Test_eds_contact_2 \ --template SyncEvolution_Client exchange_2@client-test-2 eds_event eds_contact - Create calendars named as follows in Evolution: SyncEvolution_Test_eds_event_1 SyncEvolution_Test_eds_event_2 - run test (see above for invoking client-test and setting up "testcases"): CLIENT_TEST_UNIQUE_UID=1 CLIENT_TEST_SERVER=exchange \ client-test Client::Sync::eds_event::testCopy This setup depends on overriding the username in target-config@exchange with different usernames in each sync config. This is necessary because change tracking depends on that username. Google via ActiveSync ===================== - Use "googleeas" instead of "exchange", because ActiveSyncSourceRegister.cpp needs to know that it is talking to Google, to work around the lack of Fetch command support. syncevolution_1.4/src/backends/activesync/activesync.am000066400000000000000000000035621230021373600235640ustar00rootroot00000000000000dist_noinst_DATA += \ src/backends/activesync/configure-sub.in \ src/backends/activesync/README src_backends_activesync_lib = src/backends/activesync/syncactivesync.la MOSTLYCLEANFILES += $(src_backends_activesync_lib) if ENABLE_MODULES src_backends_activesync_backenddir = $(BACKENDS_DIRECTORY) src_backends_activesync_backend_LTLIBRARIES = $(src_backends_activesync_lib) else noinst_LTLIBRARIES += $(src_backends_activesync_lib) endif src_backends_activesync_src = \ src/backends/activesync/ActiveSyncSource.h \ src/backends/activesync/ActiveSyncSource.cpp \ src/backends/activesync/ActiveSyncCalendarSource.h \ src/backends/activesync/ActiveSyncCalendarSource.cpp src_backends_activesync_syncactivesync_la_SOURCES = $(src_backends_activesync_src) src_backends_activesync_syncactivesync_la_LIBADD = $(EASCLIENT_LIBS) $(SYNCEVOLUTION_LIBS) $(LIBICAL_LIBS) $(GLIB_LIBS) $(GOBJECT_LIBS) src_backends_activesync_syncactivesync_la_LDFLAGS = -no-undefined -module -avoid-version src_backends_activesync_syncactivesync_la_CPPFLAGS = $(SYNCEVOLUTION_CFLAGS) -I$(top_srcdir)/test $(BACKEND_CPPFLAGS) src_backends_activesync_syncactivesync_la_CXXFLAGS = $(EASCLIENT_CFLAGS) $(SYNCEVOLUTION_CXXFLAGS) $(SYNCEVO_WFLAGS) $(LIBICAL_CFLAGS) $(GLIB_CFLAGS) $(GOBJECT_CFLAGS) src_backends_activesync_syncactivesync_la_DEPENDENCIES = src/syncevo/libsyncevolution.la $(EASCLIENT_DEPENDENCIES) # activated by EASCLIENT_DEPENDENCIES: usually empty, unless --with-activesyncd-src is used $(src_backends_activesync_src) src/backends/activesync/ActiveSyncSourceRegister.cpp: $(EASCLIENT_DEPENDENCIES) $(EASCLIENT_DEPENDENCIES): $(LIBEASCLIENT_LA_DEPENDENCIES) cd src/backends/activesync/activesyncd/build && $(MAKE) for i in libeasaccount/src libeasclient eas-daemon/libeas libeastest/src eas-daemon/src; do \ (cd src/backends/activesync/activesyncd/build/$$i && $(MAKE) DESTDIR= install) || exit 1; \ done syncevolution_1.4/src/backends/activesync/configure-sub.in000066400000000000000000000047441230021373600242000ustar00rootroot00000000000000dnl -*- mode: Autoconf; -*- dnl Invoke autogen.sh to produce a configure script. SE_ARG_ENABLE_BACKEND(activesync, activesync, [AS_HELP_STRING([--enable-activesync], [enable access to servers via ActiveSync (default off)])], [enable_activesync="$enableval"], [enable_activesync="no"]) AC_ARG_WITH(activesyncd-src, AS_HELP_STRING([--with-activesyncd-src=], [Specifies location of the activesyncd source root directory. Use this when activesyncd is to be compiled as part of the SyncEvolution compilation.]), [ACTIVESYNCDSRC="$withval" test "$ACTIVESYNCDSRC" != "yes" || AC_MSG_ERROR([--with-synthesis-src requires a parameter (base directory, svn URL or git URL)])], [ACTIVESYNCDSRC=""]) AM_CONDITIONAL([BUILD_ACTIVESYNCD], [test "$enable_activesync" = "yes" && test "$ACTIVESYNCDSRC" != ""]) if test "$enable_activesync" = "yes"; then if test "$ACTIVESYNCDSRC" != ""; then # brute-force dependency on any file in the source repo LIBEASCLIENT_LA_DEPENDENCIES=`find "$ACTIVESYNCDSRC"/* -type f -printf '%p ' ` AC_SUBST(LIBEASCLIENT_LA_DEPENDENCIES) mkdir -p src/backends/activesync/activesyncd/build AC_MSG_NOTICE([configuring activesyncd using the $ACTIVESYNCDSRC source code]) ( set -x; cd src/backends/activesync/activesyncd/build && $ACTIVESYNCDSRC/configure --disable-eplugin --disable-camel-backend --disable-qtconfig --prefix=`pwd`/../install) || AC_MSG_ERROR([configuring activesyncd failed]) # hard-coded replacement for pkg-config: necessary because # .pc file not installed yet EASCLIENT_LIBS=src/backends/activesync/activesyncd/install/lib/libeasclient.la EASCLIENT_DEPENDENCIES=src/backends/activesync/activesyncd/install/lib/libeasclient.la EASCLIENT_CFLAGS=-Isrc/backends/activesync/activesyncd/install/include/eas-daemon/eas-client AC_SUBST(EASCLIENT_LIBS) AC_SUBST(EASCLIENT_DEPENDENCIES) AC_SUBST(EASCLIENT_CFLAGS) else PKG_CHECK_MODULES(EASCLIENT, libeasclient) fi AC_DEFINE(ENABLE_ACTIVESYNC, 1, [ActiveSync available]) need_ical="yes" need_glib="yes" fi BACKEND_CPPFLAGS="$BACKEND_CPPFLAGS $EASCLIENT_CFLAGS" syncevolution_1.4/src/backends/addressbook/000077500000000000000000000000001230021373600212175ustar00rootroot00000000000000syncevolution_1.4/src/backends/addressbook/AddressBookConstants.cpp000066400000000000000000000156401230021373600260260ustar00rootroot00000000000000/* * Copyright (C) 2007-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "config.h" #ifdef ENABLE_ADDRESSBOOK #include #include SE_BEGIN_CXX /** * constants missing from AddressBook framework on iPhone: use strings * as found in SQLite database on iPhone * * kABTitleProperty is also missing, but the ABRecord constants which * are CFStrings on Mac OS seem to be numeric constants on the iPhone so * we cannot guess it might be (if it exists at all), so not supported. */ #ifdef __arm__ // CFStringRef kABCAIMInstantProperty; CFStringRef kABCAddressCityKey; CFStringRef kABCAddressCountryKey; CFStringRef kABCAddressHomeLabel; CFStringRef kABCAddressStateKey; CFStringRef kABCAddressStreetKey; CFStringRef kABCAddressWorkLabel; CFStringRef kABCAddressZIPKey; CFStringRef kABCAssistantLabel; CFStringRef kABCEmailHomeLabel; CFStringRef kABCEmailWorkLabel; CFStringRef kABCHomePageLabel; // CFStringRef kABCHomePageProperty; // CFStringRef kABCICQInstantProperty; CFStringRef kABCJabberHomeLabel; // CFStringRef kABCJabberInstantProperty; CFStringRef kABCJabberWorkLabel; // CFStringRef kABCMSNInstantProperty; CFStringRef kABCManagerLabel; // CFStringRef kABCOtherDatesProperty; CFStringRef kABCPhoneHomeFAXLabel; CFStringRef kABCPhoneHomeLabel; CFStringRef kABCPhoneMainLabel; CFStringRef kABCPhoneMobileLabel; CFStringRef kABCPhonePagerLabel; CFStringRef kABCPhoneWorkFAXLabel; CFStringRef kABCPhoneWorkLabel; CFStringRef kABCSpouseLabel; // CFStringRef kABCTitleProperty; // CFStringRef kABCURLsProperty; // CFStringRef kABCYahooInstantProperty; #endif class constants { public: constants() { #ifdef __arm__ // kABCAIMInstantProperty = CFStringCreateWithCString(NULL, "AIMInstant", kCFStringEncodingUTF8); kABCAddressCityKey = CFStringCreateWithCString(NULL, "City", kCFStringEncodingUTF8); kABCAddressCountryKey = CFStringCreateWithCString(NULL, "Country", kCFStringEncodingUTF8); kABCAddressHomeLabel = CFStringCreateWithCString(NULL, "_$!!$_", kCFStringEncodingUTF8); kABCAddressStateKey = CFStringCreateWithCString(NULL, "State", kCFStringEncodingUTF8); kABCAddressStreetKey = CFStringCreateWithCString(NULL, "Street", kCFStringEncodingUTF8); kABCAddressWorkLabel = CFStringCreateWithCString(NULL, "_$!!$_", kCFStringEncodingUTF8); kABCAddressZIPKey = CFStringCreateWithCString(NULL, "ZIP", kCFStringEncodingUTF8); kABCAssistantLabel = CFStringCreateWithCString(NULL, "_$!!$_", kCFStringEncodingUTF8); kABCEmailHomeLabel = CFStringCreateWithCString(NULL, "_$!!$_", kCFStringEncodingUTF8); kABCEmailWorkLabel = CFStringCreateWithCString(NULL, "_$!!$_", kCFStringEncodingUTF8); kABCHomePageLabel = CFStringCreateWithCString(NULL, "_$!!$_", kCFStringEncodingUTF8); // kABCHomePageProperty = CFStringCreateWithCString(NULL, "HomePage", kCFStringEncodingUTF8); // kABCICQInstantProperty = CFStringCreateWithCString(NULL, "ICQInstant", kCFStringEncodingUTF8); kABCJabberHomeLabel = CFStringCreateWithCString(NULL, "_$!!$_", kCFStringEncodingUTF8); // kABCJabberInstantProperty = CFStringCreateWithCString(NULL, "JabberInstant", kCFStringEncodingUTF8); kABCJabberWorkLabel = CFStringCreateWithCString(NULL, "_$!!$_", kCFStringEncodingUTF8); // kABCMSNInstantProperty = CFStringCreateWithCString(NULL, "MSNInstant", kCFStringEncodingUTF8); kABCManagerLabel = CFStringCreateWithCString(NULL, "_$!!$_", kCFStringEncodingUTF8); // kABCOtherDatesProperty = CFStringCreateWithCString(NULL, "ABDate", kCFStringEncodingUTF8); kABCPhoneHomeFAXLabel = CFStringCreateWithCString(NULL, "_$!!$_", kCFStringEncodingUTF8); kABCPhoneHomeLabel = CFStringCreateWithCString(NULL, "_$!!$_", kCFStringEncodingUTF8); kABCPhoneMainLabel = CFStringCreateWithCString(NULL, "_$!

    !$_", kCFStringEncodingUTF8); kABCPhoneMobileLabel = CFStringCreateWithCString(NULL, "_$!!$_", kCFStringEncodingUTF8); kABCPhonePagerLabel = CFStringCreateWithCString(NULL, "_$!!$_", kCFStringEncodingUTF8); kABCPhoneWorkFAXLabel = CFStringCreateWithCString(NULL, "_$!!$_", kCFStringEncodingUTF8); kABCPhoneWorkLabel = CFStringCreateWithCString(NULL, "_$!!$_", kCFStringEncodingUTF8); kABCSpouseLabel = CFStringCreateWithCString(NULL, "_$!!$_", kCFStringEncodingUTF8); // kABCTitleProperty = CFStringCreateWithCString(NULL, "Title", kCFStringEncodingUTF8); // kABCURLsProperty = CFStringCreateWithCString(NULL, "URLs", kCFStringEncodingUTF8); // kABCYahooInstantProperty = CFStringCreateWithCString(NULL, "YahooInstant", kCFStringEncodingUTF8); #endif #if 0 #define printconstant(_x) printf(#_x ": %s\n", CFString2Std((CFStringRef)_x).c_str()) printconstant(kABAIMInstantProperty); printconstant(kABAddressCityKey); printconstant(kABAddressCountryKey); printconstant(kABAddressHomeLabel); printconstant(kABAddressStateKey); printconstant(kABAddressStreetKey); printconstant(kABAddressWorkLabel); printconstant(kABAddressZIPKey); printconstant(kABAssistantLabel); printconstant(kABEmailHomeLabel); printconstant(kABEmailWorkLabel); printconstant(kABHomePageLabel); printconstant(kABHomePageProperty); printconstant(kABICQInstantProperty); printconstant(kABJabberHomeLabel); printconstant(kABJabberInstantProperty); printconstant(kABJabberWorkLabel); printconstant(kABMSNInstantProperty); printconstant(kABManagerLabel); printconstant(kABOtherDatesProperty); printconstant(kABPhoneHomeFAXLabel); printconstant(kABPhoneHomeLabel); printconstant(kABPhoneMainLabel); printconstant(kABPhoneMobileLabel); printconstant(kABPhonePagerLabel); printconstant(kABPhoneWorkFAXLabel); printconstant(kABPhoneWorkLabel); printconstant(kABSpouseLabel); // printconstant(kABTitleProperty); printconstant(kABURLsProperty); printconstant(kABYahooInstantProperty); #endif } } constants; SE_END_CXX #endif // ENABLE_ADDRESSBOOK syncevolution_1.4/src/backends/addressbook/AddressBookSource.cpp000066400000000000000000001512561230021373600253160ustar00rootroot00000000000000/* * Copyright (C) 2007-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include using namespace std; #include "config.h" #ifdef ENABLE_ADDRESSBOOK #ifdef IPHONE # define ABAddRecord ABCAddRecord # define ABCopyArrayOfAllPeople ABCCopyArrayOfAllPeople # define ABGetSharedAddressBook ABCGetSharedAddressBook # define ABMultiValueAdd ABCMultiValueAdd # define ABMultiValueCopyLabelAtIndex ABCMultiValueCopyLabelAtIndex # define ABMultiValueCopyValueAtIndex ABCMultiValueCopyValueAtIndex # define ABMultiValueCount ABCMultiValueGetCount # define ABMultiValueCreateMutable ABCMultiValueCreateMutable // # define ABPersonCopyImageData ABCPersonCopyImageData # define PersonCreateWrapper(_addressbook) ABCPersonCreateNewPerson(_addressbook) /** * The iPhone stores photos in three (?) different sizes. * Storing just one copy is okay, albeit a bit inefficient: * it needs to be scaled down each time it is accessed. * * @todo When importing photos into the address book, create * all three different sizes. */ enum { IPHONE_PHOTO_SIZE_THUMBNAIL, IPHONE_PHOTO_SIZE_MEDIUM, IPHONE_PHOTO_SIZE_ORIGINAL }; # define PersonSetImageDataWrapper(_person, _dataref) ABCPersonSetImageDataAndCropRect(_person, IPHONE_PHOTO_SIZE_THUMBNAIL, _dataref, 0,0,0,0) # define ABRecordCopyValue ABCRecordCopyValue # define ABRecordRemoveValue ABCRecordRemoveValue # define ABRecordSetValue ABCRecordSetValue # define ABRemoveRecord ABCRemoveRecord # define ABSave ABCSave # define kABAIMInstantProperty kABCAIMInstantProperty # define kABAddressCityKey kABCAddressCityKey # define kABAddressCountryKey kABCAddressCountryKey # define kABAddressHomeLabel kABCAddressHomeLabel # define kABAddressProperty kABCAddressProperty # define kABAddressStateKey kABCAddressStateKey # define kABAddressStreetKey kABCAddressStreetKey # define kABAddressWorkLabel kABCAddressWorkLabel # define kABAddressZIPKey kABCAddressZIPKey # define kABAssistantLabel kABCAssistantLabel # define kABBirthdayProperty kABCBirthdayProperty # define kABCreationDateProperty kABCCreationDateProperty # define kABDepartmentProperty kABCDepartmentProperty # define kABEmailHomeLabel kABCEmailHomeLabel # define kABEmailProperty kABCEmailProperty # define kABEmailWorkLabel kABCEmailWorkLabel # define kABFirstNameProperty kABCFirstNameProperty # define kABHomePageLabel kABCHomePageLabel /* # define kABHomePageProperty kABCHomePageProperty */ # define kABICQInstantProperty kABCICQInstantProperty # define kABJabberHomeLabel kABCJabberHomeLabel # define kABJabberInstantProperty kABCJabberInstantProperty # define kABJabberWorkLabel kABCJabberWorkLabel # define kABJobTitleProperty kABCJobTitleProperty # define kABLastNameProperty kABCLastNameProperty # define kABMSNInstantProperty kABCMSNInstantProperty # define kABManagerLabel kABCManagerLabel # define kABMiddleNameProperty kABCMiddleNameProperty # define kABModificationDateProperty kABCModificationDateProperty # define kABNicknameProperty kABCNicknameProperty # define kABNoteProperty kABCNoteProperty # define kABOrganizationProperty kABCOrganizationProperty # define kABOtherDatesProperty kABCOtherDatesProperty # define kABPhoneHomeFAXLabel kABCPhoneHomeFAXLabel # define kABPhoneHomeLabel kABCPhoneHomeLabel # define kABPhoneMainLabel kABCPhoneMainLabel # define kABPhoneMobileLabel kABCPhoneMobileLabel # define kABPhonePagerLabel kABCPhonePagerLabel # define kABPhoneProperty kABCPhoneProperty # define kABPhoneWorkFAXLabel kABCPhoneWorkFAXLabel # define kABPhoneWorkLabel kABCPhoneWorkLabel # define kABRelatedNamesProperty kABCRelatedNamesProperty # define kABSpouseLabel kABCSpouseLabel # define kABSuffixProperty kABCSuffixProperty // # define kABTitleProperty kABCTitleProperty // # define kABURLsProperty kABCURLsProperty # define kABYahooInstantProperty kABCYahooInstantProperty #else # define PersonCreateWrapper(_addressbook) ABPersonCreate() # define PersonSetImageDataWrapper(_person, _dataref) ABPersonSetImageData(_person, _dataref) #endif #include #include "AddressBookSource.h" #include #include #include "vocl/VConverter.h" #include #include SE_BEGIN_CXX using namespace vocl; /** converts a CFString to std::string in UTF-8 - does not free input, throws exception if conversion impossible */ static string CFString2Std(CFStringRef cfstring) { const char *str = CFStringGetCStringPtr(cfstring, kCFStringEncodingUTF8); if (str) { return string(str); } CFIndex len = CFStringGetLength(cfstring) * 2 + 1; for (int tries = 0; tries < 3; tries++) { arrayptr buf(new char[len], "buffer"); if (CFStringGetCString(cfstring, buf, len, kCFStringEncodingUTF8)) { return string((char *)buf); } len *= 2; } SyncContext::throwError("converting CF string failed"); return ""; } /** converts a string in UTF-8 into a CFString - throws an exception if no valid reference can be generated */ static CFStringRef Std2CFString(const string &str) { ref cfstring(CFStringCreateWithCString(NULL, str.c_str(), kCFStringEncodingUTF8), "conversion from CFString"); return cfstring.release(); } /** generic label for 'other' items in a multi-value list */ static const CFStringRef otherLabel(CFSTR("_$!!$_")); /** generic label for 'work' items in a multi-value list */ static const CFStringRef workLabel(CFSTR("_$!!$_")); /** custom label used for "TEL;PREF;WORK" */ static const CFStringRef mainWorkLabel(CFSTR("main work")); #ifdef IPHONE /** declarations and functions which are missing in iPhone framework */ extern "C" { extern const CFStringRef kABCHomePageProperty; extern const CFStringRef kABCURLProperty; ABPersonRef ABCPersonCreateNewPerson(ABAddressBookRef addressbook); ABRecordRef ABCPersonGetRecordForUniqueID(ABAddressBookRef addressBook, SInt32 uid); ABRecordRef ABCopyRecordForUniqueId(ABAddressBookRef addressBook, CFStringRef uniqueId) { SInt32 uid = CFStringGetIntValue(uniqueId); return ABCPersonGetRecordForUniqueID(addressBook, uid); } SInt32 ABCRecordGetUniqueId(ABRecordRef record); CFStringRef ABRecordCopyUniqueId(ABRecordRef record) { SInt32 uid = ABCRecordGetUniqueId(record); return CFStringCreateWithFormat(NULL, NULL, CFSTR("%d"), uid); } CFDataRef ABCPersonCopyImageData(ABPersonRef person, int format); bool ABCPersonSetImageData(ABPersonRef person, int format, CFDataRef data); bool ABCPersonSetImageDataAndCropRect(ABPersonRef person, int format, CFDataRef data, int crop_x, int crop_y, int crop_width, int crop_height); } #endif /** * a strtok_r() which does no skip delimiters at the start and end and does * not merge consecutive delimiters, i.e. returned string may be empty * * @return NULL if no further tokens */ static char *my_strtok_r(char *buffer, char delim, char **ptr, char **endptr) { char *res; if (buffer) { *ptr = buffer; *endptr = buffer + strlen(buffer); } res = *ptr; if (res == *endptr) { return NULL; } while (**ptr) { if (**ptr == delim) { **ptr = 0; (*ptr)++; break; } (*ptr)++; } return res; } /** converts between vCard and ABPerson and back */ class vCard2ABPerson { public: vCard2ABPerson(string &vcard, ABPersonRef person) : m_vcard(vcard), m_person(person) { } /** parses vcard and stores result in person */ void toPerson() { std::auto_ptr vobj(VConverter::parse((char *)m_vcard.c_str())); if (vobj.get() == 0) { throwError("parsing contact"); } vobj->toNativeEncoding(); // Remove all properties from person that we might set: // those still found in the vCard will be recreated. // Properties that we do not support are left untouched. for (int mapindex = 0; m_mapping[mapindex].m_vCardProp; mapindex++) { const mapping &map = m_mapping[mapindex]; if (map.m_abPersonProp) { if (!ABRecordRemoveValue(m_person, *map.m_abPersonProp)) { throwError("removing old value " #ifndef IPHONE + CFString2Std(*map.m_abPersonProp) + " " + #endif "failed"); } } } for (int multi = 0; multi < MAX_MULTIVALUE; multi++) { if (!ABRecordRemoveValue(m_person, *m_multiProp[multi])) { throwError(string("removing old value ") #ifndef IPHONE + CFString2Std(*m_multiProp[multi]) + " " #endif + "failed"); } } // walk through all properties and handle them int propindex = 0; VProperty *vprop; while ((vprop = vobj->getProperty(propindex)) != NULL) { for (int mapindex = 0; m_mapping[mapindex].m_vCardProp; mapindex++) { const mapping &map = m_mapping[mapindex]; if (!strcmp(map.m_vCardProp, vprop->getName())) { toPerson_t handler = map.m_toPerson; if (!handler) { handler = &vCard2ABPerson::toPersonString; } (this->*handler)(map, *vprop); break; } } propindex++; } // now copy all those values to the person which did not map directly for (int multi = 0; multi < MAX_MULTIVALUE; multi++) { if (m_multi[multi]) { setPersonProp(*m_multiProp[multi], m_multi[multi], false); } } VProperty *photo = vobj->getProperty("PHOTO"); if (photo) { int len; arrayptr decoded((char *)b64_decode(len, photo->getValue()), "photo"); ref data(CFDataCreate(NULL, (UInt8 *)(char *)decoded, len)); if (!PersonSetImageDataWrapper(m_person, data)) { SyncContext::throwError("cannot set photo data"); } } } /** convert person into vCard 2.1 or 3.0 and store it in string */ void fromPerson(bool asVCard30) { string tmp; // VObject is so broken that it neither as a reset nor // an assignment operator - no, I didn't write it :-/ // // Reseting m_vobj was supposed to allow repeated calls // to fromPerson, but this is not really necessary. // m_vobj = VObject(); m_vobj.addProperty("BEGIN", "VCARD"); m_vobj.addProperty("VERSION", asVCard30 ? "3.0" : "2.1"); m_vobj.setVersion(asVCard30 ? "3.0" : "2.1"); // iterate over all person properties and handle them for (int mapindex = 0; m_mapping[mapindex].m_vCardProp; mapindex++ ) { const mapping &map = m_mapping[mapindex]; if (map.m_abPersonProp) { #ifdef IPHONE // some of the properties returned on the iPhone can neither // be printed nor released: trying it leads to crashes, so // avoid it CFTypeRef value = ABRecordCopyValue(m_person, *map.m_abPersonProp); #else ref value(ABRecordCopyValue(m_person, *map.m_abPersonProp)); #endif if (value) { fromPerson_t handler = map.m_fromPerson; if (!handler) { handler = &vCard2ABPerson::fromPersonString; } (this->*handler)(map, value); } } } // add properties which did not map directly string n; n += m_strings[LAST_NAME]; n += VObject::SEMICOLON_REPLACEMENT; n += m_strings[FIRST_NAME]; n += VObject::SEMICOLON_REPLACEMENT; n += m_strings[MIDDLE_NAME]; n += VObject::SEMICOLON_REPLACEMENT; n += m_strings[TITLE]; n += VObject::SEMICOLON_REPLACEMENT; n += m_strings[SUFFIX]; m_vobj.addProperty("N", n.c_str()); if (m_strings[ORGANIZATION].size() || m_strings[DEPARTMENT].size() ) { string org; org += m_strings[ORGANIZATION]; org += VObject::SEMICOLON_REPLACEMENT; org += m_strings[DEPARTMENT]; m_vobj.addProperty("ORG", org.c_str()); } ref photo; #ifdef IPHONE // ask for largets size first for(int format = IPHONE_PHOTO_SIZE_ORIGINAL; format >= 0; format--) { photo.set(ABCPersonCopyImageData(m_person, format)); if (photo) { break; } } #else photo.set(ABPersonCopyImageData(m_person)); #endif if (photo) { StringBuffer encoded; b64_encode(encoded, (void *)CFDataGetBytePtr(photo), CFDataGetLength(photo)); VProperty vprop("PHOTO"); vprop.addParameter("ENCODING", asVCard30 ? "B" : "BASE64"); vprop.setValue(encoded.c_str()); m_vobj.addProperty(&vprop); } m_vobj.addProperty("END", "VCARD"); m_vobj.fromNativeEncoding(); arrayptr finalstr(m_vobj.toString(), "VOCL string"); m_vcard = (char *)finalstr; } private: string &m_vcard; ABPersonRef m_person; VObject m_vobj; void throwError(const string &error) { SyncContext::throwError(string("vCard<->Addressbook conversion: ") + error); } /** intermediate storage for strings gathered from either vcard or person */ enum { FIRST_NAME, MIDDLE_NAME, LAST_NAME, TITLE, SUFFIX, ORGANIZATION, DEPARTMENT, MAX_STRINGS }; string m_strings[MAX_STRINGS]; /** intermediate storage for multi-value data later passed to ABPerson - keep in sync with m_multiProp */ enum { URLS, EMAILS, PHONES, #ifndef IPHONE DATES, AIM, JABBER, MSN, YAHOO, ICQ, #endif NAMES, ADDRESSES, MAX_MULTIVALUE }; ref m_multi[MAX_MULTIVALUE]; /** * the ABPerson property which corresponds to the m_multi array: * a pointer because the tool chain for the iPhone did not properly * handle the constants when referenced in data initialization directly */ static const CFStringRef *m_multiProp[MAX_MULTIVALUE]; struct mapping; /** member function which handles one specific vCard property */ typedef void (vCard2ABPerson::*toPerson_t)(const mapping &map, VProperty &vprop); /** member function which handles one specific ABPerson property */ typedef void (vCard2ABPerson::*fromPerson_t)(const mapping &map, CFTypeRef cftype); /** store a string in the ABPerson */ void setPersonProp(CFStringRef property, const string &str) { ref cfstring(Std2CFString(str)); setPersonProp(property, cfstring); } /** store a string in the ABPerson */ void setPersonProp(CFStringRef property, const char *str) { ref cfstring(Std2CFString(str)); setPersonProp(property, cfstring); } /** * store a generic property in the ABPerson * @param dump avoid CFCopyDescription() for some properties (iPhone bug) */ void setPersonProp(CFStringRef property, CFTypeRef cftype, bool dump = true) { ref descr; if (dump) { descr.set(CFCopyDescription(cftype)); } if (!ABRecordSetValue(m_person, property, cftype)) { if (dump) { throwError(string("setting ") + #ifndef IPHONE CFString2Std(property) + #else "property " + #endif + " to " + CFString2Std(descr) + "'"); } else { throwError(string("setting ") + #ifndef IPHONE CFString2Std(property) #else "property" #endif ); } } } /** add another label/value pair to a multi-value list */ void toPersonMultiVal(const mapping &map, CFStringRef label, CFTypeRef value) { if (!m_multi[map.m_customInt]) { m_multi[map.m_customInt].set(ABMultiValueCreateMutable(), "multivalue"); } CFStringRef res; if (!ABMultiValueAdd(m_multi[map.m_customInt], value, label, &res)) { throwError(string("adding multi value for ") + map.m_vCardProp); } else { #ifndef IPHONE CFRelease(res); #endif } } /** * mapping between vCard and ABPerson properties */ static const struct mapping { /** the name of the vCard property, e.g. "ADDR", NULL terminates array */ const char *m_vCardProp; /** address of ABPerson property, NULL pointer if none matches directly */ const CFStringRef *m_abPersonProp; /** called when the property is found in the VObject: default is to copy string */ toPerson_t m_toPerson; /** called when the property is found in the ABPerson: default is to copy string */ fromPerson_t m_fromPerson; /** custom value available to callbacks */ int m_customInt; /** custom value available to callbacks */ CFStringRef m_customString; } m_mapping[]; /** copy normal string directly */ void fromPersonString(const mapping &map, CFTypeRef cftype) { string value(CFString2Std((CFStringRef)cftype)); m_vobj.addProperty(map.m_vCardProp, value.c_str()); } /** copy normal string directly */ void toPersonString(const mapping &map, VProperty &vprop) { const char *value = vprop.getValue(); /* * Empty strings are not properly ignored by the iPhone GUI, * better not add empty string properties. Empty vcard * properties as an indication that the property is to be * cleared are still handled because all known properties * were removed in toPerson(). */ if (value && *value) { setPersonProp(*map.m_abPersonProp, value); } } /** remember string to compose a more complex vCard property later (e.g. "N") */ void fromPersonStoreString(const mapping &map, CFTypeRef cftype) { m_strings[map.m_customInt] = CFString2Std((CFStringRef)cftype); } /** * add a generic string with a predefined label * (map.m_customString) or a work/home label to multi-value */ void toPersonStore(const mapping &map, VProperty &vprop) { const char *value = vprop.getValue(); if (!value || !value[0]) { return; } ref cfstring(Std2CFString(value)); CFStringRef label = map.m_customString; if (!label) { // IM property: label depends on type; // same simplification as in fromPersonChat if (map.m_customString) { label = map.m_customString; } else if (vprop.isType("HOME")) { label = kABJabberHomeLabel; } else if (vprop.isType("WORK")) { label = kABJabberHomeLabel; } else { label = otherLabel; } } toPersonMultiVal(map, label, cfstring); } /** copy date */ void fromPersonDate(const mapping &map, CFTypeRef cftype) { ref tz(CFTimeZoneCopyDefault()); CFGregorianDate date = CFAbsoluteTimeGetGregorianDate(CFDateGetAbsoluteTime((CFDateRef)cftype), tz); char buffer[40]; sprintf(buffer, "%04d-%02d-%02d", (int)date.year, date.month, date.day); m_vobj.addProperty(map.m_vCardProp, buffer); } /** copy date */ void toPersonDate(const mapping &map, VProperty &vprop) { int year, month, day; const char *value = vprop.getValue(); if (!value || !value[0]) { return; } // cppcheck-suppress invalidscanf if (sscanf(value, "%d-%d-%d", &year, &month, &day) == 3) { CFGregorianDate date; memset(&date, 0, sizeof(date)); date.year = year; date.month = month; date.day = day; /* * The iPhone stores absolute times for dates, but * interprets them according to the current time zone. * The effect is that a birthday changes as the system * timezone is changed. * * To mitigate this problem dates are created with * an absolute time in the current time zone, just like * the iPhone GUI does. */ ref tz(CFTimeZoneCopyDefault()); ref cfdate(CFDateCreate(NULL, CFGregorianDateGetAbsoluteTime(date, tz))); if (cfdate) { // assert(map.m_abPersonProp); setPersonProp(*map.m_abPersonProp, cfdate); } } } /** map URL multi-value to vCard URL with different TYPEs */ void fromPersonURLs(const mapping &map, CFTypeRef cftype) { int index = ABMultiValueCount((ABMultiValueRef)cftype) - 1; while (index >= 0) { ref label((CFStringRef)ABMultiValueCopyLabelAtIndex((ABMultiValueRef)cftype, index), "label"); ref value((CFStringRef)ABMultiValueCopyValueAtIndex((ABMultiValueRef)cftype, index), "value"); VProperty vprop("URL"); string url = CFString2Std(value); vprop.setValue(url.c_str()); if (CFStringCompare(label, (CFStringRef)kABHomePageLabel, 0) == kCFCompareEqualTo) { // leave type blank } else if (CFStringCompare(label, (CFStringRef)workLabel, 0) == kCFCompareEqualTo) { vprop.addParameter("TYPE", "WORK"); } else if (CFStringCompare(label, otherLabel, 0) == kCFCompareEqualTo) { vprop.addParameter("TYPE", "OTHER"); } else { string labelstr = CFString2Std(label); vprop.addParameter("TYPE", labelstr.c_str()); } m_vobj.addProperty(&vprop); index--; } } /** iPhone: add another URL to multi-value (Mac OS X only has one string property) */ void toPersonURLs(const mapping &map, VProperty &vprop) { const char *value = vprop.getValue(); if (!value || !value[0]) { return; } arrayptr buffer(wstrdup(value)); ref cfvalue(Std2CFString(value)); CFStringRef label; ref custom; const char *type = vprop.getParameterValue("TYPE"); if (vprop.isType("WORK")) { label = workLabel; } else if(vprop.isType("HOME")) { label = (CFStringRef)kABHomePageLabel; } else if(vprop.isType("OTHER")) { label = otherLabel; } else if (type) { custom.set(Std2CFString(type)); label = custom; } else { label = (CFStringRef)kABHomePageLabel; } toPersonMultiVal(map, label, cfvalue); } /** map email multi-value to vCard EMAIL with different TYPEs */ void fromPersonEMail(const mapping &map, CFTypeRef cftype) { int index = ABMultiValueCount((ABMultiValueRef)cftype) - 1; while (index >= 0) { ref label((CFStringRef)ABMultiValueCopyLabelAtIndex((ABMultiValueRef)cftype, index), "label"); ref value((CFStringRef)ABMultiValueCopyValueAtIndex((ABMultiValueRef)cftype, index), "value"); VProperty vprop("EMAIL"); if (CFStringCompare(label, kABEmailWorkLabel, 0) == kCFCompareEqualTo) { vprop.addParameter("TYPE", "WORK"); } else if (CFStringCompare(label, kABEmailHomeLabel, 0) == kCFCompareEqualTo) { vprop.addParameter("TYPE", "HOME"); } else { string labelstr = CFString2Std(label); vprop.addParameter("TYPE", labelstr.c_str()); } string email = CFString2Std(value); vprop.setValue(email.c_str()); m_vobj.addProperty(&vprop); index--; } } /** add another EMAIL to the email multi-value */ void toPersonEMail(const mapping &map, VProperty &vprop) { const char *value = vprop.getValue(); if (!value || !value[0]) { return; } arrayptr buffer(wstrdup(value)); ref cfvalue(Std2CFString(value)); CFStringRef label; ref custom; const char *type = vprop.getParameterValue("TYPE"); if (vprop.isType("WORK")) { label = kABEmailWorkLabel; } else if(vprop.isType("HOME")) { label = kABEmailHomeLabel; } else if (type) { custom.set(Std2CFString(type)); label = custom; } else { label = otherLabel; } toPersonMultiVal(map, label, cfvalue); } /** map address multi-value to vCard ADR with different TYPEs */ void fromPersonAddr(const mapping &map, CFTypeRef cftype) { int index = ABMultiValueCount((ABMultiValueRef)cftype) - 1; while (index >= 0) { ref label((CFStringRef)ABMultiValueCopyLabelAtIndex((ABMultiValueRef)cftype, index), "label"); ref value((CFDictionaryRef)ABMultiValueCopyValueAtIndex((ABMultiValueRef)cftype, index), "value"); CFStringRef part; VProperty vprop((char *)map.m_vCardProp); string adr; // no PO box adr += VObject::SEMICOLON_REPLACEMENT; // no extended address adr += VObject::SEMICOLON_REPLACEMENT; // street part = (CFStringRef)CFDictionaryGetValue(value, kABAddressStreetKey); if (part) { adr += CFString2Std(part); } adr += VObject::SEMICOLON_REPLACEMENT; // city part = (CFStringRef)CFDictionaryGetValue(value, kABAddressCityKey); if (part) { adr += CFString2Std(part); } adr += VObject::SEMICOLON_REPLACEMENT; // region part = (CFStringRef)CFDictionaryGetValue(value, kABAddressStateKey); if (part) { adr += CFString2Std(part); } adr += VObject::SEMICOLON_REPLACEMENT; // ZIP code part = (CFStringRef)CFDictionaryGetValue(value, kABAddressZIPKey); if (part) { adr += CFString2Std(part); } adr += VObject::SEMICOLON_REPLACEMENT; // country part = (CFStringRef)CFDictionaryGetValue(value, kABAddressCountryKey); if (part) { adr += CFString2Std(part); } adr += VObject::SEMICOLON_REPLACEMENT; // not supported: kABAddressCountryCodeKey if (CFStringCompare(label, kABAddressWorkLabel, 0) == kCFCompareEqualTo) { vprop.addParameter("TYPE", "WORK"); } else if (CFStringCompare(label, kABAddressHomeLabel, 0) == kCFCompareEqualTo) { vprop.addParameter("TYPE", "HOME"); } vprop.setValue(adr.c_str()); m_vobj.addProperty(&vprop); index--; } } /** add another ADR to address multi-value */ void toPersonAddr(const mapping &map, VProperty &vprop) { const char *value = vprop.getValue(); if (!value || !value[0]) { return; } arrayptr buffer(wstrdup(value)); char *saveptr, *endptr; ref dict(CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); // cannot store PO box and extended address /* char *pobox = */ my_strtok_r(buffer, VObject::SEMICOLON_REPLACEMENT, &saveptr, &endptr); /* char *extadr = */ my_strtok_r(NULL, VObject::SEMICOLON_REPLACEMENT, &saveptr, &endptr); char *street = my_strtok_r(NULL, VObject::SEMICOLON_REPLACEMENT, &saveptr, &endptr); if (street && *street) { ref cfstring(Std2CFString(street)); CFDictionarySetValue(dict, kABAddressStreetKey, cfstring); } char *city = my_strtok_r(NULL, VObject::SEMICOLON_REPLACEMENT, &saveptr, &endptr); if (city && *city) { ref cfstring(Std2CFString(city)); CFDictionarySetValue(dict, kABAddressCityKey, cfstring); } char *region = my_strtok_r(NULL, VObject::SEMICOLON_REPLACEMENT, &saveptr, &endptr); if (region && *region) { ref cfstring(Std2CFString(region)); CFDictionarySetValue(dict, kABAddressStateKey, cfstring); } char *zip = my_strtok_r(NULL, VObject::SEMICOLON_REPLACEMENT, &saveptr, &endptr); if (zip && *zip) { ref cfstring(Std2CFString(zip)); CFDictionarySetValue(dict, kABAddressZIPKey, cfstring); } char *country = my_strtok_r(NULL, VObject::SEMICOLON_REPLACEMENT, &saveptr, &endptr); if (country && *country) { ref cfstring(Std2CFString(country)); CFDictionarySetValue(dict, kABAddressCountryKey, cfstring); } CFStringRef label; if (vprop.isType("WORK")) { label = kABAddressWorkLabel; } else if(vprop.isType("HOME")) { label = kABAddressHomeLabel; } else { label = otherLabel; } toPersonMultiVal(map, label, dict); } /** map phone multi-value to vCard TEL with different TYPEs */ void fromPersonPhone(const mapping &map, CFTypeRef cftype) { int index = ABMultiValueCount((ABMultiValueRef)cftype) - 1; while (index >= 0) { ref label((CFStringRef)ABMultiValueCopyLabelAtIndex((ABMultiValueRef)cftype, index), "label"); ref value((CFStringRef)ABMultiValueCopyValueAtIndex((ABMultiValueRef)cftype, index), "value"); VProperty vprop("TEL"); if (CFStringCompare(label, kABPhoneWorkLabel, 0) == kCFCompareEqualTo) { vprop.addParameter("TYPE", "WORK"); vprop.addParameter("TYPE", "VOICE"); } else if (CFStringCompare(label, mainWorkLabel, 0) == kCFCompareEqualTo) { vprop.addParameter("TYPE", "WORK"); vprop.addParameter("TYPE", "PREF"); } else if (CFStringCompare(label, kABPhoneHomeLabel, 0) == kCFCompareEqualTo) { vprop.addParameter("TYPE", "HOME"); vprop.addParameter("TYPE", "VOICE"); } else if (CFStringCompare(label, kABPhoneMobileLabel, 0) == kCFCompareEqualTo) { vprop.addParameter("TYPE", "CELL"); } else if (CFStringCompare(label, kABPhoneMainLabel, 0) == kCFCompareEqualTo) { vprop.addParameter("TYPE", "PREF"); vprop.addParameter("TYPE", "VOICE"); } else if (CFStringCompare(label, kABPhoneHomeFAXLabel, 0) == kCFCompareEqualTo) { vprop.addParameter("TYPE", "HOME"); vprop.addParameter("TYPE", "FAX"); } else if (CFStringCompare(label, kABPhoneWorkFAXLabel, 0) == kCFCompareEqualTo) { vprop.addParameter("TYPE", "WORK"); vprop.addParameter("TYPE", "FAX"); } else if (CFStringCompare(label,kABPhonePagerLabel , 0) == kCFCompareEqualTo) { vprop.addParameter("TYPE", "PAGER"); } else { // custom phone types not supported vprop.addParameter("TYPE", "VOICE"); } string phone = CFString2Std(value); vprop.setValue(phone.c_str()); m_vobj.addProperty(&vprop); index--; } } /** add another phone to the multi-value */ void toPersonPhone(const mapping &map, VProperty &vprop) { const char *value = vprop.getValue(); if (!value || !value[0]) { return; } arrayptr buffer(wstrdup(value)); ref cfvalue(Std2CFString(value)); CFStringRef label; if (vprop.isType("WORK")) { if (vprop.isType("FAX")) { label = kABPhoneWorkFAXLabel; } else if (vprop.isType("PREF")) { label = mainWorkLabel; } else { label = kABPhoneWorkLabel; } } else if(vprop.isType("HOME")) { if (vprop.isType("FAX")) { label = kABPhoneHomeFAXLabel; } else { label = kABPhoneHomeLabel; } } else if(vprop.isType("PREF") || vprop.isType("VOICE")) { label = kABPhoneMainLabel; } else if(vprop.isType("PAGER")) { label = kABPhonePagerLabel; } else if(vprop.isType("CELL")) { label = kABPhoneMobileLabel; } else { label = otherLabel; } toPersonMultiVal(map, label, cfvalue); } /** * map chat contact multi-value to respective vCard X- properties * * complementary operation is toPersonStore() */ void fromPersonChat(const mapping &map, CFTypeRef cftype) { int index = ABMultiValueCount((ABMultiValueRef)cftype) - 1; while (index >= 0) { ref label((CFStringRef)ABMultiValueCopyLabelAtIndex((ABMultiValueRef)cftype, index), "label"); ref value((CFStringRef)ABMultiValueCopyValueAtIndex((ABMultiValueRef)cftype, index), "value"); VProperty vprop((char *)map.m_vCardProp); // this is a slight over-simplification: // the assumption is that the labels for all IM properties are interchangeable // although the header file has different constants for them if (CFStringCompare(label, kABJabberWorkLabel, 0) == kCFCompareEqualTo) { vprop.addParameter("TYPE", "WORK"); } else if (CFStringCompare(label, kABJabberHomeLabel, 0) == kCFCompareEqualTo) { vprop.addParameter("TYPE", "HOME"); } else { // custom IM types not supported } string im = CFString2Std(value); vprop.setValue(im.c_str()); m_vobj.addProperty(&vprop); index--; } } /** map related names multi-value to some vCard extension properties */ void fromPersonNames(const mapping &map, CFTypeRef cftype) { int index = ABMultiValueCount((ABMultiValueRef)cftype) - 1; while (index >= 0) { ref label((CFStringRef)ABMultiValueCopyLabelAtIndex((ABMultiValueRef)cftype, index), "label"); ref value((CFStringRef)ABMultiValueCopyValueAtIndex((ABMultiValueRef)cftype, index), "value"); string name = CFString2Std(value); // there are no standard fields for all these related names: // use the ones from Evolution because some SyncML servers have // been extended to support them if (CFStringCompare(label, kABManagerLabel, 0) == kCFCompareEqualTo) { m_vobj.addProperty("X-EVOLUTION-MANAGER", name.c_str()); } else if (CFStringCompare(label, kABAssistantLabel, 0) == kCFCompareEqualTo) { m_vobj.addProperty("X-EVOLUTION-ASSISTANT", name.c_str()); } else if (CFStringCompare(label, kABSpouseLabel, 0) == kCFCompareEqualTo) { m_vobj.addProperty("X-EVOLUTION-SPOUSE", name.c_str()); } else { // many related names not supported } index--; } } /** * decode vCard N and store in person properties * * complementary operation is fromPersonStoreString() */ void toPersonName(const mapping &map, VProperty &vprop) { const char *value = vprop.getValue(); if (!value || !value[0]) { return; } arrayptr buffer(wstrdup(value)); char *saveptr, *endptr; char *last = my_strtok_r(buffer, VObject::SEMICOLON_REPLACEMENT, &saveptr, &endptr); if (last && *last) { setPersonProp(kABLastNameProperty, last); } char *first = my_strtok_r(NULL, VObject::SEMICOLON_REPLACEMENT, &saveptr, &endptr); if (first && *first) { setPersonProp(kABFirstNameProperty, first); } char *middle = my_strtok_r(NULL, VObject::SEMICOLON_REPLACEMENT, &saveptr, &endptr); if (middle && *middle) { setPersonProp(kABMiddleNameProperty, middle); } char *prefix = my_strtok_r(NULL, VObject::SEMICOLON_REPLACEMENT, &saveptr, &endptr); #ifndef IPHONE if (prefix && *prefix) { setPersonProp(kABTitleProperty, prefix); } #endif char *suffix = my_strtok_r(NULL, VObject::SEMICOLON_REPLACEMENT, &saveptr, &endptr); if (suffix && *suffix) { setPersonProp(kABSuffixProperty, suffix); } } /** * decode ORG and store in person properties * * complementary operation is fromPersonStoreString() */ void toPersonOrg(const mapping &map, VProperty &vprop) { const char *value = vprop.getValue(); if (!value || !value[0]) { return; } arrayptr buffer(wstrdup(value)); char *saveptr, *endptr; char *company = my_strtok_r(buffer, VObject::SEMICOLON_REPLACEMENT, &saveptr, &endptr); if (company && *company) { setPersonProp(kABOrganizationProperty, company); } char *department = my_strtok_r(NULL, VObject::SEMICOLON_REPLACEMENT, &saveptr, &endptr); if (department && *department) { setPersonProp(kABDepartmentProperty, department); } } }; const CFStringRef *vCard2ABPerson::m_multiProp[MAX_MULTIVALUE] = { #ifdef IPHONE &kABCURLProperty, #else (CFStringRef*)&kABURLsProperty, #endif &kABEmailProperty, &kABPhoneProperty, #ifndef IPHONE &kABOtherDatesProperty, &kABAIMInstantProperty, &kABJabberInstantProperty, &kABMSNInstantProperty, &kABYahooInstantProperty, &kABICQInstantProperty, #endif &kABRelatedNamesProperty, &kABAddressProperty }; const vCard2ABPerson::mapping vCard2ABPerson::m_mapping[] = { { "", &kABFirstNameProperty, NULL, &vCard2ABPerson::fromPersonStoreString, FIRST_NAME }, { "", &kABLastNameProperty, NULL, &vCard2ABPerson::fromPersonStoreString, LAST_NAME }, { "", &kABMiddleNameProperty, NULL, &vCard2ABPerson::fromPersonStoreString, MIDDLE_NAME }, #ifndef IPHONE { "", &kABTitleProperty, NULL, &vCard2ABPerson::fromPersonStoreString, TITLE }, #endif { "", &kABSuffixProperty, NULL, &vCard2ABPerson::fromPersonStoreString, SUFFIX }, { "N", 0, &vCard2ABPerson::toPersonName }, /* "FN" */ /* kABFirstNamePhoneticProperty */ /* kABLastNamePhoneticProperty */ /* kABMiddleNamePhoneticProperty */ { "BDAY", &kABBirthdayProperty, &vCard2ABPerson::toPersonDate, &vCard2ABPerson::fromPersonDate }, { "", &kABOrganizationProperty, NULL, &vCard2ABPerson::fromPersonStoreString, ORGANIZATION }, { "", &kABDepartmentProperty, NULL, &vCard2ABPerson::fromPersonStoreString, DEPARTMENT }, { "ORG", 0, &vCard2ABPerson::toPersonOrg }, { "TITLE", &kABJobTitleProperty }, /* "ROLE" */ #ifdef IPHONE { "URL", &kABCURLProperty, &vCard2ABPerson::toPersonURLs, &vCard2ABPerson::fromPersonURLs, URLS }, #else /** * bug in the header files for kABHomePageProperty and kABURLsProperty, * typecast required */ { "URL", (CFStringRef *)&kABHomePageProperty }, { "", (CFStringRef *)&kABURLsProperty, NULL, &vCard2ABPerson::fromPersonURLs }, #endif #if 0 kABHomePageLabel #endif { "EMAIL", &kABEmailProperty, &vCard2ABPerson::toPersonEMail, &vCard2ABPerson::fromPersonEMail, EMAILS }, #if 0 kABEmailWorkLabel kABEmailHomeLabel #endif { "ADR", &kABAddressProperty, &vCard2ABPerson::toPersonAddr, &vCard2ABPerson::fromPersonAddr, ADDRESSES }, #if 0 kABAddressWorkLabel kABAddressHomeLabel kABAddressStreetKey kABAddressCityKey kABAddressStateKey kABAddressZIPKey kABAddressCountryKey kABAddressCountryCodeKey #endif /* LABEL */ { "TEL", &kABPhoneProperty, &vCard2ABPerson::toPersonPhone, &vCard2ABPerson::fromPersonPhone, PHONES }, #if 0 kABPhoneWorkLabel kABPhoneHomeLabel kABPhoneMobileLabel kABPhoneMainLabel kABPhoneHomeFAXLabel kABPhoneWorkFAXLabel kABPhonePagerLabel #endif #ifndef IPHONE { "X-AIM", &kABAIMInstantProperty, &vCard2ABPerson::toPersonStore, &vCard2ABPerson::fromPersonChat, AIM }, { "X-JABBER", &kABJabberInstantProperty, &vCard2ABPerson::toPersonStore, &vCard2ABPerson::fromPersonChat, JABBER }, { "X-MSN", &kABMSNInstantProperty, &vCard2ABPerson::toPersonStore, &vCard2ABPerson::fromPersonChat, MSN }, { "X-YAHOO", &kABYahooInstantProperty, &vCard2ABPerson::toPersonStore, &vCard2ABPerson::fromPersonChat, YAHOO }, { "X-ICQ", &kABICQInstantProperty, &vCard2ABPerson::toPersonStore, &vCard2ABPerson::fromPersonChat, ICQ }, #endif /* "X-GROUPWISE */ { "NOTE", &kABNoteProperty }, { "NICKNAME", &kABNicknameProperty }, /* kABMaidenNameProperty */ /* kABOtherDatesProperty */ #ifndef IPHONE { "", &kABRelatedNamesProperty, NULL, &vCard2ABPerson::fromPersonNames }, #endif #if 0 kABMotherLabel kABFatherLabel kABParentLabel kABSisterLabel kABBrotherFAXLabel kABChildLabel kABFriendLabel kABSpouseLabel kABPartnerLabel kABAssistantLabel kABManagerLabel #endif { "X-EVOLUTION-MANAGER", 0, &vCard2ABPerson::toPersonStore, NULL, NAMES, kABManagerLabel }, { "X-EVOLUTION-ASSISTANT", 0, &vCard2ABPerson::toPersonStore, NULL, NAMES, kABAssistantLabel }, { "X-EVOLUTION-SPOUSE", 0, &vCard2ABPerson::toPersonStore, NULL, NAMES, kABSpouseLabel }, /* kABPersonFlags */ /* X-EVOLUTION-FILE-AS */ /* CATEGORIES */ /* CALURI */ /* FBURL */ /* X-EVOLUTION-VIDEO-URL */ /* X-MOZILLA-HTML */ /* X-EVOLUTION-ANNIVERSARY */ { NULL } }; string AddressBookSource::getModTime(ABRecordRef record) { double absolute; #ifdef IPHONE absolute = (double)(int)ABRecordCopyValue(record, kABModificationDateProperty); #else ref itemModTime((CFDateRef)ABRecordCopyValue(record, kABModificationDateProperty)); if (!itemModTime) { itemModTime.set((CFDateRef)ABRecordCopyValue(record, kABCreationDateProperty)); } if (!itemModTime) { throwError("extracting time stamp"); } absolute = CFDateGetAbsoluteTime(itemModTime); #endif // round up to next full second: // together with a sleep of 1 second in endSyncThrow() this ensures // that our time stamps are always >= the stored time stamp even if // the time stamp is rounded in the database char buffer[128]; sprintf(buffer, "%.0f", ceil(absolute)); return buffer; } AddressBookSource::AddressBookSource(const EvolutionSyncSourceParams ¶ms, bool asVCard30) : TrackingSyncSource(params), m_addressbook(0), m_asVCard30(asVCard30) { } EvolutionSyncSource::Databases AddressBookSource::getDatabases() { Databases result; result.push_back(Database("<>", "")); return result; } void AddressBookSource::open() { m_addressbook = ABGetSharedAddressBook(); if (!m_addressbook) { throwError("opening address book"); } } void AddressBookSource::listAllItems(RevisionMap_t &revisions) { ref allPersons(ABCopyArrayOfAllPeople(m_addressbook), "list of all people"); for (CFIndex i = 0; i < CFArrayGetCount(allPersons); i++) { ref cfuid(ABRecordCopyUniqueId((ABRecordRef)CFArrayGetValueAtIndex(allPersons, i)), "reading UID"); string uid(CFString2Std(cfuid)); revisions[uid] = getModTime((ABRecordRef)CFArrayGetValueAtIndex(allPersons, i)); } } void AddressBookSource::close() { if (m_addressbook && !hasFailed()) { SE_LOG_DEBUG(getDisplayName(), "flushing address book"); // store changes persistently if (!ABSave(m_addressbook)) { throwError("saving address book"); } // time stamps are rounded to next second, // so to prevent changes in that range of inaccurracy // sleep a bit before returning control sleep(2); SE_LOG_DEBUG(getDisplayName(), "done with address book"); } m_addressbook = NULL; } void AddressBookSource::exportData(ostream &out) { ref allPersons(ABCopyArrayOfAllPeople(m_addressbook), "list of all people"); for (CFIndex i = 0; i < CFArrayGetCount(allPersons); i++) { ABRecordRef person = (ABRecordRef)CFArrayGetValueAtIndex(allPersons, i); // CFStringRef descr = CFCopyDescription(person); ref cfuid(ABRecordCopyUniqueId(person), "reading UID"); string uid(CFString2Std(cfuid)); cxxptr item(createItem(uid, true), "sync item"); out << (char *)item->getData() << "\n"; } } SyncItem *AddressBookSource::createItem(const string &uid, bool asVCard30) { logItem(uid, "extracting from address book", true); ref cfuid(Std2CFString(uid)); ref person((ABPersonRef)ABCopyRecordForUniqueId(m_addressbook, cfuid), "contact"); auto_ptr item(new SyncItem(uid.c_str())); #ifdef USE_ADDRESS_BOOK_VCARD ref vcard(ABPersonCopyVCardRepresentation(person), "vcard"); SE_LOG_DEBUG(getDisplayName(), "%*s", (int)CFDataGetLength(vcard), (const char *)CFDataGetBytePtr(vcard)); item->setData(CFDataGetBytePtr(vcard), CFDataGetLength(vcard)); #else string vcard; try { vCard2ABPerson conv(vcard, person); conv.fromPerson(asVCard30); } catch (const std::exception &ex) { throwError("creating vCard for " + uid + " failed: " + ex.what()); } item->setData(vcard.c_str(), vcard.size()); #endif item->setDataType(getMimeType()); item->setModificationTime(0); return item.release(); } AddressBookSource::InsertItemResult AddressBookSource::insertItem(const string &luid, const SyncItem &item) { bool update = !luid.empty(); string newluid = luid; string data = (const char *)item.getData(); ref person; #ifdef USE_ADDRESS_BOOK_VCARD if (uid) { // overwriting the UID of a new contact failed - resort to deleting the old contact and inserting a new one deleteItem(uid); } ref vcard(CFDataCreate(NULL, (const UInt8 *)data.c_str(), data.size()), "vcard"); person.set((ABPersonRef)ABPersonCreateWithVCardRepresentation(vcard)); if (!person) { throwError(string("parsing vcard ") + data); } #else if (update) { // overwrite existing contact ref cfuid(Std2CFString(luid)); person.set((ABPersonRef)ABCopyRecordForUniqueId(m_addressbook, cfuid), "contact"); } else { // new contact person.set(PersonCreateWrapper(m_addressbook), "contact"); } try { SE_LOG_DEBUG(getDisplayName(), "storing vCard for %s:\n%s", update ? luid.c_str() : "new contact", data.c_str()); vCard2ABPerson converter(data, person); converter.toPerson(); } catch (const std::exception &ex) { throwError(string("storing vCard for ") + (update ? luid : "new contact") + " failed: " + ex.what()); } #endif // make sure we have a modification time stamp, otherwise the address book // sets one at random times CFAbsoluteTime nowabs = CFAbsoluteTimeGetCurrent(); #ifdef IPHONE void *now = (void *)(int)round(nowabs); #else ref now(CFDateCreate(NULL, nowabs), "current time"); #endif if (!ABRecordSetValue(person, kABModificationDateProperty, now)) { throwError("setting mod time"); } // existing contacts do not have to (and cannot) be added (?) if (update || ABAddRecord(m_addressbook, person)) { // need to save to get UID (iPhone) and final modification time (Mac OS X)? ABSave(m_addressbook); ref cfuid(ABRecordCopyUniqueId(person), "uid"); newluid = CFString2Std(cfuid); } else { throwError("storing new contact"); } string modtime = getModTime(person); return InsertItemResult(newluid, modtime, false); } void AddressBookSource::deleteItem(const string &uid) { ref cfuid(Std2CFString(uid.c_str())); ref person((ABPersonRef)ABCopyRecordForUniqueId(m_addressbook, cfuid)); if (person) { if (!ABRemoveRecord(m_addressbook, person)) { throwError(string("deleting contact ") + uid); } } else { SE_LOG_DEBUG(getDisplayName(), "%s: %s: request to delete non-existant contact ignored", getName(), uid.c_str()); } } void AddressBookSource::logItem(const string &uid, const string &info, bool debug) { if (getLevel() >= (debug ? Logger::DEBUG : Logger::INFO)) { string line; #if 0 // TODO if (e_book_get_contact( m_addressbook, uid.c_str(), &contact, &gerror )) { const char *fileas = (const char *)e_contact_get_const( contact, E_CONTACT_FILE_AS ); if (fileas) { line += fileas; } else { const char *name = (const char *)e_contact_get_const( contact, E_CONTACT_FULL_NAME ); if (name) { line += name; } else { line += ""; } } } else { line += ""; } #endif line += " ("; line += uid; line += "): "; line += info; SE_LOG(getDisplayName(), debug ? Logger::DEBUG : Logger::INFO, "%s", line.c_str() ); } } void AddressBookSource::logItem(const SyncItem &item, const string &info, bool debug) { if (getLevel() >= (debug ? Logger::DEBUG : Logger::INFO)) { string line; const char *data = (const char *)item.getData(); int datasize = item.getDataSize(); if (datasize <= 0) { data = ""; datasize = 0; } string vcard( data, datasize ); size_t offset = vcard.find( "FN:"); if (offset != vcard.npos) { int len = vcard.find( "\r", offset ) - offset - 3; line += vcard.substr( offset + 3, len ); } else { line += ""; } if (!item.getKey() ) { line += ", NULL UID (?!)"; } else if (!strlen( item.getKey() )) { line += ", empty UID"; } else { line += ", "; line += item.getKey(); #if 0 // TODO EContact *contact; GError *gerror = NULL; if (e_book_get_contact( m_addressbook, item.getKey(), &contact, &gerror )) { line += ", EV "; const char *fileas = (const char *)e_contact_get_const( contact, E_CONTACT_FILE_AS ); if (fileas) { line += fileas; } else { const char *name = (const char *)e_contact_get_const( contact, E_CONTACT_FULL_NAME ); if (name) { line += name; } else { line += ""; } } } else { line += ", not in Evolution"; } #endif } line += ": "; line += info; SE_LOG(getDisplayName(), debug ? Logger::DEBUG : Logger::INFO, "%s", line.c_str() ); } } SE_END_CXX #endif /* ENABLE_ADDRESSBOOK */ syncevolution_1.4/src/backends/addressbook/AddressBookSource.h000066400000000000000000000151501230021373600247530ustar00rootroot00000000000000/* * Copyright (C) 2007-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_ADDRESSBOOKSOURCE #define INCL_ADDRESSBOOKSOURCE #include "config.h" #include #ifdef ENABLE_ADDRESSBOOK #include #include SE_BEGIN_CXX /** * a smart pointer for CoreFoundation object references * * trying to store a NULL pointer raises an exception, * unreferencing valid objects is done automatically * * @param T the pointer type * @param release CFRelease() is only called when passing true */ template class ref { /** do not allow copy construction */ ref( const ref &other) {}; /** do not allow copying */ void operator = ( const ref &other ) {} protected: T m_pointer; public: /** * create a smart pointer that owns the given object; * passing a NULL pointer and a name for the object raises an error */ ref(T pointer = NULL, const char *objectName = NULL) : m_pointer( pointer ) { if (!pointer && objectName ) { throw std::runtime_error(std::string("Error allocating ") + objectName); } }; ~ref() { set( NULL ); } /** * store another object in this pointer, replacing any which was * referenced there before; * passing a NULL pointer and a name for the object raises an error */ void set( T pointer, const char *objectName = NULL ) { if (m_pointer && doRelease) { CFRelease(m_pointer); } if (!pointer && objectName) { throw std::runtime_error(std::string("Error allocating ") + objectName); } m_pointer = pointer; } ref &operator = ( T pointer ) { set( pointer ); return *this; } T operator-> () { return m_pointer; } T operator * () { return m_pointer; } operator T () { return m_pointer; } operator bool () { return m_pointer != NULL; } T release() { T res = m_pointer; m_pointer = NULL; return res; } }; #if 0 /* template typedefs would have been handy here, but are not specified in C++ (yet) */ #ifdef IPHONE /** do not free some particular objects on the iPhone because that crashes */ template typedef ref iphoneref; #else template typedef ref iphoneref; #endif #else #ifdef IPHONE # define IPHONE_RELEASE false #else # define IPHONE_RELEASE true #endif #endif /** * The AddressBookSource synchronizes the Mac OS X and iPhone system * address book using the "AddressBook" framework. Changes are tracked * by comparing the current time stamp of a contact against its time * stamp from the previous sync, stored in a separate key/value * database. Contacts are converted to/from vCard 2.1 using custom * code because a) the mapping can be chosen so that typical SyncML * servers understand it and b) the iPhone's AddressBook does not have * vcard import/export functions. * * On the iPhone the interface is similar, but not the same. These * differences are hidden behind "ifdef IPHONE" which depends (for * simplicity reasons) on the __arm__ define. * * Some of the differences and how they are handled are listed here. * - ABC instead of AB prefix, other renames: map Mac OS X name to iPhone * name before including AddressBook.h, then use Mac OS X names * - CFRelease() and CFCopyDescription on ABMultiValueRef crash (bugs?!): * use ref for those instead the normal ref smart pointer, * avoid CFCopyDescription() * - UID is integer, not CFStringRef: added wrapper function * - the address of kABC*Property identifies properties, not the CFStringRef * at that address, caused toolchain problems when initializing data * with these addresses: added one additional address indirection * - UIDs are assigned to added contacts only during saving, but are needed * earlier: save after adding each contact (affects performance and aborted * sync changes address book - perhaps better guess UID?) * - Mac OS X 10.4 still uses the kABHomePageProperty (a single string), * the iPhone switched to the more recent kABCURLProperty/kABURLsProperty: * conversion code is slightly different * - iPhone does not have a title (e.g. "sir") property, only the job title * - label constants are not part of the framework: * defined in AddressSourceConstants */ class AddressBookSource : public TrackingSyncSource { public: AddressBookSource(const EvolutionSyncSourceParams ¶ms, bool asVCard30); virtual ~AddressBookSource() { close(); } void setVCard30(bool asVCard30) { m_asVCard30 = asVCard30; } bool getVCard30() { return m_asVCard30; } virtual Databases getDatabases(); virtual void open(); virtual void listAllItems(RevisionMap_t &revisions); virtual InsertItemResult insertItem(const string &uid, const std::string &item, bool raw); void readItem(const std::string &luid, std::string &item, bool raw); virtual void removeItem(const string &uid); virtual void close(); virtual const char *getMimeType() const { return m_asVCard30 ? "text/vcard" : "text/x-vcard"; } virtual const char *getMimeVersion() const { return m_asVCard30 ? "3.0" : "2.1"; } virtual const char *getSupportedTypes() const { return m_asVCard30 ? "text/vcard:3.0" : "text/x-vcard:2.1"; } private: /** valid after open(): the address book that this source references */ ABAddressBookRef m_addressbook; /** returns absolute modification time or (if that doesn't exist) the creation time */ string getModTime(ABRecordRef record); /** unless selected otherwise send items as vCard 2.1 */ bool m_asVCard30; }; SE_END_CXX #endif // ENABLE_EBOOK #endif // INCL_ADDRESSBOOKSOURCE syncevolution_1.4/src/backends/addressbook/AddressBookSourceRegister.cpp000066400000000000000000000100271230021373600270110ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "AddressBookSource.h" #include #include SE_BEGIN_CXX static SyncSource *createSource(const SyncSourceParams ¶ms) { SourceType sourceType = SyncSource::getSourceType(params.m_nodes); bool isMe = sourceType.m_backend == "apple-contacts"; #ifndef ENABLE_ADDRESSBOOK return isMe ? RegisterSyncSource::InactiveSource(params) : NULL; #else bool maybeMe = sourceType.m_backend == "addressbook"; if (isMe || maybeMe) { // Hack: choose default based on server URI. "card3" // indicates ScheduleWorld, which works better with (requires?) // sending vCard 3.0. bool vCard3 = false; PersistentEvolutionSyncSourceConfig config(params.m_name, params.m_nodes); if (config.getURI() && !strcmp(config.getURI(), "card3")) { vCard3 = true; } if (sourceType.m_format == "text/x-vcard") { vCard3 = false; } else if (sourceType.m_format == "text/vcard") { vCard3 = true; } return new AddressBookSource(params, vCard3); } return NULL; #endif } static RegisterSyncSource registerMe("iPhone/Mac OS X Address Book", #ifdef ENABLE_ADDRESSBOOK true, #else false, #endif createSource, "Mac OS X or iPhone Address Book = addressbook = contacts = apple-contacts\n", Values() + (Aliases("apple-contacts") + "Mac OS X Address Book" + "iPhone Address Book")); #ifdef ENABLE_ADDRESSBOOK #ifdef ENABLE_UNIT_TESTS class EvolutionAddressbookTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(EvolutionAddressbookTest); CPPUNIT_TEST(testInstantiate); CPPUNIT_TEST_SUITE_END(); protected: void testInstantiate() { boost::shared_ptr source; source.reset(SyncSource::createTestingSource("contacts", "contacts", true)); source.reset(SyncSource::createTestingSource("contacts", "addressbook", true)); source.reset(SyncSource::createTestingSource("contacts", "apple-contacts", true)); source.reset(SyncSource::createTestingSource("contacts", "Mac OS X Address Book:text/vcard", true)); source.reset(SyncSource::createTestingSource("contacts", "iPhone Address Book:text/x-vcard", true)); } }; SYNCEVOLUTION_TEST_SUITE_REGISTRATION(EvolutionAddressbookTest); #endif // ENABLE_UNIT_TESTS #ifdef ENABLE_INTEGRATION_TESTS namespace { #if 0 } #endif static class VCard21Test : public RegisterSyncSourceTest { public: VCard21Test() : RegisterSyncSourceTest("addressbook_eds_contact", "eds_contact") {} virtual void updateConfig(ClientTestConfig &config) const { config.m_type = "apple-contacts:text/x-vcard"; } } vCard21Test; static class VCard30Test : public RegisterSyncSourceTest { public: VCard30Test() : RegisterSyncSourceTest("addressbook_eds_contact", "eds_contact") {} virtual void updateConfig(ClientTestConfig &config) const { config.m_type = "apple-contacts:text/vcard"; } } vCard30Test; } #endif // ENABLE_INTEGRATION_TESTS #endif // ENABLE_ADDRESSBOOK SE_END_CXX syncevolution_1.4/src/backends/addressbook/addressbook.am000066400000000000000000000022171230021373600240400ustar00rootroot00000000000000dist_noinst_DATA += src/backends/addressbook/configure-sub.in src_backends_addressbook_lib = src/backends/addressbook/syncaddressbook.la MOSTLYCLEANFILES += $(src_backends_addressbook_lib) if ENABLE_MODULES src_backends_addressbook_backenddir = $(BACKENDS_DIRECTORY) src_backends_addressbook_backend_LTLIBRARIES = $(src_backends_addressbook_lib) else noinst_LTLIBRARIES += $(src_backends_addressbook_lib) endif src_backends_addressbook_src = \ src/backends/addressbook/AddressBookSource.h \ src/backends/addressbook/AddressBookConstants.cpp \ src/backends/addressbook/AddressBookSource.cpp src_backends_addressbook_syncaddressbook_la_SOURCES = $(src_backends_addressbook_src) src_backends_addressbook_syncaddressbook_la_LIBADD = $(ADDRESSBOOK_LIBS) $(SYNCEVOLUTION_LIBS) src_backends_addressbook_syncaddressbook_la_LDFLAGS = -module -avoid-version src_backends_addressbook_syncaddressbook_la_CXXFLAGS = $(SYNCEVOLUTION_CXXFLAGS) $(SYNCEVO_WFLAGS) src_backends_addressbook_syncaddressbook_la_CPPFLAGS = $(SYNCEVOLUTION_CFLAGS) -I$(top_srcdir)/test $(BACKEND_CPPFLAGS) src_backends_addressbook_syncaddressbook_la_DEPENDENCIES = src/syncevo/libsyncevolution.la syncevolution_1.4/src/backends/addressbook/configure-sub.in000066400000000000000000000015151230021373600243210ustar00rootroot00000000000000dnl -*- mode: Autoconf; -*- dnl Invoke autogen.sh to produce a configure script. dnl hard-coded settings for Mac OS X AddressBook ADDRESSBOOK_CFLAGS= ADDRESSBOOK_LIBS="-framework AddressBook -framework CoreFoundation" AC_SUBST(ADDRESSBOOK_CFLAGS) AC_SUBST(ADDRESSBOOK_LIBS) BACKEND_CPPFLAGS="$BACKEND_CPPFLAGS $ADDRESSBOOK_CFLAGS" SE_ARG_ENABLE_BACKEND(addressbook, addressbook, [AS_HELP_STRING([--enable-addressbook], [enable access to Mac OS X address book (default off)])], [enable_addressbook="$enableval"], [enable_addressbook="no"] ) if test "$enable_addressbook" = "yes"; then AC_DEFINE(ENABLE_ADDRESSBOOK, 1, [addressbook available]) DEVICE_TYPE=MacOS_X enable_any="yes" else ADDRESSBOOK_LIBS= fi syncevolution_1.4/src/backends/akonadi/000077500000000000000000000000001230021373600203255ustar00rootroot00000000000000syncevolution_1.4/src/backends/akonadi/AkonadiSyncSourceRegister.cpp000066400000000000000000000354731230021373600261360ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "akonadisyncsource.h" #include #include "test.h" #include #include SE_BEGIN_CXX static SyncSource *createSource(const SyncSourceParams ¶ms) { SourceType sourceType = SyncSource::getSourceType(params.m_nodes); bool isMe; isMe = sourceType.m_backend == "KDE Address Book"; if (isMe /* || sourceType.m_backend == "addressbook" */) { if (sourceType.m_format == "" || sourceType.m_format == "text/vcard" || sourceType.m_format == "text/x-vcard") { return #ifdef ENABLE_AKONADI new AkonadiContactSource(params) #else isMe ? RegisterSyncSource::InactiveSource(params) : NULL #endif ; } else { return NULL; } } isMe = sourceType.m_backend == "KDE Task List"; if (isMe /* || sourceType.m_backend == "todo" */) { if (sourceType.m_format == "" || sourceType.m_format == "text/calendar" || sourceType.m_format == "text/x-vcalendar") { return #ifdef ENABLE_AKONADI new AkonadiTaskSource(params) #else isMe ? RegisterSyncSource::InactiveSource(params) : NULL #endif ; } else { return NULL; } } isMe = sourceType.m_backend == "KDE Memos"; if (isMe /* || sourceType.m_backend == "memo" */) { if (sourceType.m_format == "" || sourceType.m_format == "text/plain") { return #ifdef ENABLE_AKONADI new AkonadiMemoSource(params) #else isMe ? RegisterSyncSource::InactiveSource(params) : NULL #endif ; } else { return NULL; } } isMe = sourceType.m_backend == "KDE Calendar"; if (isMe /* || sourceType.m_backend == "calendar" */) { if (sourceType.m_format == "" || sourceType.m_format == "text/calendar" || sourceType.m_format == "text/x-vcalendar" /* this is for backwards compatibility with broken configs */ ) { return #ifdef ENABLE_AKONADI new AkonadiCalendarSource(params) #else isMe ? RegisterSyncSource::InactiveSource(params) : NULL #endif ; } else { return NULL; } } return NULL; } static RegisterSyncSource registerMe("KDE Contact/Calendar/Task List/Memos", #ifdef ENABLE_AKONADI true, #else false, #endif createSource, "KDE Address Book = KDE Contacts = addressbook = contacts = kde-contacts\n" " vCard 2.1 (default) = text/x-vcard\n" " vCard 3.0 = text/vcard\n" " The later is the internal format of KDE and preferred with\n" " servers that support it. One such server is ScheduleWorld\n" " together with the \"card3\" uri.\n" "KDE Calendar = calendar = events = kde-events\n" " iCalendar 2.0 (default) = text/calendar\n" " vCalendar 1.0 = text/x-calendar\n" "KDE Task List = KDE Tasks = todo = tasks = kde-tasks\n" " iCalendar 2.0 (default) = text/calendar\n" " vCalendar 1.0 = text/x-calendar\n" "KDE Memos = memo = memos = kde-memos\n" " plain text in UTF-8 (default) = text/plain\n", Values() + (Aliases("KDE Address Book") + "KDE Contacts" + "kde-contacts") + (Aliases("KDE Calendar") + "kde-calendar") + (Aliases("KDE Task List") + "KDE Tasks" + "kde-tasks") + (Aliases("KDE Memos") + "kde-memos")); #ifdef ENABLE_AKONADI #ifdef ENABLE_UNIT_TESTS class AkonadiTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(AkonadiTest); CPPUNIT_TEST(testInstantiate); // There is no default database in Akonadi: // CPPUNIT_TEST(testOpenDefaultCalendar); // CPPUNIT_TEST(testOpenDefaultTodo); // CPPUNIT_TEST(testOpenDefaultMemo); // Besides, don't enable tests which depend on running Akonadi, // because that would cause "client-test SyncEvolution" unless // Akonadi was started first: // CPPUNIT_TEST(testTimezones); CPPUNIT_TEST_SUITE_END(); protected: static string addItem(boost::shared_ptr source, string &data) { SyncSourceRaw::InsertItemResult res = source->insertItemRaw("", data); return res.m_luid; } void testInstantiate() { boost::shared_ptr source; // source.reset(SyncSource::createTestingSource("addressbook", "addressbook", true)); // source.reset(SyncSource::createTestingSource("addressbook", "contacts", true)); source.reset(SyncSource::createTestingSource("addressbook", "kde-contacts", true)); source.reset(SyncSource::createTestingSource("addressbook", "KDE Contacts", true)); source.reset(SyncSource::createTestingSource("addressbook", "KDE Address Book:text/x-vcard", true)); source.reset(SyncSource::createTestingSource("addressbook", "KDE Address Book:text/vcard", true)); // source.reset(SyncSource::createTestingSource("calendar", "calendar", true)); source.reset(SyncSource::createTestingSource("calendar", "kde-calendar", true)); source.reset(SyncSource::createTestingSource("calendar", "KDE Calendar:text/calendar", true)); // source.reset(SyncSource::createTestingSource("tasks", "tasks", true)); source.reset(SyncSource::createTestingSource("tasks", "kde-tasks", true)); source.reset(SyncSource::createTestingSource("tasks", "KDE Tasks", true)); source.reset(SyncSource::createTestingSource("tasks", "KDE Task List:text/calendar", true)); // source.reset(SyncSource::createTestingSource("memos", "memos", true)); source.reset(SyncSource::createTestingSource("memos", "kde-memos", true)); source.reset(SyncSource::createTestingSource("memos", "KDE Memos:text/plain", true)); } // TODO: support default databases // void testOpenDefaultAddressBook() { // boost::shared_ptr source; // source.reset((TestingSyncSource *)SyncSource::createTestingSource("contacts", "kde-contacts", true, NULL)); // CPPUNIT_ASSERT_NO_THROW(source->open()); // } // void testOpenDefaultCalendar() { // boost::shared_ptr source; // source.reset((TestingSyncSource *)SyncSource::createTestingSource("calendar", "kde-calendar", true, NULL)); // CPPUNIT_ASSERT_NO_THROW(source->open()); // } // void testOpenDefaultTodo() { // boost::shared_ptr source; // source.reset((TestingSyncSource *)SyncSource::createTestingSource("tasks", "kde-tasks", true, NULL)); // CPPUNIT_ASSERT_NO_THROW(source->open()); // } // void testOpenDefaultMemo() { // boost::shared_ptr source; // source.reset((TestingSyncSource *)SyncSource::createTestingSource("memos", "kde-memos", true, NULL)); // CPPUNIT_ASSERT_NO_THROW(source->open()); // } void testTimezones() { const char *prefix = getenv("CLIENT_TEST_EVOLUTION_PREFIX"); if (!prefix) { prefix = "SyncEvolution_Test_"; } boost::shared_ptr source; source.reset((TestingSyncSource *)SyncSource::createTestingSource("eds_event", "kde-calendar", true, prefix)); CPPUNIT_ASSERT_NO_THROW(source->open()); string newyork = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VTIMEZONE\n" "TZID:America/New_York\n" "BEGIN:STANDARD\n" "TZOFFSETFROM:-0400\n" "TZOFFSETTO:-0500\n" "TZNAME:EST\n" "DTSTART:19701025T020000\n" "RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10\n" "END:STANDARD\n" "BEGIN:DAYLIGHT\n" "TZOFFSETFROM:-0500\n" "TZOFFSETTO:-0400\n" "TZNAME:EDT\n" "DTSTART:19700405T020000\n" "RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=4\n" "END:DAYLIGHT\n" "END:VTIMEZONE\n" "BEGIN:VEVENT\n" "UID:artificial\n" "DTSTAMP:20060416T205224Z\n" "DTSTART;TZID=America/New_York:20060406T140000\n" "DTEND;TZID=America/New_York:20060406T143000\n" "TRANSP:OPAQUE\n" "SEQUENCE:2\n" "SUMMARY:timezone New York with custom definition\n" "DESCRIPTION:timezone New York with custom definition\n" "CLASS:PUBLIC\n" "CREATED:20060416T205301Z\n" "LAST-MODIFIED:20060416T205301Z\n" "END:VEVENT\n" "END:VCALENDAR\n"; string luid; CPPUNIT_ASSERT_NO_THROW(luid = addItem(source, newyork)); string newyork_suffix = newyork; boost::replace_first(newyork_suffix, "UID:artificial", "UID:artificial-2"); boost::replace_all(newyork_suffix, "TZID:America/New_York", "TZID://FOOBAR/America/New_York-SUFFIX"); CPPUNIT_ASSERT_NO_THROW(luid = addItem(source, newyork_suffix)); string notimezone = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VEVENT\n" "UID:artificial-3\n" "DTSTAMP:20060416T205224Z\n" "DTSTART;TZID=America/New_York:20060406T140000\n" "DTEND;TZID=America/New_York:20060406T143000\n" "TRANSP:OPAQUE\n" "SEQUENCE:2\n" "SUMMARY:timezone New York without custom definition\n" "DESCRIPTION:timezone New York without custom definition\n" "CLASS:PUBLIC\n" "CREATED:20060416T205301Z\n" "LAST-MODIFIED:20060416T205301Z\n" "END:VEVENT\n" "END:VCALENDAR\n"; CPPUNIT_ASSERT_NO_THROW(luid = addItem(source, notimezone)); // fake VTIMEZONE where daylight saving starts on first Sunday in March string fake_march = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VTIMEZONE\n" "TZID:FAKE\n" "BEGIN:STANDARD\n" "TZOFFSETFROM:-0400\n" "TZOFFSETTO:-0500\n" "TZNAME:EST MARCH\n" "DTSTART:19701025T020000\n" "RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10\n" "END:STANDARD\n" "BEGIN:DAYLIGHT\n" "TZOFFSETFROM:-0500\n" "TZOFFSETTO:-0400\n" "TZNAME:EDT\n" "DTSTART:19700405T020000\n" "RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=3\n" "END:DAYLIGHT\n" "END:VTIMEZONE\n" "BEGIN:VEVENT\n" "UID:artificial-4\n" "DTSTAMP:20060416T205224Z\n" "DTSTART;TZID=FAKE:20060406T140000\n" "DTEND;TZID=FAKE:20060406T143000\n" "TRANSP:OPAQUE\n" "SEQUENCE:2\n" "SUMMARY:fake timezone with daylight starting in March\n" "CLASS:PUBLIC\n" "CREATED:20060416T205301Z\n" "LAST-MODIFIED:20060416T205301Z\n" "END:VEVENT\n" "END:VCALENDAR\n"; CPPUNIT_ASSERT_NO_THROW(luid = addItem(source, fake_march)); string fake_may = fake_march; boost::replace_first(fake_may, "UID:artificial-4", "UID:artificial-5"); boost::replace_first(fake_may, "RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=3", "RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=5"); boost::replace_first(fake_may, "starting in March", "starting in May"); boost::replace_first(fake_may, "TZNAME:EST MARCH", "TZNAME:EST MAY"); CPPUNIT_ASSERT_NO_THROW(luid = addItem(source, fake_may)); // insert again, shouldn't re-add timezone CPPUNIT_ASSERT_NO_THROW(luid = addItem(source, fake_may)); } }; SYNCEVOLUTION_TEST_SUITE_REGISTRATION(AkonadiTest); #endif // ENABLE_UNIT_TESTS namespace { #if 0 } #endif static class vCard30Test : public RegisterSyncSourceTest { public: vCard30Test() : RegisterSyncSourceTest("kde_contact", "eds_contact") {} virtual void updateConfig(ClientTestConfig &config) const { config.m_type = "kde-contacts"; } } vCard30Test; static class iCal20Test : public RegisterSyncSourceTest { public: iCal20Test() : RegisterSyncSourceTest("kde_event", "eds_event") {} virtual void updateConfig(ClientTestConfig &config) const { config.m_type = "kde-calendar"; } } iCal20Test; static class iTodo20Test : public RegisterSyncSourceTest { public: iTodo20Test() : RegisterSyncSourceTest("kde_task", "eds_task") {} virtual void updateConfig(ClientTestConfig &config) const { config.m_type = "kde-tasks"; } } iTodo20Test; static class MemoTest : public RegisterSyncSourceTest { public: MemoTest() : RegisterSyncSourceTest("kde_memo", "eds_memo") {} virtual void updateConfig(ClientTestConfig &config) const { config.m_type = "KDE Memos"; // use an alias here to test that } } memoTest; } #endif // ENABLE_AKONADI SE_END_CXX syncevolution_1.4/src/backends/akonadi/README000066400000000000000000000031331230021373600212050ustar00rootroot00000000000000Getting started with Akonadi on Debian testing: aptitude install libakonadi-dev akonadi-server \ libqt4-dev kdepim-runtime \ akonadiconsole \ kdepimlibs5-dev Controlling Akonadi server: akonadictl start/stop/restart Debugging Akonadi: akonadiconsole Configuring without Evolution and with Akonadi: /syncevolution/configure --with-synthesis-src=/libsynthesis \ CFLAGS="-g -Wall -Werror -Wno-unknown-pragmas" \ CXXFLAGS="-g -Wall -Werror -Wno-unknown-pragmas" \ --disable-shared --enable-static \ --enable-libcurl \ --enable-unit-tests --enable-integration-tests \ --disable-ecal --disable-ebook --disable-libsoup \ --enable-akonadi --enable-kwallet This creates src/syncevolution and src/client-test which can be run under a debugger directly. Query databases: syncevolution Configuring syncevolution for contacts with Akonadi as backend: syncevolution --configure --source-property sync=none \ --sync-property username=... \ --sync-property password=... \ scheduleworld syncevolution --configure --source-property sync=two-way \ --source-property type=kde-contacts \ --source-property evolutionsource=akonadi:?... \ scheduleworld addressbook Initial run: syncevolution --sync slow scheduleworld addressbook All following runs: syncevolution scheduleworld syncevolution_1.4/src/backends/akonadi/akonadi.am000066400000000000000000000017341230021373600222570ustar00rootroot00000000000000dist_noinst_DATA += \ src/backends/akonadi/configure-sub.in \ src/backends/akonadi/README src_backends_akonadi_lib = src/backends/akonadi/syncakonadi.la MOSTLYCLEANFILES += $(src_backends_akonadi_lib) if ENABLE_MODULES src_backends_akonadi_backenddir = $(BACKENDS_DIRECTORY) src_backends_akonadi_backend_LTLIBRARIES = $(src_backends_akonadi_lib) else noinst_LTLIBRARIES += $(src_backends_akonadi_lib) endif src_backends_akonadi_syncakonadi_la_SOURCES = \ src/backends/akonadi/akonadisyncsource.h \ src/backends/akonadi/akonadisyncsource.cpp src_backends_akonadi_syncakonadi_la_LIBADD = $(KDEPIM_LIBS) $(SYNCEVOLUTION_LIBS) src_backends_akonadi_syncakonadi_la_LDFLAGS = -module -avoid-version src_backends_akonadi_syncakonadi_la_CXXFLAGS = $(SYNCEVOLUTION_CXXFLAGS) $(SYNCEVO_WFLAGS) src_backends_akonadi_syncakonadi_la_CPPFLAGS = $(SYNCEVOLUTION_CFLAGS) -I$(top_srcdir)/test $(BACKEND_CPPFLAGS) src_backends_akonadi_syncakonadi_la_DEPENDENCIES = src/syncevo/libsyncevolution.la syncevolution_1.4/src/backends/akonadi/akonadisyncsource.cpp000066400000000000000000000262121230021373600245600ustar00rootroot00000000000000/* Copyright (c) 2009 Sascha Peilicke This application is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This application 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this application; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "akonadisyncsource.h" #ifdef ENABLE_AKONADI #include #include #include #include #include #include #include #include #include #include #include #include #include SE_BEGIN_CXX using namespace Akonadi; /** * We take over ownership of jobs by storing them in smart pointers * (RAII). This is how SyncEvolution does things and more predictable * than assuming that a future exec() call will auto-delete them as * part of its event processing. * * To avoid double frees, we need to disable auto-deletion. * This method does that. Use like this: * std::auto_ptr statisticsJob(DisableAutoDelete(new CollectionStatisticsJob(m_collection))); */ template J *DisableAutoDelete(J *job) { job->setAutoDelete(false); return job; } AkonadiSyncSource::AkonadiSyncSource(const char *submime, const SyncSourceParams ¶ms) : TrackingSyncSource(params) , m_subMime(submime) { } AkonadiSyncSource::~AkonadiSyncSource() { } bool AkonadiSyncSource::isEmpty() { //To Check if the respective collection is Empty, without actually loading the collections std::auto_ptr statisticsJob(DisableAutoDelete(new CollectionStatisticsJob(m_collection))); if (!statisticsJob->exec()) { throwError("Error fetching the collection stats"); } return statisticsJob->statistics().count() == 0; } void AkonadiSyncSource::start() { // Start The Akonadi Server if not already Running. if (!Akonadi::ServerManager::isRunning()) { // Don't try to start it. A normal KDE user should have it already // running. Users of other desktop systems probably don't want it // to run, if they have it installed at all. // // Starting it here also produces output that we don't want mixed // into normal SyncEvolution command line output. #if 0 SE_LOG_DEBUG(NULL, "Akonadi Server isn't running, and hence starting it."); if (!Akonadi::Control::start()) { SE_THROW("Couldn't Start Akonadi Server: hence the akonadi backend of syncevolution wont work .."); } #else SE_THROW("Akonadi is not running. It can be started with 'akonadictl start'."); #endif } } SyncSource::Databases AkonadiSyncSource::getDatabases() { start(); Databases res; QStringList mimeTypes; mimeTypes << m_subMime.c_str(); // Insert databases which match the "type" of the source, including a user-visible // description and a database IDs. Exactly one of the databases should be marked // as the default one used by the source. // res.push_back("Contacts", "some-KDE-specific-ID", isDefault); std::auto_ptr fetchJob(DisableAutoDelete(new CollectionFetchJob(Collection::root(), CollectionFetchJob::Recursive))); fetchJob->fetchScope().setContentMimeTypes(mimeTypes); if (!fetchJob->exec()) { throwError("cannot list collections"); } // Currently, the first collection of the right type is the default // This decision should go to the GUI: which deals with sync profiles. bool isFirst = true; Collection::List collections = fetchJob->collections(); foreach (const Collection &collection, collections) { res.push_back(Database(collection.name().toUtf8().constData(), collection.url().url().toUtf8().constData(), isFirst)); isFirst = false; } return res; } void AkonadiSyncSource::open() { start(); // the "evolutionsource" property, empty for default, // otherwise the collection URL or a name string id = getDatabaseID(); // hack for testing: use first resp. second database if (boost::starts_with(id, "Test_")) { Databases databases = getDatabases(); ssize_t index = -1; if (boost::ends_with(id, "_1")) { index = 0; } else if (boost::ends_with(id, "_2")) { index = 1; } if (index >= 0) { if (databases.size() <= (size_t)index) { SE_THROW("need two Akonadi resources for testing"); } id = databases[index].m_uri; SE_LOG_DEBUG(NULL, "testing Akonadi with %s", id.c_str()); } } if (!boost::starts_with(id, "akonadi:")) { // TODO: support selection by name and empty ID for default SE_THROW("database must be selected via database = akonadi:?collection="); } m_collection = Collection::fromUrl(KUrl(id.c_str())); } void AkonadiSyncSource::listAllItems(SyncSourceRevisions::RevisionMap_t &revisions) { // copy all local IDs and the corresponding revision std::auto_ptr fetchJob(DisableAutoDelete(new ItemFetchJob(m_collection))); if (!fetchJob->exec()) { throwError("listing items"); } BOOST_FOREACH (const Item &item, fetchJob->items()) { // Filter out items which don't have the right type (for example, VTODO when // syncing events) if (item.mimeType() == m_subMime.c_str()) { revisions[QByteArray::number(item.id()).constData()] = QByteArray::number(item.revision()).constData(); } } } void AkonadiSyncSource::close() { // TODO: close collection!? } TrackingSyncSource::InsertItemResult AkonadiSyncSource::insertItem(const std::string &luid, const std::string &data, bool raw) { Item item; if (luid.empty()) { item.setMimeType(m_subMime.c_str()); item.setPayloadFromData(QByteArray(data.c_str())); std::auto_ptr createJob(DisableAutoDelete(new ItemCreateJob(item, m_collection))); if (!createJob->exec()) { throwError(string("storing new item ") + luid); return InsertItemResult("", "", ITEM_OKAY); } item = createJob->item(); } else { Entity::Id syncItemId = QByteArray(luid.c_str()).toLongLong(); std::auto_ptr fetchJob(DisableAutoDelete(new ItemFetchJob(Item(syncItemId)))); if (!fetchJob->exec()) { throwError(string("checking item ") + luid); } item = fetchJob->items().first(); item.setPayloadFromData(QByteArray(data.c_str())); std::auto_ptr modifyJob(DisableAutoDelete(new ItemModifyJob(item))); // TODO: SyncEvolution must pass the known revision that // we are updating. // TODO: check that the item has not been updated in the meantime if (!modifyJob->exec()) { throwError(string("updating item ") + luid); return InsertItemResult("", "", ITEM_OKAY); } item = modifyJob->item(); } // Read-only datastores may not have actually added something here! // The Jobs themselves throw errors, and hence the return statements // above will take care of this return InsertItemResult(QByteArray::number(item.id()).constData(), QByteArray::number(item.revision()).constData(), ITEM_OKAY); } void AkonadiSyncSource::removeItem(const string &luid) { Entity::Id syncItemId = QByteArray(luid.c_str()).toLongLong(); // Delete the item from our collection // TODO: check that the revision is right (need revision from SyncEvolution) std::auto_ptr deleteJob(DisableAutoDelete(new ItemDeleteJob(Item(syncItemId)))); if (!deleteJob->exec()) { throwError(string("deleting item " ) + luid); } } void AkonadiSyncSource::readItem(const std::string &luid, std::string &data, bool raw) { Entity::Id syncItemId = QByteArray(luid.c_str()).toLongLong(); std::auto_ptr fetchJob(DisableAutoDelete(new ItemFetchJob(Item(syncItemId)))); fetchJob->fetchScope().fetchFullPayload(); if (fetchJob->exec()) { if (fetchJob->items().empty()) { throwError(STATUS_NOT_FOUND, string("extracting item ") + luid); } QByteArray payload = fetchJob->items().first().payloadData(); data.assign(payload.constData(), payload.size()); } else { throwError(string("extracting item " ) + luid); } } QString AkonadiMemoSource::toKJots(QString data){ // KJots stores it's resource in the format //Subject: Hello World //Content-Type: text/plain <------- always plain text for the akonadi resource //Date: Wed, 30 Mar 2011 01:02:48 +0530 <----date created //MIME-Version: 1.0 <----- always the same // <---- This line break seperates the content from the information // QString subject = "Subject: "; QString contentType = "Content-Type: text/plain"; QString dateTime = QDateTime::currentDateTime().toString(Qt::ISODate); QString mimeVersion = "MIME-Version: 1.0"; QString content; QStringList lines = data.split('\n'); subject += lines.first(); content = data.remove(0,data.indexOf('\n')+1); QString result = subject + '\n' + contentType + '\n' + dateTime + '\n'+ mimeVersion + "\n\n"+ content; return result; } QString AkonadiMemoSource::toSynthesis(QString data){ //Synthesis expects Plain Text in the form Subject + "\n" + Content QString subject; QString content; subject = data.split('\n').first(); subject.remove("Subject: "); content = data.remove(0,data.indexOf("\n\n")+2); return subject+'\n'+content; } void AkonadiMemoSource::readItem(const std::string &luid, std::string &data, bool raw) { AkonadiSyncSource::readItem(luid, data, raw); data = toSynthesis(QString::fromStdString(data)).toStdString(); } TrackingSyncSource::InsertItemResult AkonadiMemoSource::insertItem(const std::string &luid, const std::string &data, bool raw) { std::string formattedData = toKJots(QString::fromStdString(data)).toStdString(); return AkonadiSyncSource::insertItem(luid, formattedData , raw); } SE_END_CXX #endif // ENABLE_AKONADI #ifdef ENABLE_MODULES # include "AkonadiSyncSourceRegister.cpp" #endif syncevolution_1.4/src/backends/akonadi/akonadisyncsource.h000066400000000000000000000126731230021373600242330ustar00rootroot00000000000000/* Copyright (c) 2009 Sascha Peilicke This application is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This application 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this application; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADISYNCSOURCE_H #define AKONADISYNCSOURCE_H #include "config.h" #ifdef ENABLE_AKONADI #include #include #include #include #include SE_BEGIN_CXX class TimeTrackingObserver; /** * General purpose Akonadi Sync Source. Choosing the type of data is * done when instantiating it, using the Akonadi MIME subtypes. * Payload is always using the native Akonadi format (no special "raw" * and "engine" formats). * * Change tracking is done via the item uid/revision attributes. * * Databases (collections in Akonadi terminology) are selected via * their int64 ID number. */ class AkonadiSyncSource : public TrackingSyncSource { public: /** * @param submime the MIME type string used by Akonadi * to identify contacts, tasks, events, etc. * @param params the SyncEvolution source parameters */ AkonadiSyncSource(const char *submime, const SyncSourceParams ¶ms); virtual ~AkonadiSyncSource(); /* methods that have to be implemented to complete TrackingSyncSource */ virtual Databases getDatabases(); virtual void open(); virtual void listAllItems(SyncSourceRevisions::RevisionMap_t &revisions); virtual InsertItemResult insertItem(const std::string &luid, const std::string &item, bool raw); virtual void readItem(const std::string &luid, std::string &item, bool raw); virtual void removeItem(const string &luid); virtual void close(); virtual bool isEmpty(); private: void start(); protected: Akonadi::Collection m_collection; const std::string m_subMime; }; class AkonadiContactSource : public AkonadiSyncSource { public: AkonadiContactSource(const SyncSourceParams ¶ms) : AkonadiSyncSource("text/directory", params) { } virtual std::string getMimeType() const { return "text/vcard"; } virtual std::string getMimeVersion() const { return "3.0"; } void getSynthesisInfo(SynthesisInfo &info, XMLConfigFragments &fragments) { TrackingSyncSource::getSynthesisInfo(info, fragments); /** enable the KDE X- extensions in the Synthesis<->backend conversion */ info.m_backendRule = "KDE"; /* * Disable the default VCARD_BEFOREWRITE_SCRIPT_EVOLUTION. * If any KDE-specific transformations via such a script * are needed, it can be named here and then defined by appending * to the fragments. */ info.m_beforeWriteScript = ""; // "$VCARD_BEFOREWRITE_SCRIPT_KDE;"; // fragments.m_datatypes["VCARD_BEFOREWRITE_SCRIPT_KDE"] = ""; } }; class AkonadiCalendarSource : public AkonadiSyncSource { public: AkonadiCalendarSource(const SyncSourceParams ¶ms) : AkonadiSyncSource("application/x-vnd.akonadi.calendar.event", params) { } // TODO: the items are expected to be complete VCALENDAR with // all necessary VTIMEZONEs and one VEVENT (here) resp. VTODO // (AkonadiTodoSource). Not sure what we get from Akonadi. virtual std::string getMimeType() const { return "text/calendar"; } virtual std::string getMimeVersion() const { return "2.0"; } }; class AkonadiTaskSource : public AkonadiSyncSource { public: AkonadiTaskSource(const SyncSourceParams ¶ms) : AkonadiSyncSource("application/x-vnd.akonadi.calendar.todo", params) { } virtual std::string getMimeType() const { return "text/calendar"; } virtual std::string getMimeVersion() const { return "2.0"; } }; class AkonadiMemoSource : public AkonadiSyncSource { private: QString toKJots(QString data); QString toSynthesis(QString data); public: AkonadiMemoSource(const SyncSourceParams ¶ms) : AkonadiSyncSource("text/x-vnd.akonadi.note", params) { } // TODO: the AkonadiMemoSource is expected to import/export // plain text with the summary in the first line; currently // the AkonadiSyncSource will use VJOURNAL // Also Currently there is no application which uses akonadi backend // to display notes: probably work for knote?? virtual std::string getMimeType() const { return "text/plain"; } virtual std::string getMimeVersion() const { return "1.0"; } virtual InsertItemResult insertItem(const std::string &luid, const std::string &item, bool raw); virtual void readItem(const std::string &luid, std::string &item, bool raw); }; SE_END_CXX #endif // ENABLE_AKONADI #endif // AKONADISYNCSOURCE_H syncevolution_1.4/src/backends/akonadi/configure-sub.in000066400000000000000000000043011230021373600234230ustar00rootroot00000000000000dnl -*- mode: Autoconf; -*- dnl Invoke autogen.sh to produce a configure script. AC_CHECK_PROGS([QMAKE], [qmake qmake-qt4]) # Check for Akonadi. There is no .pc file for it, # so fall back to normal header file and library checking. # kdepimlibs5-dev >= 4.3 provides the necessary files. AKONADIFOUND=yes if ! test "$KDEPIM_CFLAGS"; then KDEPIM_CFLAGS="-I`kde4-config --path include` -I`kde4-config --path include`/KDE" if test "$QMAKE"; then KDEPIM_CFLAGS="$KDEPIM_CFLAGS -I`$QMAKE -query QT_INSTALL_HEADERS`" fi fi if ! test "$KDEPIM_LIBS"; then KDEPIM_LIBS="-L`kde4-config --install lib` -lakonadi-kde `pkg-config --libs QtDBus` -lQtCore -lkdeui -lkdecore" fi AC_LANG_PUSH(C++) old_CPPFLAGS="$CPPFLAGS" CPPFLAGS="$CPPFLAGS $KDEPIM_CFLAGS" AC_CHECK_HEADERS(Akonadi/Collection, [], [AKONADIFOUND=no]) CPPFLAGS="$old_CPPFLAGS" AC_LANG_POP(C++) # In contrast to the Evolution backend, the Akonadi backend is # currently considered optional. "configure" will enable it only # if explicitly enabled (was turned on automatically previously, # for example on Ubuntu Hardy, and didn't compile). SE_ARG_ENABLE_BACKEND(akonadi, akonadi, [AS_HELP_STRING([--disable-akonadi], [disable access to Akonadi (default is to use it if akonadi.pc is found)])], [enable_akonadi="$enableval" test $AKONADIFOUND = "yes" || test $enable_akonadi = "no" || AC_MSG_ERROR([akonadi.pc not found. Install it to compile with the Akonadi backend enabled.])], [enable_akonadi=no] ) if test "$enable_akonadi" = "yes"; then # conditional compilation in preprocessor AC_DEFINE(ENABLE_AKONADI, 1, [Akonadi available]) else # avoid unneeded dependencies on Akonadi KDEPIM_CFLAGS= KDEPIM_LIBS= fi AC_SUBST(KDEPIM_LIBS) AC_SUBST(KDEPIM_CFLAGS) # conditional compilation in make AM_CONDITIONAL([ENABLE_AKONADI], [test "$enable_akonadi" = "yes"]) # let others include Akonadi backend's header file # (not strictly necessary, could be avoided by not # including Akonadi header files in public header file # of source) BACKEND_CPPFLAGS="$BACKEND_CPPFLAGS $KDEPIM_CFLAGS" syncevolution_1.4/src/backends/akonadi/contactssyncsource.cpp000066400000000000000000000025131230021373600247660ustar00rootroot00000000000000/* Copyright (c) 2009 Sascha Peilicke This application is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This application 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this application; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "contactssyncsource.h" #include "settings.h" ContactsSyncSource::ContactsSyncSource(TimeTrackingObserver *observer, ContactsSyncSourceConfig *config, SyncManagerConfig *managerConfig) : AkonadiSyncSource(observer, config, managerConfig) { m_collectionId = Settings::self()->contactsCollectionId(); m_lastSyncTime = Settings::self()->contactsLastSyncTime(); } #include "moc_contactssyncsource.cpp" syncevolution_1.4/src/backends/akonadi/contactssyncsource.h000066400000000000000000000032431230021373600244340ustar00rootroot00000000000000/* Copyright (c) 2009 Sascha Peilicke This application is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This application 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this application; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef CONTACTSSYNCSOURCE_H #define CONTACTSSYNCSOURCE_H #include "akonadisyncsource.h" /** * */ class ContactsSyncSourceConfig : public AkonadiSyncSourceConfig { Q_OBJECT public: ContactsSyncSourceConfig() : AkonadiSyncSourceConfig(Settings::self()->contactsLastSyncTime().toTime_t(), Settings::self()->contactsRemoteDatabaseName().toLatin1()) { setName(Settings::self()->contactsCollectionName().toLatin1()); setType("text/vcard"); setSupportedTypes("text/x-vcard,text/vcard"); } }; /** * */ class ContactsSyncSource : public AkonadiSyncSource { Q_OBJECT public: ContactsSyncSource(TimeTrackingObserver *observer, ContactsSyncSourceConfig *config, SyncManagerConfig *managerConfig); }; #endif syncevolution_1.4/src/backends/akonadi/eventssyncsource.cpp000066400000000000000000000024661230021373600244630ustar00rootroot00000000000000/* Copyright (c) 2009 Sascha Peilicke This application is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This application 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this application; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "eventssyncsource.h" #include "settings.h" EventsSyncSource::EventsSyncSource(TimeTrackingObserver *observer, EventsSyncSourceConfig *config, SyncManagerConfig *managerConfig) : AkonadiSyncSource(observer, config, managerConfig) { m_collectionId = Settings::self()->eventsCollectionId(); m_lastSyncTime = Settings::self()->eventsLastSyncTime(); } #include "moc_eventssyncsource.cpp" syncevolution_1.4/src/backends/akonadi/eventssyncsource.h000066400000000000000000000032131230021373600241170ustar00rootroot00000000000000/* Copyright (c) 2009 Sascha Peilicke This application is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This application 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this application; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef EVENTSSYNCSOURCE_H #define EVENTSSYNCSOURCE_H #include "akonadisyncsource.h" /** * */ class EventsSyncSourceConfig : public AkonadiSyncSourceConfig { Q_OBJECT public: EventsSyncSourceConfig() : AkonadiSyncSourceConfig(Settings::self()->eventsLastSyncTime().toTime_t(), Settings::self()->eventsRemoteDatabaseName().toLatin1()) { setName(Settings::self()->eventsCollectionName().toLatin1()); setType("text/x-vcalendar"); setSupportedTypes("text/x-vcalendar:"); } }; /** * */ class EventsSyncSource : public AkonadiSyncSource { Q_OBJECT public: EventsSyncSource(TimeTrackingObserver *observer, EventsSyncSourceConfig *config, SyncManagerConfig *managerConfig); }; #endif syncevolution_1.4/src/backends/akonadi/notessyncsource.cpp000066400000000000000000000024521230021373600243020ustar00rootroot00000000000000/* Copyright (c) 2009 Sascha Peilicke This application is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This application 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this application; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "notessyncsource.h" #include "settings.h" NotesSyncSource::NotesSyncSource(TimeTrackingObserver *observer, NotesSyncSourceConfig *config, SyncManagerConfig *managerConfig) : AkonadiSyncSource(observer, config, managerConfig) { m_collectionId = Settings::self()->notesCollectionId(); m_lastSyncTime = Settings::self()->notesLastSyncTime(); } #include "moc_notessyncsource.cpp" syncevolution_1.4/src/backends/akonadi/notessyncsource.h000066400000000000000000000032001230021373600237370ustar00rootroot00000000000000/* Copyright (c) 2009 Sascha Peilicke This application is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This application 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this application; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef NOTESSYNCSOURCE_H #define NOTESSYNCSOURCE_H #include "akonadisyncsource.h" /** * */ class NotesSyncSourceConfig : public AkonadiSyncSourceConfig { Q_OBJECT public: NotesSyncSourceConfig() : AkonadiSyncSourceConfig(Settings::self()->notesLastSyncTime().toTime_t(), Settings::self()->notesRemoteDatabaseName().toLatin1()) { setName(Settings::self()->notesCollectionName().toLatin1()); setType("text/plain"); setSupportedTypes("text/x-vcard:,text/vcard"); } }; /** * */ class NotesSyncSource : public AkonadiSyncSource { Q_OBJECT public: NotesSyncSource(TimeTrackingObserver *observer, NotesSyncSourceConfig *config, SyncManagerConfig *managerConfig); }; #endif syncevolution_1.4/src/backends/akonadi/todossyncsource.cpp000066400000000000000000000024521230021373600243020ustar00rootroot00000000000000/* Copyright (c) 2009 Sascha Peilicke This application is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This application 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this application; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "todossyncsource.h" #include "settings.h" TodosSyncSource::TodosSyncSource(TimeTrackingObserver *observer, TodosSyncSourceConfig *config, SyncManagerConfig *managerConfig) : AkonadiSyncSource(observer, config, managerConfig) { m_collectionId = Settings::self()->todosCollectionId(); m_lastSyncTime = Settings::self()->todosLastSyncTime(); } #include "moc_todossyncsource.cpp" syncevolution_1.4/src/backends/akonadi/todossyncsource.h000066400000000000000000000032061230021373600237450ustar00rootroot00000000000000/* Copyright (c) 2009 Sascha Peilicke This application is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This application 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this application; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef TODOSSYNCSOURCE_H #define TODOSSYNCSOURCE_H #include "akonadisyncsource.h" /** * */ class TodosSyncSourceConfig : public AkonadiSyncSourceConfig { Q_OBJECT public: TodosSyncSourceConfig() : AkonadiSyncSourceConfig(Settings::self()->todosLastSyncTime().toTime_t(), Settings::self()->todosRemoteDatabaseName().toLatin1()) { setName(Settings::self()->todosCollectionName().toLatin1()); setType("text/x-vcalendar"); setSupportedTypes("text/x-vcard:,text/vcard"); } }; /** * */ class TodosSyncSource : public AkonadiSyncSource { Q_OBJECT public: TodosSyncSource(TimeTrackingObserver *observer, TodosSyncSourceConfig *config, SyncManagerConfig *managerConfig); }; #endif syncevolution_1.4/src/backends/evolution/000077500000000000000000000000001230021373600207435ustar00rootroot00000000000000syncevolution_1.4/src/backends/evolution/EvolutionCalendarSource.cpp000066400000000000000000001257101230021373600262540ustar00rootroot00000000000000/* * Copyright (C) 2005-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include using namespace std; #include "config.h" #ifdef ENABLE_ECAL // include first, it sets HANDLE_LIBICAL_MEMORY for us #include #include #include #include #include "EvolutionCalendarSource.h" #include "EvolutionMemoSource.h" #include "e-cal-check-timezones.h" #include #include SE_BEGIN_CXX static const string EVOLUTION_CALENDAR_PRODID("PRODID:-//ACME//NONSGML SyncEvolution//EN"), EVOLUTION_CALENDAR_VERSION("VERSION:2.0"); bool EvolutionCalendarSource::LUIDs::containsLUID(const ItemID &id) const { const_iterator it = findUID(id.m_uid); return it != end() && it->second.find(id.m_rid) != it->second.end(); } void EvolutionCalendarSource::LUIDs::insertLUID(const ItemID &id) { (*this)[id.m_uid].insert(id.m_rid); } void EvolutionCalendarSource::LUIDs::eraseLUID(const ItemID &id) { iterator it = find(id.m_uid); if (it != end()) { set::iterator it2 = it->second.find(id.m_rid); if (it2 != it->second.end()) { it->second.erase(it2); if (it->second.empty()) { erase(it); } } } } static int granularity() { // This long delay is necessary in combination // with Evolution Exchange Connector: when updating // a child event, it seems to take a while until // the change really is effective. static int secs = 5; static bool checked = false; if (!checked) { // allow setting the delay (used during testing to shorten runtime) const char *delay = getenv("SYNC_EVOLUTION_EVO_CALENDAR_DELAY"); if (delay) { secs = atoi(delay); } checked = true; } return secs; } EvolutionCalendarSource::EvolutionCalendarSource(EvolutionCalendarSourceType type, const SyncSourceParams ¶ms) : EvolutionSyncSource(params, granularity()), m_type(type) { switch (m_type) { case EVOLUTION_CAL_SOURCE_TYPE_EVENTS: SyncSourceLogging::init(InitList("SUMMARY") + "LOCATION", ", ", m_operations); m_typeName = "calendar"; #ifndef USE_EDS_CLIENT m_newSystem = e_cal_new_system_calendar; #endif break; case EVOLUTION_CAL_SOURCE_TYPE_TASKS: SyncSourceLogging::init(InitList("SUMMARY"), ", ", m_operations); m_typeName = "task list"; #ifndef USE_EDS_CLIENT m_newSystem = e_cal_new_system_tasks; #endif break; case EVOLUTION_CAL_SOURCE_TYPE_MEMOS: SyncSourceLogging::init(InitList("SUBJECT"), ", ", m_operations); m_typeName = "memo list"; #ifndef USE_EDS_CLIENT // This is not available in older Evolution versions. // A configure check could detect that, but as this isn't // important the functionality is simply disabled. m_newSystem = NULL /* e_cal_new_system_memos */; #endif break; default: SyncContext::throwError("internal error, invalid calendar type"); break; } } SyncSource::Databases EvolutionCalendarSource::getDatabases() { GErrorCXX gerror; Databases result; #ifdef USE_EDS_CLIENT getDatabasesFromRegistry(result, sourceExtension(), m_type == EVOLUTION_CAL_SOURCE_TYPE_EVENTS ? e_source_registry_ref_default_calendar : m_type == EVOLUTION_CAL_SOURCE_TYPE_TASKS ? e_source_registry_ref_default_task_list : m_type == EVOLUTION_CAL_SOURCE_TYPE_MEMOS ? e_source_registry_ref_default_memo_list : NULL); #else ESourceList *tmp = NULL; if (!e_cal_get_sources(&tmp, sourceType(), gerror)) { // ignore unspecific errors (like on Maemo with no support for memos) // and continue with empty list (perhaps defaults work) if (!gerror) { tmp = NULL; } else { throwError("unable to access backend databases", gerror); } } ESourceListCXX sources(tmp, TRANSFER_REF); bool first = true; for (GSList *g = sources ? e_source_list_peek_groups (sources) : NULL; g; g = g->next) { ESourceGroup *group = E_SOURCE_GROUP (g->data); for (GSList *s = e_source_group_peek_sources (group); s; s = s->next) { ESource *source = E_SOURCE (s->data); eptr uri(e_source_get_uri(source)); result.push_back(Database(e_source_peek_name(source), uri ? uri.get() : "", first)); first = false; } } if (result.empty() && m_newSystem) { eptr calendar(m_newSystem()); if (calendar.get()) { // okay, default system database exists const char *uri = e_cal_get_uri(calendar.get()); result.push_back(Database("<>", uri ? uri : "<>")); } } #endif return result; } #ifdef USE_EDS_CLIENT static EClient *newECalClient(ESource *source, ECalClientSourceType ecalSourceType, GError **gerror) { return E_CLIENT(e_cal_client_new(source, ecalSourceType, gerror)); } #else char *EvolutionCalendarSource::authenticate(const char *prompt, const char *key) { std::string passwd = getPassword(); SE_LOG_DEBUG(getDisplayName(), "authentication requested, prompt \"%s\", key \"%s\" => %s", prompt, key, !passwd.empty() ? "returning configured password" : "no password configured"); return !passwd.empty() ? strdup(passwd.c_str()) : NULL; } #endif void EvolutionCalendarSource::open() { #ifdef USE_EDS_CLIENT // Open twice. This solves an issue where Evolution's CalDAV // backend only updates its local cache *after* a sync (= while // closing the calendar?), instead of doing it *before* a sync (in // e_cal_open()). // // This workaround is applied to *all* backends because there might // be others with similar problems and for local storage it is // a reasonably cheap operation (so no harm there). for (int retries = 0; retries < 2; retries++) { m_calendar.reset(E_CAL_CLIENT(openESource(sourceExtension(), m_type == EVOLUTION_CAL_SOURCE_TYPE_EVENTS ? e_source_registry_ref_builtin_calendar : m_type == EVOLUTION_CAL_SOURCE_TYPE_TASKS ? e_source_registry_ref_builtin_task_list : m_type == EVOLUTION_CAL_SOURCE_TYPE_MEMOS ? e_source_registry_ref_builtin_memo_list : NULL, boost::bind(newECalClient, _1, sourceType(), _2)).get())); } #else GErrorCXX gerror; bool onlyIfExists = false; // always try to create address book, because even if there is // a source there's no guarantee that the actual database was // created already; the original logic below for only setting // this when explicitly requesting a new database // therefore failed in some cases ESourceList *tmp; if (!e_cal_get_sources(&tmp, sourceType(), gerror)) { throwError("unable to access backend databases", gerror); } ESourceListCXX sources(tmp, TRANSFER_REF); string id = getDatabaseID(); ESource *source = findSource(sources, id); bool created = false; // Open twice. This solves an issue where Evolution's CalDAV // backend only updates its local cache *after* a sync (= while // closing the calendar?), instead of doing it *before* a sync (in // e_cal_open()). // // This workaround is applied to *all* backends because there might // be others with similar problems and for local storage it is // a reasonably cheap operation (so no harm there). for (int retries = 0; retries < 2; retries++) { if (!source) { // might have been special "<>" or "<>", try that and // creating address book from file:// URI before giving up if ((id.empty() || id == "<>") && m_newSystem) { m_calendar.set(m_newSystem(), (string("system ") + m_typeName).c_str()); } else if (!id.compare(0, 7, "file://")) { m_calendar.set(e_cal_new_from_uri(id.c_str(), sourceType()), (string("creating ") + m_typeName).c_str()); } else { throwError(string("not found: '") + id + "'"); } created = true; onlyIfExists = false; } else { m_calendar.set(e_cal_new(source, sourceType()), m_typeName.c_str()); } e_cal_set_auth_func(m_calendar, eCalAuthFunc, this); if (!e_cal_open(m_calendar, onlyIfExists, gerror)) { if (created) { // opening newly created address books often failed, perhaps that also applies to calendars - try again gerror.clear(); sleep(5); if (!e_cal_open(m_calendar, onlyIfExists, gerror)) { throwError(string("opening ") + m_typeName, gerror); } } else { throwError(string("opening ") + m_typeName, gerror); } } } #endif g_signal_connect_after(m_calendar, "backend-died", G_CALLBACK(SyncContext::fatalError), (void *)"Evolution Data Server has died unexpectedly, database no longer available."); } bool EvolutionCalendarSource::isEmpty() { // TODO: add more efficient implementation which does not // depend on actually pulling all items from EDS RevisionMap_t revisions; listAllItems(revisions); return revisions.empty(); } #ifdef USE_EDS_CLIENT class ECalClientViewSyncHandler { public: typedef boost::function Process_t; ECalClientViewSyncHandler(ECalClientViewCXX &view, const Process_t &process) : m_process(process), m_view(view) {} bool processSync(GErrorCXX &gerror) { // Listen for view signals m_view.connectSignal("objects-added", boost::bind(m_process, _2)); m_view.connectSignal("complete", boost::bind(&ECalClientViewSyncHandler::completed, this, _2)); // Start the view e_cal_client_view_start (m_view, m_error); if (m_error) { std::swap(gerror, m_error); return false; } // Async -> Sync m_loop.run(); e_cal_client_view_stop (m_view, NULL); if (m_error) { std::swap(gerror, m_error); return false; } else { return true; } } void completed(const GError *error) { m_error = error; m_loop.quit(); } public: // Event loop for Async -> Sync EvolutionAsync m_loop; private: // Process list callback Process_t m_process; // View watched ECalClientViewCXX m_view; // Possible error while watching the view GErrorCXX m_error; }; static void list_revisions(const GSList *objects, EvolutionCalendarSource::RevisionMap_t *revisions) { const GSList *l; for (l = objects; l; l = l->next) { icalcomponent *icomp = (icalcomponent*)l->data; EvolutionCalendarSource::ItemID id = EvolutionCalendarSource::getItemID(icomp); string luid = id.getLUID(); string modTime = EvolutionCalendarSource::getItemModTime(icomp); (*revisions)[luid] = modTime; } } #endif void EvolutionCalendarSource::listAllItems(RevisionMap_t &revisions) { GErrorCXX gerror; #ifdef USE_EDS_CLIENT ECalClientView *view; if (!e_cal_client_get_view_sync (m_calendar, "#t", &view, NULL, gerror)) { throwError( "getting the view" , gerror); } ECalClientViewCXX viewPtr = ECalClientViewCXX::steal(view); // TODO: Optimization: use set fields_of_interest (UID / REV / LAST-MODIFIED) ECalClientViewSyncHandler handler(viewPtr, boost::bind(list_revisions, _1, &revisions)); if (!handler.processSync(gerror)) { throwError("watching view", gerror); } // Update m_allLUIDs m_allLUIDs.clear(); RevisionMap_t::iterator it; for(it = revisions.begin(); it != revisions.end(); ++it) { m_allLUIDs.insertLUID(it->first); } #else GList *nextItem; m_allLUIDs.clear(); if (!e_cal_get_object_list_as_comp(m_calendar, "#t", &nextItem, gerror)) { throwError("reading all items", gerror); } eptr listptr(nextItem); while (nextItem) { ECalComponent *ecomp = E_CAL_COMPONENT(nextItem->data); ItemID id = getItemID(ecomp); string luid = id.getLUID(); string modTime = getItemModTime(ecomp); m_allLUIDs.insertLUID(id); revisions[luid] = modTime; nextItem = nextItem->next; } #endif } void EvolutionCalendarSource::close() { m_calendar.reset(); } void EvolutionCalendarSource::readItem(const string &luid, std::string &item, bool raw) { ItemID id(luid); item = retrieveItemAsString(id); } #ifdef USE_EDS_CLIENT icaltimezone * my_tzlookup(const gchar *tzid, gconstpointer ecalclient, GCancellable *cancellable, GError **error) { icaltimezone *zone = NULL; GError *local_error = NULL; if (e_cal_client_get_timezone_sync((ECalClient *)ecalclient, tzid, &zone, cancellable, &local_error)) { return zone; } else if (local_error && local_error->domain == E_CAL_CLIENT_ERROR) { // Ignore *all* E_CAL_CLIENT_ERROR errors, e_cal_client_get_timezone_sync() does // not reliably return a specific code like E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND. // See the 'e_cal_client_check_timezones() + e_cal_client_tzlookup() + Could not retrieve calendar time zone: Invalid object' // mail thread. g_clear_error (&local_error); } else if (local_error) { g_propagate_error (error, local_error); } return NULL; } #endif EvolutionCalendarSource::InsertItemResult EvolutionCalendarSource::insertItem(const string &luid, const std::string &item, bool raw) { bool update = !luid.empty(); InsertItemResultState state = ITEM_OKAY; bool detached = false; string newluid = luid; string data = item; string modTime; /* * Evolution/libical can only deal with \, as separator. * Replace plain , in incoming event CATEGORIES with \, - * based on simple text search/replace and thus will not work * in all cases... * * Inverse operation in extractItemAsString(). */ size_t propstart = data.find("\nCATEGORIES"); bool modified = false; while (propstart != data.npos) { size_t eol = data.find('\n', propstart + 1); size_t comma = data.find(',', propstart); while (eol != data.npos && comma != data.npos && comma < eol) { if (data[comma-1] != '\\') { data.insert(comma, "\\"); comma++; modified = true; } comma = data.find(',', comma + 1); } propstart = data.find("\nCATEGORIES", propstart + 1); } if (modified) { SE_LOG_DEBUG(getDisplayName(), "after replacing , with \\, in CATEGORIES:\n%s", data.c_str()); } eptr icomp(icalcomponent_new_from_string((char *)data.c_str())); if( !icomp ) { throwError(string("failure parsing ical") + data); } GErrorCXX gerror; // fix up TZIDs if ( #ifdef USE_EDS_CLIENT !e_cal_client_check_timezones(icomp, NULL, my_tzlookup, (const void *)m_calendar.get(), NULL, gerror) #else !e_cal_check_timezones(icomp, NULL, e_cal_tzlookup_ecal, (const void *)m_calendar.get(), gerror) #endif ) { throwError(string("fixing timezones") + data, gerror); } // insert before adding/updating the event so that the new VTIMEZONE is // immediately available should anyone want it for (icalcomponent *tcomp = icalcomponent_get_first_component(icomp, ICAL_VTIMEZONE_COMPONENT); tcomp; tcomp = icalcomponent_get_next_component(icomp, ICAL_VTIMEZONE_COMPONENT)) { eptr zone(icaltimezone_new(), "icaltimezone"); icaltimezone_set_component(zone, tcomp); GErrorCXX gerror; const char *tzid = icaltimezone_get_tzid(zone); if (!tzid || !tzid[0]) { // cannot add a VTIMEZONE without TZID SE_LOG_DEBUG(getDisplayName(), "skipping VTIMEZONE without TZID"); } else { gboolean success = #ifdef USE_EDS_CLIENT e_cal_client_add_timezone_sync(m_calendar, zone, NULL, gerror) #else e_cal_add_timezone(m_calendar, zone, gerror) #endif ; if (!success) { throwError(string("error adding VTIMEZONE ") + tzid, gerror); } } } // the component to update/add must be the // ICAL_VEVENT/VTODO_COMPONENT of the item, // e_cal_create/modify_object() fail otherwise icalcomponent *subcomp = icalcomponent_get_first_component(icomp, getCompType()); if (!subcomp) { throwError("extracting event"); } // Remove LAST-MODIFIED: the Evolution Exchange Connector does not // properly update this property if it is already present in the // incoming data. icalproperty *modprop; while ((modprop = icalcomponent_get_first_property(subcomp, ICAL_LASTMODIFIED_PROPERTY)) != NULL) { icalcomponent_remove_property(subcomp, modprop); icalproperty_free(modprop); modprop = NULL; } if (!update) { ItemID id = getItemID(subcomp); const char *uid = NULL; // Trying to add a normal event which already exists leads to a // gerror->domain == E_CALENDAR_ERROR // gerror->code == E_CALENDAR_STATUS_OBJECT_ID_ALREADY_EXISTS // error. Depending on the Evolution version, the subcomp // UID gets removed (>= 2.12) or remains unchanged. // // Existing detached recurrences are silently updated when // trying to add them. This breaks our return code and change // tracking. // // Escape this madness by checking the existence ourselve first // based on our list of existing LUIDs. Note that this list is // not updated during a sync. This is correct as long as no LUID // gets used twice during a sync (examples: add + add, delete + add), // which should never happen. newluid = id.getLUID(); if (m_allLUIDs.containsLUID(id)) { state = ITEM_NEEDS_MERGE; } else { // if this is a detached recurrence, then we // must use e_cal_modify_object() below if // the parent or any other child already exists if (!id.m_rid.empty() && m_allLUIDs.containsUID(id.m_uid)) { detached = true; } else { // Creating the parent while children are already in // the calendar confuses EDS (at least 2.12): the // parent is stored in the .ics with the old UID, but // the uid returned to the caller is a different // one. Retrieving the item then fails. Avoid this // problem by removing the children from the calendar, // adding the parent, then updating it with the // saved children. // // TODO: still necessary with e_cal_client API? ICalComps_t children; if (id.m_rid.empty()) { children = removeEvents(id.m_uid, true); } // creating new objects works for normal events and detached occurrences alike if ( #ifdef USE_EDS_CLIENT e_cal_client_create_object_sync(m_calendar, subcomp, (gchar **)&uid, NULL, gerror) #else e_cal_create_object(m_calendar, subcomp, (gchar **)&uid, gerror) #endif ) { #ifdef USE_EDS_CLIENT PlainGStr owner((gchar *)uid); #endif // Evolution workaround: don't rely on uid being set if we already had // one. In Evolution 2.12.1 it was set to garbage. The recurrence ID // shouldn't have changed either. ItemID newid(!id.m_uid.empty() ? id.m_uid : uid, id.m_rid); newluid = newid.getLUID(); modTime = getItemModTime(newid); m_allLUIDs.insertLUID(newid); } else { throwError("storing new item", gerror); } // Recreate any children removed earlier: when we get here, // the parent exists and we must update it. BOOST_FOREACH(boost::shared_ptr< eptr > &icalcomp, children) { if ( #ifdef USE_EDS_CLIENT !e_cal_client_modify_object_sync(m_calendar, *icalcomp, CALOBJ_MOD_THIS, NULL, gerror) #else !e_cal_modify_object(m_calendar, *icalcomp, CALOBJ_MOD_THIS, gerror) #endif ) { throwError(string("recreating item ") + newluid, gerror); } } } } } if (update || (state != ITEM_OKAY && state != ITEM_NEEDS_MERGE) || detached) { ItemID id(newluid); bool isParent = id.m_rid.empty(); // ensure that the component has the right UID and // RECURRENCE-ID if (update) { if (!id.m_uid.empty()) { icalcomponent_set_uid(subcomp, id.m_uid.c_str()); } if (!id.m_rid.empty()) { // Reconstructing the RECURRENCE-ID is non-trivial, // because our luid only contains the date-time, but // not the time zone. Only do the work if the event // really doesn't have a RECURRENCE-ID. struct icaltimetype rid; rid = icalcomponent_get_recurrenceid(subcomp); if (icaltime_is_null_time(rid)) { // Preserve the original RECURRENCE-ID, including // timezone, no matter what the update contains // (might have wrong timezone or UTC). eptr orig(retrieveItem(id)); icalproperty *orig_rid = icalcomponent_get_first_property(orig, ICAL_RECURRENCEID_PROPERTY); if (orig_rid) { icalcomponent_add_property(subcomp, icalproperty_new_clone(orig_rid)); } } } } if (isParent) { // CALOBJ_MOD_THIS for parent items (UID set, no RECURRENCE-ID) // is not supported by all backends: the Exchange Connector // fails with it. It might be an incorrect usage of the API. // Therefore we have to use CALOBJ_MOD_ALL, but that removes // children. bool hasChildren = false; LUIDs::const_iterator it = m_allLUIDs.find(id.m_uid); if (it != m_allLUIDs.end()) { BOOST_FOREACH(const string &rid, it->second) { if (!rid.empty()) { hasChildren = true; break; } } } if (hasChildren) { // Use CALOBJ_MOD_ALL and temporarily remove // the children, then add them again. Otherwise they would // get deleted. ICalComps_t children = removeEvents(id.m_uid, true); // Parent is gone, too, and needs to be recreated. const char *uid = NULL; if ( #ifdef USE_EDS_CLIENT !e_cal_client_create_object_sync(m_calendar, subcomp, (char **)&uid, NULL, gerror) #else !e_cal_create_object(m_calendar, subcomp, (char **)&uid, gerror) #endif ) { throwError(string("creating updated item ") + luid, gerror); } #ifdef USE_EDS_CLIENT PlainGStr owner((gchar *)uid); #endif // Recreate any children removed earlier: when we get here, // the parent exists and we must update it. BOOST_FOREACH(boost::shared_ptr< eptr > &icalcomp, children) { if ( #ifdef USE_EDS_CLIENT !e_cal_client_modify_object_sync(m_calendar, *icalcomp, CALOBJ_MOD_THIS, NULL, gerror) #else !e_cal_modify_object(m_calendar, *icalcomp, CALOBJ_MOD_THIS, gerror) #endif ) { throwError(string("recreating item ") + luid, gerror); } } } else { // no children, updating is simple if ( #ifdef USE_EDS_CLIENT !e_cal_client_modify_object_sync(m_calendar, subcomp, CALOBJ_MOD_ALL, NULL, gerror) #else !e_cal_modify_object(m_calendar, subcomp, CALOBJ_MOD_ALL, gerror) #endif ) { throwError(string("updating item ") + luid, gerror); } } } else { // child event if ( #ifdef USE_EDS_CLIENT !e_cal_client_modify_object_sync(m_calendar, subcomp, CALOBJ_MOD_THIS, NULL, gerror) #else !e_cal_modify_object(m_calendar, subcomp, CALOBJ_MOD_THIS, gerror) #endif ) { throwError(string("updating item ") + luid, gerror); } } ItemID newid = getItemID(subcomp); newluid = newid.getLUID(); modTime = getItemModTime(newid); } return InsertItemResult(newluid, modTime, state); } EvolutionCalendarSource::ICalComps_t EvolutionCalendarSource::removeEvents(const string &uid, bool returnOnlyChildren, bool ignoreNotFound) { ICalComps_t events; LUIDs::const_iterator it = m_allLUIDs.find(uid); if (it != m_allLUIDs.end()) { BOOST_FOREACH(const string &rid, it->second) { ItemID id(uid, rid); icalcomponent *icomp = retrieveItem(id); if (icomp) { if (id.m_rid.empty() && returnOnlyChildren) { icalcomponent_free(icomp); } else { events.push_back(ICalComps_t::value_type(new eptr(icomp))); } } } } // removes all events with that UID, including children GErrorCXX gerror; if (!uid.empty() && // e_cal_client_remove_object_sync() in EDS 3.8 aborts the process for empty UID, other versions cannot succeed, so skip the call. #ifdef USE_EDS_CLIENT !e_cal_client_remove_object_sync(m_calendar, uid.c_str(), NULL, CALOBJ_MOD_ALL, NULL, gerror) #else !e_cal_remove_object(m_calendar, uid.c_str(), gerror) #endif ) { if (IsCalObjNotFound(gerror)) { SE_LOG_DEBUG(getDisplayName(), "%s: request to delete non-existant item ignored", uid.c_str()); if (!ignoreNotFound) { throwError(STATUS_NOT_FOUND, string("delete item: ") + uid); } } else { throwError(string("deleting item " ) + uid, gerror); } } return events; } void EvolutionCalendarSource::removeItem(const string &luid) { GErrorCXX gerror; ItemID id(luid); if (id.m_rid.empty()) { /* * Removing the parent item also removes all children. Evolution * does that automatically. Calling e_cal_remove_object_with_mod() * without valid rid confuses Evolution, don't do it. As a workaround * remove all items with the given uid and if we only wanted to * delete the parent, then recreate the children. */ ICalComps_t children = removeEvents(id.m_uid, true, TRANSFER_REF); // recreate children bool first = true; BOOST_FOREACH(boost::shared_ptr< eptr > &icalcomp, children) { if (first) { char *uid; if ( #ifdef USE_EDS_CLIENT !e_cal_client_create_object_sync(m_calendar, *icalcomp, &uid, NULL, gerror) #else !e_cal_create_object(m_calendar, *icalcomp, &uid, gerror) #endif ) { throwError(string("recreating first item ") + luid, gerror); } #ifdef USE_EDS_CLIENT PlainGStr owner((gchar *)uid); #endif first = false; } else { if ( #ifdef USE_EDS_CLIENT !e_cal_client_modify_object_sync(m_calendar, *icalcomp, CALOBJ_MOD_THIS, NULL, gerror) #else !e_cal_modify_object(m_calendar, *icalcomp, CALOBJ_MOD_THIS, gerror) #endif ) { throwError(string("recreating following item ") + luid, gerror); } } } } else { // workaround for EDS 2.32 API semantic: succeeds even if // detached recurrence doesn't exist and adds EXDATE, // therefore we have to check for existence first eptr item(retrieveItem(id)); gboolean success = !item ? false : #ifdef USE_EDS_CLIENT // TODO: is this necessary? e_cal_client_remove_object_sync(m_calendar, id.m_uid.c_str(), id.m_rid.c_str(), CALOBJ_MOD_ONLY_THIS, NULL, gerror) #else e_cal_remove_object_with_mod(m_calendar, id.m_uid.c_str(), id.m_rid.c_str(), CALOBJ_MOD_THIS, gerror) #endif ; if (!item || (!success && IsCalObjNotFound(gerror))) { SE_LOG_DEBUG(getDisplayName(), "%s: request to delete non-existant item", luid.c_str()); throwError(STATUS_NOT_FOUND, string("delete item: ") + id.getLUID()); } else if (!success) { throwError(string("deleting item " ) + luid, gerror); } } m_allLUIDs.eraseLUID(id); if (!id.m_rid.empty()) { // Removing the child may have modified the parent. // We must record the new LAST-MODIFIED string, // otherwise it might be reported as modified during // the next sync (timing dependent: if the parent // was updated before removing the child *and* the // update and remove fall into the same second, // then the modTime does not change again during the // removal). try { ItemID parent(id.m_uid, ""); string modTime = getItemModTime(parent); string parentLUID = parent.getLUID(); updateRevision(getTrackingNode(), parentLUID, parentLUID, modTime); } catch (...) { // There's no guarantee that the parent still exists. // Instead of checking that, ignore errors (a bit hacky, // but better than breaking the removal). } } } icalcomponent *EvolutionCalendarSource::retrieveItem(const ItemID &id) { GErrorCXX gerror; icalcomponent *comp = NULL; if ( #ifdef USE_EDS_CLIENT !e_cal_client_get_object_sync(m_calendar, id.m_uid.c_str(), !id.m_rid.empty() ? id.m_rid.c_str() : NULL, &comp, NULL, gerror) #else !e_cal_get_object(m_calendar, id.m_uid.c_str(), !id.m_rid.empty() ? id.m_rid.c_str() : NULL, &comp, gerror) #endif ) { if (IsCalObjNotFound(gerror)) { throwError(STATUS_NOT_FOUND, string("retrieving item: ") + id.getLUID()); } else { throwError(string("retrieving item: ") + id.getLUID(), gerror); } } if (!comp) { throwError(string("retrieving item: ") + id.getLUID()); } eptr ptr(comp); /* * EDS bug: if a parent doesn't exist while a child does, and we ask * for the parent, we are sent the (first?) child. Detect this and * turn it into a "not found" error. */ if (id.m_rid.empty()) { struct icaltimetype rid = icalcomponent_get_recurrenceid(comp); if (!icaltime_is_null_time(rid)) { throwError(string("retrieving item: got child instead of parent: ") + id.m_uid); } } return ptr.release(); } string EvolutionCalendarSource::retrieveItemAsString(const ItemID &id) { eptr comp(retrieveItem(id)); eptr icalstr; #ifdef USE_EDS_CLIENT icalstr = e_cal_client_get_component_as_string(m_calendar, comp); #else icalstr = e_cal_get_component_as_string(m_calendar, comp); #endif if (!icalstr) { // One reason why e_cal_get_component_as_string() can fail is // that it uses a TZID which has no corresponding VTIMEZONE // definition. Evolution GUI ignores the TZID and interprets // the times as local time. Do the same when exporting the // event by removing the bogus TZID. icalproperty *prop = icalcomponent_get_first_property (comp, ICAL_ANY_PROPERTY); while (prop) { // removes only the *first* TZID - but there shouldn't be more than one icalproperty_remove_parameter_by_kind(prop, ICAL_TZID_PARAMETER); prop = icalcomponent_get_next_property (comp, ICAL_ANY_PROPERTY); } // now try again #ifdef USE_EDS_CLIENT icalstr = e_cal_client_get_component_as_string(m_calendar, comp); #else icalstr = e_cal_get_component_as_string(m_calendar, comp); #endif if (!icalstr) { throwError(string("could not encode item as iCalendar: ") + id.getLUID()); } else { SE_LOG_DEBUG(getDisplayName(), "had to remove TZIDs because e_cal_get_component_as_string() failed for:\n%s", icalstr.get()); } } /* * Evolution/libical can only deal with \, as separator. * Replace plain \, in outgoing event CATEGORIES with , - * based on simple text search/replace and thus will not work * in all cases... * * Inverse operation in insertItem(). */ string data = string(icalstr); size_t propstart = data.find("\nCATEGORIES"); bool modified = false; while (propstart != data.npos) { size_t eol = data.find('\n', propstart + 1); size_t comma = data.find(',', propstart); while (eol != data.npos && comma != data.npos && comma < eol) { if (data[comma-1] == '\\') { data.erase(comma - 1, 1); comma--; modified = true; } comma = data.find(',', comma + 1); } propstart = data.find("\nCATEGORIES", propstart + 1); } if (modified) { SE_LOG_DEBUG(getDisplayName(), "after replacing \\, with , in CATEGORIES:\n%s", data.c_str()); } return data; } std::string EvolutionCalendarSource::getDescription(const string &luid) { try { eptr comp(retrieveItem(ItemID(luid))); std::string descr; const char *summary = icalcomponent_get_summary(comp); if (summary && summary[0]) { descr += summary; } if (m_type == EVOLUTION_CAL_SOURCE_TYPE_EVENTS) { const char *location = icalcomponent_get_location(comp); if (location && location[0]) { if (!descr.empty()) { descr += ", "; } descr += location; } } if (m_type == EVOLUTION_CAL_SOURCE_TYPE_MEMOS && descr.empty()) { // fallback to first line of body text icalproperty *desc = icalcomponent_get_first_property(comp, ICAL_DESCRIPTION_PROPERTY); if (desc) { const char *text = icalproperty_get_description(desc); if (text) { const char *eol = strchr(text, '\n'); if (eol) { descr.assign(text, eol - text); } else { descr = text; } } } } return descr; } catch (...) { // Instead of failing we log the error and ask // the caller to log the UID. That way transient // errors or errors in the logging code don't // prevent syncs. handleException(); return ""; } } string EvolutionCalendarSource::ItemID::getLUID() const { return getLUID(m_uid, m_rid); } string EvolutionCalendarSource::ItemID::getLUID(const string &uid, const string &rid) { return uid + "-rid" + rid; } EvolutionCalendarSource::ItemID::ItemID(const string &luid) { size_t ridoff = luid.rfind("-rid"); if (ridoff != luid.npos) { const_cast(m_uid) = luid.substr(0, ridoff); const_cast(m_rid) = luid.substr(ridoff + strlen("-rid")); } else { const_cast(m_uid) = luid; } } EvolutionCalendarSource::ItemID EvolutionCalendarSource::getItemID(ECalComponent *ecomp) { icalcomponent *icomp = e_cal_component_get_icalcomponent(ecomp); if (!icomp) { SE_THROW("internal error in getItemID(): ECalComponent without icalcomp"); } return getItemID(icomp); } EvolutionCalendarSource::ItemID EvolutionCalendarSource::getItemID(icalcomponent *icomp) { const char *uid; struct icaltimetype rid; uid = icalcomponent_get_uid(icomp); rid = icalcomponent_get_recurrenceid(icomp); return ItemID(uid ? uid : "", icalTime2Str(rid)); } string EvolutionCalendarSource::getItemModTime(ECalComponent *ecomp) { struct icaltimetype *modTime; e_cal_component_get_last_modified(ecomp, &modTime); eptr > modTimePtr(modTime); if (!modTimePtr) { return ""; } else { return icalTime2Str(*modTimePtr.get()); } } string EvolutionCalendarSource::getItemModTime(const ItemID &id) { if (!needChanges()) { return ""; } eptr icomp(retrieveItem(id)); return getItemModTime(icomp); } string EvolutionCalendarSource::getItemModTime(icalcomponent *icomp) { icalproperty *modprop = icalcomponent_get_first_property(icomp, ICAL_LASTMODIFIED_PROPERTY); if (!modprop) { return ""; } struct icaltimetype modTime = icalproperty_get_lastmodified(modprop); return icalTime2Str(modTime); } string EvolutionCalendarSource::icalTime2Str(const icaltimetype &tt) { static const struct icaltimetype null = { 0 }; if (!memcmp(&tt, &null, sizeof(null))) { return ""; } else { eptr timestr(ical_strdup(icaltime_as_ical_string(tt))); if (!timestr) { SE_THROW("cannot convert to time string"); } return timestr.get(); } } SE_END_CXX #endif /* ENABLE_ECAL */ #ifdef ENABLE_MODULES # include "EvolutionCalendarSourceRegister.cpp" #endif syncevolution_1.4/src/backends/evolution/EvolutionCalendarSource.h000066400000000000000000000233201230021373600257130ustar00rootroot00000000000000/* * Copyright (C) 2005-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_EVOLUTIONCALENDARSOURCE #define INCL_EVOLUTIONCALENDARSOURCE #include "config.h" #include "EvolutionSyncSource.h" #include #include #ifdef ENABLE_ECAL #include #ifdef USE_EDS_CLIENT SE_GOBJECT_TYPE(ECalClient) SE_GOBJECT_TYPE(ECalClientView) #endif SE_BEGIN_CXX /** * Source type independent from ECal / ECalClient to abstract * the two different enums in the APIs. */ typedef enum { EVOLUTION_CAL_SOURCE_TYPE_EVENTS, EVOLUTION_CAL_SOURCE_TYPE_TASKS, EVOLUTION_CAL_SOURCE_TYPE_MEMOS } EvolutionCalendarSourceType; inline bool IsCalObjNotFound(const GError *gerror) { return gerror && #ifdef USE_EDS_CLIENT gerror->domain == E_CAL_CLIENT_ERROR && gerror->code == E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND #else gerror->domain == E_CALENDAR_ERROR && gerror->code == E_CALENDAR_STATUS_OBJECT_NOT_FOUND #endif ; } /** * Implements access to Evolution calendars, either * using the to-do item or events. Change tracking * is done by looking at the modification time stamp. * Recurring events and their detached recurrences are * handled as one item for the main event and one item * for each detached recurrence. */ class EvolutionCalendarSource : public EvolutionSyncSource, public SyncSourceLogging, private boost::noncopyable { public: /** * @param type chooses which kind of calendar data to use: * EVOLUTION_CAL_SOURCE_TYPE_EVENTS, * EVOLUTION_CAL_SOURCE_TYPE_TASKS, * EVOLUTION_CAL_SOURCE_TYPE_MEMOS */ EvolutionCalendarSource(EvolutionCalendarSourceType type, const SyncSourceParams ¶ms); virtual ~EvolutionCalendarSource() { close(); } // // implementation of SyncSource // virtual Databases getDatabases(); virtual void open(); virtual bool isEmpty(); virtual void close(); virtual std::string getMimeType() const { return "text/calendar"; } virtual std::string getMimeVersion() const { return "2.0"; } void getSynthesisInfo(SynthesisInfo &info, XMLConfigFragments &fragments) { // All EDS calendar storages must suppport UID/RECURRENCE-ID, // it's part of the API. Therefore we can rely on it. EvolutionSyncSource::getSynthesisInfo(info, fragments); info.m_globalIDs = true; } /** * An item is identified in the calendar by * its UID (unique ID) and RID (recurrence ID). * The RID may be empty. * * This is turned into a SyncML LUID by * concatenating them: -rid. */ class ItemID { public: ItemID(const string &uid, const string &rid) : m_uid(uid), m_rid(rid) {} ItemID(const char *uid, const char *rid): m_uid(uid ? uid : ""), m_rid(rid ? rid : "") {} ItemID(const string &luid); const string m_uid, m_rid; string getLUID() const; static string getLUID(const string &uid, const string &rid); }; /** * Extract item ID from calendar item. An icalcomponent must * refer to the VEVENT/VTODO/VJOURNAL component. */ static ItemID getItemID(ECalComponent *ecomp); static ItemID getItemID(icalcomponent *icomp); /** * Extract modification string from calendar item. * @return empty string if no time was available */ static string getItemModTime(ECalComponent *ecomp); static string getItemModTime(icalcomponent *icomp); protected: // // implementation of TrackingSyncSource callbacks // virtual void listAllItems(RevisionMap_t &revisions); virtual InsertItemResult insertItem(const string &uid, const std::string &item, bool raw); void readItem(const std::string &luid, std::string &item, bool raw); virtual void removeItem(const string &uid); // implementation of SyncSourceLogging callback virtual std::string getDescription(const string &luid); protected: /** valid after open(): the calendar that this source references */ #ifdef USE_EDS_CLIENT ECalClientCXX m_calendar; #else eptr m_calendar; ECal *(*m_newSystem)(void); /**< e_cal_new_system_calendar, etc. */ #endif string m_typeName; /**< "calendar", "task list", "memo list" */ EvolutionCalendarSourceType m_type; /**< use events, tasks or memos? */ // Convenience function for source type casting #ifdef USE_EDS_CLIENT ECalClientSourceType sourceType() const { return (ECalClientSourceType)m_type; } virtual const char *sourceExtension() const { return m_type == EVOLUTION_CAL_SOURCE_TYPE_EVENTS ? E_SOURCE_EXTENSION_CALENDAR : m_type == EVOLUTION_CAL_SOURCE_TYPE_TASKS ? E_SOURCE_EXTENSION_TASK_LIST : m_type == EVOLUTION_CAL_SOURCE_TYPE_MEMOS ? E_SOURCE_EXTENSION_MEMO_LIST : ""; } virtual ESourceCXX refSystemDB() const { ESource *(*ref)(ESourceRegistry *) = m_type == EVOLUTION_CAL_SOURCE_TYPE_EVENTS ? e_source_registry_ref_builtin_calendar : m_type == EVOLUTION_CAL_SOURCE_TYPE_TASKS ? e_source_registry_ref_builtin_task_list : m_type == EVOLUTION_CAL_SOURCE_TYPE_MEMOS ? e_source_registry_ref_builtin_memo_list : NULL; return ESourceCXX(ref ? ref(EDSRegistryLoader::getESourceRegistry()) : NULL, TRANSFER_REF); } #else ECalSourceType sourceType() const { return (ECalSourceType)m_type; } #endif /** * retrieve the item with the given id - may throw exception * * caller has to free result */ icalcomponent *retrieveItem(const ItemID &id); /** retrieve the item with the given luid as VCALENDAR string - may throw exception */ string retrieveItemAsString(const ItemID &id); /** returns the type which the ical library uses for our components */ icalcomponent_kind getCompType() { return m_type == EVOLUTION_CAL_SOURCE_TYPE_EVENTS ? ICAL_VEVENT_COMPONENT : m_type == EVOLUTION_CAL_SOURCE_TYPE_MEMOS ? ICAL_VJOURNAL_COMPONENT : ICAL_VTODO_COMPONENT; } #ifndef USE_EDS_CLIENT /** ECalAuthFunc which calls the authenticate() methods */ static char *eCalAuthFunc(ECal *ecal, const char *prompt, const char *key, gpointer user_data) { return ((EvolutionCalendarSource *)user_data)->authenticate(prompt, key); } /** actual implementation of ECalAuthFunc */ char *authenticate(const char *prompt, const char *key); #endif /** * Returns the LUID of a calendar item. */ string getLUID(ECalComponent *ecomp); /** * Extract modification string of an item stored in * the calendar. * @return empty string if no time was available */ string getItemModTime(const ItemID &id); /** * Convert to string in canonical representation. */ static string icalTime2Str(const struct icaltimetype &tt); /** * A set of all existing objects. Initialized in the last call to * listAllItems() and then updated as items get * added/removed. Used to decide how insertItem() has to be * implemented without the troublesome querying of the EDS * backend. */ class LUIDs : public map< string, set > { public: bool containsUID(const std::string &uid) const { return findUID(uid) != end(); } const_iterator findUID(const std::string &uid) const { return find(uid); } bool containsLUID(const ItemID &id) const; void insertLUID(const ItemID &id); void eraseLUID(const ItemID &id); } m_allLUIDs; /** * A list of ref-counted smart pointers to icalcomponents. * The list members can be copied; destroying the last instance * will destroy the smart pointer, which then calls * icalcomponent_free(). */ typedef list< boost::shared_ptr< eptr > > ICalComps_t; /** * Utility function which extracts all icalcomponents with * the given UID, stores them in a list and then removes * them from the calendar. Trying to remove a non-existant * UID is logged, but not an error. It simply returns an * empty list. * * Relies on m_allLUIDs, but does not update it. The caller must * ensure that the calendar remains in a consistent state. * * @param returnOnlyChildren only return children in list, even if parent is also removed * @param ignoreNotFound don't throw a STATUS_NOT_FOUND error when deleting fails with * a NOT_FOUND error */ ICalComps_t removeEvents(const string &uid, bool returnOnlyChildren, bool ignoreNotFound = true); }; SE_END_CXX #endif // ENABLE_ECAL #endif // INCL_EVOLUTIONSYNCSOURCE syncevolution_1.4/src/backends/evolution/EvolutionCalendarSourceRegister.cpp000066400000000000000000000336431230021373600277640ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "EvolutionCalendarSource.h" #include "EvolutionMemoSource.h" #include "test.h" #include #include SE_BEGIN_CXX static SyncSource *createSource(const SyncSourceParams ¶ms) { SourceType sourceType = SyncSource::getSourceType(params.m_nodes); bool isMe; bool enabled; EDSAbiWrapperInit(); enabled = EDSAbiHaveEcal && EDSAbiHaveEdataserver; isMe = sourceType.m_backend == "Evolution Task List"; if (isMe || sourceType.m_backend == "todo") { if (sourceType.m_format == "" || sourceType.m_format == "text/calendar" || sourceType.m_format == "text/x-calendar" || sourceType.m_format == "text/x-vcalendar") { return #ifdef ENABLE_ECAL enabled ? new EvolutionCalendarSource(EVOLUTION_CAL_SOURCE_TYPE_TASKS, params) : #endif isMe ? RegisterSyncSource::InactiveSource(params) : NULL; } } isMe = sourceType.m_backend == "Evolution Memos"; if (isMe || sourceType.m_backend == "memo") { if (sourceType.m_format == "" || sourceType.m_format == "text/plain") { return #ifdef ENABLE_ECAL enabled ? new EvolutionMemoSource(params) : #endif isMe ? RegisterSyncSource::InactiveSource(params) : NULL; } else if (sourceType.m_format == "text/calendar") { return #ifdef ENABLE_ECAL enabled ? new EvolutionCalendarSource(EVOLUTION_CAL_SOURCE_TYPE_MEMOS, params) : #endif isMe ? RegisterSyncSource::InactiveSource(params) : NULL; } else { return NULL; } } isMe = sourceType.m_backend == "Evolution Calendar"; if (isMe || sourceType.m_backend == "calendar") { if (sourceType.m_format == "" || sourceType.m_format == "text/calendar" || sourceType.m_format == "text/x-calendar" || sourceType.m_format == "text/x-vcalendar") { return #ifdef ENABLE_ECAL enabled ? new EvolutionCalendarSource(EVOLUTION_CAL_SOURCE_TYPE_EVENTS, params) : #endif isMe ? RegisterSyncSource::InactiveSource(params) : NULL; } else { return NULL; } } return NULL; } static RegisterSyncSource registerMe("Evolution Calendar/Task List/Memos", #ifdef ENABLE_ECAL true, #else false, #endif createSource, "Evolution Calendar = calendar = events = evolution-events\n" " iCalendar 2.0 (default) = text/calendar\n" " vCalendar 1.0 = text/x-vcalendar\n" "Evolution Task List = Evolution Tasks = todo = tasks = evolution-tasks\n" " iCalendar 2.0 (default) = text/calendar\n" " vCalendar 1.0 = text/x-vcalendar\n" "Evolution Memos = memo = memos = evolution-memos\n" " plain text in UTF-8 (default) = text/plain\n" " iCalendar 2.0 = text/calendar\n" " vCalendar 1.0 = text/x-vcalendar\n" " The later format is not tested because none of the\n" " supported SyncML servers accepts it.\n", Values() + (Aliases("Evolution Calendar") + "evolution-calendar") + (Aliases("Evolution Task List") + "Evolution Tasks" + "evolution-tasks") + (Aliases("Evolution Memos") + "evolution-memos")); #ifdef ENABLE_ECAL #ifdef ENABLE_UNIT_TESTS class EvolutionCalendarTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(EvolutionCalendarTest); CPPUNIT_TEST(testInstantiate); CPPUNIT_TEST(testOpenDefaultCalendar); CPPUNIT_TEST(testOpenDefaultTodo); CPPUNIT_TEST(testOpenDefaultMemo); CPPUNIT_TEST(testTimezones); CPPUNIT_TEST_SUITE_END(); protected: static string addItem(boost::shared_ptr source, string &data) { SyncSourceRaw::InsertItemResult res = source->insertItemRaw("", data); return res.m_luid; } void testInstantiate() { boost::shared_ptr source; source.reset((TestingSyncSource *)SyncSource::createTestingSource("calendar", "calendar", true)); source.reset((TestingSyncSource *)SyncSource::createTestingSource("calendar", "evolution-calendar", true)); source.reset((TestingSyncSource *)SyncSource::createTestingSource("calendar", "Evolution Calendar:text/calendar", true)); source.reset((TestingSyncSource *)SyncSource::createTestingSource("calendar", "tasks", true)); source.reset((TestingSyncSource *)SyncSource::createTestingSource("calendar", "evolution-tasks", true)); source.reset((TestingSyncSource *)SyncSource::createTestingSource("calendar", "Evolution Tasks", true)); source.reset((TestingSyncSource *)SyncSource::createTestingSource("calendar", "Evolution Task List:text/calendar", true)); source.reset((TestingSyncSource *)SyncSource::createTestingSource("calendar", "memos", true)); source.reset((TestingSyncSource *)SyncSource::createTestingSource("calendar", "evolution-memos", true)); source.reset((TestingSyncSource *)SyncSource::createTestingSource("calendar", "Evolution Memos:text/plain", true)); source.reset((TestingSyncSource *)SyncSource::createTestingSource("calendar", "Evolution Memos:text/calendar", true)); } void testOpenDefaultCalendar() { boost::shared_ptr source; source.reset((TestingSyncSource *)SyncSource::createTestingSource("calendar", "evolution-calendar", true, NULL)); CPPUNIT_ASSERT_NO_THROW(source->open()); } void testOpenDefaultTodo() { boost::shared_ptr source; source.reset((TestingSyncSource *)SyncSource::createTestingSource("calendar", "evolution-tasks", true, NULL)); CPPUNIT_ASSERT_NO_THROW(source->open()); } void testOpenDefaultMemo() { boost::shared_ptr source; source.reset((TestingSyncSource *)SyncSource::createTestingSource("calendar", "evolution-memos", true, NULL)); CPPUNIT_ASSERT_NO_THROW(source->open()); } void testTimezones() { const char *prefix = getenv("CLIENT_TEST_EVOLUTION_PREFIX"); if (!prefix) { prefix = "SyncEvolution_Test_"; } boost::shared_ptr source; source.reset((TestingSyncSource *)SyncSource::createTestingSource("eds_event", "evolution-calendar", true, prefix)); CPPUNIT_ASSERT_NO_THROW(source->open()); string newyork = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VTIMEZONE\n" "TZID:America/New_York\n" "BEGIN:STANDARD\n" "TZOFFSETFROM:-0400\n" "TZOFFSETTO:-0500\n" "TZNAME:EST\n" "DTSTART:19701025T020000\n" "RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10\n" "END:STANDARD\n" "BEGIN:DAYLIGHT\n" "TZOFFSETFROM:-0500\n" "TZOFFSETTO:-0400\n" "TZNAME:EDT\n" "DTSTART:19700405T020000\n" "RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=4\n" "END:DAYLIGHT\n" "END:VTIMEZONE\n" "BEGIN:VEVENT\n" "UID:artificial\n" "DTSTAMP:20060416T205224Z\n" "DTSTART;TZID=America/New_York:20060406T140000\n" "DTEND;TZID=America/New_York:20060406T143000\n" "TRANSP:OPAQUE\n" "SEQUENCE:2\n" "SUMMARY:timezone New York with custom definition\n" "DESCRIPTION:timezone New York with custom definition\n" "CLASS:PUBLIC\n" "CREATED:20060416T205301Z\n" "LAST-MODIFIED:20060416T205301Z\n" "END:VEVENT\n" "END:VCALENDAR\n"; string luid; CPPUNIT_ASSERT_NO_THROW(luid = addItem(source, newyork)); string newyork_suffix = newyork; boost::replace_first(newyork_suffix, "UID:artificial", "UID:artificial-2"); boost::replace_all(newyork_suffix, "TZID:America/New_York", "TZID://FOOBAR/America/New_York-SUFFIX"); CPPUNIT_ASSERT_NO_THROW(luid = addItem(source, newyork_suffix)); string notimezone = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VEVENT\n" "UID:artificial-3\n" "DTSTAMP:20060416T205224Z\n" "DTSTART;TZID=America/New_York:20060406T140000\n" "DTEND;TZID=America/New_York:20060406T143000\n" "TRANSP:OPAQUE\n" "SEQUENCE:2\n" "SUMMARY:timezone New York without custom definition\n" "DESCRIPTION:timezone New York without custom definition\n" "CLASS:PUBLIC\n" "CREATED:20060416T205301Z\n" "LAST-MODIFIED:20060416T205301Z\n" "END:VEVENT\n" "END:VCALENDAR\n"; CPPUNIT_ASSERT_NO_THROW(luid = addItem(source, notimezone)); // fake VTIMEZONE where daylight saving starts on first Sunday in March string fake_march = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VTIMEZONE\n" "TZID:FAKE\n" "BEGIN:STANDARD\n" "TZOFFSETFROM:-0400\n" "TZOFFSETTO:-0500\n" "TZNAME:EST MARCH\n" "DTSTART:19701025T020000\n" "RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10\n" "END:STANDARD\n" "BEGIN:DAYLIGHT\n" "TZOFFSETFROM:-0500\n" "TZOFFSETTO:-0400\n" "TZNAME:EDT\n" "DTSTART:19700405T020000\n" "RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=3\n" "END:DAYLIGHT\n" "END:VTIMEZONE\n" "BEGIN:VEVENT\n" "UID:artificial-4\n" "DTSTAMP:20060416T205224Z\n" "DTSTART;TZID=FAKE:20060406T140000\n" "DTEND;TZID=FAKE:20060406T143000\n" "TRANSP:OPAQUE\n" "SEQUENCE:2\n" "SUMMARY:fake timezone with daylight starting in March\n" "CLASS:PUBLIC\n" "CREATED:20060416T205301Z\n" "LAST-MODIFIED:20060416T205301Z\n" "END:VEVENT\n" "END:VCALENDAR\n"; CPPUNIT_ASSERT_NO_THROW(luid = addItem(source, fake_march)); string fake_may = fake_march; boost::replace_first(fake_may, "UID:artificial-4", "UID:artificial-5"); boost::replace_first(fake_may, "RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=3", "RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=5"); boost::replace_first(fake_may, "starting in March", "starting in May"); boost::replace_first(fake_may, "TZNAME:EST MARCH", "TZNAME:EST MAY"); CPPUNIT_ASSERT_NO_THROW(luid = addItem(source, fake_may)); // insert again, shouldn't re-add timezone CPPUNIT_ASSERT_NO_THROW(luid = addItem(source, fake_may)); } }; SYNCEVOLUTION_TEST_SUITE_REGISTRATION(EvolutionCalendarTest); #endif // ENABLE_UNIT_TESTS namespace { #if 0 } #endif static class iCal20Test : public RegisterSyncSourceTest { public: iCal20Test() : RegisterSyncSourceTest("eds_event", "eds_event") {} virtual void updateConfig(ClientTestConfig &config) const { config.m_type = "evolution-calendar"; } } iCal20Test; static class iTodo20Test : public RegisterSyncSourceTest { public: iTodo20Test() : RegisterSyncSourceTest("eds_task", "eds_task") {} virtual void updateConfig(ClientTestConfig &config) const { config.m_type = "evolution-tasks"; } } iTodo20Test; static class SuperTest : public RegisterSyncSourceTest { public: SuperTest() : RegisterSyncSourceTest("calendar+todo", "calendar+todo") {} virtual void updateConfig(ClientTestConfig &config) const { config.m_type = "virtual:text/x-vcalendar"; config.m_subConfigs = "eds_event,eds_task"; } } superTest; static class MemoTest : public RegisterSyncSourceTest { public: MemoTest() : RegisterSyncSourceTest("eds_memo", "eds_memo") {} virtual void updateConfig(ClientTestConfig &config) const { config.m_type = "Evolution Memos"; // use an alias here to test that } } memoTest; } #endif // ENABLE_ECAL SE_END_CXX syncevolution_1.4/src/backends/evolution/EvolutionContactSource.cpp000066400000000000000000001275221230021373600261410ustar00rootroot00000000000000/* * Copyright (C) 2005-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include using namespace std; #include "config.h" #include "EvolutionSyncSource.h" #include #ifdef ENABLE_EBOOK #include #include "EvolutionContactSource.h" #include #include #ifdef USE_EDS_CLIENT #include #endif #include #include #include #include #include #include SE_GLIB_TYPE(EBookQuery, e_book_query) SE_BEGIN_CXX inline bool IsContactNotFound(const GError *gerror) { return gerror && #ifdef USE_EDS_CLIENT gerror->domain == E_BOOK_CLIENT_ERROR && gerror->code == E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND #else gerror->domain == E_BOOK_ERROR && gerror->code == E_BOOK_ERROR_CONTACT_NOT_FOUND #endif ; } const EvolutionContactSource::extensions EvolutionContactSource::m_vcardExtensions; const EvolutionContactSource::unique EvolutionContactSource::m_uniqueProperties; EvolutionContactSource::EvolutionContactSource(const SyncSourceParams ¶ms, EVCardFormat vcardFormat) : EvolutionSyncSource(params), m_vcardFormat(vcardFormat) { #ifdef USE_EDS_CLIENT m_cacheMisses = m_cacheStalls = m_contactReads = m_contactsFromDB = m_contactQueries = 0; m_readAheadOrder = READ_NONE; const char *mode = getEnv("SYNCEVOLUTION_EDS_ACCESS_MODE", ""); m_accessMode = boost::iequals(mode, "synchronous") ? SYNCHRONOUS : boost::iequals(mode, "batched") ? BATCHED : DEFAULT; #endif SyncSourceLogging::init(InitList("N_FIRST") + "N_MIDDLE" + "N_LAST", " ", m_operations); } EvolutionContactSource::~EvolutionContactSource() { // Don't close while we have pending operations. They might // complete after we got destroyed, causing them to use an invalid // "this" pointer. We also don't know how well EDS copes with // closing the address book while it has pending operations - EDS // maintainer mcrha wasn't sure. // // TODO: cancel the operations(). finishItemChanges(); close(); #ifdef USE_EDS_CLIENT // logCacheStats(Logger::DEBUG); #endif } #ifdef USE_EDS_CLIENT static EClient *newEBookClient(ESource *source, GError **gerror) { return E_CLIENT(e_book_client_new(source, gerror)); } #endif EvolutionSyncSource::Databases EvolutionContactSource::getDatabases() { Databases result; #ifdef USE_EDS_CLIENT getDatabasesFromRegistry(result, E_SOURCE_EXTENSION_ADDRESS_BOOK, e_source_registry_ref_default_address_book); #else ESourceList *sources = NULL; if (!e_book_get_addressbooks(&sources, NULL)) { SyncContext::throwError("unable to access address books"); } Databases secondary; for (GSList *g = e_source_list_peek_groups (sources); g; g = g->next) { ESourceGroup *group = E_SOURCE_GROUP (g->data); for (GSList *s = e_source_group_peek_sources (group); s; s = s->next) { ESource *source = E_SOURCE (s->data); eptr uri(e_source_get_uri(source)); string uristr; if (uri) { uristr = uri.get(); } Database entry(e_source_peek_name(source), uristr, false); if (boost::starts_with(uristr, "couchdb://")) { // Append CouchDB address books at the end of the list, // otherwise preserving the order of address books. // // The reason is Moblin Bugzilla #7877 (aka CouchDB // feature request #479110): the initial release of // evolution-couchdb in Ubuntu 9.10 is unusable because // it does not support the REV property. // // Reordering the entries ensures that the CouchDB // address book is not used as the default database by // SyncEvolution, as it happened in Ubuntu 9.10. // Users can still pick it intentionally via // "evolutionsource". secondary.push_back(entry); } else { result.push_back(entry); } } } result.insert(result.end(), secondary.begin(), secondary.end()); // No results? Try system address book (workaround for embedded Evolution Dataserver). if (!result.size()) { eptr book; GErrorCXX gerror; const char *name; name = "<>"; book = e_book_new_system_addressbook (gerror); gerror.clear(); if (!book) { name = "<>"; book = e_book_new_default_addressbook (gerror); } if (book) { const char *uri = e_book_get_uri (book); result.push_back(Database(name, uri, true)); } } else { // the first DB found is the default result[0].m_isDefault = true; } #endif return result; } void EvolutionContactSource::open() { #ifdef USE_EDS_CLIENT m_addressbook.reset(E_BOOK_CLIENT(openESource(E_SOURCE_EXTENSION_ADDRESS_BOOK, e_source_registry_ref_builtin_address_book, newEBookClient).get())); #else GErrorCXX gerror; bool created = false; bool onlyIfExists = false; // always try to create address book, because even if there is // a source there's no guarantee that the actual database was // created already; the original logic below for only setting // this when explicitly requesting a new address book // therefore failed in some cases ESourceList *tmp; if (!e_book_get_addressbooks(&tmp, gerror)) { throwError("unable to access address books", gerror); } ESourceListCXX sources(tmp, TRANSFER_REF); string id = getDatabaseID(); ESource *source = findSource(sources, id); if (!source) { // might have been special "<>" or "<>", try that and // creating address book from file:// URI before giving up if (id.empty() || id == "<>") { m_addressbook.set( e_book_new_system_addressbook (gerror), "system address book" ); } else if (id.empty() || id == "<>") { m_addressbook.set( e_book_new_default_addressbook (gerror), "default address book" ); } else if (boost::starts_with(id, "file://")) { m_addressbook.set(e_book_new_from_uri(id.c_str(), gerror), "creating address book"); } else { throwError(string(getName()) + ": no such address book: '" + id + "'"); } created = true; } else { m_addressbook.set( e_book_new( source, gerror ), "address book" ); } if (!e_book_open( m_addressbook, onlyIfExists, gerror) ) { if (created) { // opening newly created address books often fails, try again once more sleep(5); if (!e_book_open(m_addressbook, onlyIfExists, gerror)) { throwError("opening address book", gerror); } } else { throwError("opening address book", gerror); } } // users are not expected to configure an authentication method, // so pick one automatically if the user indicated that he wants authentication // by setting user or password UserIdentity identity = getUser(); InitStateString passwd = getPassword(); if (identity.wasSet() || passwd.wasSet()) { GList *authmethod; if (!e_book_get_supported_auth_methods(m_addressbook, &authmethod, gerror)) { throwError("getting authentication methods", gerror); } while (authmethod) { // map identity + password to plain username/password credentials Credentials cred = IdentityProviderCredentials(identity, passwd); const char *method = (const char *)authmethod->data; SE_LOG_DEBUG(getDisplayName(), "trying authentication method \"%s\", user %s, password %s", method, identity.wasSet() ? "configured" : "not configured", passwd.wasSet() ? "configured" : "not configured"); if (e_book_authenticate_user(m_addressbook, cred.m_username.c_str(), cred.m_password.c_str(), method, gerror)) { SE_LOG_DEBUG(getDisplayName(), "authentication succeeded"); break; } else { SE_LOG_ERROR(getDisplayName(), "authentication failed: %s", gerror->message); } authmethod = authmethod->next; } } g_signal_connect_after(m_addressbook, "backend-died", G_CALLBACK(SyncContext::fatalError), (void *)"Evolution Data Server has died unexpectedly, contacts no longer available."); #endif } bool EvolutionContactSource::isEmpty() { // TODO: add more efficient implementation which does not // depend on actually pulling all items from EDS RevisionMap_t revisions; listAllItems(revisions); return revisions.empty(); } #ifdef USE_EDS_CLIENT class EBookClientViewSyncHandler { public: typedef boost::function Process_t; EBookClientViewSyncHandler(const EBookClientViewCXX &view, const Process_t &process) : m_process(process), m_view(view) {} bool process(GErrorCXX &gerror) { // Listen for view signals m_view.connectSignal("objects-added", boost::bind(m_process, _2)); m_view.connectSignal("complete", boost::bind(&EBookClientViewSyncHandler::completed, this, _2)); // Start the view e_book_client_view_start (m_view, m_error); if (m_error) { std::swap(gerror, m_error); return false; } // Async -> Sync m_loop.run(); e_book_client_view_stop (m_view, NULL); if (m_error) { std::swap(gerror, m_error); return false; } else { return true; } } void completed(const GError *error) { m_error = error; m_loop.quit(); } public: // Event loop for Async -> Sync EvolutionAsync m_loop; private: // Process list callback boost::function m_process; // View watched EBookClientViewCXX m_view; // Possible error while watching the view GErrorCXX m_error; }; static void list_revisions(const GSList *contacts, EvolutionContactSource::RevisionMap_t *revisions) { const GSList *l; for (l = contacts; l; l = l->next) { EContact *contact = E_CONTACT(l->data); if (!contact) { SE_THROW("contact entry without data"); } pair revmapping; const char *uid = (const char *)e_contact_get_const(contact, E_CONTACT_UID); if (!uid || !uid[0]) { SE_THROW("contact entry without UID"); } revmapping.first = uid; const char *rev = (const char *)e_contact_get_const(contact, E_CONTACT_REV); if (!rev || !rev[0]) { SE_THROW(string("contact entry without REV: ") + revmapping.first); } revmapping.second = rev; revisions->insert(revmapping); } } #endif void EvolutionContactSource::listAllItems(RevisionMap_t &revisions) { #ifdef USE_EDS_CLIENT GErrorCXX gerror; EBookClientView *view; EBookQueryCXX allItemsQuery(e_book_query_any_field_contains(""), TRANSFER_REF); PlainGStr buffer(e_book_query_to_string (allItemsQuery.get())); const char *sexp = getenv("SYNCEVOLUTION_EBOOK_QUERY"); if (sexp) { SE_LOG_INFO(NULL, "restricting item set to items matching %s", sexp); } else { sexp = buffer; } if (!e_book_client_get_view_sync(m_addressbook, sexp, &view, NULL, gerror)) { throwError( "getting the view" , gerror); } EBookClientViewCXX viewPtr = EBookClientViewCXX::steal(view); // Optimization: set fields_of_interest (UID / REV) GListCXX interesting_field_list; interesting_field_list.push_back(e_contact_field_name (E_CONTACT_UID)); interesting_field_list.push_back(e_contact_field_name (E_CONTACT_REV)); e_book_client_view_set_fields_of_interest (viewPtr, interesting_field_list, gerror); if (gerror) { SE_LOG_ERROR(getDisplayName(), "e_book_client_view_set_fields_of_interest: %s", (const char*)gerror); gerror.clear(); } EBookClientViewSyncHandler handler(viewPtr, boost::bind(list_revisions, _1, &revisions)); if (!handler.process(gerror)) { throwError("watching view", gerror); } #else GErrorCXX gerror; eptr allItemsQuery(e_book_query_any_field_contains(""), "query"); GList *nextItem; if (!e_book_get_contacts(m_addressbook, allItemsQuery, &nextItem, gerror)) { throwError( "reading all items", gerror ); } eptr listptr(nextItem); while (nextItem) { EContact *contact = E_CONTACT(nextItem->data); if (!contact) { throwError("contact entry without data"); } pair revmapping; const char *uid = (const char *)e_contact_get_const(contact, E_CONTACT_UID); if (!uid || !uid[0]) { throwError("contact entry without UID"); } revmapping.first = uid; const char *rev = (const char *)e_contact_get_const(contact, E_CONTACT_REV); if (!rev || !rev[0]) { throwError(string("contact entry without REV: ") + revmapping.first); } revmapping.second = rev; revisions.insert(revmapping); nextItem = nextItem->next; } #endif } void EvolutionContactSource::close() { m_addressbook.reset(); } string EvolutionContactSource::getRevision(const string &luid) { if (!needChanges()) { return ""; } EContact *contact; GErrorCXX gerror; if ( #ifdef USE_EDS_CLIENT !e_book_client_get_contact_sync(m_addressbook, luid.c_str(), &contact, NULL, gerror) #else !e_book_get_contact(m_addressbook, luid.c_str(), &contact, gerror) #endif ) { if (IsContactNotFound(gerror)) { throwError(STATUS_NOT_FOUND, string("retrieving item: ") + luid); } else { throwError(string("reading contact ") + luid, gerror); } } eptr contactptr(contact, "contact"); const char *rev = (const char *)e_contact_get_const(contact, E_CONTACT_REV); if (!rev || !rev[0]) { throwError(string("contact entry without REV: ") + luid); } return rev; } #ifdef USE_EDS_CLIENT class ContactCache : public std::map { public: /** Asynchronous method call still pending. */ bool m_running; /** The last luid requested in this query. Needed to start with the next contact after it. */ std::string m_lastLUID; /** Result of batch read. Any error here means that the call failed completely. */ GErrorCXX m_gerror; /** A debug logging name for this query. */ std::string m_name; }; void EvolutionContactSource::setReadAheadOrder(ReadAheadOrder order, const ReadAheadItems &luids) { SE_LOG_DEBUG(getDisplayName(), "reading: set order '%s', %ld luids", order == READ_NONE ? "none" : order == READ_ALL_ITEMS ? "all" : order == READ_CHANGED_ITEMS ? "changed" : order == READ_SELECTED_ITEMS ? "selected" : "???", (long)luids.size()); m_readAheadOrder = order; m_nextLUIDs = luids; // Be conservative and throw away all cached data. Not doing so // can confuse our "cache miss" counting, for example when it uses // a cache where some entries have been removed in // invalidateCachedContact() and then mistakes the gaps for cache // misses. // // Another reason is that we want to use fairly recent data (in // case of concurrent changes in the DB, which currently is not // detected by the cache). m_contactCache.reset(); m_contactCacheNext.reset(); } void EvolutionContactSource::getReadAheadOrder(ReadAheadOrder &order, ReadAheadItems &luids) { order = m_readAheadOrder; luids = m_nextLUIDs; } void EvolutionContactSource::checkCacheForError(boost::shared_ptr &cache) { if (cache->m_gerror) { GErrorCXX gerror; std::swap(gerror, cache->m_gerror); cache.reset(); throwError(StringPrintf("reading contacts %s", cache->m_name.c_str()), gerror); } } void EvolutionContactSource::invalidateCachedContact(const std::string &luid) { invalidateCachedContact(m_contactCache, luid); invalidateCachedContact(m_contactCacheNext, luid); } void EvolutionContactSource::invalidateCachedContact(boost::shared_ptr &cache, const std::string &luid) { if (cache) { ContactCache::iterator it = cache->find(luid); if (it != cache->end()) { SE_LOG_DEBUG(getDisplayName(), "reading: remove contact %s from cache because of remove or update", luid.c_str()); // If we happen to read that contact (unlikely), it'll be // considered a cache miss. That's okay. Together with // counting cache misses it'll help us avoid using // read-ahead when the Synthesis engine is randomly // accessing contacts. cache->erase(it); } } } bool EvolutionContactSource::getContact(const string &luid, EContact **contact, GErrorCXX &gerror) { SE_LOG_DEBUG(getDisplayName(), "reading: getting contact %s", luid.c_str()); // Use switch and let compiler tell us when we don't cover a case. ReadAheadOrder order #ifndef __clang_analyzer__ // scan-build would complain: value stored to 'order' during // its initialization is never read. But we need to keep it // otherwise, to avoid: 'order' may be used uninitialized in // this function [-Werror=maybe-uninitialized] = m_readAheadOrder #endif ; switch (m_accessMode) { case SYNCHRONOUS: case DEFAULT: order = READ_NONE; break; case BATCHED: order = m_readAheadOrder; break; }; m_contactReads++; if (order == READ_NONE) { m_contactsFromDB++; m_contactQueries++; return e_book_client_get_contact_sync(m_addressbook, luid.c_str(), contact, NULL, gerror); } else { return getContactFromCache(luid, contact, gerror); } } bool EvolutionContactSource::getContactFromCache(const string &luid, EContact **contact, GErrorCXX &gerror) { *contact = NULL; // Use ContactCache. if (m_contactCache) { SE_LOG_DEBUG(getDisplayName(), "reading: active cache %s", m_contactCache->m_name.c_str()); // Ran into a problem? checkCacheForError(m_contactCache); // Does the cache cover our item? ContactCache::const_iterator it = m_contactCache->find(luid); if (it == m_contactCache->end()) { if (m_contactCacheNext) { SE_LOG_DEBUG(getDisplayName(), "reading: not in cache, try cache %s", m_contactCacheNext->m_name.c_str()); // Throw away old cache, try with next one. This is not // a cache miss (yet). m_contactCache = m_contactCacheNext; m_contactCacheNext.reset(); return getContactFromCache(luid, contact, gerror); } else { SE_LOG_DEBUG(getDisplayName(), "reading: not in cache, nothing pending -> start reading"); // Throw away cache, start new read below. m_contactCache.reset(); } } else { SE_LOG_DEBUG(getDisplayName(), "reading: in %s cache", m_contactCache->m_running ? "running" : "loaded"); if (m_contactCache->m_running) { m_cacheStalls++; GRunWhile(boost::lambda::var(m_contactCache->m_running)); } // Problem? checkCacheForError(m_contactCache); SE_LOG_DEBUG(getDisplayName(), "reading: in cache, %s", it->second ? "available" : "not found"); if (it->second) { // Got it. *contact = it->second.ref(); } else { // Delay throwing error. We need to go through the read-ahead code below. gerror.take(g_error_new(E_BOOK_CLIENT_ERROR, E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND, "uid %s not found in batch read", luid.c_str())); } } } // No current cache? In that case we must read and block. if (!m_contactCache) { m_contactCache = startReading(luid, START); // Call code above recursively, which will block. return getContactFromCache(luid, contact, gerror); } // Can we read ahead? if (!m_contactCacheNext && !m_contactCache->m_running) { m_contactCacheNext = startReading(m_contactCache->m_lastLUID, CONTINUE); } // Everything is okay when we get here. Either we have the contact or // it wasn't in the database. SE_LOG_DEBUG(getDisplayName(), "reading: read %s: %s", luid.c_str(), gerror ? gerror->message : "<>"); logCacheStats(Logger::DEBUG); return !gerror; } static int MaxBatchSize() { int maxBatchSize = atoi(getEnv("SYNCEVOLUTION_EDS_BATCH_SIZE", "50")); if (maxBatchSize < 1) { maxBatchSize = 1; } return maxBatchSize; } boost::shared_ptr EvolutionContactSource::startReading(const std::string &luid, ReadingMode mode) { SE_LOG_DEBUG(getDisplayName(), "reading: %s contact %s", mode == START ? "must read" : mode == CONTINUE ? "continue after" : "???", luid.c_str()); static int maxBatchSize = MaxBatchSize(); std::vector uidQueries; uidQueries.resize(maxBatchSize); std::vector uids; uids.resize(maxBatchSize); int size = 0; bool found = false; switch (m_readAheadOrder) { case READ_ALL_ITEMS: case READ_CHANGED_ITEMS: { const Items_t &items = getAllItems(); const Items_t &newItems = getNewItems(); const Items_t &updatedItems = getUpdatedItems(); Items_t::const_iterator it = items.find(luid); // Always read the requested item, even if not found in item list? if (mode == START) { uids[0] = &luid; size++; } // luid is dealt with, either way. if (it != items.end()) { // Check that it is a valid candidate for caching, else // we have a cache miss prediction. if (m_readAheadOrder == READ_ALL_ITEMS || newItems.find(luid) != newItems.end() || updatedItems.find(luid) != updatedItems.end()) { found = true; } ++it; } while (size < maxBatchSize && it != items.end()) { const std::string &luid = *it; if (m_readAheadOrder == READ_ALL_ITEMS || newItems.find(luid) != newItems.end() || updatedItems.find(luid) != updatedItems.end()) { uids[size] = &luid; ++size; } ++it; } break; } case READ_SELECTED_ITEMS: { ReadAheadItems::const_iterator it = boost::find(std::make_pair(m_nextLUIDs.begin(), m_nextLUIDs.end()), luid); // Always read the requested item, even if not found in item list? if (mode == START) { uids[0] = &luid; size++; } // luid is dealt with, either way. if (it != m_nextLUIDs.end()) { found = true; ++it; } while (size < maxBatchSize && it != m_nextLUIDs.end()) { uids[size] = &*it; ++size; ++it; } break; } case READ_NONE: // May be reached when read-ahead was turned off while // preparing for it. if (mode == START) { uids[0] = &luid; size++; } break; } if (m_readAheadOrder != READ_NONE && mode == START && !found) { // The requested contact was not on our list. Consider this // a cache miss (or rather, cache prediction failure) and turn // of the read-ahead. m_cacheMisses++; SE_LOG_DEBUG(getDisplayName(), "reading: disable read-ahead due to cache miss"); m_readAheadOrder = READ_NONE; } boost::shared_ptr cache; if (size) { // Prepare parameter for EDS C call. Ownership of query instances is in uidQueries array. boost::scoped_array queries(new EBookQuery *[size]); for (int i = 0; i < size; i++) { // This shouldn't compile because we don't specify how ownership is handled. // The reset() method always bumps the ref count, which is not what we want here! // uidQueries[i].reset(e_book_query_field_test(E_CONTACT_UID, E_BOOK_QUERY_IS, it->c_str())); // // Take over ownership. uidQueries[i] = EBookQueryCXX::steal(e_book_query_field_test(E_CONTACT_UID, E_BOOK_QUERY_IS, uids[i]->c_str())); queries[i] = uidQueries[i].get(); } EBookQueryCXX query(e_book_query_or(size, queries.get(), false), TRANSFER_REF); PlainGStr sexp(e_book_query_to_string(query.get())); cache.reset(new ContactCache); cache->m_running = true; cache->m_name = StringPrintf("%s-%s (%d)", uids[0]->c_str(), uids[size - 1]->c_str(), size); cache->m_lastLUID = *uids[size - 1]; BOOST_FOREACH (const std::string *uid, std::make_pair(uids.begin(), uids.begin() + size)) { (*cache)[*uid] = EContactCXX(); } m_contactsFromDB += size; m_contactQueries++; SYNCEVO_GLIB_CALL_ASYNC(e_book_client_get_contacts, boost::bind(&EvolutionContactSource::completedRead, this, boost::weak_ptr(cache), _1, _2, _3), m_addressbook, sexp, NULL); SE_LOG_DEBUG(getDisplayName(), "reading: started contact read %s", cache->m_name.c_str()); } return cache; } typedef GListCXX< EContact, GSList, GObjectDestructor > ContactListCXX; void EvolutionContactSource::completedRead(const boost::weak_ptr &cachePtr, gboolean success, GSList *contactsPtr, const GError *gerror) throw() { try { ContactListCXX contacts(contactsPtr); // transfers ownership boost::shared_ptr cache = cachePtr.lock(); if (!cache) { SE_LOG_DEBUG(getDisplayName(), "reading: contact read finished, results no longer needed: %s", gerror ? gerror->message : "<>"); return; } SE_LOG_DEBUG(getDisplayName(), "reading: contact read %s finished: %s", cache->m_name.c_str(), gerror ? gerror->message : "<>"); if (success) { BOOST_FOREACH (EContact *contact, contacts) { const char *uid = (const char *)e_contact_get_const(contact, E_CONTACT_UID); SE_LOG_DEBUG(getDisplayName(), "reading: contact read %s got %s", cache->m_name.c_str(), uid); (*cache)[uid] = EContactCXX(contact, ADD_REF); } } else { cache->m_gerror = gerror; } cache->m_running = false; } catch (...) { Exception::handle(HANDLE_EXCEPTION_FATAL); } } void EvolutionContactSource::logCacheStats(Logger::Level level) { SE_LOG(getDisplayName(), level, "requested %d, retrieved %d from DB in %d queries, misses %d/%d (%d%%), stalls %d", m_contactReads, m_contactsFromDB, m_contactQueries, m_cacheMisses, m_contactReads, m_contactReads ? m_cacheMisses * 100 / m_contactReads : 0, m_cacheStalls); } #endif void EvolutionContactSource::readItem(const string &luid, std::string &item, bool raw) { EContact *contact; GErrorCXX gerror; if ( #ifdef USE_EDS_CLIENT !getContact(luid, &contact, gerror) #else !e_book_get_contact(m_addressbook, luid.c_str(), &contact, gerror) #endif ) { if (IsContactNotFound(gerror)) { throwError(STATUS_NOT_FOUND, string("reading contact: ") + luid); } else { throwError(string("reading contact ") + luid, gerror); } } eptr contactptr(contact, "contact"); // Inline PHOTO data if exporting, leave VALUE=uri references unchanged // when processing inside engine (will be inlined by engine as needed). // The function for doing the inlining was added in EDS 3.4. // In compatibility mode, we must check the function pointer for non-NULL. // In direct call mode, the existence check is done by configure. if (raw #ifdef EVOLUTION_COMPATIBILITY && e_contact_inline_local_photos #endif ) { #if defined(EVOLUTION_COMPATIBILITY) || defined(HAVE_E_CONTACT_INLINE_LOCAL_PHOTOS) if (!e_contact_inline_local_photos(contactptr, gerror)) { throwError(string("inlining PHOTO file data in ") + luid, gerror); } #endif } eptr vcardstr(e_vcard_to_string(&contactptr->parent, EVC_FORMAT_VCARD_30)); if (!vcardstr) { throwError(string("failure extracting contact from Evolution " ) + luid); } item = vcardstr.get(); } #ifdef USE_EDS_CLIENT TrackingSyncSource::InsertItemResult EvolutionContactSource::checkBatchedInsert(const boost::shared_ptr &pending) { SE_LOG_DEBUG(pending->m_name, "checking operation: %s", pending->m_status == MODIFYING ? "waiting" : "inserted"); if (pending->m_status == MODIFYING) { return TrackingSyncSource::InsertItemResult(boost::bind(&EvolutionContactSource::checkBatchedInsert, this, pending)); } if (pending->m_gerror) { pending->m_gerror.throwError(pending->m_name); } string newrev = getRevision(pending->m_uid); return TrackingSyncSource::InsertItemResult(pending->m_uid, newrev, ITEM_OKAY); } void EvolutionContactSource::completedAdd(const boost::shared_ptr &batched, gboolean success, GSList *uids, const GError *gerror) throw() { try { // The destructor ensures that the pending operations complete // before destructing the instance, so our "this" pointer is // always valid here. SE_LOG_DEBUG(getDisplayName(), "batch add of %d contacts completed", (int)batched->size()); m_numRunningOperations--; PendingContainer_t::iterator it = (*batched).begin(); GSList *uid = uids; while (it != (*batched).end() && uid) { SE_LOG_DEBUG((*it)->m_name, "completed: %s", success ? "<>" : gerror ? gerror->message : "<>"); if (success) { (*it)->m_uid = static_cast(uid->data); // Get revision when engine checks the item. (*it)->m_status = REVISION; } else { (*it)->m_status = DONE; (*it)->m_gerror = gerror; } ++it; uid = uid->next; } while (it != (*batched).end()) { // Should never happen. SE_LOG_DEBUG((*it)->m_name, "completed: missing uid?!"); (*it)->m_status = DONE; ++it; } g_slist_free_full(uids, g_free); } catch (...) { Exception::handle(HANDLE_EXCEPTION_FATAL); } } void EvolutionContactSource::completedUpdate(const boost::shared_ptr &batched, gboolean success, const GError *gerror) throw() { try { SE_LOG_DEBUG(getDisplayName(), "batch update of %d contacts completed", (int)batched->size()); m_numRunningOperations--; PendingContainer_t::iterator it = (*batched).begin(); while (it != (*batched).end()) { SE_LOG_DEBUG((*it)->m_name, "completed: %s", success ? "<>" : gerror ? gerror->message : "<>"); if (success) { (*it)->m_status = REVISION; } else { (*it)->m_status = DONE; (*it)->m_gerror = gerror; } ++it; } } catch (...) { Exception::handle(HANDLE_EXCEPTION_FATAL); } } void EvolutionContactSource::flushItemChanges() { if (!m_batchedAdd.empty()) { SE_LOG_DEBUG(getDisplayName(), "batch add of %d contacts starting", (int)m_batchedAdd.size()); m_numRunningOperations++; GListCXX contacts; // Iterate backwards, push to front (cheaper for single-linked list) -> same order in the end. BOOST_REVERSE_FOREACH (const boost::shared_ptr &pending, m_batchedAdd) { contacts.push_front(pending->m_contact.get()); } // Transfer content without copying and then copy only the shared pointer. boost::shared_ptr batched(new PendingContainer_t); std::swap(*batched, m_batchedAdd); SYNCEVO_GLIB_CALL_ASYNC(e_book_client_add_contacts, boost::bind(&EvolutionContactSource::completedAdd, this, batched, _1, _2, _3), m_addressbook, contacts, NULL); } if (!m_batchedUpdate.empty()) { SE_LOG_DEBUG(getDisplayName(), "batch update of %d contacts starting", (int)m_batchedUpdate.size()); m_numRunningOperations++; GListCXX contacts; BOOST_REVERSE_FOREACH (const boost::shared_ptr &pending, m_batchedUpdate) { contacts.push_front(pending->m_contact.get()); } boost::shared_ptr batched(new PendingContainer_t); std::swap(*batched, m_batchedUpdate); SYNCEVO_GLIB_CALL_ASYNC(e_book_client_modify_contacts, boost::bind(&EvolutionContactSource::completedUpdate, this, batched, _1, _2), m_addressbook, contacts, NULL); } } void EvolutionContactSource::finishItemChanges() { if (m_numRunningOperations) { SE_LOG_DEBUG(getDisplayName(), "waiting for %d pending operations to complete", m_numRunningOperations.get()); while (m_numRunningOperations) { g_main_context_iteration(NULL, true); } SE_LOG_DEBUG(getDisplayName(), "pending operations completed"); } } #endif TrackingSyncSource::InsertItemResult EvolutionContactSource::insertItem(const string &uid, const std::string &item, bool raw) { EContactCXX contact(e_contact_new_from_vcard(item.c_str()), TRANSFER_REF); if (contact) { e_contact_set(contact, E_CONTACT_UID, uid.empty() ? NULL : const_cast(uid.c_str())); GErrorCXX gerror; #ifdef USE_EDS_CLIENT invalidateCachedContact(uid); switch (m_accessMode) { case SYNCHRONOUS: case DEFAULT: if (uid.empty()) { gchar* newuid; if (!e_book_client_add_contact_sync(m_addressbook, contact, &newuid, NULL, gerror)) { throwError("add new contact", gerror); } PlainGStr newuidPtr(newuid); string newrev = getRevision(newuid); return InsertItemResult(newuid, newrev, ITEM_OKAY); } else { if (!e_book_client_modify_contact_sync(m_addressbook, contact, NULL, gerror)) { throwError("updating contact "+ uid, gerror); } string newrev = getRevision(uid); return InsertItemResult(uid, newrev, ITEM_OKAY); } break; case BATCHED: std::string name = StringPrintf("%s: %s operation #%d", getDisplayName().c_str(), uid.empty() ? "add" : ("insert " + uid).c_str(), m_asyncOpCounter++); SE_LOG_DEBUG(name, "queueing for batched %s", uid.empty() ? "add" : "update"); boost::shared_ptr pending(new Pending); pending->m_name = name; pending->m_contact = contact; pending->m_uid = uid; if (uid.empty()) { m_batchedAdd.push_back(pending); } else { m_batchedUpdate.push_back(pending); } // SyncSource is going to live longer than Synthesis // engine, so using "this" is safe here. return InsertItemResult(boost::bind(&EvolutionContactSource::checkBatchedInsert, this, pending)); break; } #else if (uid.empty() ? e_book_add_contact(m_addressbook, contact, gerror) : e_book_commit_contact(m_addressbook, contact, gerror)) { const char *newuid = (const char *)e_contact_get_const(contact, E_CONTACT_UID); if (!newuid) { throwError("no UID for contact"); } string newrev = getRevision(newuid); return InsertItemResult(newuid, newrev, ITEM_OKAY); } else { throwError(uid.empty() ? "storing new contact" : string("updating contact ") + uid, gerror); } #endif } else { throwError(string("failure parsing vcard " ) + item); } // not reached! return InsertItemResult("", "", ITEM_OKAY); } void EvolutionContactSource::removeItem(const string &uid) { GErrorCXX gerror; if ( #ifdef USE_EDS_CLIENT (invalidateCachedContact(uid), !e_book_client_remove_contact_by_uid_sync(m_addressbook, uid.c_str(), NULL, gerror)) #else !e_book_remove_contact(m_addressbook, uid.c_str(), gerror) #endif ) { if (IsContactNotFound(gerror)) { throwError(STATUS_NOT_FOUND, string("deleting contact: ") + uid); } else { throwError( string( "deleting contact " ) + uid, gerror); } } } std::string EvolutionContactSource::getDescription(const string &luid) { try { EContact *contact; GErrorCXX gerror; if ( #ifdef USE_EDS_CLIENT !getContact(luid, &contact, gerror) #else !e_book_get_contact(m_addressbook, luid.c_str(), &contact, gerror) #endif ) { throwError(string("reading contact ") + luid, gerror); } eptr contactptr(contact, "contact"); const char *name = (const char *)e_contact_get_const(contact, E_CONTACT_FULL_NAME); if (name) { return name; } const char *fileas = (const char *)e_contact_get_const(contact, E_CONTACT_FILE_AS); if (fileas) { return fileas; } EContactName *names = (EContactName *)e_contact_get(contact, E_CONTACT_NAME); std::list buffer; if (names) { try { if (names->given && names->given[0]) { buffer.push_back(names->given); } if (names->additional && names->additional[0]) { buffer.push_back(names->additional); } if (names->family && names->family[0]) { buffer.push_back(names->family); } } catch (...) { } e_contact_name_free(names); } return boost::join(buffer, " "); } catch (...) { // Instead of failing we log the error and ask // the caller to log the UID. That way transient // errors or errors in the logging code don't // prevent syncs. handleException(); return ""; } } std::string EvolutionContactSource::getMimeType() const { switch( m_vcardFormat ) { case EVC_FORMAT_VCARD_21: return "text/x-vcard"; break; case EVC_FORMAT_VCARD_30: default: return "text/vcard"; break; } } std::string EvolutionContactSource::getMimeVersion() const { switch( m_vcardFormat ) { case EVC_FORMAT_VCARD_21: return "2.1"; break; case EVC_FORMAT_VCARD_30: default: return "3.0"; break; } } SE_END_CXX #endif /* ENABLE_EBOOK */ #ifdef ENABLE_MODULES # include "EvolutionContactSourceRegister.cpp" #endif syncevolution_1.4/src/backends/evolution/EvolutionContactSource.h000066400000000000000000000211631230021373600256000ustar00rootroot00000000000000/* * Copyright (C) 2005-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_EVOLUTIONCONTACTSOURCE #define INCL_EVOLUTIONCONTACTSOURCE #include "config.h" #include "EvolutionSyncSource.h" #include #include #include #ifdef ENABLE_EBOOK #include #include #ifdef USE_EDS_CLIENT SE_GOBJECT_TYPE(EBookClient) SE_GOBJECT_TYPE(EBookClientView) #endif SE_GOBJECT_TYPE(EContact) SE_BEGIN_CXX class ContactCache; /** * Implements access to Evolution address books. */ class EvolutionContactSource : public EvolutionSyncSource, public SyncSourceLogging, private boost::noncopyable { public: EvolutionContactSource(const SyncSourceParams ¶ms, EVCardFormat vcardFormat = EVC_FORMAT_VCARD_30); virtual ~EvolutionContactSource(); // // implementation of SyncSource // virtual Databases getDatabases(); virtual void open(); virtual bool isEmpty(); virtual void close(); virtual std::string getMimeType() const; virtual std::string getMimeVersion() const; protected: // // implementation of TrackingSyncSource callbacks // virtual void listAllItems(RevisionMap_t &revisions); virtual InsertItemResult insertItem(const string &uid, const std::string &item, bool raw); void readItem(const std::string &luid, std::string &item, bool raw); virtual void removeItem(const string &uid); // implementation of SyncSourceLogging callback virtual std::string getDescription(const string &luid); // need to override native format: it is always vCard 3.0 void getSynthesisInfo(SynthesisInfo &info, XMLConfigFragments &fragments) { EvolutionSyncSource::getSynthesisInfo(info, fragments); info.m_profile = "\"vCard\", 2"; info.m_native = "vCard30EDS"; // Replace normal vCard30 and vCard21 types with the // EDS flavors which apply EDS specific transformations *before* // letting the engine process the incoming item. This ensures // that during a slow sync, modified (!) incoming item and // DB item really match. Otherwise the engine compares unmodified // incoming item and modified DB item, finding a mismatch caused // by the transformations, and writes an item which ends up being // identical to the one which is in the DB. boost::replace_all(info.m_datatypes, "vCard30", "vCard30EDS"); boost::replace_all(info.m_datatypes, "vCard21", "vCard21EDS"); // Redundant when the same transformations are already applied to // incoming items. But disabling it does not improve performance much, // so keep it enabled just to be on the safe side. info.m_beforeWriteScript = "$VCARD_BEFOREWRITE_SCRIPT_EVOLUTION;"; info.m_afterReadScript = "$VCARD_AFTERREAD_SCRIPT_EVOLUTION;"; } #ifdef USE_EDS_CLIENT virtual const char *sourceExtension() const { return E_SOURCE_EXTENSION_ADDRESS_BOOK; } virtual ESourceCXX refSystemDB() const { return ESourceCXX(e_source_registry_ref_builtin_address_book(EDSRegistryLoader::getESourceRegistry()), TRANSFER_REF); } #endif private: /** extract REV string for contact, throw error if not found */ std::string getRevision(const std::string &uid); /** valid after open(): the address book that this source references */ #ifdef USE_EDS_CLIENT EBookClientCXX m_addressbook; enum AccessMode { SYNCHRONOUS, BATCHED, DEFAULT } m_accessMode; InitState m_asyncOpCounter; enum AsyncStatus { MODIFYING, /**< insert or update request sent */ REVISION, /**< asked for revision */ DONE /**< finished successfully or with failure, depending on m_gerror */ }; struct Pending { std::string m_name; EContactCXX m_contact; std::string m_uid; std::string m_revision; AsyncStatus m_status; GErrorCXX m_gerror; Pending() : m_status(MODIFYING) {} }; typedef std::list< boost::shared_ptr >PendingContainer_t; /** * Batched "contact add/update" operations. * Delete is not batched because we need per-item status * information - see removeItem(). */ PendingContainer_t m_batchedAdd; PendingContainer_t m_batchedUpdate; InitState m_numRunningOperations; InsertItemResult checkBatchedInsert(const boost::shared_ptr &pending); void completedAdd(const boost::shared_ptr &batched, gboolean success, /* const GStringListFreeCXX &uids */ GSList *uids, const GError *gerror) throw (); void completedUpdate(const boost::shared_ptr &batched, gboolean success, const GError *gerror) throw (); virtual void flushItemChanges(); virtual void finishItemChanges(); // Read-ahead of item data. boost::shared_ptr m_contactCache, m_contactCacheNext; int m_cacheMisses, m_cacheStalls; int m_contactReads; /**< number of readItemAsKey() calls */ int m_contactsFromDB; /**< number of contacts requested from DB (including ones not found) */ int m_contactQueries; /**< total number of e_book_client_get_contacts() calls */ ReadAheadOrder m_readAheadOrder; ReadAheadItems m_nextLUIDs; void checkCacheForError(boost::shared_ptr &cache); void invalidateCachedContact(const std::string &luid); void invalidateCachedContact(boost::shared_ptr &cache, const std::string &luid); bool getContact(const string &luid, EContact **contact, GErrorCXX &gerror); bool getContactFromCache(const string &luid, EContact **contact, GErrorCXX &gerror); enum ReadingMode { START, /**< luid is needed, must be read */ CONTINUE /**< luid is from old request, find next ones */ }; boost::shared_ptr startReading(const std::string &luid, ReadingMode mode); void completedRead(const boost::weak_ptr &cachePtr, gboolean success, GSList *contactsPtr, const GError *gerror) throw(); void logCacheStats(Logger::Level level); // Use the information provided to us to implement read-ahead efficiently. virtual void setReadAheadOrder(ReadAheadOrder order, const ReadAheadItems &luids); virtual void getReadAheadOrder(ReadAheadOrder &order, ReadAheadItems &luids); #else eptr m_addressbook; #endif /** the format of vcards that new items are expected to have */ const EVCardFormat m_vcardFormat; /** * a list of Evolution vcard properties which have to be encoded * as X-SYNCEVOLUTION-* when sending to server in 2.1 and decoded * back when receiving. */ static const class extensions : public set { public: extensions() : prefix("X-SYNCEVOLUTION-") { this->insert("FBURL"); this->insert("CALURI"); } const string prefix; } m_vcardExtensions; /** * a list of properties which SyncEvolution (in contrast to * the server) will only store once in each contact */ static const class unique : public set { public: unique () { insert("X-AIM"); insert("X-GROUPWISE"); insert("X-ICQ"); insert("X-YAHOO"); insert("X-EVOLUTION-ANNIVERSARY"); insert("X-EVOLUTION-ASSISTANT"); insert("X-EVOLUTION-BLOG-URL"); insert("X-EVOLUTION-FILE-AS"); insert("X-EVOLUTION-MANAGER"); insert("X-EVOLUTION-SPOUSE"); insert("X-EVOLUTION-VIDEO-URL"); insert("X-MOZILLA-HTML"); insert("FBURL"); insert("CALURI"); } } m_uniqueProperties; }; SE_END_CXX #endif // ENABLE_EBOOK #endif // INCL_EVOLUTIONCONTACTSOURCE syncevolution_1.4/src/backends/evolution/EvolutionContactSourceRegister.cpp000066400000000000000000000160211230021373600276350ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "EvolutionContactSource.h" #include "test.h" #include SE_BEGIN_CXX static SyncSource *createSource(const SyncSourceParams ¶ms) { SourceType sourceType = SyncSource::getSourceType(params.m_nodes); bool isMe = sourceType.m_backend == "Evolution Address Book"; bool maybeMe = sourceType.m_backend == "addressbook"; bool enabled; EDSAbiWrapperInit(); enabled = EDSAbiHaveEbook && EDSAbiHaveEdataserver; if (isMe || maybeMe) { if (sourceType.m_format == "text/x-vcard") { return #ifdef ENABLE_EBOOK enabled ? new EvolutionContactSource(params, EVC_FORMAT_VCARD_21) : #endif isMe ? RegisterSyncSource::InactiveSource(params) : NULL; } else if (sourceType.m_format == "" || sourceType.m_format == "text/vcard") { return #ifdef ENABLE_EBOOK enabled ? new EvolutionContactSource(params, EVC_FORMAT_VCARD_30) : #endif isMe ? RegisterSyncSource::InactiveSource(params) : NULL; } } return NULL; } static RegisterSyncSource registerMe("Evolution Address Book", #ifdef ENABLE_EBOOK true, #else false, #endif createSource, "Evolution Address Book = Evolution Contacts = addressbook = contacts = evolution-contacts\n" " vCard 2.1 = text/x-vcard\n" " vCard 3.0 (default) = text/vcard\n" " The later is the internal format of Evolution and preferred with\n" " servers that support it.", Values() + (Aliases("Evolution Address Book") + "Evolution Contacts" + "evolution-contacts")); #ifdef ENABLE_EBOOK #ifdef ENABLE_UNIT_TESTS class EvolutionContactTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(EvolutionContactTest); CPPUNIT_TEST(testInstantiate); CPPUNIT_TEST(testImport); CPPUNIT_TEST_SUITE_END(); protected: void testInstantiate() { boost::shared_ptr source; source.reset(SyncSource::createTestingSource("addressbook", "addressbook", true)); source.reset(SyncSource::createTestingSource("addressbook", "contacts", true)); source.reset(SyncSource::createTestingSource("addressbook", "evolution-contacts", true)); source.reset(SyncSource::createTestingSource("addressbook", "Evolution Contacts", true)); source.reset(SyncSource::createTestingSource("addressbook", "Evolution Address Book:text/x-vcard", true)); source.reset(SyncSource::createTestingSource("addressbook", "Evolution Address Book:text/vcard", true)); } /** * Tests parsing of contacts as they might be send by certain servers. * This complements the actual testing with real servers and might cover * cases not occurring with servers that are actively tested against. */ void testImport() { // this only tests that we can instantiate something under the type "addressbook"; // it might not be an EvolutionContactSource boost::shared_ptr source21(dynamic_cast(SyncSource::createTestingSource("evolutioncontactsource21", "evolution-contacts:text/x-vcard", true))); boost::shared_ptr source30(dynamic_cast(SyncSource::createTestingSource("evolutioncontactsource30", "Evolution Address Book:text/vcard", true))); string parsed; #if 0 // TODO: enable testing of incoming items again. Right now preparse() doesn't // do anything and needs to be replaced with Synthesis mechanisms. // SF bug 1796086: sync with EGW: lost or messed up telephones parsed = "BEGIN:VCARD\r\nVERSION:3.0\r\nTEL;CELL:cell\r\nEND:VCARD\r\n"; CPPUNIT_ASSERT_EQUAL(parsed, preparse(*source21, "BEGIN:VCARD\nVERSION:2.1\nTEL;CELL:cell\nEND:VCARD\n", "text/x-vcard")); parsed = "BEGIN:VCARD\r\nVERSION:3.0\r\nTEL;TYPE=CAR:car\r\nEND:VCARD\r\n"; CPPUNIT_ASSERT_EQUAL(parsed, preparse(*source21, "BEGIN:VCARD\nVERSION:2.1\nTEL;TYPE=CAR:car\nEND:VCARD\n", "text/x-vcard")); parsed = "BEGIN:VCARD\r\nVERSION:3.0\r\nTEL;TYPE=HOME:home\r\nEND:VCARD\r\n"; CPPUNIT_ASSERT_EQUAL(parsed, preparse(*source21, "BEGIN:VCARD\nVERSION:2.1\nTEL:home\nEND:VCARD\n", "text/x-vcard")); // TYPE=PARCEL not supported by Evolution, used to represent Evolutions TYPE=OTHER parsed = "BEGIN:VCARD\r\nVERSION:3.0\r\nTEL;TYPE=OTHER:other\r\nEND:VCARD\r\n"; CPPUNIT_ASSERT_EQUAL(parsed, preparse(*source21, "BEGIN:VCARD\nVERSION:2.1\nTEL;TYPE=PARCEL:other\nEND:VCARD\n", "text/x-vcard")); parsed = "BEGIN:VCARD\r\nVERSION:3.0\r\nTEL;TYPE=HOME;TYPE=VOICE:cell\r\nEND:VCARD\r\n"; CPPUNIT_ASSERT_EQUAL(parsed, preparse(*source21, "BEGIN:VCARD\nVERSION:2.1\nTEL;TYPE=HOME,VOICE:cell\nEND:VCARD\n", "text/x-vcard")); #endif } }; SYNCEVOLUTION_TEST_SUITE_REGISTRATION(EvolutionContactTest); #endif // ENABLE_UNIT_TESTS namespace { #if 0 } #endif static class VCard30Test : public RegisterSyncSourceTest { public: VCard30Test() : RegisterSyncSourceTest("eds_contact", "eds_contact") {} virtual void updateConfig(ClientTestConfig &config) const { config.m_type = "evolution-contacts:text/vcard"; config.m_update = config.m_genericUpdate; // this property gets re-added by EDS and thus cannot be removed config.m_essentialProperties.insert("X-EVOLUTION-FILE-AS"); } } vCard30Test; } #endif // ENABLE_EBOOK SE_END_CXX syncevolution_1.4/src/backends/evolution/EvolutionMemoSource.cpp000066400000000000000000000176271230021373600254470ustar00rootroot00000000000000/* * Copyright (C) 2005-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include using namespace std; #include "config.h" #ifdef ENABLE_ECAL #include "EvolutionMemoSource.h" #include #include SE_BEGIN_CXX void EvolutionMemoSource::readItem(const string &luid, std::string &item, bool raw) { if (raw) { EvolutionCalendarSource::readItem(luid, item, false); return; } ItemID id(luid); eptr comp(retrieveItem(id)); icalcomponent *cal = icalcomponent_get_first_component(comp, ICAL_VCALENDAR_COMPONENT); if (!cal) { cal = comp; } icalcomponent *journal = icalcomponent_get_first_component(cal, ICAL_VJOURNAL_COMPONENT); if (!journal) { journal = comp; } icalproperty *summaryprop = icalcomponent_get_first_property(journal, ICAL_SUMMARY_PROPERTY); string summary; if (summaryprop) { const char *summaryptr = icalproperty_get_summary(summaryprop); if (summaryptr) { summary = summaryptr; } } icalproperty *desc = icalcomponent_get_first_property(journal, ICAL_DESCRIPTION_PROPERTY); if (desc) { const char *text = icalproperty_get_description(desc); if (text) { size_t len = strlen(text); bool insertSummary = false; const char *eol; // Check the first line: if it is the same as the summary, // then ignore the summary. Otherwise include the summary // as first line in the text body. At a receiving Evolution // the summary will remain part of the text for compatibility // with other clients which might use the first line as part // of the normal text. eol = strchr(text, '\n'); if (!eol) { eol = text + len; } if (summary.size() && summary.compare(0, summary.size(), text, eol - text)) { insertSummary = true; } // Replace all \n with \r\n: in the worst case the text // becomes twice as long. Also make room for summary. eptr dostext((char *)malloc(len * 2 + 1 + (insertSummary ? summary.size() + 2 : 0))); const char *from = text; char *to = dostext; if (insertSummary) { memcpy(to, summary.c_str(), summary.size()); memcpy(to + summary.size(), "\r\n", 2); to += summary.size() + 2; } eol = strchr(from, '\n'); while (eol) { size_t linelen = eol - from; memcpy(to, from, linelen); to += linelen; from += linelen; to[0] = '\r'; to[1] = '\n'; to += 2; from++; eol = strchr(from, '\n'); } memcpy(to, from, strlen(from) + 1); item = dostext.get(); } } if (item.empty()) { // no description, use summary item = summary; } } EvolutionCalendarSource::InsertItemResult EvolutionMemoSource::insertItem(const string &luid, const std::string &item, bool raw) { if (raw) { return EvolutionCalendarSource::insertItem(luid, item, false); } bool update = !luid.empty(); InsertItemResultState state = ITEM_OKAY; string newluid = luid; string modTime; eptr text; text.set((char *)malloc(item.size() + 1), "copy of item"); memcpy(text, item.c_str(), item.size()); text.get()[item.size()] = 0; // replace all \r\n with \n char *from = text, *to = text; const char *eol = strstr(from, "\r\n"); while (eol) { size_t linelen = eol - from; if (to != from) { memmove(to, from, linelen); } to += linelen; from += linelen; *to = '\n'; to++; from += 2; eol = strstr(from, "\r\n"); } if (to != from) { memmove(to, from, strlen(from) + 1); } eol = strchr(text, '\n'); string summary; if (eol) { summary.insert(0, (char *)text, eol - (char *)text); } else { summary = (char *)text; } eptr subcomp(icalcomponent_vanew( ICAL_VJOURNAL_COMPONENT, icalproperty_new_summary(summary.c_str()), icalproperty_new_description(text), 0)); if( !subcomp ) { throwError(string("failure creating vjournal " ) + summary); } GErrorCXX gerror; if (!update) { const char *uid = NULL; #ifdef USE_EDS_CLIENT // UID only needs to be freed in ECalClient API PlainGStr uidOwner; #endif if ( #ifdef USE_EDS_CLIENT !e_cal_client_create_object_sync(m_calendar, subcomp, (gchar **)&uid, NULL, gerror) #else !e_cal_create_object(m_calendar, subcomp, (gchar **)&uid, gerror) #endif ) { if ( #ifdef USE_EDS_CLIENT gerror->domain == E_CAL_CLIENT_ERROR && gerror->code == E_CAL_CLIENT_ERROR_OBJECT_ID_ALREADY_EXISTS #else gerror->domain == E_CALENDAR_ERROR && gerror->code == E_CALENDAR_STATUS_OBJECT_ID_ALREADY_EXISTS #endif ) { // Deal with error due to adding already existing item. // Should never happen for plain text journal entries because // they have no embedded ID, but who knows... state = ITEM_NEEDS_MERGE; uid = icalcomponent_get_uid(subcomp); if (!uid) { throwError("storing new memo item, no UID set", gerror); } } else { throwError("storing new memo item", gerror); } } #ifdef USE_EDS_CLIENT else { uidOwner = PlainGStr((gchar *)uid); } #endif ItemID id(uid, ""); newluid = id.getLUID(); if (state != ITEM_NEEDS_MERGE) { modTime = getItemModTime(id); } } else { ItemID id(newluid); // ensure that the component has the right UID if (update && !id.m_uid.empty()) { icalcomponent_set_uid(subcomp, id.m_uid.c_str()); } if ( #ifdef USE_EDS_CLIENT !e_cal_client_modify_object_sync(m_calendar, subcomp, CALOBJ_MOD_ALL, NULL, gerror) #else !e_cal_modify_object(m_calendar, subcomp, CALOBJ_MOD_ALL, gerror) #endif ) { throwError(string("updating memo item ") + luid, gerror); } ItemID newid = getItemID(subcomp); newluid = newid.getLUID(); modTime = getItemModTime(newid); } return InsertItemResult(newluid, modTime, state); } bool EvolutionMemoSource::isNativeType(const char *type) { return type && (!strcasecmp(type, "raw") || !strcasecmp(type, "text/x-vcalendar") || !strcasecmp(type, "text/calendar")); } SE_END_CXX #endif /* ENABLE_ECAL */ syncevolution_1.4/src/backends/evolution/EvolutionMemoSource.h000066400000000000000000000035741230021373600251100ustar00rootroot00000000000000/* * Copyright (C) 2005-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_EVOLUTIONMEMOSOURCE #define INCL_EVOLUTIONMEMOSOURCE #include "config.h" #include #include SE_BEGIN_CXX #ifdef ENABLE_ECAL /** * Implements access to Evolution memo lists (stored as calendars), * exporting/importing the memos in plain UTF-8 text. Only the DESCRIPTION * part of a memo is synchronized. */ class EvolutionMemoSource : public EvolutionCalendarSource { public: EvolutionMemoSource(const SyncSourceParams ¶ms) : EvolutionCalendarSource(EVOLUTION_CAL_SOURCE_TYPE_MEMOS, params) {} // // implementation of SyncSource // virtual InsertItemResult insertItem(const string &uid, const std::string &item, bool raw); void readItem(const std::string &luid, std::string &item, bool raw); virtual std::string getMimeType() const { return "text/plain"; } virtual std::string getMimeVersion() const { return "1.0"; } private: bool isNativeType(const char *type); }; #endif // ENABLE_ECAL SE_END_CXX #endif // INCL_EVOLUTIONMEMOSOURCE syncevolution_1.4/src/backends/evolution/EvolutionSyncSource.cpp000066400000000000000000000326421230021373600254600ustar00rootroot00000000000000/* * Copyright (C) 2005-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "EvolutionSyncSource.h" #include #include #include #ifdef USE_EDS_CLIENT #include SE_GLIB_TYPE(GKeyFile, g_key_file) #endif #include SE_BEGIN_CXX #ifdef HAVE_EDS #ifdef USE_EDS_CLIENT void EvolutionSyncSource::getDatabasesFromRegistry(SyncSource::Databases &result, const char *extension, ESource *(*refDef)(ESourceRegistry *)) { ESourceRegistryCXX registry = EDSRegistryLoader::getESourceRegistry(); ESourceListCXX sources(e_source_registry_list_sources(registry, extension)); ESourceCXX def(refDef ? refDef(registry) : NULL, TRANSFER_REF); BOOST_FOREACH (ESource *source, sources) { result.push_back(Database(e_source_get_display_name(source), e_source_get_uid(source), e_source_equal(def, source))); } } static void handleErrorCB(EClient */*client*/, const gchar *error_msg, gpointer user_data) { EvolutionSyncSource *that = static_cast(user_data); SE_LOG_ERROR(that->getDisplayName(), "%s", error_msg); } EClientCXX EvolutionSyncSource::openESource(const char *extension, ESource *(*refBuiltin)(ESourceRegistry *), const boost::function &newClient) { EClientCXX client; GErrorCXX gerror; ESourceRegistryCXX registry = EDSRegistryLoader::getESourceRegistry(); ESourceListCXX sources(e_source_registry_list_sources(registry, extension)); string id = getDatabaseID(); ESource *source = findSource(sources, id); bool created = false; if (!source) { if (refBuiltin && (id.empty() || id == "<>")) { ESourceCXX builtin(refBuiltin(registry), TRANSFER_REF); client = EClientCXX::steal(newClient(builtin, gerror)); // } else if (!id.compare(0, 7, "file://")) { // TODO: create source // m_calendar = ECalClientCXX::steal(e_cal_client_new_from_uri(id.c_str(), sourceType(), gerror)); } else { throwError(string("database not found: '") + id + "'"); } created = true; } else { client = EClientCXX::steal(newClient(source, gerror)); } if (!client) { throwError("accessing database", gerror); } // Listen for errors g_signal_connect (client, "backend-error", G_CALLBACK(handleErrorCB), this); g_signal_connect_after(client, "backend-died", G_CALLBACK(SyncContext::fatalError), (void *)"Evolution Data Server has died unexpectedly."); while (true) { // Always allow EDS to create the database. "only-if-exists = // true" does not make sense. if (!e_client_open_sync(client, false, NULL, gerror)) { if (gerror && g_error_matches(gerror, E_CLIENT_ERROR, E_CLIENT_ERROR_BUSY)) { gerror.clear(); sleep(1); } else if (created) { // Opening newly created address books often failed in // old EDS releases - try again. Probably covered by // more recently added E_CLIENT_ERROR_BUSY check above. gerror.clear(); sleep(5); } else { throwError("opening database", gerror); } } else { // Success! break; } } // record result for SyncSource::getDatabase() source = e_client_get_source(client); if (source) { Database database(e_source_get_display_name(source), e_source_get_uid(source)); setDatabase(database); } return client; } SyncSource::Database EvolutionSyncSource::createDatabase(const Database &database) { // We'll need this later. Create it before doing any real work. ESourceRegistryCXX registry = EDSRegistryLoader::getESourceRegistry(); // Clone the system DB. This allows the distro to change the // configuration (backend, extensions (= in particular // the contacts DB summary fields)) without having to // modify SyncEvolution. ESourceCXX systemSource = refSystemDB(); gsize len; PlainGStr ini(e_source_to_string(systemSource, &len)); // Modify the entries in the key file directly. We can't // instantiate an ESource (no API for it), copying the values from // the key file into a fresh ESource is difficult (would have to // reimplement EDS internal encoding/decoding), and copying from // systemSource is hard (don't know which extensions it has, // cannot instantiate extensions of unknown types, because // e_source_get_extension() only works for types that were // created). static const char *mainSection = "Data Source"; GKeyFileCXX keyfile(g_key_file_new(), TRANSFER_REF); GErrorCXX gerror; if (!g_key_file_load_from_data(keyfile, ini, len, G_KEY_FILE_NONE, gerror)) { gerror.throwError("parsing ESource .ini data"); } PlainGStrArray keys(g_key_file_get_keys(keyfile, mainSection, NULL, gerror)); if (!keys) { gerror.throwError("listing keys in main section"); } for (int i = 0; keys.at(i); i++) { if (boost::starts_with(keys.at(i), "DisplayName[")) { if (!g_key_file_remove_key(keyfile, mainSection, keys.at(i), gerror)) { gerror.throwError("remove key"); } } } g_key_file_set_string(keyfile, mainSection, "DisplayName", database.m_name.c_str()); g_key_file_set_boolean(keyfile, mainSection, "Enabled", true); ini = g_key_file_to_data(keyfile, &len, NULL); const char *configDir = g_get_user_config_dir(); int fd; std::string filename; std::string uid; // Create sources dir. It might have been removed (for example, while // testing) without having been recreated by evolution-source-registry. std::string sourceDir = StringPrintf("%s/evolution/sources", configDir); mkdir_p(sourceDir); // Create unique ID if necessary. while (true) { uid = database.m_uri.empty() ? UUID() : database.m_uri; filename = StringPrintf("%s/%s.source", sourceDir.c_str(), uid.c_str()); fd = ::open(filename.c_str(), O_WRONLY|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR); if (fd >= 0) { break; } if (errno == EEXIST) { if (!database.m_uri.empty()) { SE_THROW(StringPrintf("ESource UUID %s already in use", database.m_uri.c_str())); } else { // try again with new random UUID } } else { SE_THROW(StringPrintf("creating %s failed: %s", filename.c_str(), strerror(errno))); } } ssize_t written = write(fd, ini.get(), len); int res = ::close(fd); if (written != (ssize_t)len || res) { SE_THROW(StringPrintf("writing to %s failed: %s", filename.c_str(), strerror(errno))); } // We need to wait until ESourceRegistry notices the new file. SE_LOG_DEBUG(getDisplayName(), "waiting for ESourceRegistry to notice new ESource %s", uid.c_str()); while (!ESourceCXX(e_source_registry_ref_source(registry, uid.c_str()), TRANSFER_REF)) { // This will block forever if called from the non-main-thread. // Don't do that... g_main_context_iteration(NULL, true); } SE_LOG_DEBUG(getDisplayName(), "ESourceRegistry has new ESource %s", uid.c_str()); // Try triggering that by attempting to create an ESource with the same // UUID. Does not work! evolution-source-registry simply overwrites the // file that we created earlier. // ESourceCXX source(e_source_new_with_uid(uid.c_str(), // NULL, gerror), // TRANSFER_REF); // e_source_set_display_name(source, "syncevolution-fake"); // e_source_set_parent(source, "local-stub"); // ESourceListCXX sources; // sources.push_back(source.ref()); // ESourceListCXX unrefs sources it points to // if (!e_source_registry_create_sources_sync(registry, // sources, // NULL, // gerror)) { // gerror.throwError(StringPrintf("creating EDS database of type %s with name '%s'%s%s", // sourceExtension(), // database.m_name.c_str(), // database.m_uri.empty() ? "" : " and URI ", // database.m_uri.c_str())); // } else { // SE_THROW("creating syncevolution-fake ESource succeeded although it should have failed"); // } return Database(database.m_name, uid); } void EvolutionSyncSource::deleteDatabase(const std::string &uri, RemoveData removeData) { ESourceRegistryCXX registry = EDSRegistryLoader::getESourceRegistry(); ESourceCXX source(e_source_registry_ref_source(registry, uri.c_str()), TRANSFER_REF); if (!source) { throwError(StringPrintf("EDS database with URI '%s' cannot be deleted, does not exist", uri.c_str())); } GErrorCXX gerror; if (!e_source_remove_sync(source, NULL, gerror)) { throwError(StringPrintf("deleting EDS database with URI '%s'", uri.c_str()), gerror); } if (removeData == REMOVE_DATA_FORCE) { // Don't wait for evolution-source-registry cache-reaper to // run, instead remove files ourselves. The reaper runs only // once per day and also only moves the data into a trash // folder, were it would linger until finally removed after 30 // days. // // This is equivalent to "rm -rf $XDG_DATA_HOME/evolution/*/". std::string basedir = StringPrintf("%s/evolution", g_get_user_data_dir()); if (isDir(basedir)) { BOOST_FOREACH (const std::string &kind, ReadDir(basedir)) { std::string subdir = basedir + "/" + kind; if (isDir(subdir)) { BOOST_FOREACH (const std::string &source, ReadDir(subdir)) { // We assume that the UUID of the database // consists only of characters which can be // used in the directory name, i.e., no // special encoding of the directory name. if (source == uri) { rm_r(subdir + "/" + source); // Keep searching, just in case, although // there should only be one. } } } } } } } #endif // USE_EDS_CLIENT ESource *EvolutionSyncSource::findSource(const ESourceListCXX &list, const string &id ) { string finalID; if (!id.empty()) { finalID = id; } else { // Nothing selected specifically, use the one marked as default. BOOST_FOREACH(const Database &db, getDatabases()) { if (db.m_isDefault) { finalID = db.m_uri; break; } } } #ifdef USE_EDS_CLIENT BOOST_FOREACH (ESource *source, list) { bool found = !finalID.compare(e_source_get_display_name(source)) || !finalID.compare(e_source_get_uid(source)); if (found) { return source; } } #else for (GSList *g = e_source_list_peek_groups (list.get()); g; g = g->next) { ESourceGroup *group = E_SOURCE_GROUP (g->data); GSList *s; for (s = e_source_group_peek_sources (group); s; s = s->next) { ESource *source = E_SOURCE (s->data); GStringPtr uri(e_source_get_uri(source)); bool found = finalID.empty() || !finalID.compare(e_source_peek_name(source)) || (uri && !finalID.compare(uri)); if (found) { return source; } } } #endif return NULL; } void EvolutionSyncSource::throwError(const string &action, GErrorCXX &gerror) { string gerrorstr; if (gerror) { gerrorstr += ": "; gerrorstr += gerror->message; } else { gerrorstr = ": failure"; } throwError(action + gerrorstr); } #endif // HAVE_EDS SE_END_CXX syncevolution_1.4/src/backends/evolution/EvolutionSyncSource.h000066400000000000000000000111021230021373600251110ustar00rootroot00000000000000/* * Copyright (C) 2005-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_EVOLUTIONSYNCSOURCE #define INCL_EVOLUTIONSYNCSOURCE #include #include #include #include #if defined(HAVE_EDS) # if defined(USE_EDS_CLIENT) # include # else SE_GOBJECT_TYPE(ESourceList) #endif #endif SE_BEGIN_CXX /** * The base class for all Evolution backends. * Same as TrackingSyncSource plus some Evolution * specific helper methods. */ class EvolutionSyncSource : public TrackingSyncSource { public: /** * Creates a new Evolution sync source. */ EvolutionSyncSource(const SyncSourceParams ¶ms, int granularitySeconds = 1) : TrackingSyncSource(params, granularitySeconds) { } void getSynthesisInfo(SynthesisInfo &info, XMLConfigFragments &fragments) { TrackingSyncSource::getSynthesisInfo(info, fragments); info.m_backendRule = "EVOLUTION"; } protected: #ifdef HAVE_EDS #ifdef USE_EDS_CLIENT void getDatabasesFromRegistry(SyncSource::Databases &result, const char *extension, ESource *(*getDef)(ESourceRegistry *)); EClientCXX openESource(const char *extension, ESource *(*refBuiltin)(ESourceRegistry *), const boost::function &newClient); // Implementation of SyncSource calls which only works when using EDS Client API // and EDS > 3.4. Older EDS has no way of creating sources easily (or at all). virtual Database createDatabase(const Database &database); virtual void deleteDatabase(const std::string &uri, RemoveData removeData); /** E_SOURCE_EXTENSION_ADDRESS_BOOK, etc. */ virtual const char *sourceExtension() const = 0; /** reference the system address book, calendar, etc. */ virtual ESourceCXX refSystemDB() const = 0; #endif /** * searches the list for a source with the given uri or name * * @param list a list previously obtained from Gnome * @param id a string identifying the data source: either its name or uri * @return pointer to source (caller owns reference) or NULL if not found */ ESource *findSource(const ESourceListCXX &list, const string &id); #endif public: #ifdef HAVE_EDS using SyncSourceBase::throwError; /** * throw an exception after an operation failed and * remember that this instance has failed * * output format: : : * * @param action a string describing the operation or object involved * @param gerror a more detailed description of the failure, * may be empty */ void throwError(const string &action, GErrorCXX &gerror); #endif }; /** * Utility class which hides the mechanisms needed to handle events * during asynchronous calls. */ class EvolutionAsync { public: EvolutionAsync() { m_loop = GMainLoopStealCXX(g_main_loop_new(NULL, TRUE)); } /** start processing events */ void run() { if (g_main_context_is_owner(g_main_context_default())) { g_main_loop_run(m_loop.get()); } else { // Let master thread handle events. while (g_main_loop_is_running(m_loop.get())) { Sleep(0.1); } } } /** stop processing events, to be called inside run() by callback */ void quit() { g_main_loop_quit(m_loop.get()); } private: GMainLoopCXX m_loop; }; SE_END_CXX #endif // INCL_EVOLUTIONSYNCSOURCE syncevolution_1.4/src/backends/evolution/configure-sub.in000066400000000000000000000103561230021373600240500ustar00rootroot00000000000000dnl -*- mode: Autoconf; -*- dnl Invoke autogen.sh to produce a configure script. anymissing=" Please install the development packages of Evolution and/or set the PKG_CONFIG_PATH variable so that it points towards the .pc files of libedataserver, libecal and libebook (the latter two are optional). You can check that these packages are available by running pkg-config --list-all." evomissing="No compatible evolution-data-server was found. $anymissing" dnl check for Evolution packages PKG_CHECK_MODULES(EPACKAGE, libedataserver-1.2, EDSFOUND=yes, [EDSFOUND=no]) PKG_CHECK_MODULES(ECAL, libecal-1.2, ECALFOUND=yes, [ECALFOUND=no]) PKG_CHECK_MODULES(EBOOK, libebook-1.2, EBOOKFOUND=yes, [EBOOKFOUND=no]) PKG_CHECK_MODULES(EBOOK_VERSION, [libebook-1.2 >= 3.3], [AC_DEFINE(HAVE_E_CONTACT_INLINE_LOCAL_PHOTOS, 1, [have e_contact_inline_local_photos()])], [true]) SE_ARG_ENABLE_BACKEND(ebook, evolution, [AS_HELP_STRING([--disable-ebook], [disable access to Evolution addressbooks (must be used to compile without it)])], [enable_ebook="$enableval"], [test "$EBOOKFOUND" = "yes" && enable_ebook="yes" || AC_MSG_ERROR([libebook not found. Use --disable-ebook to compile without or install the necessary development files.])] ) SE_ARG_ENABLE_BACKEND(ecal, evolution, [AS_HELP_STRING([--disable-ecal], [disable access to Evolution calendars and tasks (must be used to compile without it)])], [enable_ecal="$enableval"], [test "$ECALFOUND" = "yes" && enable_ecal="yes" || AC_MSG_ERROR([libecal not found. Use --disable-ecal to compile without or install the necessary development files.])] ) enable_evo="no" if test "$enable_ebook" = "yes"; then test "x${EBOOKFOUND}" = "xyes" || AC_MSG_ERROR([--enable-ebook requires pkg-config information for libebook, which was not found]) AC_DEFINE(ENABLE_EBOOK, 1, [libebook available]) enable_evo="yes" else EBOOK_CFLAGS= EBOOK_LIBS= fi AM_CONDITIONAL([ENABLE_ECAL], [test "$enable_ecal" = "yes"]) if test "$enable_ecal" = "yes"; then need_ical="yes" test "x${ECALFOUND}" = "xyes" || AC_MSG_ERROR([--enable-ecal requires pkg-config information for libecal, which was not found"]) AC_DEFINE(ENABLE_ECAL, 1, [libecal available]) enable_evo="yes" else ECAL_CFLAGS= ECAL_LIBS= fi if test "$enable_evo" = "yes"; then need_glib="yes" if test "$EDSFOUND" = "yes"; then AC_DEFINE(HAVE_EDS, 1, [evolution-dataserver available]) else AC_MSG_ERROR($evomissing) fi # Only the EClient code supports the API in EDS 3.5.x. PKG_CHECK_MODULES(EDS_VERSION, [libedataserver-1.2 >= 3.5], [AC_DEFINE(USE_EDS_CLIENT, 1, [use e_book/cal_client_* calls]) # Was used for a while (ESourceBackendSummarySetup), but not anymore. # Don't depend on it, it wasn't in 3.6 yet. # PKG_CHECK_MODULES(EBOOKCONTACTS, libebook-contacts-1.2) AC_CHECK_LIB(ebook-1.2, e_book_client_new_direct, [AC_DEFINE(HAVE_E_BOOK_CLIENT_NEW_DIRECT, 1, [use e_book_client_new_direct])], [true], [$EDS_VERSION_LIBS]) AC_CHECK_LIB(ebook-1.2, e_book_client_connect_direct_sync, [AC_DEFINE(HAVE_E_BOOK_CLIENT_CONNECT_DIRECT_SYNC, 1, [use e_book_client_connect_direct_sync])], [true], [$EDS_VERSION_LIBS]) ], [CFLAGS_old="$CFLAGS" CFLAGS="$CFLAGS $EPACKAGE_CFLAGS" AC_CHECK_HEADERS(libedataserver/eds-version.h) CFLAGS="$CFLAGS_old"]) else EPACKAGE_CFLAGS= EPACKAGE_LIBS= fi BACKEND_CPPFLAGS="$BACKEND_CPPFLAGS $EPACKAGE_CFLAGS $ECAL_CFLAGS $EBOOK_CFLAGS $EBOOKCONTACTS_CFLAGS" syncevolution_1.4/src/backends/evolution/e-cal-check-timezones.c000066400000000000000000000430421230021373600251610ustar00rootroot00000000000000/* * Copyright (C) 2008 Novell, Inc. * Copyright (C) 2009 Patrick Ohly * * Authors: Patrick Ohly * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef HANDLE_LIBICAL_MEMORY # define HANDLE_LIBICAL_MEMORY 1 #endif #include #ifdef LIBICAL_MEMFIXES /* avoid dependency on icalstrdup.h, works when compiling as part of EDS >= 2.22 */ # define ical_strdup(_x) (_x) #else /* use icalstrdup.h to get runtime detection of memory fix patch */ # include #endif #include "e-cal-check-timezones.h" #include #include /** * Matches a location to a system timezone definition via a fuzzy * search and returns the matching TZID, or NULL if none found. * * Currently simply strips a suffix introduced by a hyphen, * as in "America/Denver-(Standard)". */ static const char *e_cal_match_location(const char *location) { icaltimezone *icomp; const char *tail; size_t len; char *buffer; icomp = icaltimezone_get_builtin_timezone (location); if (icomp) { return icaltimezone_get_tzid(icomp); } /* try a bit harder by stripping trailing suffix */ tail = strrchr(location, '-'); len = tail ? (tail - location) : strlen(location); buffer = g_malloc(len + 1); if (buffer) { memcpy(buffer, location, len); buffer[len] = 0; icomp = icaltimezone_get_builtin_timezone (buffer); g_free(buffer); if (icomp) { return icaltimezone_get_tzid(icomp); } } return NULL; } /** * e_cal_match_tzid: * matches a TZID against the system timezone definitions * and returns the matching TZID, or NULL if none found */ const char *e_cal_match_tzid(const char *tzid) { const char *location; const char *systzid; size_t len = strlen(tzid); ssize_t eostr; /* * Try without any trailing spaces/digits: they might have been added * by e_cal_check_timezones() in order to distinguish between * different incompatible definitions. At that time mapping * to system time zones must have failed, but perhaps now * we have better code and it succeeds... */ eostr = len - 1; while (eostr >= 0 && isdigit(tzid[eostr])) { eostr--; } while (eostr >= 0 && isspace(tzid[eostr])) { eostr--; } if (eostr + 1 < len) { char *strippedtzid = g_strndup(tzid, eostr + 1); if (strippedtzid) { systzid = e_cal_match_tzid(strippedtzid); g_free(strippedtzid); if (systzid) { return systzid; } } } /* * old-style Evolution: /softwarestudio.org/Olson_20011030_5/America/Denver * * jump from one slash to the next and check whether the remainder * is a known location; start with the whole string (just in case) */ for (location = tzid; location && location[0]; location = strchr(location + 1, '/')) { systzid = e_cal_match_location(location[0] == '/' ? location + 1 : location); if (systzid) { return systzid; } } /* TODO: lookup table for Exchange TZIDs */ return NULL; } static void patch_tzids(icalcomponent *subcomp, GHashTable *mapping) { char *tzid = NULL; if (icalcomponent_isa(subcomp) != ICAL_VTIMEZONE_COMPONENT) { icalproperty *prop = icalcomponent_get_first_property(subcomp, ICAL_ANY_PROPERTY); while (prop) { icalparameter *param = icalproperty_get_first_parameter(prop, ICAL_TZID_PARAMETER); while (param) { const char *oldtzid; const char *newtzid; g_free(tzid); tzid = g_strdup(icalparameter_get_tzid(param)); if (!g_hash_table_lookup_extended(mapping, tzid, (gpointer *)&oldtzid, (gpointer *)&newtzid)) { /* Corresponding VTIMEZONE not seen before! */ newtzid = e_cal_match_tzid(tzid); } if (newtzid) { icalparameter_set_tzid(param, newtzid); } param = icalproperty_get_next_parameter(prop, ICAL_TZID_PARAMETER); } prop = icalcomponent_get_next_property(subcomp, ICAL_ANY_PROPERTY); } } g_free(tzid); } static void addsystemtz(gpointer key, gpointer value, gpointer user_data) { const char *tzid = key; icalcomponent *comp = user_data; icaltimezone *zone; zone = icaltimezone_get_builtin_timezone_from_tzid(tzid); if (zone) { icalcomponent_add_component(comp, icalcomponent_new_clone(icaltimezone_get_component(zone))); } } /** * e_cal_check_timezones: * @comp: a VCALENDAR containing a list of * VTIMEZONE and arbitrary other components, in * arbitrary order: these other components are * modified by this call * @comps: a list of icalcomponent instances which * also have to be patched; may be NULL * @tzlookup: a callback function which is called to retrieve * a calendar's VTIMEZONE definition; the returned * definition is *not* freed by e_cal_check_timezones() * (to be compatible with e_cal_get_timezone()); * NULL indicates that no such timezone exists * or an error occurred * @custom: an arbitrary pointer which is passed through to * the tzlookup function * @error: an error description in case of a failure * * This function cleans up VEVENT, VJOURNAL, VTODO and VTIMEZONE * items which are to be imported into Evolution. * * Using VTIMEZONE definitions is problematic because they cannot be * updated properly when timezone definitions change. They are also * incomplete (for compatibility reason only one set of rules for * summer saving changes can be included, even if different rules * apply in different years). This function looks for matches of the * used TZIDs against system timezones and replaces such TZIDs with * the corresponding system timezone. This works for TZIDs containing * a location (found via a fuzzy string search) and for Outlook TZIDs * (via a hard-coded lookup table). * * Some programs generate broken meeting invitations with TZID, but * without including the corresponding VTIMEZONE. Importing such * invitations unchanged causes problems later on (meeting displayed * incorrectly, #e_cal_get_component_as_string fails). The situation * where this occurred in the past (found by a SyncEvolution user) is * now handled via the location based mapping. * * If this mapping fails, this function also deals with VTIMEZONE * conflicts: such conflicts occur when the calendar already contains * an old VTIMEZONE definition with the same TZID, but different * summer saving rules. Replacing the VTIMEZONE potentially breaks * displaying of old events, whereas not replacing it breaks the new * events (the behavior in Evolution <= 2.22.1). * * The way this problem is resolved is by renaming the new VTIMEZONE * definition until the TZID is unique. A running count is appended to * the TZID. All items referencing the renamed TZID are adapted * accordingly. * * Return value: TRUE if successful, FALSE otherwise. */ gboolean e_cal_check_timezones(icalcomponent *comp, GList *comps, icaltimezone *(*tzlookup)(const char *tzid, const void *custom, GError **error), const void *custom, GError **error) { gboolean success = TRUE; icalcomponent *subcomp = NULL; icaltimezone *zone = icaltimezone_new(); char *key = NULL, *value = NULL; char *buffer = NULL; char *zonestr = NULL; char *tzid = NULL; GList *l; /** a hash from old to new tzid; strings dynamically allocated */ GHashTable *mapping = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); /** a hash of all system time zone IDs which have to be added; strings are shared with mapping hash */ GHashTable *systemtzids = g_hash_table_new(g_str_hash, g_str_equal); *error = NULL; if (!mapping || !zone) { goto nomem; } /* iterate over all VTIMEZONE definitions */ subcomp = icalcomponent_get_first_component(comp, ICAL_VTIMEZONE_COMPONENT); while (subcomp) { if (icaltimezone_set_component(zone, subcomp)) { g_free(tzid); tzid = g_strdup(icaltimezone_get_tzid(zone)); if (tzid) { const char *newtzid = e_cal_match_tzid(tzid); if (newtzid) { /* matched against system time zone */ g_free(key); key = g_strdup(tzid); if (!key) { goto nomem; } g_free(value); value = g_strdup(newtzid); if (!value) { goto nomem; } g_hash_table_insert(mapping, key, value); g_hash_table_insert(systemtzids, value, NULL); key = value = NULL; } else { zonestr = ical_strdup(icalcomponent_as_ical_string(subcomp)); /* check for collisions with existing timezones */ int counter; for (counter = 0; counter < 100 /* sanity limit */; counter++) { icaltimezone *existing_zone; if (counter) { g_free(value); value = g_strdup_printf("%s %d", tzid, counter); } existing_zone = tzlookup(counter ? value : tzid, custom, error); if (!existing_zone) { if (*error) { goto failed; } else { break; } } g_free(buffer); buffer = ical_strdup(icalcomponent_as_ical_string(icaltimezone_get_component(existing_zone))); if (counter) { char *fulltzid = g_strdup_printf("TZID:%s", value); size_t baselen = strlen("TZID:") + strlen(tzid); size_t fulllen = strlen(fulltzid); char *tzidprop; /* * Map TZID with counter suffix back to basename. */ tzidprop = strstr(buffer, fulltzid); if (tzidprop) { memmove(tzidprop + baselen, tzidprop + fulllen, strlen(tzidprop + fulllen) + 1); } g_free(fulltzid); } /* * If the strings are identical, then the * VTIMEZONE definitions are identical. If * they are not identical, then VTIMEZONE * definitions might still be semantically * correct and we waste some space by * needlesly duplicating the VTIMEZONE. This * is expected to occur rarely (if at all) in * practice. */ if (!strcmp(zonestr, buffer)) { break; } } if (!counter) { /* does not exist, nothing to do */ } else { /* timezone renamed */ icalproperty *prop = icalcomponent_get_first_property(subcomp, ICAL_TZID_PROPERTY); while (prop) { icalproperty_set_value_from_string(prop, value, "NO"); prop = icalcomponent_get_next_property(subcomp, ICAL_ANY_PROPERTY); } g_free(key); key = g_strdup(tzid); g_hash_table_insert(mapping, key, value); key = value = NULL; } } } } subcomp = icalcomponent_get_next_component(comp, ICAL_VTIMEZONE_COMPONENT); } /* * now replace all TZID parameters in place */ subcomp = icalcomponent_get_first_component(comp, ICAL_ANY_COMPONENT); while (subcomp) { /* * Leave VTIMEZONE unchanged, iterate over properties of * everything else. * * Note that no attempt is made to remove unused VTIMEZONE * definitions. That would just make the code more complex for * little additional gain. However, newly used time zones are * added below. */ patch_tzids (subcomp, mapping); subcomp = icalcomponent_get_next_component(comp, ICAL_ANY_COMPONENT); } for (l = comps; l; l = l->next) { patch_tzids (l->data, mapping); } /* * add system time zones that we mapped to: adding them ensures * that the VCALENDAR remains consistent */ g_hash_table_foreach(systemtzids, addsystemtz, comp); goto done; nomem: /* set gerror for "out of memory" if possible, otherwise abort via g_error() */ *error = g_error_new(E_CALENDAR_ERROR, E_CALENDAR_STATUS_OTHER_ERROR, "out of memory"); if (!*error) { g_error("e_cal_check_timezones(): out of memory, cannot proceed - sorry!"); } failed: /* gerror should have been set already */ success = FALSE; done: if (mapping) { g_hash_table_destroy(mapping); } if (systemtzids) { g_hash_table_destroy(systemtzids); } if (zone) { icaltimezone_free(zone, 1); } g_free(tzid); g_free(zonestr); g_free(buffer); g_free(key); g_free(value); return success; } /** * e_cal_tzlookup_ecal: * @custom: must be a valid ECal pointer * * An implementation of the tzlookup callback which clients * can use. Calls #e_cal_get_timezone. */ icaltimezone *e_cal_tzlookup_ecal(const char *tzid, const void *custom, GError **error) { ECal *ecal = (ECal *)custom; icaltimezone *zone = NULL; if (e_cal_get_timezone(ecal, tzid, &zone, error)) { g_assert(*error == NULL); return zone; } else { g_assert(*error); if ((*error)->domain == E_CALENDAR_ERROR && ((*error)->code == E_CALENDAR_STATUS_OBJECT_NOT_FOUND /* EDS < 2.30 */ || (*error)->code == E_CALENDAR_STATUS_INVALID_OBJECT /* EDS >= 2.30 */ )) { /* * we had to trigger this error to check for the timezone existance, * clear it and return NULL */ g_clear_error(error); } return NULL; } } /** * e_cal_tzlookup_icomp: * @custom: must be a icalcomponent pointer which contains * either a VCALENDAR with VTIMEZONEs or VTIMEZONES * directly * * An implementation of the tzlookup callback which backends * like the file backend can use. Searches for the timezone * in the component list. */ icaltimezone *e_cal_tzlookup_icomp(const char *tzid, const void *custom, GError **error) { icalcomponent *icomp = (icalcomponent *)custom; return icalcomponent_get_timezone(icomp, (char *)tzid); } syncevolution_1.4/src/backends/evolution/e-cal-check-timezones.h000066400000000000000000000035551230021373600251730ustar00rootroot00000000000000/* * Copyright (C) 2008 Novell, Inc. * Copyright (C) 2009 Patrick Ohly * * Authors: Patrick Ohly * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef E_CAL_CHECK_TIMEZONES_H #define E_CAL_CHECK_TIMEZONES_H #include /* #include */ #include G_BEGIN_DECLS gboolean e_cal_check_timezones(icalcomponent *comp, GList *comps, icaltimezone *(*tzlookup)(const char *tzid, const void *custom, GError **error), const void *custom, GError **error); icaltimezone *e_cal_tzlookup_ecal(const char *tzid, const void *custom, GError **error); icaltimezone *e_cal_tzlookup_icomp(const char *tzid, const void *custom, GError **error); const char *e_cal_match_tzid(const char *tzid); G_END_DECLS #endif /* E_CAL_CHECK_TIMEZONES_H */ syncevolution_1.4/src/backends/evolution/evolution.am000066400000000000000000000055121230021373600233110ustar00rootroot00000000000000dist_noinst_DATA += src/backends/evolution/configure-sub.in src_backends_evolution_lib = src/backends/evolution/syncecal.la src/backends/evolution/syncebook.la MOSTLYCLEANFILES += $(src_backends_evolution_lib) if ENABLE_MODULES src_backends_evolution_backenddir = $(BACKENDS_DIRECTORY) src_backends_evolution_backend_LTLIBRARIES = $(src_backends_evolution_lib) else noinst_LTLIBRARIES += $(src_backends_evolution_lib) endif src_backends_evolution_syncecal_src = \ src/backends/evolution/EvolutionSyncSource.h \ src/backends/evolution/EvolutionSyncSource.cpp \ src/backends/evolution/EvolutionCalendarSource.h \ src/backends/evolution/EvolutionMemoSource.h \ src/backends/evolution/EvolutionCalendarSource.cpp \ src/backends/evolution/EvolutionMemoSource.cpp if ENABLE_ECAL src_backends_evolution_syncecal_src += \ src/backends/evolution/e-cal-check-timezones.c \ src/backends/evolution/e-cal-check-timezones.h endif src_backends_evolution_syncebook_src = \ src/backends/evolution/EvolutionSyncSource.h \ src/backends/evolution/EvolutionSyncSource.cpp \ src/backends/evolution/EvolutionContactSource.h \ src/backends/evolution/EvolutionContactSource.cpp src_backends_evolution_cppflags = \ $(SYNCEVOLUTION_CFLAGS) \ -I$(top_srcdir)/test \ $(BACKEND_CPPFLAGS) \ -I$(top_srcdir)/src/backends/evolution src_backends_evolution_syncecal_la_SOURCES = $(src_backends_evolution_syncecal_src) # $(EBOOKCONTACTS_LIBS) is needed for ESourceBackendSummary, which we # use in EvolutionSyncSource and thus in syncecal. src_backends_evolution_syncecal_la_LIBADD = $(ECAL_LIBS) $(EBOOKCONTACTS_LIBS) $(SYNCEVOLUTION_LIBS) $(GLIB_LIBS) $(GOBJECT_LIBS) # _GNU_SOURCE and -ldl for libical.c + dlsym(): src_backends_evolution_syncecal_la_CPPFLAGS = -D_GNU_SOURCE \ -De_cal_check_timezones=syncevolution_check_timezones \ -De_cal_tzlookup_ecal=syncevolution_tzlookup_ecal \ -De_cal_tzlookup_icomp=syncevolution_tzlookup_icomp \ -De_cal_match_tzid=syncevolution_match_tzid \ $(src_backends_evolution_cppflags) src_backends_evolution_syncecal_la_LDFLAGS = -module -avoid-version -ldl src_backends_evolution_syncecal_la_CXXFLAGS = $(SYNCEVOLUTION_CXXFLAGS) $(SYNCEVO_WFLAGS) $(GLIB_CFLAGS) $(GOBJECT_CFLAGS) src_backends_evolution_syncecal_la_DEPENDENCIES = src/syncevo/libsyncevolution.la src_backends_evolution_syncebook_la_SOURCES = $(src_backends_evolution_syncebook_src) src_backends_evolution_syncebook_la_LIBADD = $(EBOOK_LIBS) $(EBOOKCONTACTS_LIBS) $(SYNCEVOLUTION_LIBS) $(GLIB_LIBS) $(GOBJECT_LIBS) src_backends_evolution_syncebook_la_LDFLAGS = -module -avoid-version src_backends_evolution_syncebook_la_CXXFLAGS = $(SYNCEVOLUTION_CXXFLAGS) $(SYNCEVO_WFLAGS) $(GLIB_CFLAGS) $(GOBJECT_CFLAGS) src_backends_evolution_syncebook_la_CPPFLAGS = $(src_backends_evolution_cppflags) src_backends_evolution_syncebook_la_DEPENDENCIES = src/syncevo/libsyncevolution.la syncevolution_1.4/src/backends/file/000077500000000000000000000000001230021373600176365ustar00rootroot00000000000000syncevolution_1.4/src/backends/file/FileSyncSource.cpp000066400000000000000000000177431230021373600232530ustar00rootroot00000000000000/* * Copyright (C) 2007-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "config.h" #ifdef ENABLE_FILE #include "FileSyncSource.h" // SyncEvolution includes a copy of Boost header files. // They are safe to use without creating additional // build dependencies. boost::filesystem requires a // library and therefore is avoided here. Some // utility functions from SyncEvolution are used // instead, plus standard C/Posix functions. #include #include #include #include #include #include #include #include #include #include #include #include SE_BEGIN_CXX FileSyncSource::FileSyncSource(const SyncSourceParams ¶ms, const string &dataformat) : TrackingSyncSource(params), m_mimeType(dataformat), m_entryCounter(0) { if (dataformat.empty()) { throwError("a database format must be specified"); } } std::string FileSyncSource::getMimeType() const { return m_mimeType.c_str(); } std::string FileSyncSource::getMimeVersion() const { if (boost::iequals(m_mimeType, "text/vcard")) { return "3.0"; } else if (boost::iequals(m_mimeType, "text/x-vcard")) { return "2.1"; } else if (boost::iequals(m_mimeType, "text/calendar")) { return "2.0"; } else if (boost::iequals(m_mimeType, "text/x-vcalendar")) { return "1.0"; } else { return ""; } } void FileSyncSource::open() { const string &database = getDatabaseID(); const string prefix("file://"); string basedir; bool createDir = false; std::string varname = StringPrintf("SYNCEVOLUTION_FILE_SOURCE_DELAY_OPEN_%s", getDisplayName().c_str()); const char *delay = getenv(varname.c_str()); if (delay) { int seconds = atoi(delay); SE_LOG_DEBUG(getDisplayName(), "sleeping %ds while opening file source", seconds); Sleep(seconds); SE_LOG_DEBUG(getDisplayName(), "continue opening file source"); } // file:// is optional. It indicates that the // directory is to be created. if (boost::starts_with(database, prefix)) { basedir = database.substr(prefix.size()); createDir = true; } else { basedir = database; } // check and, if allowed and necessary, create it if (!isDir(basedir)) { if (errno == ENOENT && createDir) { mkdir_p(basedir.c_str()); } else { throwError(basedir, errno); } } // success! m_basedir = basedir; } bool FileSyncSource::isEmpty() { DIR *dir = NULL; bool empty = true; try { dir = opendir(m_basedir.c_str()); if (!dir) { SyncContext::throwError(m_basedir, errno); } errno = 0; struct dirent *entry = readdir(dir); while (entry) { if (strcmp(entry->d_name, ".") && strcmp(entry->d_name, "..")) { empty = false; break; } entry = readdir(dir); } if (errno) { SyncContext::throwError(m_basedir, errno); } } catch(...) { if (dir) { closedir(dir); } throw; } closedir(dir); return empty; } void FileSyncSource::close() { m_basedir.clear(); } FileSyncSource::Databases FileSyncSource::getDatabases() { Databases result; result.push_back(Database("select database via directory path", "[file://]")); return result; } void FileSyncSource::listAllItems(RevisionMap_t &revisions) { ReadDir dirContent(m_basedir); std::string varname = StringPrintf("SYNCEVOLUTION_FILE_SOURCE_DELAY_LISTALL_%s", getDisplayName().c_str()); const char *delay = getenv(varname.c_str()); if (delay) { int seconds = atoi(delay); SE_LOG_DEBUG(getDisplayName(), "sleeping %ds while listing items in file source", seconds); Sleep(seconds); SE_LOG_DEBUG(getDisplayName(), "continue listing items in file source"); } BOOST_FOREACH(const string &entry, dirContent) { string filename = createFilename(entry); string revision = getATimeString(filename); long entrynum = atoll(entry.c_str()); if (entrynum >= m_entryCounter) { m_entryCounter = entrynum + 1; } revisions[entry] = revision; } } void FileSyncSource::readItem(const string &uid, std::string &item, bool raw) { string filename = createFilename(uid); if (!ReadFile(filename, item)) { throwError(filename + ": reading failed", errno); } } TrackingSyncSource::InsertItemResult FileSyncSource::insertItem(const string &uid, const std::string &item, bool raw) { string newuid = uid; string creationTime; string filename; // Inserting a new and updating an existing item often uses // very similar code. In this case only the code for determining // the filename differs. // // In other sync sources the database might also have limitations // for the content of different items, for example, only one // VCALENDAR:EVENT with a certain UID. If the server does not // recognize that and sends a new item which collides with an // existing one, then the existing one should be updated. if (uid.size()) { // valid local ID: update that file filename = createFilename(uid); } else { // no local ID: create new file while (true) { ostringstream buff; buff << m_entryCounter; filename = createFilename(buff.str()); // only create and truncate if file does not // exist yet, otherwise retry with next counter struct stat dummy; if (stat(filename.c_str(), &dummy)) { if (errno == ENOENT) { newuid = buff.str(); break; } else { throwError(filename, errno); } } m_entryCounter++; } } ofstream out; out.open(filename.c_str()); out.write(item.c_str(), item.size()); out.close(); if (!out.good()) { throwError(filename + ": writing failed", errno); } return InsertItemResult(newuid, getATimeString(filename), ITEM_OKAY); } void FileSyncSource::removeItem(const string &uid) { string filename = createFilename(uid); if (unlink(filename.c_str())) { throwError(filename, errno); } } string FileSyncSource::getATimeString(const string &filename) { struct stat buf; if (stat(filename.c_str(), &buf)) { throwError(filename, errno); } time_t mtime = buf.st_mtime; int mnsec = buf.st_mtim.tv_nsec; ostringstream revision; revision << mtime; if (mnsec) { revision << "." << mnsec; } return revision.str(); } string FileSyncSource::createFilename(const string &entry) { string filename = m_basedir + "/" + entry; return filename; } SE_END_CXX #endif /* ENABLE_FILE */ #ifdef ENABLE_MODULES # include "FileSyncSourceRegister.cpp" #endif syncevolution_1.4/src/backends/file/FileSyncSource.h000066400000000000000000000100751230021373600227070ustar00rootroot00000000000000/* * Copyright (C) 2007-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_FILESYNCSOURCE #define INCL_FILESYNCSOURCE #include #ifdef ENABLE_FILE #include #include #include SE_BEGIN_CXX /** * Stores each SyncML item as a separate file in a directory. The * directory has to be specified via the database name, using * [file://] as format. The file:// prefix is optional, but the * directory is only created if it is used. * SyncSource::getDatabaseID() gives us the database name. * * Change tracking is done via the file systems modification time * stamp: editing a file treats it as modified and then sends it to * the server in the next sync. Removing and adding files also works. * * The local unique identifier for each item is its name in the * directory. New files are created using a running count which * initialized based on the initial content of the directory to * "highest existing number + 1" and incremented to avoid collisions. * * Although this sync source itself does not care about the content of * each item/file, the server needs to know what each item sent to it * contains and what items the source is able to receive. Therefore * the "type" property for this source must contain a data format * specified, including a version for it. Here are some examples: * - type=file:text/vcard:3.0 * - type=file:text/plain:1.0 */ class FileSyncSource : public TrackingSyncSource, private boost::noncopyable { public: FileSyncSource(const SyncSourceParams ¶ms, const string &dataformat); protected: /* implementation of SyncSource interface */ virtual void open(); virtual bool isEmpty(); virtual void close(); virtual Databases getDatabases(); virtual std::string getMimeType() const; virtual std::string getMimeVersion() const; /* implementation of TrackingSyncSource interface */ virtual void listAllItems(RevisionMap_t &revisions); virtual InsertItemResult insertItem(const string &luid, const std::string &item, bool raw); void readItem(const std::string &luid, std::string &item, bool raw); virtual void removeItem(const string &uid); private: /** * @name values obtained from the source's "database format" configuration property * * Other sync sources only support one hard-coded type and * don't need such variables. */ /**@{*/ string m_mimeType; /**@}*/ /** directory selected via the database name in open(), reset in close() */ string m_basedir; /** a counter which is used to name new files */ long m_entryCounter; /** * get access time for file, formatted as revision string * @param filename absolute path or path relative to current directory */ string getATimeString(const string &filename); /** * create full filename from basedir and entry name */ string createFilename(const string &entry); void getSynthesisInfo(SynthesisInfo &info, XMLConfigFragments &fragments) { TrackingSyncSource::getSynthesisInfo(info, fragments); // files can store all kinds of extensions, so tell // engine to enable them info.m_backendRule = "ALL"; } }; SE_END_CXX #endif // ENABLE_FILE #endif // INCL_FILESYNCSOURCE syncevolution_1.4/src/backends/file/FileSyncSourceRegister.cpp000066400000000000000000000153061230021373600247510ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "FileSyncSource.h" #include "test.h" #include SE_BEGIN_CXX static SyncSource *createSource(const SyncSourceParams ¶ms) { SourceType sourceType = SyncSource::getSourceType(params.m_nodes); // The string returned by getSourceType() is always the one // registered as main Aliases() below. bool isMe = sourceType.m_backend == "file"; #ifndef ENABLE_FILE // tell SyncEvolution if the user wanted to use a disabled sync source, // otherwise let it continue searching return isMe ? RegisterSyncSource::InactiveSource(params) : NULL; #else // Also recognize one of the standard types? // Not in the FileSyncSource! bool maybeMe = false /* sourceType.m_backend == "addressbook" */; if (isMe || maybeMe) { // The FileSyncSource always needs the database format. if (!sourceType.m_localFormat.empty()) { return new FileSyncSource(params, sourceType.m_localFormat); } else { return NULL; } } return NULL; #endif } static RegisterSyncSource registerMe("Files in one directory", #ifdef ENABLE_FILE true, #else false, #endif createSource, "Files in one directory = file\n" " Stores items in one directory as one file per item.\n" " The directory is selected via database=[file://].\n" " It will only be created if the prefix is given, otherwise\n" " it must exist already.\n" " The database format *must* be specified explicitly. It may be\n" " different from the sync format, as long as there are\n" " conversion rules (for example, vCard 2.1 <-> vCard 3.0). If\n" " the sync format is empty, the database format is used.\n" " Examples for databaseFormat + syncFormat:\n" " text/plain + text/plain\n" " text/x-vcard + text/vcard\n" " text/calendar\n" " Examples for evolutionsource:\n" " /home/joe/datadir - directory must exist\n" " file:///tmp/scratch - directory is created\n", Values() + (Aliases("file") + "Files in one directory")); #ifdef ENABLE_FILE #ifdef ENABLE_UNIT_TESTS class FileSyncSourceUnitTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(FileSyncSourceUnitTest); CPPUNIT_TEST(testInstantiate); CPPUNIT_TEST_SUITE_END(); protected: void testInstantiate() { boost::shared_ptr source; source.reset(SyncSource::createTestingSource("file", "file:text/vcard:3.0", true)); source.reset(SyncSource::createTestingSource("file", "file:text/plain:1.0", true)); source.reset(SyncSource::createTestingSource("file", "Files in one directory:text/x-vcard:2.1", true)); } }; SYNCEVOLUTION_TEST_SUITE_REGISTRATION(FileSyncSourceUnitTest); #endif // ENABLE_UNIT_TESTS // The anonymous namespace ensures that we don't get // name clashes: although the classes and objects are // only defined in this file, the methods generated // for local classes will clash with other methods // of other classes with the same name if no namespace // is used. // // The code above is not yet inside the anonymous // name space because it would show up inside // the CPPUnit unit test names. Use a unique class // name there. namespace { #if 0 } #endif static class VCard30Test : public RegisterSyncSourceTest { public: VCard30Test() : RegisterSyncSourceTest("file_contact", "eds_contact") {} virtual void updateConfig(ClientTestConfig &config) const { // tell testInsertTwice that we won't detect duplicates config.m_sourceKnowsItemSemantic = false; config.m_type = "file:text/vcard:3.0"; } } VCard30Test; static class ICal20Test : public RegisterSyncSourceTest { public: ICal20Test() : RegisterSyncSourceTest("file_event", "eds_event") {} virtual void updateConfig(ClientTestConfig &config) const { config.m_type = "file:text/calendar:2.0"; // A sync source which supports linked items (= recurring // event with detached exception) is expected to handle // inserting the parent or child twice by turning the // second operation into an update. The file backend is // to dumb for that and therefore fails these tests: // // Client::Source::file_event::testLinkedItemsInsertParentTwice // Client::Source::file_event::testLinkedItemsInsertChildTwice // // Disable linked item testing to avoid this. config.m_sourceKnowsItemSemantic = false; } } ICal20Test; static class ITodo20Test : public RegisterSyncSourceTest { public: ITodo20Test() : RegisterSyncSourceTest("file_task", "eds_task") {} virtual void updateConfig(ClientTestConfig &config) const { config.m_sourceKnowsItemSemantic = false; config.m_type = "file:text/calendar:2.0"; } } ITodo20Test; static class SuperTest : public RegisterSyncSourceTest { public: SuperTest() : RegisterSyncSourceTest("file_calendar+todo", "calendar+todo") {} virtual void updateConfig(ClientTestConfig &config) const { config.m_sourceKnowsItemSemantic = false; config.m_type = "virtual:text/x-vcalendar"; config.m_subConfigs = "file_event,file_task"; } } superTest; } #endif // ENABLE_FILE SE_END_CXX syncevolution_1.4/src/backends/file/configure-sub.in000066400000000000000000000027251230021373600227440ustar00rootroot00000000000000dnl -*- mode: Autoconf; -*- dnl Invoke autogen.sh to produce a configure script. dnl Checks for required libraris can go here; none required for simple files. dnl dnl This is from the sqlite backend: dnl PKG_CHECK_MODULES(SQLITE, sqlite3, SQLITEFOUND=yes, [SQLITEFOUND=no]) dnl AC_SUBST(SQLITE_CFLAGS) dnl AC_SUBST(SQLITE_LIBS) FILE_CFLAGS= FILE_LIBS= AC_SUBST(FILE_CFLAGS) AC_SUBST(FILE_LIBS) dnl If additional compile flags are necessary to include the header dnl files of the backend, then add them here. BACKEND_CPPFLAGS="$BACKEND_CPPFLAGS $FILE_CFLAGS" dnl name of backend library (there could be more than one per directory), dnl name of the directory, dnl help string, dnl --enable/disable chosen explicitly dnl default, may depend on availability of prerequisites in more complex backends SE_ARG_ENABLE_BACKEND(file, file, [AS_HELP_STRING([--disable-file], [disable file-based backend which stores items in separate files in a fixed directory (default on)])], [enable_file="$enableval"], [enable_file="yes"] ) if test "$enable_file" = "yes"; then dnl It's good to check the prerequisites here, in case --enable-file was used. dnl test "x${SQLITEFOUND}" = "xyes" || AC_MSG_ERROR([--enable-sqlite requires pkg-config information for sqlite3, which was not found]) AC_DEFINE(ENABLE_FILE, 1, [file available]) fi syncevolution_1.4/src/backends/file/file.am000066400000000000000000000024411230021373600210750ustar00rootroot00000000000000dist_noinst_DATA += src/backends/file/configure-sub.in src_backends_file_lib = src/backends/file/syncfile.la MOSTLYCLEANFILES += $(src_backends_file_lib) if ENABLE_MODULES src_backends_file_backenddir = $(BACKENDS_DIRECTORY) src_backends_file_backend_LTLIBRARIES = $(src_backends_file_lib) else noinst_LTLIBRARIES += $(src_backends_file_lib) endif src_backends_file_src = \ src/backends/file/FileSyncSource.h \ src/backends/file/FileSyncSource.cpp src_backends_file_syncfile_la_SOURCES = $(src_backends_file_src) src_backends_file_syncfile_la_LIBADD = $(FILE_LIBS) $(SYNCEVOLUTION_LIBS) src_backends_file_syncfile_la_LDFLAGS = -module -avoid-version src_backends_file_syncfile_la_CXXFLAGS = $(SYNCEVOLUTION_CXXFLAGS) $(SYNCEVO_WFLAGS) src_backends_file_syncfile_la_CPPFLAGS = $(SYNCEVOLUTION_CFLAGS) -I$(top_srcdir)/test $(BACKEND_CPPFLAGS) src_backends_file_syncfile_la_DEPENDENCIES = src/syncevo/libsyncevolution.la # If you need special test cases for your sync source, then # install them here. Here's how the sqlite backend does that: # #../../testcases/sqlite_vcard21.vcf: $(FUNAMBOL_SUBDIR)/test/test/testcases/vcard21.vcf # mkdir -p ${@D} # perl -e '$$_ = join("", <>); s/^(ADR|TEL|EMAIL|PHOTO).*?(?=^\S)//msg; s/;X-EVOLUTION-UI-SLOT=\d+//g; print;' $< >$@ #all: ../../testcases/sqlite_vcard21.vcf syncevolution_1.4/src/backends/gnome/000077500000000000000000000000001230021373600200245ustar00rootroot00000000000000syncevolution_1.4/src/backends/gnome/GNOMEPlatform.cpp000066400000000000000000000213571230021373600231120ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #ifdef USE_GNOME_KEYRING extern "C" { #include } #include "GNOMEPlatform.h" #include #include #include SE_BEGIN_CXX // Occasionally, libgnome-keyring fails with the following error messages: // Gkr: received an invalid, unencryptable, or non-utf8 secret // Gkr: call to daemon returned an invalid response: (null).(null) // // We work around that by retrying the operation a few times, for at // most this period of time. Didn't really help, so disable it for now // by using a zero duration. static const double GNOMEKeyringRetryDuration = 2; // seconds static const double GNOMEKeyringRetryInterval = 0.1; // seconds /** * libgnome-keyring has an internal gkr_reset_session() * method which gets called when the "org.freedesktop.secrets" * disconnects from the D-Bus session bus. * * We cannot call that method directly, but we can get it called by * faking the "disconnect" signal. That works because * on_connection_filter() in gkr-operation.c doesn't check who the * sender of the signal is. * * Once gkr_reset_session() got called, the next operation will * re-establish the connection. After the failure above, the second * attempt usually works. * * Any other client using libgnome-keyring will also be tricked into * disconnecting temporarily. That should be fine, any running * operation will continue to run and complete (?). */ static void FlushGNOMEKeyring() { // Invoking dbus-send is easier than writing this in C++. // Besides, it ensures that the signal comes from some other // process. Not sure whether signals are sent back to the sender. system("dbus-send --session --type=signal /org/freedesktop/DBus org.freedesktop.DBus.NameOwnerChanged string:'org.freedesktop.secrets' string:':9.99' string:''"); } /** * GNOME keyring distinguishes between empty and unset * password keys. This function returns NULL for an * empty std::string. */ inline const char *passwdStr(const std::string &str) { return str.empty() ? NULL : str.c_str(); } static bool UseGNOMEKeyring(const InitStateTri &keyring) { // Disabled by user? if (keyring.getValue() == InitStateTri::VALUE_FALSE) { return false; } // If explicitly selected, it must be us. if (keyring.getValue() == InitStateTri::VALUE_STRING && !boost::iequals(keyring.get(), "GNOME")) { return false; } // Use GNOME Keyring. return true; } bool GNOMELoadPasswordSlot(const InitStateTri &keyring, const std::string &passwordName, const std::string &descr, const ConfigPasswordKey &key, InitStateString &password) { if (!UseGNOMEKeyring(keyring)) { SE_LOG_DEBUG(NULL, "not using GNOME keyring"); return false; } GnomeKeyringResult result = GNOME_KEYRING_RESULT_OK; GList* list; Timespec start = Timespec::monotonic(); double sleepSecs = 0; do { if (sleepSecs != 0) { SE_LOG_DEBUG(NULL, "%s: previous attempt to load password '%s' from GNOME keyring failed, will try again: %s", key.description.c_str(), key.toString().c_str(), gnome_keyring_result_to_message(result)); FlushGNOMEKeyring(); Sleep(sleepSecs); } result = gnome_keyring_find_network_password_sync(passwdStr(key.user), passwdStr(key.domain), passwdStr(key.server), passwdStr(key.object), passwdStr(key.protocol), passwdStr(key.authtype), key.port, &list); sleepSecs = GNOMEKeyringRetryInterval; } while (result != GNOME_KEYRING_RESULT_OK && (Timespec::monotonic() - start).duration() < GNOMEKeyringRetryDuration); // if find password stored in gnome keyring if(result == GNOME_KEYRING_RESULT_OK && list && list->data ) { GnomeKeyringNetworkPasswordData *key_data; key_data = (GnomeKeyringNetworkPasswordData*)list->data; password = std::string(key_data->password); gnome_keyring_network_password_list_free(list); SE_LOG_DEBUG(NULL, "%s: loaded password from GNOME keyring using %s", key.description.c_str(), key.toString().c_str()); } else { SE_LOG_DEBUG(NULL, "password not in GNOME keyring using %s: %s", key.toString().c_str(), result == GNOME_KEYRING_RESULT_NO_MATCH ? "no match" : result != GNOME_KEYRING_RESULT_OK ? gnome_keyring_result_to_message(result) : "empty result list"); } return true; } bool GNOMESavePasswordSlot(const InitStateTri &keyring, const std::string &passwordName, const std::string &password, const ConfigPasswordKey &key) { if (!UseGNOMEKeyring(keyring)) { SE_LOG_DEBUG(NULL, "not using GNOME keyring"); return false; } // Cannot store a password for just a user, that's ambiguous. // Also, a password without server ("user=foo") somehow removes // the password with server ("user=foo server=bar"). if (key.user.empty() || (key.domain.empty() && key.server.empty() && key.object.empty())) { SE_THROW(StringPrintf("%s: cannot store password in GNOME keyring, not enough attributes (%s). Try setting syncURL or remoteDeviceID if this is a sync password.", key.description.c_str(), key.toString().c_str())); } guint32 itemId; GnomeKeyringResult result = GNOME_KEYRING_RESULT_OK; // write password to keyring Timespec start = Timespec::monotonic(); double sleepSecs = 0; do { if (sleepSecs != 0) { SE_LOG_DEBUG(NULL, "%s: previous attempt to save password '%s' in GNOME keyring failed, will try again: %s", key.description.c_str(), key.toString().c_str(), gnome_keyring_result_to_message(result)); FlushGNOMEKeyring(); Sleep(sleepSecs); } result = gnome_keyring_set_network_password_sync(NULL, passwdStr(key.user), passwdStr(key.domain), passwdStr(key.server), passwdStr(key.object), passwdStr(key.protocol), passwdStr(key.authtype), key.port, password.c_str(), &itemId); sleepSecs = GNOMEKeyringRetryInterval; } while (result != GNOME_KEYRING_RESULT_OK && (Timespec::monotonic() - start).duration() < GNOMEKeyringRetryDuration); if (result != GNOME_KEYRING_RESULT_OK) { SyncContext::throwError(StringPrintf("%s: saving password '%s' in GNOME keyring failed: %s", key.description.c_str(), key.toString().c_str(), gnome_keyring_result_to_message(result))); } SE_LOG_DEBUG(NULL, "saved password in GNOME keyring using %s", key.toString().c_str()); // handled return true; } SE_END_CXX #endif // USE_GNOME_KEYRING syncevolution_1.4/src/backends/gnome/GNOMEPlatform.h000066400000000000000000000027531230021373600225560ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_GNOMEPLATFORM #define INCL_GNOMEPLATFORM #include #include #include SE_BEGIN_CXX struct ConfigPasswordKey; bool GNOMELoadPasswordSlot(const InitStateTri &keyring, const std::string &passwordName, const std::string &descr, const ConfigPasswordKey &key, InitStateString &password); bool GNOMESavePasswordSlot(const InitStateTri &keyring, const std::string &passwordName, const std::string &password, const ConfigPasswordKey &key); SE_END_CXX #endif // INCL_GNOMEPLATFORM syncevolution_1.4/src/backends/gnome/GNOMEPlatformRegister.cpp000066400000000000000000000022501230021373600246060ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #ifdef USE_GNOME_KEYRING #include "GNOMEPlatform.h" #include #include SE_BEGIN_CXX static class GNOMEInit { public: GNOMEInit() { GetLoadPasswordSignal().connect(1, GNOMELoadPasswordSlot); GetSavePasswordSignal().connect(1, GNOMESavePasswordSlot); } } gnomeinit; SE_END_CXX #endif // USE_GNOME_KEYRING syncevolution_1.4/src/backends/gnome/configure-sub.in000066400000000000000000000020271230021373600231250ustar00rootroot00000000000000PKG_CHECK_MODULES(KEYRING, [gnome-keyring-1 >= 2.20], HAVE_KEYRING=yes, HAVE_KEYRING=no) AC_ARG_ENABLE(gnome-keyring, AS_HELP_STRING([--enable-gnome-keyring], [enables or disables support for the GNOME keyring; default is on if development files are available]), [enable_gnome_keyring="$enableval" test "$enable_gnome_keyring" = "yes" || test "$enable_gnome_keyring" = "no" || AC_MSG_ERROR([invalid value for --enable-gnome-keyring: $enable_gnome_keyring]) test "$enable_gnome_keyring" = "no" || test "$HAVE_KEYRING" = "yes" || AC_MSG_ERROR([gnome-keyring-1 pkg >= 2.20 not found, needed for --enable-gnome-keyring])], enable_gnome_keyring="$HAVE_KEYRING") if test $enable_gnome_keyring = "yes"; then have_keyring=yes AC_DEFINE(USE_GNOME_KEYRING, 1, [define if gnome keyring should be used in dbus service]) # link into static executables, similar to a SyncSource SYNCSOURCES="$SYNCSOURCES src/backends/gnome/platformgnome.la" fi syncevolution_1.4/src/backends/gnome/gnome.am000066400000000000000000000017501230021373600214530ustar00rootroot00000000000000dist_noinst_DATA += src/backends/gnome/configure-sub.in src_backends_gnome_lib = src/backends/gnome/platformgnome.la MOSTLYCLEANFILES += $(src_backends_gnome_lib) src_backends_gnome_platformgnome_la_SOURCES = \ src/backends/gnome/GNOMEPlatform.h \ src/backends/gnome/GNOMEPlatform.cpp if ENABLE_MODULES src_backends_gnome_backenddir = $(BACKENDS_DIRECTORY) src_backends_gnome_backend_LTLIBRARIES = $(src_backends_gnome_lib) src_backends_gnome_platformgnome_la_SOURCES += \ src/backends/gnome/GNOMEPlatformRegister.cpp else noinst_LTLIBRARIES += $(src_backends_gnome_lib) endif src_backends_gnome_platformgnome_la_LIBADD = $(KEYRING_LIBS) $(SYNCEVOLUTION_LIBS) src_backends_gnome_platformgnome_la_LDFLAGS = -module -avoid-version src_backends_gnome_platformgnome_la_CXXFLAGS = $(KEYRING_CFLAGS) $(SYNCEVOLUTION_CFLAGS) src_backends_gnome_platformgnome_la_CPPFLAGS = -I$(top_srcdir)/test $(BACKEND_CPPFLAGS) src_backends_gnome_platformgnome_la_DEPENDENCIES = src/syncevo/libsyncevolution.la syncevolution_1.4/src/backends/goa/000077500000000000000000000000001230021373600174655ustar00rootroot00000000000000syncevolution_1.4/src/backends/goa/GOARegister.cpp000066400000000000000000000032261230021373600223070ustar00rootroot00000000000000/* * Copyright (C) 2013 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include "goa.h" #include #include SE_BEGIN_CXX static class GOAProvider : public IdentityProvider { public: GOAProvider() : IdentityProvider("goa", "goa:\n" " Authentication using GNOME Online Accounts,\n" " using an account created and managed with GNOME Control Center.") {} virtual boost::shared_ptr create(const InitStateString &username, const InitStateString &password) { // Returning NULL if not enabled... boost::shared_ptr provider; #ifdef USE_GOA provider = createGOAAuthProvider(username, password); #endif return provider; } } gsso; SE_END_CXX syncevolution_1.4/src/backends/goa/README000066400000000000000000000106571230021373600203560ustar00rootroot00000000000000Google CalDAV/CardDAV via OAuth2 with GNOME Online Accounts (GOA) ================================================================= Setup ----- SyncEvolution depends on a GNOME Online Accounts with CalDAV *and* CardDAV enabled for Google. This is hard-coded in the source code, so recompiling is the only (sane) way to change that. CalDAV has been enabled for a while, CardDAV is recent (>= 3.10). It is possible to patch 3.8 without recompiling (see below). Versions older than 3.8 do not work because they lack OAuth2 support. SyncEvolution needs an active account for Google in the GNOME Control Center, under "online accounts". Enable the different data categories if and only if you want to access the data with the core GNOME apps. SyncEvolution ignores these settings. Usage ----- OAuth2 authentication with GNOME Online Accounts is enabled by setting username or databaseUser to a string of the format goa: Typically there is only one account using a Google email address, so that can be used to select the account. SyncEvolution checks if it is really unique and if not, provides a list of all accounts with their account ID. Then the unique account ID should be used instead. The base URL for each service currently needs to be given via syncURL: syncevolution --print-databases \ backend=carddav \ username=goa:john.doe@gmail.com \ syncURL=https://www.googleapis.com/.well-known/carddav src/syncevolution --print-databases \ backend=caldav \ username=goa:john.doe@gmail.com \ syncURL=https://apidata.googleusercontent.com/caldav/v2 Once that works, follow the "CalDAV and CardDAV" instructions from the README with the different username and syncURL. Debugging --------- Add --daemon=no to the command line to prevent shifting the actual command executing into syncevo-dbus-server and (from there) syncevo-dbus-helper. Set SYNCEVOLUTION_DEBUG=1 to see all debug messages and increase the loglevel to see HTTP requests: SYNCEVOLUTION_DEBUG=1 syncevolution --daemon=no \ loglevel=4 \ --print-databases \ ... Known Problems -------------- When accessing CardDAV: status-line] < HTTP/1.1 401 Unauthorized [hdr] WWW-Authenticate: AuthSub realm="https://www.google.com/accounts/AuthSubRequest" allowed-scopes="https://www.googleapis.com/auth/carddav" ... GData authError Authorization Invalid Credentials ... [INFO] operation temporarily (?) failed, going to retry in 5.0s before giving up in 295.8s: PROPFIND: Neon error code 3 = NE_AUTH, HTTP status 401: Could not authenticate to server: ignored AuthSub challenge ... This happens when using a GNOME Online Accounts which does (or did) not request CardDAV access when logging into Google. Install GNOME Online Accounts >= 3.10 or patch it (see below), "killall goa-daemon", then re-create the account in the GNOME Control Center. Patching GOA 3.8 ---------------- It is possible to add CardDAV support to 3.8 without recompiling GNOME Online Accounts. However, the downside is that this approach has to disable access to some other kind of data and breaks when updating or reinstalling GOA. 1. Locate libgoa-backend-1.0.so.0.0.0: typically it is in /usr/lib or /usr/lib64. 2. Open it in a text editor which can handle binary data (like emacs). 3. Switch to "overwrite mode". 4. Find the string starting with https://www.googleapis.com/auth/userinfo.email 6. Overwrite the part which you don't need with https://www.googleapis.com/auth/carddav and spaces. For example, if Google Docs access is not needed, replace "https://docs.google.com/feeds/ https://docs.googleusercontent.com/ https://spreadsheets.google.com/feeds/ " with "https://www.googleapis.com/auth/carddav " Here's a perl command which replaces Google Docs with CardDAV: perl -pi -e 's;https://docs.google.com/feeds/ https://docs.googleusercontent.com/ https://spreadsheets.google.com/feeds/ ;https://www.googleapis.com/auth/carddav ;' /usr/lib*/libgoa-backend-1.0.so.0.0.0 syncevolution_1.4/src/backends/goa/configure-sub.in000066400000000000000000000013151230021373600225650ustar00rootroot00000000000000AC_ARG_ENABLE(goa, AS_HELP_STRING([--disable-goa], [enables or disables support for the GNOME Online Account single-sign-on system; default is on]), [enable_goa="$enableval" test "$enable_goa" = "yes" || test "$enable_goa" = "no" || AC_MSG_ERROR([invalid value for --enable-goa: $enable_goa]) ], enable_goa="yes") if test $enable_goa = "yes"; then AC_DEFINE(USE_GOA, 1, [use GNOME Online Accounts]) # link into static executables, similar to a SyncSource SYNCSOURCES="$SYNCSOURCES src/backends/goa/providergoa.la" fi # conditional compilation in make AM_CONDITIONAL([USE_GOA], [test "$use_goa" = "yes"]) syncevolution_1.4/src/backends/goa/goa.am000066400000000000000000000020121230021373600205450ustar00rootroot00000000000000dist_noinst_DATA += src/backends/goa/configure-sub.in \ src/backends/goa/README \ $(NONE) src_backends_goa_lib = src/backends/goa/providergoa.la MOSTLYCLEANFILES += $(src_backends_goa_lib) src_backends_goa_providergoa_la_SOURCES = \ src/backends/goa/goa.h \ src/backends/goa/goa.cpp \ $(NONE) if ENABLE_MODULES src_backends_goa_backenddir = $(BACKENDS_DIRECTORY) src_backends_goa_backend_LTLIBRARIES = $(src_backends_goa_lib) src_backends_goa_providergoa_la_SOURCES += \ src/backends/goa/GOARegister.cpp else noinst_LTLIBRARIES += $(src_backends_goa_lib) endif src_backends_goa_providergoa_la_LIBADD = $(SYNCEVOLUTION_LIBS) $(gdbus_build_dir)/libgdbussyncevo.la $(DBUS_LIBS) src_backends_goa_providergoa_la_LDFLAGS = -module -avoid-version src_backends_goa_providergoa_la_CXXFLAGS = $(SYNCEVOLUTION_CFLAGS) $(SYNCEVO_WFLAGS) $(DBUS_CFLAGS) src_backends_goa_providergoa_la_CPPFLAGS = -I$(gdbus_dir) -I$(top_srcdir)/test $(BACKEND_CPPFLAGS) src_backends_goa_providergoa_la_DEPENDENCIES = src/syncevo/libsyncevolution.la syncevolution_1.4/src/backends/goa/goa.cpp000066400000000000000000000231501230021373600207400ustar00rootroot00000000000000/* * Copyright (C) 2013 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #ifdef USE_GOA #include "goa.h" #include #include #include #include #include #include SE_BEGIN_CXX /* * We call the GOA D-Bus API directly. This is easier than using * libgoa because our own D-Bus wrapper gives us data in C++ data * structures. It also avoids another library dependency. */ static const char GOA_BUS_NAME[] = "org.gnome.OnlineAccounts"; static const char GOA_PATH[] = "/org/gnome/OnlineAccounts"; static const char OBJECT_MANAGER_INTERFACE[] = "org.freedesktop.DBus.ObjectManager"; static const char OBJECT_MANAGER_GET_MANAGED_OBJECTS[] = "GetManagedObjects"; static const char GOA_ACCOUNT_INTERFACE[] = "org.gnome.OnlineAccounts.Account"; static const char GOA_ACCOUNT_ENSURE_CREDENTIALS[] = "EnsureCredentials"; static const char GOA_ACCOUNT_PRESENTATION_IDENTITY[] = "PresentationIdentity"; static const char GOA_ACCOUNT_ID[] = "Id"; static const char GOA_ACCOUNT_PROVIDER_NAME[] = "ProviderName"; static const char GOA_OAUTH2_INTERFACE[] = "org.gnome.OnlineAccounts.OAuth2Based"; static const char GOA_OAUTH2_GET_ACCESS_TOKEN[] = "GetAccessToken"; class GOAAccount; class GOAManager : private GDBusCXX::DBusRemoteObject { typedef std::map // property value - we only care about strings > Properties; typedef std::map Interfaces; typedef std::map ManagedObjects; GDBusCXX::DBusClientCall1 m_getManagedObjects; public: GOAManager(const GDBusCXX::DBusConnectionPtr &conn); /** * Find a particular account, identified by its representation ID * (the unique user visible string). The account must support OAuth2, * otherwise an error is thrown. */ boost::shared_ptr lookupAccount(const std::string &representationID); }; class GOAAccount { GDBusCXX::DBusRemoteObject m_account; GDBusCXX::DBusRemoteObject m_oauth2; public: GOAAccount(const GDBusCXX::DBusConnectionPtr &conn, const std::string &path); GDBusCXX::DBusClientCall1 m_ensureCredentials; GDBusCXX::DBusClientCall1 m_getAccessToken; }; GOAManager::GOAManager(const GDBusCXX::DBusConnectionPtr &conn) : GDBusCXX::DBusRemoteObject(conn, GOA_PATH, OBJECT_MANAGER_INTERFACE, GOA_BUS_NAME), m_getManagedObjects(*this, OBJECT_MANAGER_GET_MANAGED_OBJECTS) { } boost::shared_ptr GOAManager::lookupAccount(const std::string &username) { SE_LOG_DEBUG(NULL, "Looking up all accounts in GNOME Online Accounts, searching for '%s'.", username.c_str()); ManagedObjects objects = m_getManagedObjects(); GDBusCXX::DBusObject_t accountPath; bool unique = true; bool hasOAuth2 = false; std::vector accounts; BOOST_FOREACH (const ManagedObjects::value_type &object, objects) { const GDBusCXX::DBusObject_t &path = object.first; const Interfaces &interfaces = object.second; // boost::adaptors::keys() would be nicer, but is not available on Ubuntu Lucid. std::list interfaceKeys; BOOST_FOREACH (const Interfaces::value_type &entry, interfaces) { interfaceKeys.push_back(entry.first); } SE_LOG_DEBUG(NULL, "GOA object %s implements %s", path.c_str(), boost::join(interfaceKeys, ", ").c_str()); Interfaces::const_iterator it = interfaces.find(GOA_ACCOUNT_INTERFACE); if (it != interfaces.end()) { const Properties &properties = it->second; Properties::const_iterator id = properties.find(GOA_ACCOUNT_ID); Properties::const_iterator presentationID = properties.find(GOA_ACCOUNT_PRESENTATION_IDENTITY); if (id != properties.end() && presentationID != properties.end()) { const std::string &idStr = boost::get(id->second); const std::string &presentationIDStr = boost::get(presentationID->second); Properties::const_iterator provider = properties.find(GOA_ACCOUNT_PROVIDER_NAME); std::string description = StringPrintf("%s, %s = %s", provider == properties.end() ? "???" : boost::get(provider->second).c_str(), presentationIDStr.c_str(), idStr.c_str()); SE_LOG_DEBUG(NULL, "GOA account %s", description.c_str()); accounts.push_back(description); // The assumption here is that ID and presentation // identifier are so different that there can be // no overlap. Otherwise we would have to know // whether the user gave us an ID or presentation // identifier. if (idStr == username || presentationIDStr == username) { if (accountPath.empty()) { accountPath = path; hasOAuth2 = interfaces.find(GOA_OAUTH2_INTERFACE) != interfaces.end(); SE_LOG_DEBUG(NULL, "found matching GNOME Online Account for '%s': %s", username.c_str(), description.c_str()); } else { unique = false; } } } else { SE_LOG_DEBUG(NULL, "ignoring %s, lacks expected properties", path.c_str()); } } } std::sort(accounts.begin(), accounts.end()); if (accountPath.empty()) { if (accounts.empty()) { SE_THROW(StringPrintf("GNOME Online Account '%s' not found. You must set up the account in GNOME Control Center/Online Accounts first.", username.c_str())); } else { SE_THROW(StringPrintf("GNOME Online Account '%s' not found. Choose one of the following:\n%s", username.c_str(), boost::join(accounts, "\n").c_str())); } } else if (!unique) { SE_THROW(StringPrintf("GNOME Online Account '%s' is not unique. Choose one of the following, using the unique ID instead of the more ambiguous representation name:\n%s", username.c_str(), boost::join(accounts, "\n").c_str())); } else if (!hasOAuth2) { SE_THROW(StringPrintf("Found GNOME Online Account '%s', but it does not support OAuth2. Are you sure that you picked the right account and that you are using GNOME Online Accounts >= 3.8?", username.c_str())); } boost::shared_ptr account(new GOAAccount(getConnection(), accountPath)); return account; } GOAAccount::GOAAccount(const GDBusCXX::DBusConnectionPtr &conn, const std::string &path) : m_account(conn, path, GOA_ACCOUNT_INTERFACE, GOA_BUS_NAME), m_oauth2(conn, path, GOA_OAUTH2_INTERFACE, GOA_BUS_NAME), m_ensureCredentials(m_account, GOA_ACCOUNT_ENSURE_CREDENTIALS), m_getAccessToken(m_oauth2, GOA_OAUTH2_GET_ACCESS_TOKEN) { } class GOAAuthProvider : public AuthProvider { boost::shared_ptr m_account; public: GOAAuthProvider(const boost::shared_ptr &account) : m_account(account) {} virtual bool methodIsSupported(AuthMethod method) const { return method == AUTH_METHOD_OAUTH2; } virtual Credentials getCredentials() const { SE_THROW("only OAuth2 is supported"); } virtual std::string getOAuth2Bearer(int failedTokens) const { m_account->m_ensureCredentials(); std::string token = m_account->m_getAccessToken(); return token; } virtual std::string getUsername() const { return ""; } }; boost::shared_ptr createGOAAuthProvider(const InitStateString &username, const InitStateString &password) { // Because we share the connection, hopefully this won't be too expensive. GDBusCXX::DBusErrorCXX err; GDBusCXX::DBusConnectionPtr conn = dbus_get_bus_connection("SESSION", NULL, false, &err); if (!conn) { err.throwFailure("connecting to session bus"); } GOAManager manager(conn); boost::shared_ptr account = manager.lookupAccount(username); boost::shared_ptr provider(new GOAAuthProvider(account)); return provider; } SE_END_CXX #endif // USE_GOA syncevolution_1.4/src/backends/goa/goa.h000066400000000000000000000022611230021373600204050ustar00rootroot00000000000000/* * Copyright (C) 2013 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_SYNC_EVOLUTION_GOA_AUTH_PROVIDER # define INCL_SYNC_EVOLUTION_GOA_AUTH_PROVIDER #include #include #include SE_BEGIN_CXX class AuthProvider; boost::shared_ptr createGOAAuthProvider(const InitStateString &username, const InitStateString &password); SE_END_CXX #endif syncevolution_1.4/src/backends/kcalextended/000077500000000000000000000000001230021373600213525ustar00rootroot00000000000000syncevolution_1.4/src/backends/kcalextended/KCalExtendedSource.cpp000066400000000000000000000551431230021373600255420ustar00rootroot00000000000000/* * Copyright (C) 2007-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "config.h" #ifdef ENABLE_KCALEXTENDED #include "KCalExtendedSource.h" #include #include #include #include #include #include #include #include #include #include SE_BEGIN_CXX /** * All std::string and plain C strings in SyncEvolution are in UTF-8. * QString must be told about that explicitly. */ static QString std2qstring(const std::string &str) { return QString::fromUtf8(str.c_str()); } // static QString std2qstring(const char *str) { return QString::fromUtf8(str); } /** * Convert back to UTF-8. */ static std::string qstring2std(const QString str) { return str.toUtf8().constData(); } class KCalExtendedData { KCalExtendedSource *m_parent; bool m_modified; QString m_notebook; QString m_notebookUID; KCalCore::IncidenceBase::IncidenceType m_type; mKCal::ExtendedCalendar::Ptr m_calendar; mKCal::ExtendedStorage::Ptr m_storage; public: KCalExtendedData(KCalExtendedSource *parent, const QString ¬ebook, const KCalExtendedSource::Type &type) : m_parent(parent), m_modified(false), m_notebook(notebook) { switch (type) { case KCalExtendedSource::Event: m_type = KCalCore::IncidenceBase::TypeEvent; break; case KCalExtendedSource::Todo: m_type = KCalCore::IncidenceBase::TypeTodo; break; case KCalExtendedSource::Journal: m_type = KCalCore::IncidenceBase::TypeJournal; break; default: m_type = KCalCore::IncidenceBase::TypeUnknown; break; } if (!qApp) { static const char *argv[] = { "SyncEvolution" }; static int argc = 1; new QCoreApplication(argc, (char **)argv); } } void extractIncidences(KCalCore::Incidence::List &incidences, SyncSourceChanges::State state, SyncSourceChanges &changes) { foreach (KCalCore::Incidence::Ptr incidence, incidences) { if (incidence->type() == m_type) { SE_LOG_DEBUG(NULL, "item %s %s", getItemID(incidence).getLUID().c_str(), state == SyncSourceChanges::ANY ? "exists" : state == SyncSourceChanges::NEW ? "is new" : state == SyncSourceChanges::UPDATED ? "is updated" : state == SyncSourceChanges::DELETED ? "was deleted" : "unknown state"); changes.addItem(getItemID(incidence).getLUID(), state); } } } /** * An item is identified in the calendar by * its UID (unique ID) and RID (recurrence ID). * The RID may be empty. * * This is turned into a SyncML LUID by * concatenating them: -rid. */ class ItemID { public: ItemID(const string &uid, const string &rid) : m_uid(uid), m_rid(rid) {} ItemID(const char *uid, const char *rid): m_uid(uid ? uid : ""), m_rid(rid ? rid : "") {} ItemID(const string &luid); const string m_uid, m_rid; QString getIDString() const { return std2qstring(m_uid); } KDateTime getDateTime() const { return KDateTime::fromString(std2qstring(m_rid)); } string getLUID() const; static string getLUID(const string &uid, const string &rid); }; ItemID getItemID(const KCalCore::Incidence::Ptr &incidence); KCalCore::Incidence::Ptr findIncidence(const string &luid); friend class KCalExtendedSource; }; string KCalExtendedData::ItemID::getLUID() const { return getLUID(m_uid, m_rid); } string KCalExtendedData::ItemID::getLUID(const string &uid, const string &rid) { return uid + "-rid" + rid; } KCalExtendedData::ItemID::ItemID(const string &luid) { size_t ridoff = luid.rfind("-rid"); if (ridoff != luid.npos) { const_cast(m_uid) = luid.substr(0, ridoff); const_cast(m_rid) = luid.substr(ridoff + strlen("-rid")); } else { const_cast(m_uid) = luid; } } KCalCore::Incidence::Ptr KCalExtendedData::findIncidence(const string &luid) { ItemID id(luid); QString uid = id.getIDString(); KDateTime rid = id.getDateTime(); // if (!m_storage->load(uid, rid)) { // m_parent->throwError(string("failed to load incidence ") + luid); // } KCalCore::Incidence::Ptr incidence = m_calendar->incidence(uid, rid); return incidence; } KCalExtendedData::ItemID KCalExtendedData::getItemID(const KCalCore::Incidence::Ptr &incidence) { QString uid = incidence->uid(); KDateTime rid = incidence->recurrenceId(); string ridStr; if (rid.isValid()) { ridStr = qstring2std(rid.toString()); } return ItemID(qstring2std(uid), ridStr); } KCalExtendedSource::KCalExtendedSource(const SyncSourceParams ¶ms, Type type) : TestingSyncSource(params) { SyncSourceRevisions::init(this, this, 0, m_operations); switch (type) { case Event: SyncSourceLogging::init(InitList("SUMMARY") + "LOCATION", ", ", m_operations); break; case Todo: SyncSourceLogging::init(InitList("SUMMARY"), ", ", m_operations); break; case Journal: SyncSourceLogging::init(InitList("SUBJECT"), ", ", m_operations); break; default: throwError("invalid calendar type"); break; } m_data = NULL; m_type = type; m_delete_run = 0; m_insert_run = 0; } KCalExtendedSource::~KCalExtendedSource() { delete m_data; } std::string KCalExtendedSource::getMimeType() const { return m_type == Journal ? "text/calendar+plain" : "text/calendar"; } std::string KCalExtendedSource::getMimeVersion() const { return "2.0"; } void KCalExtendedSource::open() { // read specified database name from "database" property std::string databaseID = getDatabaseID(); // TODO: also support todoType m_data = new KCalExtendedData(this, databaseID.c_str(), m_type); m_data->m_calendar = mKCal::ExtendedCalendar::Ptr(new mKCal::ExtendedCalendar(KDateTime::Spec::LocalZone())); if (databaseID.empty() || boost::starts_with(databaseID, "file://") ) { // if databaseID is empty, create default storage at default location // else if databaseID has a "file://" prefix, create storage at the specified place // use default notebook in default storage if ( boost::starts_with(databaseID, "file://") ) { mKCal::SqliteStorage::Ptr ss(new mKCal::SqliteStorage(m_data->m_calendar, QString(databaseID.c_str() + strlen("file://")), false)); m_data->m_storage = ss.staticCast(); } else { m_data->m_storage = mKCal::ExtendedCalendar::defaultStorage(m_data->m_calendar); } if (!m_data->m_storage->open()) { throwError("failed to open storage"); } #ifdef ENABLE_MAEMO mKCal::Notebook::Ptr defaultNotebook; // For notes, we need a different default database: // Notes (uid:66666666-7777-8888-9999-000000000000) if (databaseID.empty() && m_type == Journal) { defaultNotebook = m_data->m_storage->notebook("66666666-7777-8888-9999-000000000000"); } else { defaultNotebook = m_data->m_storage->defaultNotebook(); } #else mKCal::Notebook::Ptr defaultNotebook = m_data->m_storage->defaultNotebook(); #endif if (!defaultNotebook) { throwError("no default Notebook"); } m_data->m_notebookUID = defaultNotebook->uid(); #ifdef ENABLE_MAEMO } else if (boost::starts_with(databaseID, "uid:")) { // if databaseID has a "uid:" prefix, open existing notebook with given ID in default storage m_data->m_storage = mKCal::ExtendedCalendar::defaultStorage(m_data->m_calendar); if (!m_data->m_storage->open()) { throwError("failed to open storage"); } QString uid = databaseID.c_str() + strlen("uid:"); mKCal::Notebook::Ptr notebook = m_data->m_storage->notebook(uid); if ( !notebook ) { throwError(string("no such notebook with UID \"") + uid.toStdString() + string("\" in default storage")); } m_data->m_notebookUID = notebook->uid(); #endif } else { // use databaseID as notebook name to search for an existing notebook // if found use it, otherwise: // 1) with "SyncEvolution_Test_" prefix, create a new notebook with given name and add it to default storage // 2) without a special prefix, throw an error m_data->m_storage = mKCal::ExtendedCalendar::defaultStorage(m_data->m_calendar); if (!m_data->m_storage->open()) { throwError("failed to open storage"); } QString name = databaseID.c_str(); mKCal::Notebook::Ptr notebook; mKCal::Notebook::List notebookList = m_data->m_storage->notebooks(); mKCal::Notebook::List::Iterator it; for ( it = notebookList.begin(); it != notebookList.end(); ++it ) { if ( name == (*it)->name() ) { break; } } if ( it == notebookList.end() ) { if ( boost::starts_with(databaseID, "SyncEvolution_Test_") ) { notebook = mKCal::Notebook::Ptr ( new mKCal::Notebook(QString(), name, QString(), QString(), false, true, false, false,true) ); if ( !notebook ) { throwError("failed to create notebook"); } m_data->m_storage->addNotebook(notebook, false); } else { throwError(string("no such notebook with name \"") + string(databaseID) + string("\" in default storage")); } } else { notebook = *it; } m_data->m_notebookUID = notebook->uid(); } // we are not currently using partial loading because there were // issues with it (BMC #6061); the load() calls elsewhere in this // file are commented out if (!m_data->m_storage->loadNotebookIncidences(m_data->m_notebookUID)) { throwError("failed to load calendar"); } } bool KCalExtendedSource::isEmpty() { switch (m_data->m_type) { case KCalCore::IncidenceBase::TypeEvent: return !m_data->m_calendar->eventCount(); case KCalCore::IncidenceBase::TypeTodo: return !m_data->m_calendar->todoCount(); case KCalCore::IncidenceBase::TypeJournal: return !m_data->m_calendar->journalCount(); default: return true; } } void KCalExtendedSource::close() { if (m_data->m_storage) { m_data->m_storage->close(); } if (m_data->m_calendar) { m_data->m_calendar->close(); } } void KCalExtendedSource::enableServerMode() { SyncSourceAdmin::init(m_operations, this); SyncSourceBlob::init(m_operations, getCacheDir()); } bool KCalExtendedSource::serverModeEnabled() const { return m_operations.m_loadAdminData; } KCalExtendedSource::Databases KCalExtendedSource::getDatabases() { Databases result; m_data = new KCalExtendedData(this, getDatabaseID().c_str(), m_type); m_data->m_calendar = mKCal::ExtendedCalendar::Ptr(new mKCal::ExtendedCalendar(KDateTime::Spec::LocalZone())); m_data->m_storage = mKCal::ExtendedCalendar::defaultStorage(m_data->m_calendar); if (!m_data->m_storage->open()) { throwError("failed to open storage"); } mKCal::Notebook::List notebookList = m_data->m_storage->notebooks(); mKCal::Notebook::List::Iterator it; for ( it = notebookList.begin(); it != notebookList.end(); ++it ) { #ifdef ENABLE_MAEMO string name = (*it)->name().toStdString(); string uid = (*it)->uid().toStdString(); // For notes, we need a different default database: // Notes (uid:66666666-7777-8888-9999-000000000000) bool isDefault = (m_type != Journal) ? (*it)->isDefault() : (uid == "66666666-7777-8888-9999-000000000000"); result.push_back(Database( name, "uid:" + uid, isDefault )); #else bool isDefault = (*it)->isDefault(); result.push_back(Database( (*it)->name().toStdString(), (m_data->m_storage).staticCast()->databaseName().toStdString(), isDefault)); #endif } m_data->m_storage->close(); m_data->m_calendar->close(); return result; } void KCalExtendedSource::beginSync(const std::string &lastToken, const std::string &resumeToken) { const char *anchor = resumeToken.empty() ? lastToken.c_str() : resumeToken.c_str(); KCalCore::Incidence::List incidences; // return all items incidences = m_data->m_calendar->incidences(); // if (!m_data->m_storage->allIncidences(&incidences, m_data->m_notebookUID)) { // throwError("allIncidences() failed"); // } m_data->extractIncidences(incidences, SyncSourceChanges::ANY, *this); if (*anchor) { SE_LOG_DEBUG(NULL, "checking for changes since %s UTC", anchor); KDateTime endSyncTime(QDateTime::fromString(QString(anchor), Qt::ISODate), KDateTime::Spec::UTC()); KCalCore::Incidence::List added, modified, deleted; if (!m_data->m_storage->insertedIncidences(&added, endSyncTime, m_data->m_notebookUID)) { throwError("insertedIncidences() failed"); } if (!m_data->m_storage->modifiedIncidences(&modified, endSyncTime, m_data->m_notebookUID)) { throwError("modifiedIncidences() failed"); } if (!m_data->m_storage->deletedIncidences(&deleted, endSyncTime, m_data->m_notebookUID)) { throwError("deletedIncidences() failed"); } // It is guaranteed that modified and inserted items are // returned as inserted, so no need to check that. m_data->extractIncidences(added, SyncSourceChanges::NEW, *this); m_data->extractIncidences(modified, SyncSourceChanges::UPDATED, *this); m_data->extractIncidences(deleted, SyncSourceChanges::DELETED, *this); } } std::string KCalExtendedSource::endSync(bool success) { if (m_data->m_modified) { if (!m_data->m_storage->save()) { throwError("could not save calendar"); } time_t modtime = time(NULL); // Saving set the modified time stamps of all items needed // saving, so ensure that we sleep for one second starting now. // Must sleep before taking the time stamp for the anchor, // because changes made after and including (>= instead of >) that time // stamp will be considered as "changes made after last sync". time_t current = modtime; do { sleep(1 - (current - modtime)); current = time(NULL); } while (current - modtime < 1); m_delete_run = 0; m_insert_run = 0; } QDateTime now = QDateTime::currentDateTime().toUTC(); string anchor(qstring2std(now.toString(Qt::ISODate))); return anchor; } void KCalExtendedSource::readItem(const string &uid, std::string &item) { KCalCore::Incidence::Ptr incidence(m_data->findIncidence(uid)); if (!incidence) { throwError(string("failure extracting ") + uid); } KCalCore::Calendar::Ptr calendar(new KCalCore::MemoryCalendar(KDateTime::Spec::LocalZone())); calendar->addIncidence(incidence); KCalCore::ICalFormat formatter; item = qstring2std(formatter.toString(calendar)); } TestingSyncSource::InsertItemResult KCalExtendedSource::insertItem(const string &uid, const std::string &item) { if (m_delete_run > 0) { // Since the storage's save() might do deletes *after* inserts, // the final save() could fail if we're doing a refresh-from-peer // (where everything is first deleted and then reinserted), as // the inserts will fail due to the existing entries not being // deleted yet. To avoid the problem, make sure we save between // the deletes and the inserts. if (!m_data->m_storage->save()) { throwError("could not save calendar"); } m_delete_run = 0; m_insert_run = 0; } KCalCore::Calendar::Ptr calendar(new KCalCore::MemoryCalendar(KDateTime::Spec::LocalZone())); KCalCore::ICalFormat parser; if (!parser.fromString(calendar, std2qstring(item))) { throwError("error parsing iCalendar 2.0 item"); } KCalCore::Incidence::List incidences = calendar->rawIncidences(); if (incidences.empty()) { throwError("iCalendar 2.0 item empty?!"); } InsertItemResultState updated; string newUID; string oldUID = uid; // check for existing incidence with this UID and RECURRENCE-ID first, // update when found even if caller didn't know about that existing // incidence if (uid.empty()) { QString id = incidences[0]->uid(); KDateTime rid = incidences[0]->recurrenceId(); if (!id.isEmpty()) { // m_data->m_storage->load(id, rid); KCalCore::Incidence::Ptr incidence = m_data->m_calendar->incidence(id, rid); if (incidence) { oldUID = m_data->getItemID(incidence).getLUID(); } } } // Brute-force copying of all time zone definitions. Ignores name // conflicts, which is something better handled in a generic mKCal // API function (BMC #8604). KCalCore::ICalTimeZones *source = calendar->timeZones(); if (source) { KCalCore::ICalTimeZones *target = m_data->m_calendar->timeZones(); if (target) { BOOST_FOREACH(const KCalCore::ICalTimeZone &zone, source->zones().values()) { target->add(zone); } } } if (oldUID.empty()) { KCalCore::Incidence::Ptr incidence = incidences[0]; updated = ITEM_OKAY; if (!m_data->m_calendar->addIncidence(incidence)) { throwError("could not add incidence"); } m_data->m_calendar->setNotebook(incidence, m_data->m_notebookUID); newUID = m_data->getItemID(incidence).getLUID(); } else { KCalCore::Incidence::Ptr incidence = incidences[0]; updated = uid.empty() ? ITEM_REPLACED : ITEM_OKAY; newUID = oldUID; KCalCore::Incidence::Ptr original = m_data->findIncidence(oldUID); if (!original) { throwError("incidence to be updated not found"); } if (original->type() != incidence->type()) { throwError("cannot update incidence, wrong type?!"); } // preserve UID and RECURRENCE-ID, because this must not change // and some peers don't preserve it incidence->setUid(original->uid()); if (original->hasRecurrenceId()) { incidence->setRecurrenceId(original->recurrenceId()); } // created() corresponds to the CREATED property (= time when // item was created in the local storage for the first time), // so it can never be modified by our peer and must be // preserved unconditionally in updates. incidence->setCreated(original->created()); // now overwrite item in calendar (KCalCore::IncidenceBase &)*original = (KCalCore::IncidenceBase &)*incidence; m_data->m_calendar->setNotebook(original, m_data->m_notebookUID); // no need to save } m_data->m_modified = true; m_insert_run++; return InsertItemResult(newUID, "", updated); } void KCalExtendedSource::deleteItem(const string &uid) { KCalCore::Incidence::Ptr incidence = m_data->findIncidence(uid); if (!incidence) { // throwError(string("incidence ") + uid + " not found"); // don't treat this as error, it can happen, for example // when the master event was removed before (MBC #6061) return; } if (!m_data->m_calendar->deleteIncidence(incidence)) { throwError(string("could not delete incidence") + uid); } m_data->m_modified = true; m_delete_run++; } void KCalExtendedSource::listAllItems(RevisionMap_t &revisions) { KCalCore::Incidence::List incidences; incidences = m_data->m_calendar->incidences(); // if (!m_data->m_storage->allIncidences(&incidences, m_data->m_notebookUID)) { // throwError("allIncidences() failed"); // } foreach (KCalCore::Incidence::Ptr incidence, incidences) { if (incidence->type() == m_data->m_type) { revisions[m_data->getItemID(incidence).getLUID()] = "1"; } } } std::string KCalExtendedSource::getDescription(const string &luid) { try { KCalCore::Incidence::Ptr incidence = m_data->findIncidence(luid); if (incidence) { list parts; QString str; // for VEVENT str = incidence->summary(); if (!str.isEmpty()) { parts.push_back(qstring2std(str)); } str = incidence->location(); if (!str.isEmpty()) { parts.push_back(qstring2std(str)); } return boost::join(parts, ", "); } else { return ""; } } catch (...) { return ""; } } SE_END_CXX #endif /* ENABLE_KCALEXTENDED */ #ifdef ENABLE_MODULES # include "KCalExtendedSourceRegister.cpp" #endif syncevolution_1.4/src/backends/kcalextended/KCalExtendedSource.h000066400000000000000000000064131230021373600252030ustar00rootroot00000000000000/* * Copyright (C) 2007-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_KCALEXTENDEDSYNCSOURCE #define INCL_KCALEXTENDEDSYNCSOURCE #include #ifdef ENABLE_KCALEXTENDED #include #include SE_BEGIN_CXX class KCalExtendedData; /** * Access contacts stored in KCalExtended. * * Change tracking is based on time stamps instead of id/revision * pairs as in other sources. Items are imported/export as iCalendar 2.0 * strings. This allows us to implement TestingSyncSource (and thus * use client-test). We have to override the begin/end methods * to get time stamps recorded as anchors. * * This class is designed so that no KCalExtended header files are required * to include this header file. */ class KCalExtendedSource : public TestingSyncSource, private SyncSourceAdmin, private SyncSourceBlob, private SyncSourceRevisions, public SyncSourceLogging, private boost::noncopyable { public: enum Type { Event = 0, Todo = 1, Journal = 2 }; KCalExtendedSource(const SyncSourceParams ¶ms, Type type); ~KCalExtendedSource(); protected: /* implementation of SyncSource interface */ virtual void open(); virtual bool isEmpty(); virtual void close(); virtual Databases getDatabases(); virtual void enableServerMode(); virtual bool serverModeEnabled() const; virtual std::string getPeerMimeType() const { return getMimeType(); } /* implementation of SyncSourceSession interface */ virtual void beginSync(const std::string &lastToken, const std::string &resumeToken); virtual std::string endSync(bool success); /* implementation of SyncSourceDelete interface */ virtual void deleteItem(const string &luid); /* implementation of SyncSourceSerialize interface */ virtual std::string getMimeType() const; virtual std::string getMimeVersion() const; virtual InsertItemResult insertItem(const std::string &luid, const std::string &item); virtual void readItem(const std::string &luid, std::string &item); /* * implementation of SyncSourceRevisions * * Used for backup/restore (with dummy revision string). */ virtual void listAllItems(RevisionMap_t &revisions); /* implementation of SyncSourceLogging */ virtual std::string getDescription(const string &luid); private: KCalExtendedData *m_data; Type m_type; unsigned m_delete_run; unsigned m_insert_run; }; SE_END_CXX #endif // ENABLE_KCALEXTENDED #endif // INCL_KCALEXTENDEDSYNCSOURCE syncevolution_1.4/src/backends/kcalextended/KCalExtendedSourceRegister.cpp000066400000000000000000000141371230021373600272450ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * Copyright (C) 2010 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "KCalExtendedSource.h" #include "test.h" #include SE_BEGIN_CXX static SyncSource *createSource(const SyncSourceParams ¶ms) { SourceType sourceType = SyncSource::getSourceType(params.m_nodes); bool isMe = sourceType.m_backend == "mkcal-events"; bool maybeMe = sourceType.m_backend == "calendar"; if (isMe || maybeMe) { if (sourceType.m_format == "" || sourceType.m_format == "text/x-vcalendar" || sourceType.m_format == "text/x-calendar" || sourceType.m_format == "text/calendar") { return #ifdef ENABLE_KCALEXTENDED true ? new KCalExtendedSource(params, KCalExtendedSource::Event) : #endif isMe ? RegisterSyncSource::InactiveSource(params) : NULL; } } isMe = sourceType.m_backend == "mkcal-todos"; maybeMe = sourceType.m_backend == "todo"; if (isMe || maybeMe) { if (sourceType.m_format == "" || sourceType.m_format == "text/x-vcalendar" || sourceType.m_format == "text/x-calendar" || sourceType.m_format == "text/calendar") { return #ifdef ENABLE_KCALEXTENDED true ? new KCalExtendedSource(params, KCalExtendedSource::Todo) : #endif isMe ? RegisterSyncSource::InactiveSource(params) : NULL; } } isMe = sourceType.m_backend == "mkcal-notes"; maybeMe = sourceType.m_backend == "memo"; if (isMe || maybeMe) { if (sourceType.m_format == "" || sourceType.m_format == "text/x-vcalendar" || sourceType.m_format == "text/x-calendar" || sourceType.m_format == "text/calendar") { return #ifdef ENABLE_KCALEXTENDED true ? new KCalExtendedSource(params, KCalExtendedSource::Journal) : #endif isMe ? RegisterSyncSource::InactiveSource(params) : NULL; } } return NULL; } static RegisterSyncSource registerMe("KCalExtended", #ifdef ENABLE_KCALEXTENDED true, #else false, #endif createSource, "mkcal-events = mkcal = KCalExtended = calendar\n" " 'database' normally is the name of a calendar\n" " inside the default calendar storage. If it starts\n" " with the 'SyncEvolution_Test_' prefix, it will be\n" " created as needed, otherwise it must exist.\n" #ifdef ENABLE_MAEMO " If it starts with the 'uid:' prefix, the specified\n" " calendar in the default SQLite storage file will\n" " be used. It must exist.\n" #endif " If it starts with the 'file://' prefix, the default\n" " calendar in the specified SQLite storage file will\n" " created (if needed) and used.\n" "mkcal-todos = todo\n" " Same as above.\n" "mkcal-notes = memo\n" #ifdef ENABLE_MAEMO " Same as above. Keep in mind that, by default, notes\n" " are stored in their own database, separate from\n" " events and todos. The name of this database is\n" " hardcoded into the device's builtin Notes app.\n" " Don't override the default unless you know what\n" " you are doing.\n", #else " Same as above.\n", #endif Values() + (Aliases("mkcal-events") + "mkcal" + "KCalExtended" + "MeeGo Calendar") + (Aliases("mkcal-todos") + "MeeGo Tasks") + (Aliases("mkcal-notes") + "MeeGo Notes")); #ifdef ENABLE_KCALEXTENDED #ifdef ENABLE_UNIT_TESTS class KCalExtendedSourceUnitTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(KCalExtendedSourceUnitTest); CPPUNIT_TEST(testInstantiate); CPPUNIT_TEST_SUITE_END(); protected: void testInstantiate() { boost::shared_ptr source; source.reset(SyncSource::createTestingSource("KCalExtended", "KCalExtended:text/calendar:2.0", true)); } }; SYNCEVOLUTION_TEST_SUITE_REGISTRATION(KCalExtendedSourceUnitTest); #endif // ENABLE_UNIT_TESTS namespace { #if 0 } #endif static class ICal20Test : public RegisterSyncSourceTest { public: ICal20Test() : RegisterSyncSourceTest("kcal_event", "eds_event") {} virtual void updateConfig(ClientTestConfig &config) const { config.m_type = "KCalExtended:text/calendar"; // after fixing BMC #6061, mKCal is able to delete individual // VEVENTs, without enforcing the "each child must have parent" rule config.m_linkedItemsRelaxedSemantic = true; } } iCal20Test; } #endif // ENABLE_KCALEXTENDED SE_END_CXX syncevolution_1.4/src/backends/kcalextended/configure-sub.in000066400000000000000000000012261230021373600244530ustar00rootroot00000000000000dnl -*- mode: Autoconf; -*- dnl Invoke autogen.sh to produce a configure script. # BACKEND_CPPFLAGS="$BACKEND_CPPFLAGS ..." SE_ARG_ENABLE_BACKEND(kcalextended, kcalextended, [AS_HELP_STRING([--enable-kcalextended], [enable support for Maemo's KCalExtended storage])], [enable_kcalextended="$enableval"], [enable_kcalextended="no"] ) if test "$enable_kcalextended" = "yes"; then AC_DEFINE(ENABLE_KCALEXTENDED, 1, [KCalExtended available]) PKG_CHECK_MODULES(KCALEXTENDED, libmkcal libkcalcoren) fi syncevolution_1.4/src/backends/kcalextended/kcalextended.am000066400000000000000000000021121230021373600243200ustar00rootroot00000000000000dist_noinst_DATA += src/backends/kcalextended/configure-sub.in src_backends_kcalextended_lib = src/backends/kcalextended/synckcalextended.la MOSTLYCLEANFILES += $(src_backends_kcalextended_lib) if ENABLE_MODULES src_backends_kcalextended_backenddir = $(BACKENDS_DIRECTORY) src_backends_kcalextended_backend_LTLIBRARIES = $(src_backends_kcalextended_lib) else noinst_LTLIBRARIES += $(src_backends_kcalextended_lib) endif src_backends_kcalextended_synckcalextended_la_SOURCES = \ src/backends/kcalextended/KCalExtendedSource.h \ src/backends/kcalextended/KCalExtendedSource.cpp src_backends_kcalextended_synckcalextended_la_LIBADD = $(KCALEXTENDED_LIBS) $(SYNCEVOLUTION_LIBS) src_backends_kcalextended_synckcalextended_la_LDFLAGS = -module -avoid-version src_backends_kcalextended_synckcalextended_la_CXXFLAGS = $(SYNCEVOLUTION_CXXFLAGS) $(KCALEXTENDED_CFLAGS) $(SYNCEVO_WFLAGS) src_backends_kcalextended_synckcalextended_la_CPPFLAGS = $(SYNCEVOLUTION_CFLAGS) -I$(top_srcdir)/test $(BACKEND_CPPFLAGS) src_backends_kcalextended_synckcalextended_la_DEPENDENCIES = src/syncevo/libsyncevolution.la syncevolution_1.4/src/backends/kde/000077500000000000000000000000001230021373600174625ustar00rootroot00000000000000syncevolution_1.4/src/backends/kde/KDEPlatform.cpp000066400000000000000000000217141230021373600223030ustar00rootroot00000000000000/* * Copyright (C) 2011 Dinesh * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #ifdef USE_KDE_KWALLET #include "KDEPlatform.h" #include #include // Qt headers may define "signals" as preprocessor symbol, // which conflicts with glib C headers included indirectly // above. This order of header files works. #include #include #include #include #include #include #include #include #include #include SE_BEGIN_CXX // TODO: this check should be global static bool HaveDBus; void KDEInitMainSlot(const char *appname) { // Very simple check. API doesn't say whether asking // for the bus connection will connect immediately. // We use a private connection here instead of the shared // QDBusConnection::sessionBus() because we don't have // a QCoreApplication yet. Otherwise we get a warning: // "QDBusConnection: session D-Bus connection created before QCoreApplication. Application may misbehave." { QDBusConnection dbus = QDBusConnection::connectToBus(QDBusConnection::SessionBus, "org.syncevolution.kde-platform-test-connection"); HaveDBus = dbus.isConnected(); } if (!HaveDBus) { // KApplication has been seen to crash without D-Bus (BMC #25596). // Bail out here if we don't have D-Bus. return; } //QCoreApplication *app; int argc = 1; static char *argv[] = { const_cast(appname), NULL }; KAboutData aboutData(// The program name used internally. "syncevolution", // The message catalog name // If null, program name is used instead. 0, // A displayable program name string. ki18n("SyncEvolution"), // The program version string. VERSION, // Short description of what the app does. ki18n("Lets Akonadi synchronize with a SyncML Peer"), // The license this code is released under KAboutData::License_GPL, // Copyright Statement ki18n("(c) 2010-12"), // Optional text shown in the About box. // Can contain any information desired. ki18n(""), // The program homepage string. "http://www.syncevolution.org/", // The bug report email address "syncevolution@syncevolution.org"); KCmdLineArgs::init(argc, argv, &aboutData); if (!kapp) { // Don't allow KApplication to mess with SIGINT/SIGTERM. // Restore current behavior after construction. struct sigaction oldsigint, oldsigterm; sigaction(SIGINT, NULL, &oldsigint); sigaction(SIGTERM, NULL, &oldsigterm); // Explicitly disable GUI mode in the KApplication. Otherwise // the whole binary will fail to run when there is no X11 // display. new KApplication(false); //To stop KApplication from spawning it's own DBus Service ... Will have to patch KApplication about this QDBusConnection::sessionBus().unregisterService("org.syncevolution.syncevolution-"+QString::number(getpid())); sigaction(SIGINT, &oldsigint, NULL); sigaction(SIGTERM, &oldsigterm, NULL); } } static bool UseKWallet(const InitStateTri &keyring, int slotCount) { // Disabled by user? if (keyring.getValue() == InitStateTri::VALUE_FALSE) { return false; } // When both (presumably) GNOME keyring and KWallet are available, // check if the user really wanted KWallet before using KWallet // instead of GNOME keyring. This default favors GNOME keyring // over KWallet because SyncEvolution traditionally used that. if (keyring.getValue() == InitStateTri::VALUE_TRUE && slotCount > 1) { return false; } // If explicitly selected, it must be us. if (keyring.getValue() == InitStateTri::VALUE_STRING && !boost::iequals(keyring.get(), "KDE")) { return false; } // User wants KWallet, but is it usable? if (!HaveDBus) { SE_THROW("KDE KWallet requested, but it is not usable (running outside of a D-Bus session)"); } // Use KWallet. return true; } /** * Here we use server sync url without protocol prefix and * user account name as the key in the keyring. * * Also since the KWallet's API supports only storing (key,password) * or Map , the former is used. */ bool KWalletLoadPasswordSlot(const InitStateTri &keyring, const std::string &passwordName, const std::string &descr, const ConfigPasswordKey &key, InitStateString &password) { if (!UseKWallet(keyring, GetLoadPasswordSignal().num_slots() - INTERNAL_LOAD_PASSWORD_SLOTS)) { SE_LOG_DEBUG(NULL, "not using KWallet"); return false; } QString walletPassword; QString walletKey = QString(key.user.c_str()) + ',' + QString(key.domain.c_str())+ ','+ QString(key.server.c_str())+','+ QString(key.object.c_str())+','+ QString(key.protocol.c_str())+','+ QString(key.authtype.c_str())+','+ QString::number(key.port); QString wallet_name = KWallet::Wallet::NetworkWallet(); //QString folder = QString::fromUtf8("Syncevolution"); const QLatin1String folder("Syncevolution"); bool found = false; if (!KWallet::Wallet::keyDoesNotExist(wallet_name, folder, walletKey)) { KWallet::Wallet *wallet = KWallet::Wallet::openWallet(wallet_name, -1, KWallet::Wallet::Synchronous); if (wallet && wallet->setFolder(folder) && wallet->readPassword(walletKey, walletPassword) == 0) { password = walletPassword.toStdString(); found = true; } } SE_LOG_DEBUG(NULL, "%s password in KWallet using %s", found ? "found" : "no", key.toString().c_str()); return true; } bool KWalletSavePasswordSlot(const InitStateTri &keyring, const std::string &passwordName, const std::string &password, const ConfigPasswordKey &key) { if (!UseKWallet(keyring, GetSavePasswordSignal().num_slots() - INTERNAL_SAVE_PASSWORD_SLOTS)) { SE_LOG_DEBUG(NULL, "not using KWallet"); return false; } /* It is possible to let CmdlineSyncClient decide which of fields in ConfigPasswordKey it would use * but currently only use passed key instead */ // write password to keyring const QString walletKey = QString::fromStdString(key.user + ',' + key.domain + ',' + key.server + ',' + key.object + ',' + key.protocol + ',' + key.authtype + ',')+ QString::number(key.port); const QString walletPassword = QString::fromStdString(password); bool write_success = false; const QString wallet_name = KWallet::Wallet::NetworkWallet(); const QLatin1String folder("Syncevolution"); KWallet::Wallet *wallet = KWallet::Wallet::openWallet(wallet_name, -1, KWallet::Wallet::Synchronous); if (wallet) { if (!wallet->hasFolder(folder)) { wallet->createFolder(folder); } if (wallet->setFolder(folder) && wallet->writePassword(walletKey, walletPassword) == 0) { write_success = true; } } if (!write_success) { SyncContext::throwError("Saving " + passwordName + " in KWallet failed."); } SE_LOG_DEBUG(NULL, "stored password in KWallet using %s", key.toString().c_str()); return write_success; } SE_END_CXX #endif // USE_KDE_WALLET syncevolution_1.4/src/backends/kde/KDEPlatform.h000066400000000000000000000030431230021373600217430ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_KDEPLATFORM #define INCL_KDEPLATFORM #include #include #include SE_BEGIN_CXX struct ConfigPasswordKey; void KDEInitMainSlot(const char *appname); bool KWalletLoadPasswordSlot(const InitStateTri &keyring, const std::string &passwordName, const std::string &descr, const ConfigPasswordKey &key, InitStateString &password); bool KWalletSavePasswordSlot(const InitStateTri &keyring, const std::string &passwordName, const std::string &password, const ConfigPasswordKey &key); SE_END_CXX #endif // INCL_KDEPLATFORM syncevolution_1.4/src/backends/kde/KDEPlatformRegister.cpp000066400000000000000000000024701230021373600240060ustar00rootroot00000000000000/* * Copyright (C) 2011 Dinesh * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #ifdef USE_KDE_KWALLET #include "KDEPlatform.h" #include #include #include SE_BEGIN_CXX static class KDEInit { public: KDEInit() { GetLoadPasswordSignal().connect(0, KWalletLoadPasswordSlot); GetSavePasswordSignal().connect(0, KWalletSavePasswordSlot); SyncContext::GetInitMainSignal().connect(KDEInitMainSlot); } } kdeinit; SE_END_CXX #endif // USE_KDE_WALLET syncevolution_1.4/src/backends/kde/configure-sub.in000066400000000000000000000041051230021373600225620ustar00rootroot00000000000000# first check for qmake-qt4, because qmake may point to qmake-qt3. AC_CHECK_PROGS([QMAKE], [qmake-qt4 qmake]) if test "x$QMAKE" != 'x' then AC_PATH_PROG([KDE4_CONFIG], [kde4-config], [no]) if test "x$KDE4_CONFIG" != 'xno' then KDEKWALLETFOUND=yes if ! test "$KDE_KWALLET_CFLAGS"; then KDE_KWALLET_CFLAGS="-I`$KDE4_CONFIG --path include` -I`$KDE4_CONFIG --path include`/KDE `pkg-config --cflags QtDBus QtCore`" fi if ! test "$KDE_KWALLET_LIBS"; then KDE_KWALLET_LIBS="-lkdeui -lkdecore -L`kde4-config --install lib` `pkg-config --libs QtDBus QtCore`" fi AC_LANG_PUSH(C++) old_CPPFLAGS="$CPPFLAGS" CPPFLAGS="$CPPFLAGS $KDE_KWALLET_CFLAGS" AC_CHECK_HEADERS(kwallet.h, [], [KDEKWALLETFOUND=no]) CPPFLAGS="$old_CPPFLAGS" AC_LANG_POP(C++) else KDEKWALLETFOUND=no fi else KDEKWALLETFOUND=no fi # In contrast to the GNOME KEYRING, the KWallet is # currently considered optional. "configure" will never enable # by default, because that is a change that might not be # expected by traditional users. AC_ARG_ENABLE(kwallet, AS_HELP_STRING([--enable-kwallet], [enable access to KWallet]), [use_kde_kwallet="$enableval" test $KDEKWALLETFOUND = "yes" || test "$use_kde_kwallet" = "no" || AC_MSG_ERROR([kwallet.pc not found. Install it to compile with the KWallet enabled.])], [use_kde_kwallet="no"]) if test "$use_kde_kwallet" = "yes"; then have_keyring=yes # conditional compilation in preprocessor AC_DEFINE(USE_KDE_KWALLET, 1, [KWallet available]) # link into static executables, similar to a SyncSource SYNCSOURCES="$SYNCSOURCES src/backends/kde/platformkde.la" # TODO: KWallet needs Qt. Enable the Qt check in # configure-post.in, otherwise it fails to compiler # when none of the backends ask for Qt. else # avoid unneeded dependencies on KWallet KDE_KWALLET_CFLAGS= KDE_KWALLET_LIBS= fi AC_SUBST(KDE_KWALLET_LIBS) AC_SUBST(KDE_KWALLET_CFLAGS) # conditional compilation in make AM_CONDITIONAL([USE_KDE_KWALLET], [test "$use_kde_kwallet" = "yes"]) syncevolution_1.4/src/backends/kde/kde.am000066400000000000000000000016661230021373600205550ustar00rootroot00000000000000dist_noinst_DATA += src/backends/kde/configure-sub.in src_backends_kde_lib = src/backends/kde/platformkde.la MOSTLYCLEANFILES += $(src_backends_kde_lib) src_backends_kde_platformkde_la_SOURCES = \ src/backends/kde/KDEPlatform.h \ src/backends/kde/KDEPlatform.cpp if ENABLE_MODULES src_backends_kde_backenddir = $(BACKENDS_DIRECTORY) src_backends_kde_backend_LTLIBRARIES = $(src_backends_kde_lib) src_backends_kde_platformkde_la_SOURCES += \ src/backends/kde/KDEPlatformRegister.cpp else noinst_LTLIBRARIES += $(src_backends_kde_lib) endif src_backends_kde_platformkde_la_LIBADD = $(KDE_KWALLET_LIBS) $(SYNCEVOLUTION_LIBS) src_backends_kde_platformkde_la_LDFLAGS = -module -avoid-version src_backends_kde_platformkde_la_CXXFLAGS = $(KDE_KWALLET_CFLAGS) $(SYNCEVOLUTION_CFLAGS) src_backends_kde_platformkde_la_CPPFLAGS = -I$(top_srcdir)/test $(BACKEND_CPPFLAGS) src_backends_kde_platformkde_la_DEPENDENCIES = src/syncevo/libsyncevolution.la syncevolution_1.4/src/backends/maemo/000077500000000000000000000000001230021373600200155ustar00rootroot00000000000000syncevolution_1.4/src/backends/maemo/MaemoCalendarSource.cpp000066400000000000000000000271401230021373600243760ustar00rootroot00000000000000/* * Copyright (C) 2007-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "config.h" #ifdef ENABLE_MAEMO_CALENDAR #include "MaemoCalendarSource.h" #include #include #include #include #include #include #include #include #include #include SE_BEGIN_CXX MaemoCalendarSource::MaemoCalendarSource(int EntryType, int EntryFormat, const SyncSourceParams ¶ms) : TrackingSyncSource(params), entry_type(EntryType), entry_format(EntryFormat) { switch (EntryType) { case EVENT: SyncSourceLogging::init(InitList("SUMMARY") + "LOCATION", ", ", m_operations); break; case TODO: SyncSourceLogging::init(InitList("SUMMARY"), ", ", m_operations); break; case JOURNAL: SyncSourceLogging::init(InitList("SUBJECT"), ", ", m_operations); break; default: throwError("invalid calendar type"); break; } mc = CMulticalendar::MCInstance(); cal = NULL; conv = NULL; if (!mc) { throwError("Could not connect to Maemo Calendar backend"); } } MaemoCalendarSource::~MaemoCalendarSource() { // Don't rely on close() getting called to free resources. There's // no hard guarantee that it gets called in all cases. MaemoCalendarSource::close(); } std::string MaemoCalendarSource::getMimeType() const { switch (entry_format) { case -1: return "text/plain"; case ICAL_TYPE: return entry_type == JOURNAL ? "text/calendar+plain" : "text/calendar"; case VCAL_TYPE: return "text/x-calendar"; default: return NULL; } } std::string MaemoCalendarSource::getMimeVersion() const { switch (entry_format) { case -1: return "1.0"; case ICAL_TYPE: return "2.0"; case VCAL_TYPE: return "1.0"; default: return NULL; } } void MaemoCalendarSource::getSynthesisInfo(SynthesisInfo &info, XMLConfigFragments &fragments) { TrackingSyncSource::getSynthesisInfo(info, fragments); info.m_backendRule = "MAEMO-CALENDAR"; info.m_afterReadScript += "$FIX_EXDATE_SCRIPT;\n"; } void MaemoCalendarSource::open() { string id = getDatabaseID(); const string id_prefix("id:"); int err; if (!id.size()) { CCalendar *def_cal = mc->getSynchronizedCalendar(); // generate a new instance of default calendar, // which we can safely delete in the close() method cal = mc->getCalendarById(def_cal->getCalendarId(), err); } else if (boost::starts_with(id, id_prefix)) { istringstream uri(id.substr(id_prefix.size())); int cid; uri >> cid; cal = mc->getCalendarById(cid, err); } else { // try the calendar's name cal = mc->getCalendarByName(id, err); } if (!cal) { throwError(string("not found: ") + id); } conv = new ICalConverter; conv->setSyncing(true); // not sure what this does, but may as well tell the truth } bool MaemoCalendarSource::isEmpty() { int id = cal->getCalendarId(), err; switch (entry_type) { case EVENT: return !mc->getEventCount(id, err); case TODO: return !mc->getTodoCount(id, err); case JOURNAL: return !mc->getNoteCount(id, err); default: return true; } } void MaemoCalendarSource::close() { delete conv; conv = NULL; delete cal; cal = NULL; } MaemoCalendarSource::Databases MaemoCalendarSource::getDatabases() { // getDefaultCalendar returns the Private calendar, // getSynchronizedCalendar returns the Main calendar // (the same calendar Nokia PC Suite would sync with) CCalendar *def_cal = mc->getSynchronizedCalendar(); int def_id = def_cal->getCalendarId(); vector< CCalendar * > calendars = mc->getListCalFromMc(); Databases result; BOOST_FOREACH(CCalendar * c, calendars) { int id = c->getCalendarId(); ostringstream uri; uri << "id:" << id; result.push_back(Database(c->getCalendarName(), uri.str(), id == def_id)); } mc->releaseListCalendars(calendars); return result; } void MaemoCalendarSource::listAllItems(RevisionMap_t &revisions) { #if 0 /* this code exposes a bug in calendar-backend, https://bugs.maemo.org/show_bug.cgi?id=8277 */ // I've found no way to query the last modified time of a component // without getting the whole component. // This limit should hopefully reduce memory usage of that a bit, // though it could be bad if the database happens to change // between getComponents() calls. static const int limit = 1024; int ofs = 0, err; vector< CComponent * > comps; for (;;) { comps = cal->getComponents(entry_type, -1, -1, limit, ofs, err); // Note that non-success value in "err" is not necessarily fatal, // I seem to get a nonspecific "application error" if there are no // components of the specified type, so just ignore it for now if (!comps.size()) break; BOOST_FOREACH(CComponent * c, comps) { revisions[c->getId()] = get_revision(c); // Testing shows that the backend doesn't free the memory itself delete c; ofs++; } } #else // Instead, get a full list of IDs from getIdList(), then // load each entry from the calendar one by one. The // alternative would be to load the whole calendar into // memory all at once, but that's probably not all that // desirable, given the N900's limited memory. int err; vector< string > ids = cal->getIdList(entry_type, err); BOOST_FOREACH(std::string& id, ids) { CComponent *c = cal->getEntry(id, entry_type, err); if (!c) { throwError(string("retrieving item: ") + id); } revisions[id] = get_revision(c); delete c; } #endif } void MaemoCalendarSource::readItem(const string &uid, std::string &item, bool raw) { int err; CComponent * c = cal->getEntry(uid, entry_type, err); if (!c) { throwError(string("retrieving item: ") + uid); } if (entry_format == -1) { item = c->getSummary(); err = CALENDAR_OPERATION_SUCCESSFUL; } else { item = conv->localToIcalVcal(c, FileType(entry_format), err); } delete c; if (err != CALENDAR_OPERATION_SUCCESSFUL) { throwError(string("generating ical for item: ") + uid); } } TrackingSyncSource::InsertItemResult MaemoCalendarSource::insertItem(const string &uid, const std::string &item, bool raw) { int err; CComponent *c; bool r; InsertItemResultState u = ITEM_OKAY; TrackingSyncSource::InsertItemResult result; if (cal->getCalendarType() == BIRTHDAY_CALENDAR) { // stubbornly refuse to try this throwError(string("can't sync smart calendar ") + cal->getCalendarName()); } if (entry_format == -1) { c = new CJournal(item); err = CALENDAR_OPERATION_SUCCESSFUL; } else { vector< CComponent * > comps = conv->icalVcalToLocal(item, FileType(entry_format), err); // Note that a non-success value in "err" is not necessarily fatal, // I seem to get a nonspecific "application error" on certain types of // barely-legal input (mostly on todo entries), yet a component is returned if (!comps.size()) { if (err != CALENDAR_OPERATION_SUCCESSFUL) { throwError(string("parsing ical: ") + item); } else { throwError(string("no events in ical: ") + item); } } vector< CComponent * >::iterator it = comps.begin(); if (comps.size() > 1) { for (; it != comps.end(); ++it) { delete (*it); } throwError(string("too many events in ical: ") + item); } c = *it; } // I wish there were public modifyEntry and addEntry methods, // so I wouldn't need the switches // (using the batch-operation modifyComponents and addComponents methods on // individual items would probably be inefficient) if (uid.size()) { c->setId(uid); switch (entry_type) { case EVENT: r = cal->modifyEvent (static_cast< CEvent * >(c), err); break; case TODO: r = cal->modifyTodo (static_cast< CTodo * >(c), err); break; case JOURNAL: r = cal->modifyJournal(static_cast< CJournal * >(c), err); break; default: r = false; err = CALENDAR_SYSTEM_ERROR; } if (!r) { throwError(string("updating item ") + uid); } } else { switch (entry_type) { case EVENT: r = cal->addEvent (static_cast< CEvent * >(c), err); break; case TODO: r = cal->addTodo (static_cast< CTodo * >(c), err); break; case JOURNAL: r = cal->addJournal(static_cast< CJournal * >(c), err); break; default: r = false; err = CALENDAR_SYSTEM_ERROR; } if (!r) { throwError(string("creating item ")); } if (err == CALENDAR_ENTRY_DUPLICATED) { u = ITEM_REPLACED; } } result = InsertItemResult(c->getId(), get_revision(c), u); delete c; return result; } void MaemoCalendarSource::removeItem(const string &uid) { int err; if (cal->getCalendarType() == BIRTHDAY_CALENDAR) { // stubbornly refuse to try this throwError(string("can't sync smart calendar ") + cal->getCalendarName()); } cal->deleteComponent(uid, err); if (err != CALENDAR_OPERATION_SUCCESSFUL) { throwError(string("deleting item: ") + uid); } } string MaemoCalendarSource::get_revision(CComponent * c) { time_t mtime = c->getLastModified(); ostringstream revision; revision << mtime; return revision.str(); } std::string MaemoCalendarSource::getDescription(const string &uid) { string ret; int err; CComponent * c = cal->getEntry(uid, entry_type, err); if (c) { list parts; string str; str = c->getSummary(); if (!str.empty()) { parts.push_back(str); } if (entry_type == EVENT) { str = c->getLocation(); if (!str.empty()) { parts.push_back(str); } } ret = boost::join(parts, ", "); delete c; } return ret; } SE_END_CXX #endif /* ENABLE_MAEMO_CALENDAR */ #ifdef ENABLE_MODULES # include "MaemoCalendarSourceRegister.cpp" #endif syncevolution_1.4/src/backends/maemo/MaemoCalendarSource.h000066400000000000000000000054771230021373600240540ustar00rootroot00000000000000/* * Copyright (C) 2007-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_MAEMOCALENDARSOURCE #define INCL_MAEMOCALENDARSOURCE #include #ifdef ENABLE_MAEMO_CALENDAR #include #include #include #include #include SE_BEGIN_CXX /** * Implement access to Maemo calendar. * Change tracking is done by using the last-modified time * as TrackingSyncSource revisions. While it might be possible * to improve performance by using the getAllAdded/Modified * etc methods, it would make the change tracking less robust, * so it doesn't seem worth it. */ class MaemoCalendarSource : public TrackingSyncSource, public SyncSourceLogging, private boost::noncopyable { public: MaemoCalendarSource(int EntryType, int EntryFormat, const SyncSourceParams ¶ms); ~MaemoCalendarSource(); protected: /* implementation of SyncSource interface */ virtual void open(); virtual bool isEmpty(); virtual void close(); virtual Databases getDatabases(); virtual std::string getMimeType() const; virtual std::string getMimeVersion() const; virtual void getSynthesisInfo(SynthesisInfo &info, XMLConfigFragments &fragments); /* implementation of TrackingSyncSource interface */ virtual void listAllItems(RevisionMap_t &revisions); virtual InsertItemResult insertItem(const string &luid, const std::string &item, bool raw); void readItem(const std::string &luid, std::string &item, bool raw); virtual void removeItem(const string &luid); /* implementation of SyncSourceLogging interface */ virtual std::string getDescription(const string &luid); private: CMulticalendar *mc; /**< multicalendar */ CCalendar *cal; /**< calendar */ int entry_type; /**< entry type */ int entry_format; /**< entry format */ ICalConverter *conv; /**< converter */ string get_revision(CComponent * c); }; SE_END_CXX #endif // ENABLE_MAEMO_CALENDAR #endif // INCL_MAEMOCALENDARSOURCE syncevolution_1.4/src/backends/maemo/MaemoCalendarSourceRegister.cpp000066400000000000000000000134651230021373600261100ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "MaemoCalendarSource.h" #include "test.h" #include SE_BEGIN_CXX static SyncSource *createSource(const SyncSourceParams ¶ms) { SourceType sourceType = SyncSource::getSourceType(params.m_nodes); bool isMe = sourceType.m_backend == "Maemo Calendar"; #ifndef ENABLE_MAEMO_CALENDAR if (isMe) return RegisterSyncSource::InactiveSource(params); #else bool maybeMe = sourceType.m_backend == "calendar"; if (isMe || maybeMe) { if (sourceType.m_format == "" || sourceType.m_format == "text/calendar") { return new MaemoCalendarSource(EVENT, ICAL_TYPE, params); } else if (sourceType.m_format == "text/x-vcalendar") { return new MaemoCalendarSource(EVENT, VCAL_TYPE, params); } else { return NULL; } } #endif isMe = sourceType.m_backend == "Maemo Tasks"; #ifndef ENABLE_MAEMO_CALENDAR if (isMe) return RegisterSyncSource::InactiveSource(params); #else maybeMe = sourceType.m_backend == "todo"; if (isMe || maybeMe) { if (sourceType.m_format == "" || sourceType.m_format == "text/calendar") { return new MaemoCalendarSource(TODO, ICAL_TYPE, params); } else if (sourceType.m_format == "text/x-vcalendar") { return new MaemoCalendarSource(TODO, VCAL_TYPE, params); } else { return NULL; } } #endif isMe = sourceType.m_backend == "Maemo Notes"; #ifndef ENABLE_MAEMO_CALENDAR if (isMe) return RegisterSyncSource::InactiveSource(params); #else maybeMe = sourceType.m_backend == "memo"; if (isMe || maybeMe) { if (sourceType.m_format == "" || sourceType.m_format == "text/calendar") { return new MaemoCalendarSource(JOURNAL, ICAL_TYPE, params); } else if (sourceType.m_format == "text/x-vcalendar") { return new MaemoCalendarSource(JOURNAL, VCAL_TYPE, params); } else if (sourceType.m_format == "text/plain") { return new MaemoCalendarSource(JOURNAL, -1, params); } else { return NULL; } } #endif return NULL; } static RegisterSyncSource registerMe("Maemo Calendar/Tasks/Notes", #ifdef ENABLE_MAEMO_CALENDAR true, #else false, #endif createSource, "Maemo Calendar = calendar = events = maemo-events\n" " iCalendar 2.0 (default) = text/calendar\n" " vCalendar 1.0 = text/x-vcalendar\n" "Maemo Tasks = todo = tasks = maemo-tasks\n" " iCalendar 2.0 (default) = text/calendar\n" " vCalendar 1.0 = text/x-vcalendar\n" "Maemo Notes = memo = memos = notes = journal = maemo-notes\n" " plain text in UTF-8 (default) = text/plain\n" " iCalendar 2.0 = text/calendar\n" " vCalendar 1.0 = text/x-vcalendar\n", Values() + (Aliases("Maemo Calendar") + "maemo-events") + (Aliases("Maemo Tasks") + "maemo-tasks") + (Aliases("Maemo Notes") + "maemo-notes")); #ifdef ENABLE_MAEMO_CALENDAR #ifdef ENABLE_UNIT_TESTS class MaemoCalendarSourceUnitTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(MaemoCalendarSourceUnitTest); CPPUNIT_TEST(testInstantiate); CPPUNIT_TEST_SUITE_END(); protected: void testInstantiate() { boost::shared_ptr source; source.reset(SyncSource::createTestingSource("calendar", "calendar", true)); source.reset(SyncSource::createTestingSource("calendar", "maemo-events", true)); source.reset(SyncSource::createTestingSource("calendar", "Maemo Calendar:text/calendar", true)); } }; SYNCEVOLUTION_TEST_SUITE_REGISTRATION(MaemoCalendarSourceUnitTest); #endif // ENABLE_UNIT_TESTS namespace { #if 0 } #endif static class iCal20Test : public RegisterSyncSourceTest { public: iCal20Test() : RegisterSyncSourceTest("maemo_event", "eds_event") {} virtual void updateConfig(ClientTestConfig &config) const { config.m_type = "maemo-events"; } } iCal20Test; static class iTodo20Test : public RegisterSyncSourceTest { public: iTodo20Test() : RegisterSyncSourceTest("maemo_task", "eds_task") {} virtual void updateConfig(ClientTestConfig &config) const { config.m_type = "maemo-tasks"; } } iTodo20Test; static class MemoTest : public RegisterSyncSourceTest { public: MemoTest() : RegisterSyncSourceTest("maemo_memo", "eds_memo") {} virtual void updateConfig(ClientTestConfig &config) const { config.m_type = "maemo-notes"; } } memoTest; } #endif // ENABLE_MAEMO_CALENDAR SE_END_CXX syncevolution_1.4/src/backends/maemo/configure-sub.in000066400000000000000000000015521230021373600231200ustar00rootroot00000000000000dnl -*- mode: Autoconf; -*- dnl Invoke autogen.sh to produce a configure script. dnl Check for calendar-backend PKG_CHECK_MODULES(MCALB, calendar-backend, MCALBFOUND=yes, [MCALBFOUND=no]) AC_SUBST(MCALB_CFLAGS) AC_SUBST(MCALB_LIBS) BACKEND_CPPFLAGS="$BACKEND_CPPFLAGS $MCALB_CFLAGS" SE_ARG_ENABLE_BACKEND(maemocal, maemo, [AS_HELP_STRING([--enable-maemocal], [enable access to PIM data stored in Maemo 5 calendar application (default off)])], [enable_maemocal="$enableval"], [enable_maemocal="no"] ) if test "$enable_maemocal" = "yes"; then test "x${MCALBFOUND}" = "xyes" || AC_MSG_ERROR([--enable-maemocal requires pkg-config information for calendar-backend, which was not found]) AC_DEFINE(ENABLE_MAEMO_CALENDAR, 1, [Maemo 5 calendar available]) fi syncevolution_1.4/src/backends/maemo/maemo.am000066400000000000000000000017261230021373600214400ustar00rootroot00000000000000dist_noinst_DATA += src/backends/maemo/configure-sub.in src_backends_maemo_lib = src/backends/maemo/syncmaemocal.la MOSTLYCLEANFILES += $(src_backends_maemo_lib) if ENABLE_MODULES src_backends_maemo_backenddir = $(BACKENDS_DIRECTORY) src_backends_maemo_backend_LTLIBRARIES = $(src_backends_maemo_lib) else noinst_LTLIBRARIES += $(src_backends_maemo_lib) endif src_backends_maemo_src = \ src/backends/maemo/MaemoCalendarSource.h \ src/backends/maemo/MaemoCalendarSource.cpp src_backends_maemo_syncmaemocal_la_SOURCES = $(src_backends_maemo_src) src_backends_maemo_syncmaemocal_la_LIBADD = $(MCALB_LIBS) $(SYNCEVOLUTION_LIBS) src_backends_maemo_syncmaemocal_la_LDFLAGS = -module -avoid-version src_backends_maemo_syncmaemocal_la_CXXFLAGS = $(SYNCEVOLUTION_CXXFLAGS) $(SYNCEVO_WFLAGS) src_backends_maemo_syncmaemocal_la_CPPFLAGS = $(SYNCEVOLUTION_CFLAGS) -I$(top_srcdir)/test $(BACKEND_CPPFLAGS) src_backends_maemo_syncmaemocal_la_DEPENDENCIES = src/syncevo/libsyncevolution.la syncevolution_1.4/src/backends/pbap/000077500000000000000000000000001230021373600176415ustar00rootroot00000000000000syncevolution_1.4/src/backends/pbap/PbapSyncSource.cpp000066400000000000000000001044411230021373600232510ustar00rootroot00000000000000/* * Copyright (C) 2007-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * Copyright (C) 2012 BMW Car IT GmbH. All rights reserved. * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "config.h" #ifdef ENABLE_PBAP #include "PbapSyncSource.h" #include #include #include #include #include #include #include #include #include // PBAP backend does not compile without GLib. #include #include #include "gdbus-cxx-bridge.h" #include #include #include #include #include SE_BEGIN_CXX #define OBC_SERVICE "org.openobex.client" // obexd < 0.47 #define OBC_SERVICE_NEW "org.bluez.obex.client" // obexd >= 0.47, including 0.48 (with yet another slight API change!) #define OBC_SERVICE_NEW5 "org.bluez.obex" // obexd in Bluez 5.0 #define OBC_CLIENT_INTERFACE "org.openobex.Client" #define OBC_CLIENT_INTERFACE_NEW "org.bluez.obex.Client" #define OBC_CLIENT_INTERFACE_NEW5 "org.bluez.obex.Client1" #define OBC_PBAP_INTERFACE "org.openobex.PhonebookAccess" #define OBC_PBAP_INTERFACE_NEW "org.bluez.obex.PhonebookAccess" #define OBC_PBAP_INTERFACE_NEW5 "org.bluez.obex.PhonebookAccess1" #define OBC_TRANSFER_INTERFACE_NEW "org.bluez.obex.Transfer" #define OBC_TRANSFER_INTERFACE_NEW5 "org.bluez.obex.Transfer1" typedef std::map Content; class PullAll { std::string m_buffer; // vCards kept in memory when using old obexd. TmpFile m_tmpFile; // Stored in temporary file and mmapped with more recent obexd. Content m_content; // Refers to chunks of m_buffer or m_tmpFile without copying them. int m_numContacts; // Number of existing contacts, according to GetSize() or after downloading. int m_currentContact; // Numbered starting with zero according to discovery in addVCards. boost::shared_ptr m_session; // Only set when there is a transfer ongoing. int m_tmpFileOffset; // Number of bytes already parsed. friend class PbapSession; public: std::string getNextID(); bool getContact(int contactNumber, pcrecpp::StringPiece &vcard); const char *addVCards(int startIndex, const pcrecpp::StringPiece &content); }; enum PullData { PULL_AS_CONFIGURED, PULL_WITHOUT_PHOTOS }; class PbapSession : private boost::noncopyable { public: static boost::shared_ptr create(PbapSyncSource &parent); void initSession(const std::string &address, const std::string &format); typedef std::map Content; typedef std::map > Params; boost::shared_ptr startPullAll(PullData pullata); void checkForError(); // Throws exception if transfer failed. Timespec transferComplete() const; void resetTransfer(); void shutdown(void); private: PbapSession(PbapSyncSource &parent); PbapSyncSource &m_parent; boost::weak_ptr m_self; std::auto_ptr m_client; enum { OBEXD_OLD, // obexd < 0.47 OBEXD_NEW, // obexd == 0.47, file-based transfer // OBEXD_048 // obexd == 0.48, file-based transfer without SetFilter and with filter parameter to PullAll() BLUEZ5 // obexd in Bluez >= 5.0 } m_obexAPI; /** filter parameters for BLUEZ5 PullAll */ typedef std::list Properties; typedef boost::variant< std::string, Properties > Bluez5Values; std::map m_filter5; Properties m_filterFields; Properties supportedProperties() const; /** * m_transferComplete will be set to the current monotonic time when observing a * "Complete" signal on a transfer object path which has the * current session as prefix. There may be more than one such transfer, * so record all completions that we see and then pick the right one. * * It also gets set when an error occurred for such a transfer, * in which case m_error will also be set. * * This only works as long as the session is only used for a * single transfer. Otherwise a more complex tracking of * completion, for example per transfer object path, is needed. */ class Completion { public: Timespec m_transferComplete; std::string m_transferErrorCode; std::string m_transferErrorMsg; static Completion now() { Completion res; res.m_transferComplete = Timespec::monotonic(); return res; } }; typedef std::map Transfers; Transfers m_transfers; std::string m_currentTransfer; std::auto_ptr > m_errorSignal; void errorCb(const GDBusCXX::Path_t &path, const std::string &error, const std::string &msg); // Bluez 5 typedef GDBusCXX::SignalWatch4 > PropChangedSignal_t; std::auto_ptr m_propChangedSignal; void propChangedCb(const GDBusCXX::Path_t &path, const std::string &interface, const Params &changed, const std::vector &invalidated); // new obexd API typedef GDBusCXX::SignalWatch1 CompleteSignal_t; std::auto_ptr m_completeSignal; void completeCb(const GDBusCXX::Path_t &path); typedef GDBusCXX::SignalWatch3 > PropertyChangedSignal_t; std::auto_ptr m_propertyChangedSignal; void propertyChangedCb(const GDBusCXX::Path_t &path, const std::string &name, const boost::variant &value); std::auto_ptr m_session; }; PbapSession::PbapSession(PbapSyncSource &parent) : m_parent(parent) { } boost::shared_ptr PbapSession::create(PbapSyncSource &parent) { boost::shared_ptr session(new PbapSession(parent)); session->m_self = session; return session; } void PbapSession::propChangedCb(const GDBusCXX::Path_t &path, const std::string &interface, const Params &changed, const std::vector &invalidated) { // Called for a path which matches the current session, so we know // that the signal is for our transfer. Only need to check the status. Params::const_iterator it = changed.find("Status"); if (it != changed.end()) { std::string status = boost::get(it->second); SE_LOG_DEBUG(NULL, "OBEXD transfer %s: %s", path.c_str(), status.c_str()); if (status == "complete" || status == "error") { Completion completion = Completion::now(); if (status == "error") { // We have to make up some error descriptions. The Bluez // 5 API no longer seems to provide that. completion.m_transferErrorCode = "transfer failed"; completion.m_transferErrorMsg = "reason unknown"; } m_transfers[path] = completion; } } } void PbapSession::completeCb(const GDBusCXX::Path_t &path) { SE_LOG_DEBUG(NULL, "obexd transfer %s completed", path.c_str()); m_transfers[path] = Completion::now(); } void PbapSession::errorCb(const GDBusCXX::Path_t &path, const std::string &error, const std::string &msg) { SE_LOG_DEBUG(NULL, "obexd transfer %s failed: %s %s", path.c_str(), error.c_str(), msg.c_str()); Completion &completion = m_transfers[path]; completion.m_transferComplete = Timespec::monotonic(); completion.m_transferErrorCode = error; completion.m_transferErrorMsg = msg; } void PbapSession::propertyChangedCb(const GDBusCXX::Path_t &path, const std::string &name, const boost::variant &value) { const int64_t *tmp = boost::get(&value); if (tmp) { SE_LOG_DEBUG(NULL, "obexd transfer %s property change: %s = %ld", path.c_str(), name.c_str(), (long signed)*tmp); } else { SE_LOG_DEBUG(NULL, "obexd transfer %s property change: %s", path.c_str(), name.c_str()); } } PbapSession::Properties PbapSession::supportedProperties() const { Properties props; static const std::set supported = boost::assign::list_of("VERSION") ("FN") ("N") ("PHOTO") ("BDAY") ("ADR") ("LABEL") ("TEL") ("EMAIL") ("MAILER") ("TZ") ("GEO") ("TITLE") ("ROLE") ("LOGO") ("AGENT") ("ORG") ("NOTE") ("REV") ("SOUND") ("URL") ("UID") ("KEY") ("NICKNAME") ("CATEGORIES") ("CLASS"); BOOST_FOREACH (const std::string &prop, m_filterFields) { // Be conservative and only ask for properties that we // really know how to use. obexd also lists the bit field // strings ("BIT01") but phones have been seen to reject // queries when those were enabled. if (supported.find(prop) != supported.end()) { props.push_back(prop); } } return props; } void PbapSession::initSession(const std::string &address, const std::string &format) { if (m_session.get()) { return; } // format string uses: // [(2.1|3.0):][^]propname,propname,... // // 3.0:^PHOTO = download in vCard 3.0 format, excluding PHOTO // 2.1:PHOTO = download in vCard 2.1 format, only the PHOTO std::string version; std::string tmp; std::string properties; const pcrecpp::RE re("(?:(2\\.1|3\\.0):?)?(\\^?)([-a-zA-Z,]*)"); if (!re.FullMatch(format, &version, &tmp, &properties)) { m_parent.throwError(StringPrintf("invalid specification of PBAP vCard format (databaseFormat): %s", format.c_str())); } char negated = tmp.c_str()[0]; if (version.empty()) { // same default as in obexd version = "2.1"; } if (version != "2.1" && version != "3.0") { m_parent.throwError(StringPrintf("invalid vCard version prefix in PBAP vCard format specification (databaseFormat): %s", format.c_str())); } std::set keywords; boost::split(keywords, properties, boost::is_from_range(',', ',')); typedef std::map > Params; Params params; params["Target"] = std::string("PBAP"); std::string session; GDBusCXX::DBusConnectionPtr conn = GDBusCXX::dbus_get_bus_connection("SESSION", NULL, true, NULL); // We must attempt to use the new interface(s), otherwise we won't know whether // the daemon exists or can be started. m_obexAPI = BLUEZ5; m_client.reset(new GDBusCXX::DBusRemoteObject(conn, "/org/bluez/obex", OBC_CLIENT_INTERFACE_NEW5, OBC_SERVICE_NEW5, true)); try { SE_LOG_DEBUG(NULL, "trying to use bluez 5 obexd service %s", OBC_SERVICE_NEW5); session = GDBusCXX::DBusClientCall1(*m_client, "CreateSession")(address, params); } catch (const std::exception &error) { if (!strstr(error.what(), "org.freedesktop.DBus.Error.ServiceUnknown") && !strstr(error.what(), "org.freedesktop.DBus.Error.UnknownObject")) { throw; } // Fall back to old interface. SE_LOG_DEBUG(NULL, "bluez obex service not available (%s), falling back to previous obexd one %s", error.what(), OBC_SERVICE_NEW); m_obexAPI = OBEXD_NEW; } if (session.empty()) { m_client.reset(new GDBusCXX::DBusRemoteObject(conn, "/", OBC_CLIENT_INTERFACE_NEW, OBC_SERVICE_NEW, true)); try { SE_LOG_DEBUG(NULL, "trying to use new obexd service %s", OBC_SERVICE_NEW); session = GDBusCXX::DBusClientCall1(*m_client, "CreateSession")(address, params); } catch (const std::exception &error) { if (!strstr(error.what(), "org.freedesktop.DBus.Error.ServiceUnknown")) { throw; } // Fall back to old interface. SE_LOG_DEBUG(NULL, "new obexd service(s) not available (%s), falling back to old one %s", error.what(), OBC_SERVICE); m_obexAPI = OBEXD_OLD; } } if (session.empty()) { m_client.reset(new GDBusCXX::DBusRemoteObject(conn, "/", OBC_CLIENT_INTERFACE, OBC_SERVICE, true)); params["Destination"] = std::string(address); session = GDBusCXX::DBusClientCall1(*m_client, "CreateSession")(params); } if (session.empty()) { m_parent.throwError("PBAP: failed to create session"); } if (m_obexAPI != OBEXD_OLD) { m_session.reset(new GDBusCXX::DBusRemoteObject(m_client->getConnection(), session, m_obexAPI == BLUEZ5 ? OBC_PBAP_INTERFACE_NEW5 : OBC_PBAP_INTERFACE_NEW, m_obexAPI == BLUEZ5 ? OBC_SERVICE_NEW5 : OBC_SERVICE_NEW, true)); // Filter Transfer signals via path prefix. Discussions on Bluez // list showed that this is meant to be possible, even though the // client-api.txt documentation itself didn't (and still doesn't) // make it clear: // "[PATCH obexd v0] client-doc: Guarantee prefix in transfer paths" // http://www.spinics.net/lists/linux-bluetooth/msg28409.html // // Be extra careful with asynchronous callbacks: bind to weak // pointer and ignore callback when the instance is already gone. // Should not happen with signals (destructing the class unregisters // the watch), but very well may happen in asynchronous method // calls. Therefore maintain m_self and show how to use it here. if (m_obexAPI == BLUEZ5) { // Bluez 5 m_propChangedSignal.reset(new PropChangedSignal_t (GDBusCXX::SignalFilter(m_client->getConnection(), session, "org.freedesktop.DBus.Properties", "PropertiesChanged", GDBusCXX::SignalFilter::SIGNAL_FILTER_PATH_PREFIX))); m_propChangedSignal->activate(boost::bind(&PbapSession::propChangedCb, m_self, _1, _2, _3, _4)); } else { // obexd >= 0.47 m_completeSignal.reset(new CompleteSignal_t (GDBusCXX::SignalFilter(m_client->getConnection(), session, OBC_TRANSFER_INTERFACE_NEW, "Complete", GDBusCXX::SignalFilter::SIGNAL_FILTER_PATH_PREFIX))); m_completeSignal->activate(boost::bind(&PbapSession::completeCb, m_self, _1)); // same for error m_errorSignal.reset(new GDBusCXX::SignalWatch3 (GDBusCXX::SignalFilter(m_client->getConnection(), session, OBC_TRANSFER_INTERFACE_NEW, "Error", GDBusCXX::SignalFilter::SIGNAL_FILTER_PATH_PREFIX))); m_errorSignal->activate(boost::bind(&PbapSession::errorCb, m_self, _1, _2, _3)); // and property changes m_propertyChangedSignal.reset(new PropertyChangedSignal_t(GDBusCXX::SignalFilter(m_client->getConnection(), session, OBC_TRANSFER_INTERFACE_NEW, "PropertyChanged", GDBusCXX::SignalFilter::SIGNAL_FILTER_PATH_PREFIX))); m_propertyChangedSignal->activate(boost::bind(&PbapSession::propertyChangedCb, m_self, _1, _2, _3)); } } else { // obexd < 0.47 m_session.reset(new GDBusCXX::DBusRemoteObject(m_client->getConnection(), session, OBC_PBAP_INTERFACE, OBC_SERVICE, true)); } SE_LOG_DEBUG(NULL, "PBAP session created: %s", m_session->getPath()); // get filter list so that we can continue validating our format specifier m_filterFields = GDBusCXX::DBusClientCall1< Properties >(*m_session, "ListFilterFields")(); SE_LOG_DEBUG(NULL, "supported PBAP filter fields:\n %s", boost::join(m_filterFields, "\n ").c_str()); Properties filter; if (negated) { // negated, start with everything set filter = supportedProperties(); } // validate parameters and update filter BOOST_FOREACH (const std::string &prop, keywords) { if (prop.empty()) { continue; } Properties::const_iterator entry = std::find_if(m_filterFields.begin(), m_filterFields.end(), boost::bind(&boost::iequals, _1, prop, std::locale())); if (entry == m_filterFields.end()) { m_parent.throwError(StringPrintf("invalid property name in PBAP vCard format specification (databaseFormat): %s", prop.c_str())); } if (negated) { filter.remove(*entry); } else { filter.push_back(*entry); } } GDBusCXX::DBusClientCall0(*m_session, "Select")(std::string("int"), std::string("PB")); m_filter5["Format"] = version == "2.1" ? "vcard21" : "vcard30"; m_filter5["Fields"] = filter; SE_LOG_DEBUG(NULL, "PBAP session initialized"); } boost::shared_ptr PbapSession::startPullAll(PullData pullData) { resetTransfer(); // Update prepared filter to match pullData. std::map currentFilter = m_filter5; std::string &format = boost::get(currentFilter["Format"]); std::list &filter = boost::get< std::list >(currentFilter["Fields"]); switch (pullData) { case PULL_AS_CONFIGURED: SE_LOG_DEBUG(NULL, "pull all with configured filter: '%s'", boost::join(filter, " ").c_str()); break; case PULL_WITHOUT_PHOTOS: // Remove PHOTO from list or create list with the other properties. if (filter.empty()) { filter = supportedProperties(); } for (Properties::iterator it = filter.begin(); it != filter.end(); ++it) { if (*it == "PHOTO") { filter.erase(it); break; } } SE_LOG_DEBUG(NULL, "pull all without photos: '%s'", boost::join(filter, " ").c_str()); break; } bool pullAllWithFiltersFallback = false; if (m_obexAPI == OBEXD_OLD || m_obexAPI == OBEXD_NEW) { try { GDBusCXX::DBusClientCall0(*m_session, "SetFilter")(filter); GDBusCXX::DBusClientCall0(*m_session, "SetFormat")(format); } catch (...) { // Ignore failure, can happen with 0.48. Instead send filter together // with PullAll method call. Exception::handle(HANDLE_EXCEPTION_NO_ERROR); pullAllWithFiltersFallback = true; } } boost::shared_ptr state(new PullAll); state->m_currentContact = 0; if (m_obexAPI != OBEXD_OLD) { // Beware, this will lead to a "Complete" signal in obexd // 0.47. We need to be careful with looking at the right // transfer to determine whether PullAll completed. state->m_numContacts = GDBusCXX::DBusClientCall1(*m_session, "GetSize")(); SE_LOG_DEBUG(NULL, "Expecting %d contacts.", state->m_numContacts); state->m_tmpFile.create(); SE_LOG_DEBUG(NULL, "Created temporary file for PullAll %s", state->m_tmpFile.filename().c_str()); GDBusCXX::DBusClientCall1 > pullall(*m_session, "PullAll"); std::pair tuple = pullAllWithFiltersFallback ? // 0.48 GDBusCXX::DBusClientCall1 >(*m_session, "PullAll")(state->m_tmpFile.filename(), currentFilter) : m_obexAPI == OBEXD_NEW ? // 0.47 GDBusCXX::DBusClientCall1 >(*m_session, "PullAll")(state->m_tmpFile.filename()) : // 5.x GDBusCXX::DBusClientCall2(*m_session, "PullAll")(state->m_tmpFile.filename(), currentFilter); const GDBusCXX::DBusObject_t &transfer = tuple.first; const Params &properties = tuple.second; m_currentTransfer = transfer; SE_LOG_DEBUG(NULL, "pullall transfer path %s, %ld properties", transfer.c_str(), (long)properties.size()); // Work will be finished incrementally in PullAll::getContact(). // // In the meantime we return IDs by simply enumerating the expected ones. // If we don't get as many contacts as expected, we return 404 in getContact() // and the Synthesis engine will ignore the ID (src/sysync/binfileimplds.cpp: // "Record does not exist any more in database%s -> ignore"). state->m_tmpFileOffset = 0; state->m_session = m_self.lock(); } else { // < 0.47 // // This only works once. Incremental syncing with the same // session leads to a "PullAll method with no arguments not // found" error from obex-client. Looks like a bug/limitation // of obex-client < 0.47. Not sure what we should do about // this: disable incremental sync for old obex-client? Reject // it? Catch the error and add a better exlanation? GDBusCXX::DBusClientCall1 pullall(*m_session, "PullAll"); state->m_buffer = pullall(); state->addVCards(0, state->m_buffer); state->m_numContacts = state->m_content.size(); } return state; } const char *PullAll::addVCards(int startIndex, const pcrecpp::StringPiece &vcards) { pcrecpp::StringPiece vcarddata; pcrecpp::StringPiece tmp = vcards; int count = startIndex; pcrecpp::RE re("[\\r\\n]*(^BEGIN:VCARD.*?^END:VCARD)", pcrecpp::RE_Options().set_dotall(true).set_multiline(true)); while (re.Consume(&tmp, &vcarddata)) { m_content[count] = vcarddata; ++count; } SE_LOG_DEBUG(NULL, "PBAP content parsed: %d entries starting at %d", count - startIndex, startIndex); return tmp.data(); } void PbapSession::checkForError() { Transfers::const_iterator it = m_transfers.find(m_currentTransfer); if (it != m_transfers.end()) { if (!it->second.m_transferErrorCode.empty()) { m_parent.throwError(StringPrintf("%s: %s", it->second.m_transferErrorCode.c_str(), it->second.m_transferErrorMsg.c_str())); } } } Timespec PbapSession::transferComplete() const { Timespec res; Transfers::const_iterator it = m_transfers.find(m_currentTransfer); if (it != m_transfers.end()) { res = it->second.m_transferComplete; } return res; } void PbapSession::resetTransfer() { m_transfers.clear(); } std::string PullAll::getNextID() { std::string id; if (m_currentContact < m_numContacts) { id = StringPrintf("%d", m_currentContact); m_currentContact++; } return id; } bool PullAll::getContact(int contactNumber, pcrecpp::StringPiece &vcard) { SE_LOG_DEBUG(NULL, "get PBAP contact #%d", contactNumber); if (contactNumber < 0 || contactNumber >= m_numContacts) { SE_LOG_DEBUG(NULL, "invalid contact number"); return false; } Content::iterator it; while ((it = m_content.find(contactNumber)) == m_content.end() && m_session && (!m_session->transferComplete() || m_tmpFile.moreData())) { // Wait? We rely on regular propgress signals to wake us up. // obex 0.47 sends them every 64KB, at least in combination // with a Samsung Galaxy SIII. This may depend on both obexd // and the phone, so better check ourselves and perhaps do it // less often - unmap/map can be expensive and invalidates // some of the unread data (at least how it is implemented // now). while (!m_session->transferComplete() && m_tmpFile.moreData() < 128 * 1024) { g_main_context_iteration(NULL, true); } m_session->checkForError(); if (m_tmpFile.moreData()) { // Remap. This shifts all addresses already stored in // m_content, so beware and update those. pcrecpp::StringPiece oldMem = m_tmpFile.stringPiece(); m_tmpFile.unmap(); m_tmpFile.map(); pcrecpp::StringPiece newMem = m_tmpFile.stringPiece(); ssize_t delta = newMem.data() - oldMem.data(); BOOST_FOREACH (Content::value_type &entry, m_content) { pcrecpp::StringPiece &vcard = entry.second; vcard.set(vcard.data() + delta, vcard.size()); } // File exists and obexd has written into it, so now we // can unlink it to avoid leaking it if we crash. m_tmpFile.remove(); // Continue parsing where we stopped before. pcrecpp::StringPiece next(newMem.data() + m_tmpFileOffset, newMem.size() - m_tmpFileOffset); const char *end = addVCards(m_content.size(), next); int newTmpFileOffset = end - newMem.data(); SE_LOG_DEBUG(NULL, "PBAP content parsed: %d out of %d (total), %d out of %d (last update)", newTmpFileOffset, newMem.size(), (int)(end - next.data()), next.size()); m_tmpFileOffset = newTmpFileOffset; } } if (it == m_content.end()) { SE_LOG_DEBUG(NULL, "did not get the expected contact #%d, perhaps some contacts were deleted?", contactNumber); return false; } vcard = it->second; return true; } void PbapSession::shutdown(void) { GDBusCXX::DBusClientCall0 removeSession(*m_client, "RemoveSession"); // always clear pointer, even if method call fails GDBusCXX::DBusObject_t path(m_session->getPath()); //m_session.reset(); SE_LOG_DEBUG(NULL, "removed session: %s", path.c_str()); removeSession(path); SE_LOG_DEBUG(NULL, "PBAP session closed"); } PbapSyncSource::PbapSyncSource(const SyncSourceParams ¶ms) : SyncSource(params) { SyncSourceSession::init(m_operations); m_operations.m_readNextItem = boost::bind(&PbapSyncSource::readNextItem, this, _1, _2, _3); m_operations.m_readItemAsKey = boost::bind(&PbapSyncSource::readItemAsKey, this, _1, _2); m_session = PbapSession::create(*this); const char *PBAPSyncMode = getenv("SYNCEVOLUTION_PBAP_SYNC"); m_PBAPSyncMode = !PBAPSyncMode ? PBAP_SYNC_NORMAL : boost::iequals(PBAPSyncMode, "incremental") ? PBAP_SYNC_INCREMENTAL : boost::iequals(PBAPSyncMode, "text") ? PBAP_SYNC_TEXT : boost::iequals(PBAPSyncMode, "all") ? PBAP_SYNC_NORMAL : (throwError(StringPrintf("invalid value for SYNCEVOLUTION_PBAP_SYNC: %s", PBAPSyncMode)), PBAP_SYNC_NORMAL); m_isFirstCycle = true; m_hadContacts = false; } PbapSyncSource::~PbapSyncSource() { } void PbapSyncSource::open() { string database = getDatabaseID(); const string prefix("obex-bt://"); if (!boost::starts_with(database, prefix)) { throwError("database should specifiy device address (obex-bt://)"); } std::string address = database.substr(prefix.size()); m_session->initSession(address, getDatabaseFormat()); } void PbapSyncSource::beginSync(const std::string &lastToken, const std::string &resumeToken) { if (!lastToken.empty()) { throwError(STATUS_SLOW_SYNC_508, std::string("PBAP cannot do change detection")); } } std::string PbapSyncSource::endSync(bool success) { m_pullAll.reset(); // Non-empty so that beginSync() can detect non-slow syncs and ask // for one. return "1"; } bool PbapSyncSource::isEmpty() { return false; // We don't know for sure. Doesn't matter, so pretend to not be empty. } void PbapSyncSource::close() { m_session->shutdown(); } PbapSyncSource::Databases PbapSyncSource::getDatabases() { Databases result; result.push_back(Database("select database via bluetooth address", "[obex-bt://]")); return result; } void PbapSyncSource::enableServerMode() { SE_THROW("PbapSyncSource does not implement server mode."); } bool PbapSyncSource::serverModeEnabled() const { return false; } std::string PbapSyncSource::getPeerMimeType() const { return "text/vcard"; } void PbapSyncSource::getSynthesisInfo(SynthesisInfo &info, XMLConfigFragments &fragments) { // We send vCards in either 2.1 or 3.0. The Synthesis engine // handles both when parsing, so we don't have to be exact here. info.m_native = "vCard21"; info.m_fieldlist = "contacts"; info.m_profile = "\"vCard\", 1"; // vCard 3.0 is the more sane format for exchanging with the // Synthesis peer in a local sync, so use that by default. std::string type = "text/vcard"; SourceType sourceType = getSourceType(); if (!sourceType.m_format.empty()) { type = sourceType.m_format; } info.m_datatypes = getDataTypeSupport(type, sourceType.m_forceFormat); /** * Access to data must be done early so that a slow sync can be * enforced. */ info.m_earlyStartDataRead = true; } // TODO: return IDs based on GetSize(), read only when engine needs data. sysync::TSyError PbapSyncSource::readNextItem(sysync::ItemID aID, sysync::sInt32 *aStatus, bool aFirst) { if (aFirst) { m_pullAll = m_session->startPullAll((m_PBAPSyncMode == PBAP_SYNC_TEXT || (m_PBAPSyncMode == PBAP_SYNC_INCREMENTAL && m_isFirstCycle)) ? PULL_WITHOUT_PHOTOS : PULL_AS_CONFIGURED); } if (!m_pullAll) { throwError("logic error: readNextItem without aFirst=true before"); } std::string id = m_pullAll->getNextID(); if (id.empty()) { *aStatus = sysync::ReadNextItem_EOF; if (m_PBAPSyncMode == PBAP_SYNC_INCREMENTAL && m_hadContacts && m_isFirstCycle) { requestAnotherSync(); m_isFirstCycle = false; } } else { *aStatus = sysync::ReadNextItem_Unchanged; aID->item = StrAlloc(id.c_str()); aID->parent = NULL; m_hadContacts = true; } return sysync::LOCERR_OK; } sysync::TSyError PbapSyncSource::readItemAsKey(sysync::cItemID aID, sysync::KeyH aItemKey) { if (!m_pullAll) { throwError("logic error: readItemAsKey() without preceeding readNextItem()"); } pcrecpp::StringPiece vcard; if (m_pullAll->getContact(atoi(aID->item), vcard)) { return getSynthesisAPI()->setValue(aItemKey, "data", vcard.data(), vcard.size()); } else { return sysync::DB_NotFound; } } SyncSourceRaw::InsertItemResult PbapSyncSource::insertItemRaw(const std::string &luid, const std::string &item) { throwError("writing via PBAP is not supported"); return InsertItemResult(); } void PbapSyncSource::readItemRaw(const std::string &luid, std::string &item) { if (!m_pullAll) { throwError("logic error: readItemRaw() without preceeding readNextItem()"); } pcrecpp::StringPiece vcard; if (m_pullAll->getContact(atoi(luid.c_str()), vcard)) { item.assign(vcard.data(), vcard.size()); } else { throwError(STATUS_NOT_FOUND, string("retrieving item: ") + luid); } } SE_END_CXX #endif /* ENABLE_PBAP */ #ifdef ENABLE_MODULES # include "PbapSyncSourceRegister.cpp" #endif syncevolution_1.4/src/backends/pbap/PbapSyncSource.cpp.orig000066400000000000000000000245071230021373600242140ustar00rootroot00000000000000/* * Copyright (C) 2007-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * Copyright (C) 2012 BMW Car IT GmbH. All rights reserved. * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "config.h" #ifdef ENABLE_PBAP #include "PbapSyncSource.h" // SyncEvolution includes a copy of Boost header files. // They are safe to use without creating additional // build dependencies. boost::filesystem requires a // library and therefore is avoided here. Some // utility functions from SyncEvolution are used // instead, plus standard C/Posix functions. #include #include #include #include #include #include #include #include "gdbus-cxx-bridge.h" #include #include #include #include SE_BEGIN_CXX #define OBC_SERVICE "org.openobex.client" #define OBC_CLIENT_INTERFACE "org.openobex.Client" #define OBC_PBAP_INTERFACE "org.openobex.PhonebookAccess" class PbapSession { public: PbapSession(void); void initSession(const std::string &address, const std::string &format); typedef std::map Content; void pullAll(Content &dst); void shutdown(void); private: GDBusCXX::DBusRemoteObject m_client; std::auto_ptr m_session; }; PbapSession::PbapSession(void) : m_client(GDBusCXX::dbus_get_bus_connection("SESSION", NULL, true, NULL), "/", OBC_CLIENT_INTERFACE, OBC_SERVICE, true) { } void PbapSession::initSession(const std::string &address, const std::string &format) { if (m_session.get()) { return; } // format string uses: // [(2.1|3.0):][^]propname,propname,... // // 3.0:^PHOTO = download in vCard 3.0 format, excluding PHOTO // 2.1:PHOTO = download in vCard 2.1 format, only the PHOTO std::string version; std::string tmp; std::string properties; const pcrecpp::RE re("(?:(2\\.1|3\\.0):?)?(\\^?)([-a-zA-Z,]*)"); if (!re.FullMatch(format, &version, &tmp, &properties)) { SE_THROW(StringPrintf("invalid specification of PBAP vCard format (databaseFormat): %s", format.c_str())); } char negated = tmp.c_str()[0]; if (version.empty()) { // same default as in obexd version = "2.1"; } if (version != "2.1" && version != "3.0") { SE_THROW(StringPrintf("invalid vCard version prefix in PBAP vCard format specification (databaseFormat): %s", format.c_str())); } std::set keywords; boost::split(keywords, properties, boost::is_from_range(',', ',')); typedef std::map > Params; GDBusCXX::DBusClientCall1 createSession(m_client, "CreateSession"); Params params; params["Destination"] = std::string(address); params["Target"] = std::string("PBAP"); std::string session = createSession(params); if (session.empty()) { SE_THROW("PBAP: got empty session from CreateSession()"); } m_session.reset(new GDBusCXX::DBusRemoteObject(m_client.getConnection(), session, OBC_PBAP_INTERFACE, OBC_SERVICE, true)); SE_LOG_DEBUG(NULL, NULL, "PBAP session created: %s", m_session->getPath()); // get filter list so that we can continue validating our format specifier std::vector filterFields = GDBusCXX::DBusClientCall1< std::vector >(*m_session, "ListFilterFields")(); std::list filter; if (negated) { // negated, start with everything set filter.insert(filter.begin(), filterFields.begin(), filterFields.end()); } // validate parameters and update filter BOOST_FOREACH (const std::string &prop, keywords) { if (prop.empty()) { continue; } std::vector::const_iterator entry = std::find_if(filterFields.begin(), filterFields.end(), boost::bind(&boost::iequals, _1, prop, std::locale())); if (entry == filterFields.end()) { SE_THROW(StringPrintf("invalid property name in PBAP vCard format specification (databaseFormat): %s", prop.c_str())); } if (negated) { filter.remove(*entry); } else { filter.push_back(*entry); } } GDBusCXX::DBusClientCall0(*m_session, "Select")(std::string("int"), std::string("PB")); GDBusCXX::DBusClientCall0(*m_session, "SetFilter")(std::vector(filter.begin(), filter.end())); GDBusCXX::DBusClientCall0(*m_session, "SetFormat")(std::string(version == "2.1" ? "vcard21" : "vcard30")); SE_LOG_DEBUG(NULL, NULL, "PBAP session initialized"); } void vcardParse(const std::string &content, std::size_t begin, std::size_t end, std::map &dst) { static const boost::char_separator lineSep("\n\r"); typedef boost::tokenizer > Tokenizer; Tokenizer tok(content.begin() + begin, content.begin() + end, lineSep); for(Tokenizer::iterator it = tok.begin(); it != tok.end(); it ++) { const std::string &line = *it; size_t i = line.find(':'); if(i != std::string::npos) { std::size_t j = line.find(';'); j = (j == std::string::npos)? i : std::min(i, j); std::string key = line.substr(0, j); std::string value = line.substr(i + 1); dst[key] = value; } } } void PbapSession::pullAll(Content &dst) { GDBusCXX::DBusClientCall1 pullall(*m_session, "PullAll"); std::string content = pullall(); // empty content not treated as an error typedef std::map CounterMap; CounterMap counterMap; std::size_t pos = 0; int count = 0; while(pos < content.size()) { static const std::string beginStr("BEGIN:VCARD"); static const std::string endStr("END:VCARD"); pos = content.find(beginStr, pos); if(pos == std::string::npos) { break; } std::size_t endPos = content.find(endStr, pos + beginStr.size()); if(endPos == std::string::npos) { break; } endPos += endStr.size(); typedef std::map VcardMap; VcardMap vcard; vcardParse(content, pos, endPos, vcard); VcardMap::const_iterator it = vcard.find("N"); if(it != vcard.end() && !it->second.empty()) { const std::string &fn = it->second; const std::pair &r = counterMap.insert(CounterMap::value_type(fn, 0)); if(!r.second) { r.first->second ++; } char suffix[8]; sprintf(suffix, "%07d", r.first->second); std::string id = StringPrintf("%d", count); // fn + std::string(suffix); dst[id] = content.substr(pos, endPos - pos); } pos = endPos; count++; } SE_LOG_DEBUG(NULL, NULL, "PBAP content pulled: %d entries", (int) dst.size()); } void PbapSession::shutdown(void) { GDBusCXX::DBusClientCall0 removeSession(m_client, "RemoveSession"); // always clear pointer, even if method call fails GDBusCXX::DBusObject_t path(m_session->getPath()); m_session.reset(); SE_LOG_DEBUG(NULL, NULL, "removed session: %s", path.c_str()); removeSession(path); SE_LOG_DEBUG(NULL, NULL, "PBAP session closed"); } PbapSyncSource::PbapSyncSource(const SyncSourceParams ¶ms) : TrackingSyncSource(params) { m_session.reset(new PbapSession()); } std::string PbapSyncSource::getMimeType() const { return "text/x-vcard"; } std::string PbapSyncSource::getMimeVersion() const { return "2.1"; } void PbapSyncSource::open() { string database = getDatabaseID(); const string prefix("obex-bt://"); if (!boost::starts_with(database, prefix)) { throwError("database should specifiy device address (obex-bt://)"); } std::string address = database.substr(prefix.size()); m_session->initSession(address, getDatabaseFormat()); m_session->pullAll(m_content); m_session->shutdown(); } bool PbapSyncSource::isEmpty() { return m_content.empty(); } void PbapSyncSource::close() { } PbapSyncSource::Databases PbapSyncSource::getDatabases() { Databases result; result.push_back(Database("select database via bluetooth address", "[obex-bt://]")); return result; } void PbapSyncSource::listAllItems(RevisionMap_t &revisions) { typedef std::pair Entry; BOOST_FOREACH(const Entry &entry, m_content) { revisions[entry.first] = "0"; } } void PbapSyncSource::readItem(const string &uid, std::string &item, bool raw) { Content::iterator it = m_content.find(uid); if(it != m_content.end()) { item = it->second; } } TrackingSyncSource::InsertItemResult PbapSyncSource::insertItem(const string &uid, const std::string &item, bool raw) { throw std::runtime_error("Operation not supported"); } void PbapSyncSource::removeItem(const string &uid) { throw std::runtime_error("Operation not supported"); } SE_END_CXX #endif /* ENABLE_PBAP */ #ifdef ENABLE_MODULES # include "PbapSyncSourceRegister.cpp" #endif syncevolution_1.4/src/backends/pbap/PbapSyncSource.cpp.rej000066400000000000000000000013031230021373600240210ustar00rootroot00000000000000--- src/backends/pbap/PbapSyncSource.cpp +++ src/backends/pbap/PbapSyncSource.cpp @@ -81,16 +83,15 @@ return; } - typedef std::map > Params; GDBusCXX::DBusClientCall1 createSession(m_client, "CreateSession"); Params params; - params["Destination"] = std::string(address); + // params["Destination"] = std::string(address); params["Target"] = std::string("PBAP"); - std::string session = createSession(params); + std::string session = createSession(std::string(address), params); if (session.empty()) { SE_THROW("PBAP: got empty session from CreateSession()"); } syncevolution_1.4/src/backends/pbap/PbapSyncSource.h000066400000000000000000000062251230021373600227170ustar00rootroot00000000000000/* * Copyright (C) 2007-2009 Patrick Ohly * Copyright (C) 2012 BMW Car IT GmbH. All rights reserved. * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_PBAPSYNCSOURCE #define INCL_PBAPSYNCSOURCE #include #ifdef ENABLE_PBAP #include #include #include #include #include SE_BEGIN_CXX class PullAll; class PbapSession; class PbapSyncSource : virtual public SyncSource, virtual public SyncSourceSession, virtual public SyncSourceRaw, private boost::noncopyable { public: PbapSyncSource(const SyncSourceParams ¶ms); ~PbapSyncSource(); protected: /* implementation of SyncSource interface */ virtual void open(); virtual bool isEmpty(); virtual void close(); virtual Databases getDatabases(); virtual void enableServerMode(); virtual bool serverModeEnabled() const; virtual std::string getPeerMimeType() const; virtual void getSynthesisInfo(SynthesisInfo &info, XMLConfigFragments &fragments); /* implementation of SyncSourceSession interface */ virtual void beginSync(const std::string &lastToken, const std::string &resumeToken); virtual std::string endSync(bool success); /* implementation of SyncSourceRaw interface */ virtual InsertItemResult insertItemRaw(const std::string &luid, const std::string &item); virtual void readItemRaw(const std::string &luid, std::string &item); private: boost::shared_ptr m_session; boost::shared_ptr m_pullAll; enum PBAPSyncMode { PBAP_SYNC_NORMAL, ///< Read contact data according to filter. PBAP_SYNC_TEXT, ///< Sync without reading photo data from phone and keeping local photos instead. PBAP_SYNC_INCREMENTAL ///< Sync first without photo data (as in PBAP_SYNC_TEXT), /// then add photo data in second cycle. } m_PBAPSyncMode; bool m_isFirstCycle; bool m_hadContacts; /** * List items as expected by Synthesis engine. */ sysync::TSyError readNextItem(sysync::ItemID aID, sysync::sInt32 *aStatus, bool aFirst); /** * Copy item into Synthesis key. */ sysync::TSyError readItemAsKey(sysync::cItemID aID, sysync::KeyH aItemKey); }; SE_END_CXX #endif // ENABLE_PBAP #endif // INCL_PBAPSYNCSOURCE syncevolution_1.4/src/backends/pbap/PbapSyncSourceRegister.cpp000066400000000000000000000047141230021373600247600ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * Copyright (C) 2012 BMW Car IT GmbH. All rights reserved. * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "PbapSyncSource.h" #include "test.h" #include SE_BEGIN_CXX static SyncSource *createSource(const SyncSourceParams ¶ms) { SourceType sourceType = SyncSource::getSourceType(params.m_nodes); // The string returned by getSourceType() is always the one // registered as main Aliases() below. bool isMe = sourceType.m_backend == "PBAP Address Book"; #ifndef ENABLE_PBAP // tell SyncEvolution if the user wanted to use a disabled sync source, // otherwise let it continue searching return isMe ? RegisterSyncSource::InactiveSource(params) : NULL; #else // Also recognize one of the standard types? // Not in the PbapSyncSource! bool maybeMe = false /* sourceType.m_backend == "addressbook" */; if (isMe || maybeMe) { return new PbapSyncSource(params); } return NULL; #endif } static RegisterSyncSource registerMe("One-way sync using PBAP", #ifdef ENABLE_PBAP true, #else false, #endif createSource, "One-way sync using PBAP = pbap\n" " Requests phonebook entries using PBAP profile, and thus\n" " supporting read-only operations.\n" " The BT address is selected via database=obex-bt://.\n", Values() + (Aliases("PBAP Address Book") + "pbap")); SE_END_CXX syncevolution_1.4/src/backends/pbap/README000066400000000000000000000076171230021373600205340ustar00rootroot00000000000000Usage ===== The "pbap" backend provides access to an addressbook stored in a remote device using the Bluetooth PBAP protocol. This is achieved using the D-Bus services from obex-client (obexd), which needs to be running. The "database" property specifies the Bluetooth address of the device to be connected. The "databaseFormat" can be used as follows: [(2.1|3.0)][:][^]propname,propname,... 3.0 = download in vCard 3.0 instead of the default 2.1 3.0:^PHOTO = download in vCard 3.0 format, excluding PHOTO PHOTO = download in vCard 2.1 format, only the PHOTO Valid property names are the ones listed by obexd in ListFilterFields(). These fields may include: VERSION FN N PHOTO BDAY ADR LABEL TEL EMAIL MAILER TZ GEO TITLE ROLE LOGO AGENT ORG NOTE REV SOUND URL UID KEY NICKNAME CATEGORIES CLASS BITxy (for different values of xy) Set "SYNCEVOLUTION_DEBUG=1" as environment variable when using the backend to see a list of filter fields supported by obexd. Syncing ======= The backend itself is not able to detect changes since the last sync, mostly because PBAP as a protocol makes that hard. For example, it makes no guarantee that an item has the same ID in two different sessions. The intended use for the PBAP backend therefore is in the "slow local cache" sync mode: always pull all data and let the sync engine update a local database. The local database is not allowed to be modified outside of the sync. Any such change may get lost at any time. # configure backend # # # Preventing a slow sync is possible in local-cache mode. It # makes no sense for PBAP and thus has to be turned off. syncevolution --configure \ syncURL= \ addressbook/backend=pbap \ addressbook/database=obex-bt://XX:XX:XX:XX:XX:XX \ preventSlowSync=false \ target-config@pbap addressbook # print items syncevolution --print-items \ target-config@pbap addressbook # Configure synchronization. # # 'local-cache' expands to 'local-cache-incremental', which will try # to do an incremental sync but be turned into a slow sync by # the PBAP backend. syncevolution --configure \ --template SyncEvolution_Client \ syncURL=local://@pbap \ sync=local-cache \ pbap addressbook # run synchronization syncevolution pbap It may or may not be desirable to sync only the text properties of a contact. This can be considerably faster, because the PHOTO property is large and increases the download and processing time. SyncEvolution supports that in three different ways, chosen via the SYNCEVOLUTION_PBAP_SYNC env variable: SYNCEVOLUTION_PBAP_SYNC=text syncevolution pbap Synchronize only the text properties by excluding the PHOTO property, keep all photos already stored locally. SYNCEVOLUTION_PBAP_SYNC=all syncevolution pbap Synchronize all properties according to the databaseFormat in one go, adding/updating/removing photos locally to match the phone. SYNCEVOLUTION_PBAP_SYNC=incremental syncevolution pbap Synchronize first text, then all data. Conceptually this is the same as two invocations with "text" and then "all". Because the implementation reuses the same PBAP session and sync session, it is slightly faster. It is slower than a single "all" sync because the entire set of contacts must be downloaded and synced twice, once without photo, then with. SYNCEVOLUTION_PBAP_SYNC has side effect on the entire sync and thus may only be used in a sync config which synchronizes contacts via PBAP. The PIM Manager defaults to SYNCEVOLUTION_PBAP_SYNC=incremental. This can be overriden by setting the env variable when starting syncevo-dbus-server, for example by patching src/dbus/server/syncevo-dbus-server-startup.sh.in = /usr/libexec/syncevo-dbus-server-startup.sh. syncevolution_1.4/src/backends/pbap/configure-sub.in000066400000000000000000000026161230021373600227460ustar00rootroot00000000000000dnl -*- mode: Autoconf; -*- dnl Invoke autogen.sh to produce a configure script. dnl Checks for required libraris can go here; none required for simple files. dnl dnl This is from the sqlite backend: dnl PKG_CHECK_MODULES(SQLITE, sqlite3, SQLITEFOUND=yes, [SQLITEFOUND=no]) dnl AC_SUBST(SQLITE_CFLAGS) dnl AC_SUBST(SQLITE_LIBS) PBAP_CFLAGS= PBAP_LIBS= AC_SUBST(PBAP_CFLAGS) AC_SUBST(BAP_LIBS) dnl If additional compile flags are necessary to include the header dnl files of the backend, then add them here. BACKEND_CPPFLAGS="$BACKEND_CPPFLAGS $PBAP_CFLAGS" dnl name of backend library (there could be more than one per directory), dnl name of the directory, dnl help string, dnl --enable/disable chosen explicitly dnl default, may depend on availability of prerequisites in more complex backends SE_ARG_ENABLE_BACKEND(pbap, pbap, [AS_HELP_STRING([--enable-pbap], [enable Phone Book Access Protocol (PBAP) based backend which provides one-way synchronization (default off)])], [enable_pbap="$enableval"], [enable_pbap="no"] ) if test "$enable_pbap" = "yes"; then dnl It's good to check the prerequisites here, in case --enable-pbap was used. AC_DEFINE(ENABLE_PBAP, 1, [pbap available]) dnl Needed for g_file_open_tmp(). need_glib="yes" fi syncevolution_1.4/src/backends/pbap/pbap.am000066400000000000000000000027431230021373600211100ustar00rootroot00000000000000dist_noinst_DATA += \ src/backends/pbap/configure-sub.in \ src/backends/pbap/README \ $(NONE) src_backends_pbap_lib = src/backends/pbap/syncpbap.la MOSTLYCLEANFILES += $(src_backends_pbap_lib) if ENABLE_MODULES src_backends_pbap_backenddir = $(BACKENDS_DIRECTORY) src_backends_pbap_backend_LTLIBRARIES = $(src_backends_pbap_lib) else noinst_LTLIBRARIES += $(src_backends_pbap_lib) endif src_backends_pbap_src = \ src/backends/pbap/PbapSyncSource.h \ src/backends/pbap/PbapSyncSource.cpp src_backends_pbap_syncpbap_la_SOURCES = $(src_backends_pbap_src) src_backends_pbap_syncpbap_la_LIBADD = $(PBAP_LIBS) $(PCRECPP_LIBS) $(SYNCEVOLUTION_LIBS) $(DBUS_LIBS) $(gdbus_build_dir)/libgdbussyncevo.la src_backends_pbap_syncpbap_la_LDFLAGS = -module -avoid-version $(DBUS_LIBS) src_backends_pbap_syncpbap_la_CXXFLAGS = $(SYNCEVOLUTION_CXXFLAGS) $(SYNCEVO_WFLAGS) $(DBUS_CFLAGS) src_backends_pbap_syncpbap_la_CPPFLAGS = $(SYNCEVOLUTION_CFLAGS) -I$(gdbus_dir) -I$(top_srcdir)/test $(BACKEND_CPPFLAGS) src_backends_pbap_syncpbap_la_DEPENDENCIES = src/syncevo/libsyncevolution.la $(gdbus_build_dir)/libgdbussyncevo.la # If you need special test cases for your sync source, then # install them here. Here's how the sqlite backend does that: # #../../testcases/sqlite_vcard21.vcf: $(FUNAMBOL_SUBDIR)/test/test/testcases/vcard21.vcf # mkdir -p ${@D} # perl -e '$$_ = join("", <>); s/^(ADR|TEL|EMAIL|PHOTO).*?(?=^\S)//msg; s/;X-EVOLUTION-UI-SLOT=\d+//g; print;' $< >$@ #all: ../../testcases/sqlite_vcard21.vcf syncevolution_1.4/src/backends/qtcontacts/000077500000000000000000000000001230021373600211025ustar00rootroot00000000000000syncevolution_1.4/src/backends/qtcontacts/QtContactsSource.cpp000066400000000000000000000573201230021373600250610ustar00rootroot00000000000000/* * Copyright (C) 2007-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "config.h" #ifdef ENABLE_QTCONTACTS #include "QtContactsSource.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include SE_BEGIN_CXX using namespace QtMobility; /** * This handler represents QContactDetails which have no * mapping to vCard by storing them inside a X-SYNCEVO-QTCONTACTS * property. * * The exact format is: * X-SYNCEVOLUTION-QTCONTACTS:;(;;)* * * = detail name * = field name * = as in backup plugin (BOOL/INT/UINT/DATE/TIME/DATETIME/STRING/VARIANT) * STRING = QString as UTF-8 string, with special characters escaped * as in N * VARIANT = anything else, including byte arrays * * This is similar to the QtMobility 1.1 backup plugin (http://doc.qt.nokia.com/qtmobility-1.1/versitplugins.html). * The main differences are: * - This handler has a 1:1 mapping between QContactDetail and * vCard property; the backup plugin uses one property per QContactDetail * field and groups to combine them. * - Details which have a mapping to vCard are left untouched. * The backup plugin always adds at least the DetailUri. * * The reasons for implementing our own handler is: * - The "restore" part of the backup/restore plugin is completely missing * in QtMobility 1.1 and therefore it is unusable. * - The single property per detail approach is more readable (IMHO, of course). * - Turning a property back into a detail is likely to be easier when all information * is in single property. * - Groups in vCard are unusual and thus more likely to confuse peers. * The extended format used by this handler only relies on the normal X- property * extension. * * The restore from property part of this handler ignores all details * and fields which are not valid for the contact. In other words, it * does not define details. * * Example backup plugin: * G1.UID:{8c0bc9aa-9379-4aec-b8f1-78ba55992076} * G1.X-NOKIA-QCONTACTFIELD;DETAIL=Guid;FIELD=DetailUri:http://www.semanticdesk * top.org/ontologies/2007/03/22/nco#default-contact-me#Guid * G2.N:;Me;;; * G2.X-NOKIA-QCONTACTFIELD;DETAIL=Name;FIELD=DetailUri:http://www.semanticdesk * top.org/ontologies/2007/03/22/nco#default-contact-me#Name * G3.TEL;TYPE=VOICE: * G3.X-NOKIA-QCONTACTFIELD;DETAIL=PhoneNumber;FIELD=DetailUri:urn:uuid:5087e2a * 2-39f4-37a9-757c-ee291294f9e9 * G4.X-NOKIA-QCONTACTFIELD;DETAIL=Pet;FIELD=Name:Rex * G4.X-NOKIA-QCONTACTFIELD;DETAIL=Pet;FIELD=Age;DATATYPE=INT:14 * * Example this handler: * UID:{8c0bc9aa-9379-4aec-b8f1-78ba55992076} * N:;Me;;; * TEL: * X-SYNCEVO-QTCONTACTS:Pet^Name^STRING^Rex^Age^INT^14 * * The somewhat strange ^ separator is necessary because custom properties cannot * be of compound type in QVersit (http://bugreports.qt.nokia.com/browse/QTMOBILITY-1298). */ class SyncEvoQtContactsHandler : public QVersitContactImporterPropertyHandlerV2, public QVersitContactExporterDetailHandlerV2 { const QMap m_details; public: /** * @param details definition of all details that are valid for a contact (only relevant for parsing vCard) */ SyncEvoQtContactsHandler(const QMap &details = QMap()) : m_details(details) {} virtual void contactProcessed(const QContact &contact, QVersitDocument *document ) {} virtual void detailProcessed( const QContact &contact, const QContactDetail &detail, const QVersitDocument &document, QSet *processedFields, QList *toBeRemoved, QList *toBeAdded) { // ignore details if // - already encoded (assumed to do a good enough job) // - read-only = synthesized (we would not be able to write it back anyway) // - the default "Type = Contact" // - empty detail (empty QContactName otherwise would be encoded) if (!toBeAdded->empty() || (detail.accessConstraints() & QContactDetail::ReadOnly) || (detail.definitionName() == "Type" && contact.type() == "Contact") || detail.isEmpty()) { return; } QStringList content; content << detail.definitionName(); // QVariantMap fields = detail.variantValues(); for (QVariantMap::const_iterator entry = fields.begin(); entry != fields.end(); ++entry) { const QString &fieldName = entry.key(); const QVariant &value = entry.value(); content << fieldName; // if (value.type() == QVariant::String) { content << "STRING" << value.toString().toUtf8(); } else if (value.type() == QVariant::Bool) { content << "BOOL" << (value.toBool() ? "1" : "0"); } else if (value.type() == QVariant::Int) { content << "INT" << QString::number(value.toInt()); } else if (value.type() == QVariant::UInt) { content << "UINT" << QString::number(value.toUInt()); } else if (value.type() == QVariant::Date) { content << "DATE" << value.toDate().toString(Qt::ISODate); } else if (value.type() == QVariant::DateTime) { content << "DATETIME" << value.toDateTime().toString(Qt::ISODate); } else { QByteArray valueBytes; QDataStream stream(&valueBytes, QIODevice::WriteOnly); stream << value; content << "VARIANT" << valueBytes.toHex().constData(); } *processedFields << fieldName; } // Using QVersitProperty::CompoundType and the string list // as-is would be nice, but isn't supported by QtMobility 1.2.0 beta // because QVersitReader will not know that the property is // of compound type and will replace \; with ; without splitting // into individual strings first. See http://bugreports.qt.nokia.com/browse/QTMOBILITY-1298 // // Workaround: replace ^ inside strings with | and then use ^ as separator // These characters were chosen because they are not special in vCard and thus // require no further escaping. QVersitProperty prop; prop.setName("X-SYNCEVO-QTCONTACTS"); #ifdef USE_QVERSIT_COMPOUND prop.setValueType(QVersitProperty::CompoundType); prop.setValue(QVariant(content)); #else StringEscape escape('|', "^"); std::list strings; BOOST_FOREACH(const QString &str, content) { strings.push_back(escape.escape(string(str.toUtf8().constData()))); } prop.setValue(QVariant(QString::fromUtf8(boost::join(strings, "^").c_str()))); #endif *toBeAdded << prop; } virtual void documentProcessed(const QVersitDocument &document, QContact *contact) {} virtual void propertyProcessed( const QVersitDocument &document, const QVersitProperty &property, const QContact &contact, bool *alreadyProcessed, QList *updatedDetails) { if (*alreadyProcessed || property.name() != "X-SYNCEVO-QTCONTACTS") { // not something that we need to parse return; } *alreadyProcessed = true; #ifdef USE_QVERSIT_COMPOUND QStringList content = property.value(); #else QStringList content; StringEscape escape('|', "^"); typedef boost::split_iterator string_split_iterator; string valueString = property.value().toUtf8().constData(); string_split_iterator it = boost::make_split_iterator(valueString, boost::first_finder("^", boost::is_iequal())); while (it != string_split_iterator()) { content << QString::fromUtf8(escape.unescape(std::string(it->begin(), it->end())).c_str()); ++it; } #endif // detail name available? if (content.size() > 0) { const QString &detailName = content[0]; QMap::const_iterator it = m_details.constFind(detailName); // detail still exists? if (it != m_details.constEnd()) { const QContactDetailDefinition &definition = *it; // now decode all fields and copy into new detail QContactDetail detail(content[0]); int i = 1; while (i + 2 < content.size()) { const QString &fieldName = content[i++]; const QString &type = content[i++]; const QString &valueString = content[i++]; QVariant value; if (type == "STRING") { value.setValue(valueString); } else if (type == "BOOL") { value.setValue(valueString == "1"); } else if (type == "INT") { value.setValue(valueString.toInt()); } else if (type == "UINT") { value.setValue(valueString.toUInt()); } else if (type == "DATE") { value.setValue(QDate::fromString(valueString, Qt::ISODate)); } else if (type == "DATETIME") { value.setValue(QDateTime::fromString(valueString, Qt::ISODate)); } else if (type == "VARIANT") { QByteArray valueBytes = QByteArray::fromHex(valueString.toAscii()); QDataStream stream(&valueBytes, QIODevice::ReadOnly); stream >> value; } else { // unknown type, skip it continue; } // skip fields which are (no longer) valid, have wrong type or wrong value QMap fields = definition.fields(); QMap::const_iterator it2 = fields.constFind(fieldName); if (it2 != fields.constEnd()) { if (it2->dataType() == value.type()) { QVariantList allowed = it2->allowableValues(); if (allowed.empty() || allowed.indexOf(value) != -1) { // add field detail.setValue(fieldName, value); } } } } // update contact with the new detail *updatedDetails << detail; } } } }; class QtContactsData { QtContactsSource *m_parent; QString m_managerURI; cxxptr m_manager; public: QtContactsData(QtContactsSource *parent, const QString &managerURI) : m_parent(parent), m_managerURI(managerURI) { if (!qApp) { static const char *argv[] = { "SyncEvolution" }; static int argc = 1; new QCoreApplication(argc, (char **)argv); } } static QList createContactList(const string &uid) { QList list; list.append(atoi(uid.c_str())); return list; } static QContactLocalIdFilter createFilter(const string &uid) { QContactLocalIdFilter filter; filter.setIds(createContactList(uid)); return filter; } static string getLUID(const QContact &contact) { QContactLocalId id = contact.localId(); return StringPrintf("%u", id); } static string getRev(const QContact &contact) { QContactTimestamp rev = contact.detail(); QDateTime stamp = rev.lastModified(); if (!stamp.isValid()) { stamp = rev.created(); } return stamp.toString().toLocal8Bit().constData(); } template void checkError(const char *op, T &req) { if (req.error()) { m_parent->throwError(StringPrintf("%s: failed with error %d", op, req.error())); } } template void checkError(const char *op, T &req, const QMap &errors) { if (errors.isEmpty()) { checkError(op, req); } else { list res; foreach (int index, errors.keys()) { res.push_back(StringPrintf("entry #%d failed with error %d", index, errors[index])); } m_parent->throwError(StringPrintf("%s: failed with error %d, ", op, req.error()) + boost::join(res, ", ")); } } friend class QtContactsSource; }; QtContactsSource::QtContactsSource(const SyncSourceParams ¶ms) : TrackingSyncSource(params) { m_data = NULL; SyncSourceLogging::init(InitList("N_FIRST") + "N_MIDDLE" + "N_LAST", " ", m_operations); } QtContactsSource::~QtContactsSource() { delete m_data; } void QtContactsSource::open() { QString buffer; QDebug(&buffer) << "available managers (default one first): " << QContactManager::availableManagers(); SE_LOG_DEBUG(NULL, buffer.toUtf8().data()); string id = getDatabaseID(); m_data = new QtContactsData(this, id.c_str()); cxxptr manager(QContactManager::fromUri(m_data->m_managerURI), "QTContactManager"); if (manager->error()) { throwError(StringPrintf("failed to open QtContact database %s, error code %d", m_data->m_managerURI.toLocal8Bit().constData(), manager->error())); } buffer = ""; QDebug(&buffer) << manager->managerUri() << " manager supports contact types: " << manager->supportedContactTypes() << " and data types: " << manager->supportedDataTypes(); SE_LOG_DEBUG(NULL, buffer.toUtf8().data()); m_data->m_manager = manager; } bool QtContactsSource::isEmpty() { return false; } void QtContactsSource::close() { m_data->m_manager.set(0); } QtContactsSource::Databases QtContactsSource::getDatabases() { Databases result; QStringList availableManagers = QContactManager::availableManagers(); bool isDefault = true; #if 0 result.push_back(Database("select database via QtContacts Manager URL", "qtcontacts:tracker:")); #endif foreach (QString manager, availableManagers) { QMap params; QString uri = QContactManager::buildUri(manager, params); result.push_back(Database(manager.toStdString(), uri.toStdString(), isDefault)); isDefault = false; } return result; } void QtContactsSource::listAllItems(RevisionMap_t &revisions) { #ifdef ENABLE_MAEMO QContactLocalId self_id = m_data->m_manager->selfContactId(); #endif QContactFetchRequest fetch; fetch.setManager(m_data->m_manager.get()); #ifdef ENABLE_MAEMO // only sync contacts from addressbook, not from Telepathy or wherever QContactDetailFilter filter; filter.setDetailDefinitionName(QContactSyncTarget::DefinitionName, QContactSyncTarget::FieldSyncTarget); filter.setValue("addressbook"); filter.setMatchFlags(QContactFilter::MatchExactly); fetch.setFilter(filter); #endif // only need ID and time stamps QContactFetchHint hint; hint.setOptimizationHints(QContactFetchHint::OptimizationHints(QContactFetchHint::NoRelationships|QContactFetchHint::NoBinaryBlobs)); hint.setDetailDefinitionsHint(QStringList() << QContactTimestamp::DefinitionName); fetch.setFetchHint(hint); fetch.start(); fetch.waitForFinished(); m_data->checkError("read all items", fetch); foreach (const QContact &contact, fetch.contacts()) { #ifdef ENABLE_MAEMO if (contact.localId() == self_id) { // Do not synchronize "self" contact continue; } #endif string revision = QtContactsData::getRev(contact); string luid = QtContactsData::getLUID(contact); if (luid == "2147483647" && revision == "") { // Seems to be a special, artifical contact that always // exists. Ignore it. // // Also note that qtcontacts-tracker and/or QtContacts // spew out a warning on stdout about it (?): // skipping contact with unsupported IRI: "http://www.semanticdesktop.org/ontologies/2007/03/22/nco#default-contact-emergency" continue; } revisions[luid] = revision; } } void QtContactsSource::readItem(const string &uid, std::string &item, bool raw) { QContactFetchRequest fetch; fetch.setManager(m_data->m_manager.get()); fetch.setFilter(QtContactsData::createFilter(uid)); fetch.start(); fetch.waitForFinished(); QList contacts = fetch.contacts(); for (int i = 0; i < contacts.size(); ++i) { QContact &contact = contacts[i]; const QContactAvatar avatar = contact.detail(QContactAvatar::DefinitionName); const QContactThumbnail thumb = contact.detail(QContactThumbnail::DefinitionName); if (!avatar.isEmpty() && thumb.isEmpty()) { QImage image(avatar.imageUrl().path()); QContactThumbnail thumbnail; thumbnail.setThumbnail(image); contact.saveDetail(&thumbnail); } // foreach (const QContactSyncTarget &target, contact.details()) { // std::cout << " Sync Target: " << target.syncTarget().toUtf8().data() << std::endl; // } } QStringList profiles; #ifdef USE_PROFILE_BACKUP_RAW_FORMAT if (raw) { profiles << QVersitContactHandlerFactory::ProfileBackup; } #endif SyncEvoQtContactsHandler handler; QVersitContactExporter exporter(profiles); exporter.setDetailHandler(&handler); if (!exporter.exportContacts(contacts, QVersitDocument::VCard30Type)) { throwError(uid + ": encoding as vCard 3.0 failed"); } QByteArray vcard; QVersitWriter writer(&vcard); if (!writer.startWriting(exporter.documents())) { throwError(uid + ": writing as vCard 3.0 failed"); } writer.waitForFinished(); item = vcard.constData(); m_data->checkError("encoding as vCard 3.0", writer); } TrackingSyncSource::InsertItemResult QtContactsSource::insertItem(const string &uid, const std::string &item, bool raw) { QVersitReader reader(QByteArray(item.c_str())); if (!reader.startReading()) { throwError("reading vCard failed"); } reader.waitForFinished(); m_data->checkError("decoding vCard", reader); QStringList profiles; #ifdef USE_PROFILE_BACKUP_RAW_FORMAT if (raw) { profiles << QVersitContactHandlerFactory::ProfileBackup; } #endif SyncEvoQtContactsHandler handler(m_data->m_manager->detailDefinitions()); QVersitContactImporter importer(profiles); importer.setPropertyHandler(&handler); if (!importer.importDocuments(reader.results())) { throwError("importing vCard failed"); } QList contacts = importer.contacts(); QContact &contact = contacts.first(); if (!uid.empty()) { QContactId id; id.setManagerUri(m_data->m_managerURI); id.setLocalId(atoi(uid.c_str())); contact.setId(id); } QContactSaveRequest save; save.setManager(m_data->m_manager.get()); save.setContacts(QList() << contact); save.start(); save.waitForFinished(); m_data->checkError("saving contact", save, save.errorMap()); QList savedContacts = save.contacts(); QContact &savedContact = savedContacts.first(); // Saving is not guaranteed to update the time stamp (BMC #5710). // Need to read again. QContactFetchRequest fetch; fetch.setManager(m_data->m_manager.get()); fetch.setFilter(QtContactsData::createFilter(QtContactsData::getLUID(savedContact))); QContactFetchHint hint; hint.setOptimizationHints(QContactFetchHint::OptimizationHints(QContactFetchHint::NoRelationships|QContactFetchHint::NoBinaryBlobs)); hint.setDetailDefinitionsHint(QStringList() << QContactTimestamp::DefinitionName); fetch.setFetchHint(hint); fetch.start(); fetch.waitForFinished(); QContact &finalContact = fetch.contacts().first(); return InsertItemResult(QtContactsData::getLUID(savedContact), QtContactsData::getRev(finalContact), ITEM_OKAY); } void QtContactsSource::removeItem(const string &uid) { #if 1 QContactRemoveRequest remove; remove.setManager(m_data->m_manager.get()); remove.setContactIds(QtContactsData::createContactList(uid)); remove.start(); remove.waitForFinished(); m_data->checkError("remove contact", remove, remove.errorMap()); #else m_data->m_manager->removeContact(atoi(uid.c_str())); #endif } std::string QtContactsSource::getDescription(const string &luid) { try { QContactFetchRequest fetch; fetch.setManager(m_data->m_manager.get()); fetch.setFilter(QtContactsData::createFilter(luid)); fetch.start(); fetch.waitForFinished(); if (fetch.contacts().isEmpty()) { return ""; } QContact contact = fetch.contacts().first(); string descr = contact.displayLabel().toLocal8Bit().constData(); return descr; } catch (...) { // Instead of failing we log the error and ask // the caller to log the UID. That way transient // errors or errors in the logging code don't // prevent syncs. handleException(); return ""; } } SE_END_CXX #endif /* ENABLE_QTCONTACTS */ #ifdef ENABLE_MODULES # include "QtContactsSourceRegister.cpp" #endif syncevolution_1.4/src/backends/qtcontacts/QtContactsSource.h000066400000000000000000000043541230021373600245250ustar00rootroot00000000000000/* * Copyright (C) 2007-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_QTCONTACTSSYNCSOURCE #define INCL_QTCONTACTSSYNCSOURCE #include #ifdef ENABLE_QTCONTACTS #include #include #include SE_BEGIN_CXX class QtContactsData; /** * Access contacts stored in QtContacts. * * This class is designed so that no Qt header files are required * to include this header file. */ class QtContactsSource : public TrackingSyncSource, public SyncSourceLogging, private boost::noncopyable { public: QtContactsSource(const SyncSourceParams ¶ms); ~QtContactsSource(); protected: /* implementation of SyncSource interface */ virtual void open(); virtual bool isEmpty(); virtual void close(); virtual Databases getDatabases(); virtual std::string getMimeType() const { return "text/vcard"; } virtual std::string getMimeVersion() const { return "3.0"; } /* implementation of TrackingSyncSource interface */ virtual void listAllItems(RevisionMap_t &revisions); virtual InsertItemResult insertItem(const string &luid, const std::string &item, bool raw); void readItem(const std::string &luid, std::string &item, bool raw); virtual void removeItem(const string &uid); /* implementation of SyncSourceLogging */ virtual std::string getDescription(const string &luid); private: QtContactsData *m_data; }; SE_END_CXX #endif // ENABLE_QTCONTACTS #endif // INCL_QTCONTACTSSYNCSOURCE syncevolution_1.4/src/backends/qtcontacts/QtContactsSourceRegister.cpp000066400000000000000000000204741230021373600265660ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "QtContactsSource.h" #include "test.h" #ifdef ENABLE_QTCONTACTS #include #include #include #endif #include SE_BEGIN_CXX static SyncSource *createSource(const SyncSourceParams ¶ms) { SourceType sourceType = SyncSource::getSourceType(params.m_nodes); bool isMe = sourceType.m_backend == "QtContacts"; bool maybeMe = sourceType.m_backend == "addressbook"; if (isMe || maybeMe) { if (sourceType.m_format == "" || sourceType.m_format == "text/x-vcard" || sourceType.m_format == "text/vcard") { return #ifdef ENABLE_QTCONTACTS true ? new QtContactsSource(params) : #endif isMe ? RegisterSyncSource::InactiveSource(params) : NULL; } } return NULL; } static RegisterSyncSource registerMe("QtContacts", #ifdef ENABLE_QTCONTACTS true, #else false, #endif createSource, "QtContacts = addressbook = contacts = qt-contacts\n" " vCard 3.0 = text/vcard\n" " 'database' is specified via a QtContacts URI, which\n" " consists of qtcontacts::.\n" " Examples: 'qtcontacts:tracker:' or 'qtcontacts:eds:source=local:/system'\n", Values() + (Aliases("QtContacts") + "qt-contacts")); #ifdef ENABLE_QTCONTACTS #ifdef ENABLE_UNIT_TESTS class QtContactsSourceUnitTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(QtContactsSourceUnitTest); CPPUNIT_TEST(testInstantiate); CPPUNIT_TEST(testHandler); CPPUNIT_TEST_SUITE_END(); protected: void testInstantiate() { boost::shared_ptr source; source.reset(SyncSource::createTestingSource("qtcontacts", "qtcontacts:text/vcard:3.0", true)); source.reset(SyncSource::createTestingSource("qtcontacts", "QtContacts", true)); } void testHandler() { QContact out; QList contacts; QContactDetailFieldDefinition field; QContactDetailDefinition unique; QMap details; unique.setName("Unique"); unique.setUnique(true); field.setDataType(QVariant::Bool); unique.insertField("Bool", field); field.setDataType(QVariant::Int); unique.insertField("Int", field); field.setDataType(QVariant::UInt); unique.insertField("UInt", field); field.setDataType(QVariant::Date); unique.insertField("Date", field); field.setDataType(QVariant::DateTime); unique.insertField("DateTime", field); field.setDataType(QVariant::String); unique.insertField("String", field); field.setDataType(QVariant::ByteArray); unique.insertField("ByteArray", field); details["Unique"] = unique; QContactDetailDefinition multiple; multiple.setName("Multiple"); field.setDataType(QVariant::String); multiple.insertField("String", field); details["Multiple"] = multiple; QContactBirthday birthday; birthday.setDate(QDate(2000, 1, 1)); CPPUNIT_ASSERT(out.saveDetail(&birthday)); QContactEmailAddress email; email.setEmailAddress("john.doe@foo.com"); CPPUNIT_ASSERT(out.saveDetail(&email)); QContactDetail detailUnique("Unique"); detailUnique.setValue("Bool", QVariant(true)); detailUnique.setValue("Int", QVariant(-1)); detailUnique.setValue("UInt", QVariant(4294967295u)); detailUnique.setValue("Date", QVariant(QDate(2011, 12, 1))); detailUnique.setValue("DateTime", QVariant(QDateTime(QDate(2011, 12, 1), QTime(23, 59, 59)))); detailUnique.setValue("String", QVariant(QString("hello world;\nhow are you?"))); detailUnique.setValue("ByteArray", QVariant(QByteArray() + 'a' + 'b' + 'c')); CPPUNIT_ASSERT(out.saveDetail(&detailUnique)); QContactDetail detailMulti1("Multiple"); detailMulti1.setValue("String", QVariant(QString("hello"))); CPPUNIT_ASSERT(out.saveDetail(&detailMulti1)); QContactDetail detailMulti2("Multiple"); detailMulti2.setValue("String", QVariant(QString("world"))); CPPUNIT_ASSERT(out.saveDetail(&detailMulti2)); // empty name because parser otherwise does things like // synthesizing custom and display name, which breaks the // comparison below QContactName name; CPPUNIT_ASSERT(out.saveDetail(&name)); contacts << out; SyncEvoQtContactsHandler handler(details); QVersitContactExporter exporter; exporter.setDetailHandler(&handler); CPPUNIT_ASSERT(exporter.exportContacts(contacts, QVersitDocument::VCard30Type)); QByteArray vcard; QVersitWriter writer(&vcard); CPPUNIT_ASSERT(writer.startWriting(exporter.documents())); writer.waitForFinished(); CPPUNIT_ASSERT(!writer.error()); string item = vcard.constData(); CPPUNIT_ASSERT_EQUAL(string("BEGIN:VCARD\r\n" "VERSION:3.0\r\n" "BDAY:2000-01-01\r\n" "EMAIL:john.doe@foo.com\r\n" "X-SYNCEVO-QTCONTACTS:Unique^Bool^BOOL^1^ByteArray^VARIANT^0000000c0000000003\r\n" " 616263^Date^DATE^2011-12-01^DateTime^DATETIME^2011-12-01T23:59:59^Int^INT^-\r\n" " 1^String^STRING^hello world\\;|0ahow are you?^UInt^UINT^4294967295\r\n" "X-SYNCEVO-QTCONTACTS:Multiple^String^STRING^hello\r\n" "X-SYNCEVO-QTCONTACTS:Multiple^String^STRING^world\r\n" "FN:\r\n" "N:;;;;\r\n" "END:VCARD\r\n"), item); QVersitReader reader(QByteArray(item.c_str())); CPPUNIT_ASSERT(reader.startReading()); reader.waitForFinished(); CPPUNIT_ASSERT(!reader.error()); QVersitContactImporter importer; importer.setPropertyHandler(&handler); CPPUNIT_ASSERT(importer.importDocuments(reader.results())); contacts = importer.contacts(); QContact &in = contacts.first(); QString outString, inString; QDebug(&outString) << out; QDebug(&inString) << in; if (out != in) { // strings are never quite equal due to key, so skip this check // if QContact itself thinks its values are equal CPPUNIT_ASSERT_EQUAL(string(outString.toUtf8().constData()), string(inString.toUtf8().constData())); } } }; SYNCEVOLUTION_TEST_SUITE_REGISTRATION(QtContactsSourceUnitTest); #endif // ENABLE_UNIT_TESTS namespace { #if 0 } #endif static class VCard30Test : public RegisterSyncSourceTest { public: VCard30Test() : RegisterSyncSourceTest("qt_contact", "eds_contact") {} virtual void updateConfig(ClientTestConfig &config) const { config.m_type = "qt-contacts:text/vcard"; config.m_testcases = "testcases/qt_contact.vcf"; } } vCard30Test; } #endif // ENABLE_QTCONTACTS SE_END_CXX syncevolution_1.4/src/backends/qtcontacts/configure-sub.in000066400000000000000000000016051230021373600242040ustar00rootroot00000000000000dnl -*- mode: Autoconf; -*- dnl Invoke autogen.sh to produce a configure script. # BACKEND_CPPFLAGS="$BACKEND_CPPFLAGS ..." SE_ARG_ENABLE_BACKEND(qtcontacts, qtcontacts, [AS_HELP_STRING([--enable-qtcontacts], [enable support for QtMobility's QtContacts storage])], [enable_qtcontacts="$enableval"], [enable_qtcontacts="no"] ) if test "$enable_qtcontacts" = "yes"; then AC_DEFINE(ENABLE_QTCONTACTS, 1, [QtContacts available]) # AC_WITH_QT() will be called in configure-post if need_qt_modules is not empty, # setting QT_* flags. need_qt_modules="$need_qt_modules +gui" # GUI needed for QVersit qt_config="$qt_config +mobility" qt_misc="$qt_misc MOBILITY += contacts versit" fi AC_SUBST(QT_CONTACTS_LIBS) syncevolution_1.4/src/backends/qtcontacts/qtcontacts.am000066400000000000000000000024521230021373600236070ustar00rootroot00000000000000dist_noinst_DATA += src/backends/qtcontacts/configure-sub.in src_backends_qtcontacts_lib = src/backends/qtcontacts/syncqtcontacts.la MOSTLYCLEANFILES += $(src_backends_qtcontacts_lib) if ENABLE_MODULES src_backends_qtcontacts_backenddir = $(BACKENDS_DIRECTORY) src_backends_qtcontacts_backend_LTLIBRARIES = $(src_backends_qtcontacts_lib) else noinst_LTLIBRARIES += $(src_backends_qtcontacts_lib) endif src_backends_qtcontacts_syncqtcontacts_la_SOURCES = \ src/backends/qtcontacts/QtContactsSource.h \ src/backends/qtcontacts/QtContactsSource.cpp src_backends_qtcontacts_syncqtcontacts_la_LIBADD = $(QT_CONTACTS_LIBS) $(QT_LIBS) $(SYNCEVOLUTION_LIBS) src_backends_qtcontacts_syncqtcontacts_la_LDFLAGS = -module -avoid-version $(QT_LDFLAGS) src_backends_qtcontacts_syncqtcontacts_la_CPPFLAGS = $(SYNCEVOLUTION_CFLAGS) -I$(top_srcdir)/test $(BACKEND_CPPFLAGS) $(QT_CPPFLAGS) src_backends_qtcontacts_syncqtcontacts_la_DEPENDENCIES = src/syncevo/libsyncevolution.la # Allow Qt to set some compile flags, but not the ones normally set via configure. # In particular -W is not compatible with the SyncEvolution header files (we have # unused parameters in some inline functions). src_backends_qtcontacts_syncqtcontacts_la_CXXFLAGS = $(SYNCEVOLUTION_CXXFLAGS) $(filter-out -O2 -g -W -Wall, $(QT_CXXFLAGS)) $(SYNCEVO_WFLAGS) syncevolution_1.4/src/backends/signon/000077500000000000000000000000001230021373600202145ustar00rootroot00000000000000syncevolution_1.4/src/backends/signon/README000066400000000000000000000065371230021373600211070ustar00rootroot00000000000000Google CalDAV/CardDAV via OAuth2 ================================ Setup ----- SyncEvolution needs an active account for Google. Services for CardDAV and CalDAV can be used, but are not required if the provider was already configured to ask for CardDAV and CalDAV access. SyncEvolution source code ships with example .provider, .service, and .service-type files in the src/backends/signon/accounts directory. These files work with gSSO. When compiling, it is possible to insert valid client ID+secret into these files using the --with-google-client-* configure options. The example files then get installed into $docdir/accounts/[providers|services|service_types]. SyncEvolution binaries from syncevolution.org are compiled with valid client ID+secret. Distributions are encouraged to *not* reuse the same credentials and register themselves with Google instead. That way, abuse of the credentials affects less users and the quota available to all users of the same credentials does not get exhausted as quickly. Users of the syncevolution.org binaries need to install the /usr/share/doc/syncevolution/accounts themselves, typically in their home directory: cp -a /usr/share/doc/syncevolution/accounts ~/.local/share/accounts A "google" account needs to be created and enabled: ag-tool create-account google google-for-syncevolution ID=`ag-tool list-accounts | grep google-for-syncevolution | sed -e 's/ .*//'` ag-tool enable-account $ID Optionally, one can also enable the services: ag-tool enable-service $ID google-carddav ag-tool enable-service $ID google-caldav It is not possible to create signond identities via command line tools. SyncEvolution will create those automatically and store them for the provider so that it can be reused in future operations. Usage ----- OAuth2 authentication with gSSO is enabled by setting username or databaseUser to a string of the format gsso:[,] When used without service name, SyncEvolution will ask for access to CalDAV and CardDAV only once. When used with service name, it will ask for access to only CalDAV or CardDAV, but it will have to ask twice (once for each service). The base URL for each service currently needs to be given via syncURL: syncevolution --print-databases \ backend=carddav \ username=gsso:$ID,google-carddav \ syncURL=https://www.googleapis.com/.well-known/carddav src/syncevolution --print-databases \ backend=caldav \ username=gsso:$ID,google-caldav \ syncURL=https://apidata.googleusercontent.com/caldav/v2 Once that works, follow the "CalDAV and CardDAV" instructions from the README with the different username and syncURL. Debugging --------- Add --daemon=no to the command line to prevent shifting the actual command executing into syncevo-dbus-server and (from there) syncevo-dbus-helper. Warning: gsignond limits access to the identity to the executable which created it. One has to use different accounts for syncevolution with and without --daemon=no and another account when using valgrind. Set SYNCEVOLUTION_DEBUG=1 to see all debug messages and increase the loglevel to see HTTP requests: SYNCEVOLUTION_DEBUG=1 syncevolution --daemon=no \ loglevel=4 \ --print-databases \ ... syncevolution_1.4/src/backends/signon/accounts/000077500000000000000000000000001230021373600220335ustar00rootroot00000000000000syncevolution_1.4/src/backends/signon/accounts/caldav.service-type000066400000000000000000000002641230021373600256300ustar00rootroot00000000000000 CalDAV Integrate your events, tasks, memos via CalDAV syncevolution_1.4/src/backends/signon/accounts/carddav.service-type000066400000000000000000000002531230021373600260000ustar00rootroot00000000000000 CardDAV Integrate your contacts via CardDAV syncevolution_1.4/src/backends/signon/accounts/google-caldav.service.in000066400000000000000000000016161230021373600265320ustar00rootroot00000000000000 contacts Google Contacts google syncevolution_1.4/src/backends/signon/accounts/google-carddav.service.in000066400000000000000000000016171230021373600267050ustar00rootroot00000000000000 contacts Google Contacts google syncevolution_1.4/src/backends/signon/accounts/google.provider.in000066400000000000000000000022071230021373600254710ustar00rootroot00000000000000 Google Contacts syncevolution_1.4/src/backends/signon/configure-sub.in000066400000000000000000000100601230021373600233110ustar00rootroot00000000000000PKG_CHECK_MODULES(GSSO, [libgsignon-glib libaccounts-glib], HAVE_GSSO=yes, HAVE_GSSO=no) AC_ARG_ENABLE(gsso, AS_HELP_STRING([--enable-gsso], [enables or disables support for the gSSO single-sign-on system; default is on if development files are available]), [enable_gsso="$enableval" test "$enable_gsso" = "yes" || test "$enable_gsso" = "no" || AC_MSG_ERROR([invalid value for --enable-gsso: $enable_gsso]) test "$enable_gsso" = "no" || test "$HAVE_GSSO" = "yes" || AC_MSG_ERROR([required pkg(s) not found that are needed for --enable-gsso])], enable_gsso="$HAVE_GSSO") if test $enable_gsso = "yes"; then AC_DEFINE(USE_GSSO, 1, [use gSSO]) # link into static executables, similar to a SyncSource SYNCSOURCES="$SYNCSOURCES src/backends/gsso/providergsso.la" GOOGLE_METHOD="oauth" GOOGLE_MECHANISM="oauth2" AC_SUBST(GOOGLE_METHOD) AC_SUBST(GOOGLE_MECHANISM) fi # conditional compilation in make AM_CONDITIONAL([USE_GSSO], [test "$use_gsso" = "yes"]) PKG_CHECK_MODULES(UOA, [libsignon-glib >= 1.7 libaccounts-glib >= 1.8], HAVE_UOA=yes, HAVE_UOA=no) AC_ARG_ENABLE(uoa, AS_HELP_STRING([--enable-uoa], [enables or disables support for the UOA single-sign-on system; default is on if development files are available]), [enable_uoa="$enableval" test "$enable_uoa" = "yes" || test "$enable_uoa" = "no" || AC_MSG_ERROR([invalid value for --enable-uoa: $enable_uoa]) test "$enable_uoa" = "no" || test "$HAVE_UOA" = "yes" || AC_MSG_ERROR([required pkg(s) not found that are needed for --enable-uoa])], enable_uoa="$HAVE_UOA") if test $enable_uoa = "yes"; then AC_DEFINE(USE_UOA, 1, [use UOA]) # link into static executables, similar to a SyncSource SYNCSOURCES="$SYNCSOURCES src/backends/uoa/provideruoa.la" GOOGLE_METHOD="oauth2" GOOGLE_MECHANISM="web_server" AC_SUBST(GOOGLE_METHOD) AC_SUBST(GOOGLE_MECHANISM) fi # conditional compilation in make AM_CONDITIONAL([USE_UOA], [test "$enable_uoa" = "yes"]) AC_ARG_WITH([google-client-id], AS_HELP_STRING([--with-google-client-id=...], [OAuth2 client ID for google.provider]), [GOOGLE_CLIENT_ID=$withval], [GOOGLE_CLIENT_ID=insert-your-client-id-here]) AC_ARG_WITH([google-client-secret], AS_HELP_STRING([--with-google-client-secret=...], [OAuth2 client secret for google.provider]), [GOOGLE_CLIENT_SECRET=$withval], [GOOGLE_CLIENT_SECRET=insert-your-client-secret-here]) AC_SUBST(GOOGLE_CLIENT_ID) AC_SUBST(GOOGLE_CLIENT_SECRET) AC_ARG_WITH([google-client-id-caldav], AS_HELP_STRING([--with-google-client-id-caldav=...], [OAuth2 client ID for google-caldav.service]), [GOOGLE_CLIENT_ID_CALDAV=$withval], [GOOGLE_CLIENT_ID_CALDAV=insert-your-client-id-here]) AC_ARG_WITH([google-client-secret-caldav], AS_HELP_STRING([--with-google-client-secret-caldav=...], [OAuth2 client secret for google-caldav.service]), [GOOGLE_CLIENT_SECRET_CALDAV=$withval], [GOOGLE_CLIENT_SECRET_CALDAV=insert-your-client-secret-here]) AC_SUBST(GOOGLE_CLIENT_ID_CALDAV) AC_SUBST(GOOGLE_CLIENT_SECRET_CALDAV) AC_ARG_WITH([google-client-id-carddav], AS_HELP_STRING([--with-google-client-id-carddav=...], [OAuth2 client ID for google-carddav.service]), [GOOGLE_CLIENT_ID_CARDDAV=$withval], [GOOGLE_CLIENT_ID_CARDDAV=insert-your-client-id-here]) AC_ARG_WITH([google-client-secret-carddav], AS_HELP_STRING([--with-google-client-secret-carddav=...], [OAuth2 client secret for google-carddav.service]), [GOOGLE_CLIENT_SECRET_CARDDAV=$withval], [GOOGLE_CLIENT_SECRET_CARDDAV=insert-your-client-secret-here]) AC_SUBST(GOOGLE_CLIENT_ID_CARDDAV) AC_SUBST(GOOGLE_CLIENT_SECRET_CARDDAV) AC_CONFIG_FILES([ src/backends/signon/accounts/google-caldav.service src/backends/signon/accounts/google-carddav.service src/backends/signon/accounts/google.provider ]) syncevolution_1.4/src/backends/signon/signon.am000066400000000000000000000057341230021373600220410ustar00rootroot00000000000000dist_noinst_DATA += src/backends/signon/configure-sub.in \ src/backends/signon/README \ $(NONE) # Distribute the .in file, install the generated one. accounts_servicesdir = $(docdir)/accounts/services nodist_accounts_services_DATA = \ src/backends/signon/accounts/google-caldav.service \ src/backends/signon/accounts/google-carddav.service \ $(NONE) dist_noinst_DATA += $(nodist_accounts_services_DATA:%:%.in) # Distribute the .in file, install the generated one. accounts_providersdir = $(docdir)/accounts/providers nodist_accounts_providers_DATA = \ src/backends/signon/accounts/google.provider \ $(NONE) dist_noinst_DATA += $(nodist_accounts_providers_DATA:%:%.in) # Distribute and install the same file. accounts_service_typesdir = $(docdir)/accounts/service_types dist_accounts_service_types_DATA = \ src/backends/signon/accounts/caldav.service-type \ src/backends/signon/accounts/carddav.service-type \ $(NONE) src_backends_signon_libs = if USE_GSSO src_backends_signon_libs += src/backends/signon/providergsso.la endif if USE_UOA src_backends_signon_libs += src/backends/signon/provideruoa.la endif MOSTLYCLEANFILES += $(src_backends_signon_libs) src_backends_signon_common_sources = \ src/backends/signon/signon.h \ src/backends/signon/signon.cpp \ $(NONE) if ENABLE_MODULES src_backends_signon_backenddir = $(BACKENDS_DIRECTORY) src_backends_signon_backend_LTLIBRARIES = $(src_backends_signon_libs) src_backends_signon_common_sources += \ src/backends/signon/signonRegister.cpp else noinst_LTLIBRARIES += $(src_backends_signon_libs) endif src_backends_signon_common_libadd = $(SYNCEVOLUTION_LIBS) src_backends_signon_common_ldflags = -module -avoid-version src_backends_signon_common_cxxflags = $(SYNCEVOLUTION_CFLAGS) src_backends_signon_common_cppflags = -I$(top_srcdir)/test $(BACKEND_CPPFLAGS) src_backends_signon_common_dependencies = src/syncevo/libsyncevolution.la if USE_GSSO src_backends_signon_providergsso_la_SOURCES = $(src_backends_signon_common_sources) src_backends_signon_providergsso_la_LIBADD = $(GSSO_LIBS) $(src_backends_signon_common_libadd) src_backends_signon_providergsso_la_LDFLAGS = $(src_backends_signon_common_ldflags) src_backends_signon_providergsso_la_CXXFLAGS = $(GSSO_CFLAGS) $(src_backends_signon_common_cxxflags) src_backends_signon_providergsso_la_CPPFLAGS = $(src_backends_signon_common_cppflags) src_backends_signon_providergsso_la_DEPENDENCIES = $(src_backends_signon_common_dependencies) endif if USE_UOA src_backends_signon_provideruoa_la_SOURCES = $(src_backends_signon_common_sources) src_backends_signon_provideruoa_la_LIBADD = $(UOA_LIBS) $(src_backends_signon_common_libadd) src_backends_signon_provideruoa_la_LDFLAGS = $(src_backends_signon_common_ldflags) src_backends_signon_provideruoa_la_CXXFLAGS = $(UOA_CFLAGS) $(src_backends_signon_common_cxxflags) src_backends_signon_provideruoa_la_CPPFLAGS = $(src_backends_signon_common_cppflags) src_backends_signon_provideruoa_la_DEPENDENCIES = $(src_backends_signon_common_dependencies) endif syncevolution_1.4/src/backends/signon/signon.cpp000066400000000000000000000322261230021373600222220ustar00rootroot00000000000000/* * Copyright (C) 2013 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #define USE_SIGNON (defined USE_GSSO || defined USE_UOA) #if USE_SIGNON #ifdef USE_GSSO #include "libgsignon-glib/signon-auth-service.h" #include "libgsignon-glib/signon-identity.h" #elif defined USE_UOA #include "libsignon-glib/signon-auth-service.h" #include "libsignon-glib/signon-identity.h" #endif // USE_GSSO #include "libaccounts-glib/ag-account.h" #include "libaccounts-glib/ag-account-service.h" #include #include #include #include #include #include SE_GOBJECT_TYPE(SignonAuthService) SE_GOBJECT_TYPE(SignonAuthSession) SE_GOBJECT_TYPE(SignonIdentity) SE_GOBJECT_TYPE(AgAccount) SE_GOBJECT_TYPE(AgAccountService) SE_GOBJECT_TYPE(AgManager) SE_GLIB_TYPE(AgService, ag_service) SE_GLIB_TYPE(AgAuthData, ag_auth_data) SE_GLIB_TYPE(GHashTable, g_hash_table) #endif // USE_SIGNON #include SE_BEGIN_CXX #ifdef USE_SIGNON typedef GListCXX ServiceListCXX; /** * Simple auto_ptr for GVariant. */ class GVariantCXX : boost::noncopyable { GVariant *m_var; public: /** takes over ownership */ GVariantCXX(GVariant *var = NULL) : m_var(var) {} ~GVariantCXX() { if (m_var) { g_variant_unref(m_var); } } operator GVariant * () { return m_var; } GVariantCXX &operator = (GVariant *var) { if (m_var != var) { if (m_var) { g_variant_unref(m_var); } m_var = var; } return *this; } }; // Originally from google-oauth2-example.c, which is also under LGPL // 2.1, or any later version. static GVariant *HashTable2Variant(const GHashTable *hashTable) throw () { GVariantBuilder builder; GHashTableIter iter; const gchar *key; GVariant *value; if (!hashTable) { return NULL; } g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); g_hash_table_iter_init(&iter, (GHashTable *)hashTable); while (g_hash_table_iter_next(&iter, (gpointer *)&key, (gpointer *)&value)) { g_variant_builder_add(&builder, "{sv}", key, g_variant_ref(value)); } return g_variant_builder_end(&builder); } /** * The created GHashTable maps strings to GVariants which are * reference counted, so when adding or setting values, use g_variant_ref_sink(g_variant_new_...()). */ static GHashTable *Variant2HashTable(GVariant *variant) throw () { if (!variant) { return NULL; } GHashTable *hashTable; GVariantIter iter; GVariant *value; gchar *key; hashTable = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_variant_unref); g_variant_iter_init(&iter, variant); while (g_variant_iter_next(&iter, "{sv}", &key, &value)) { g_hash_table_insert(hashTable, g_strdup(key), g_variant_ref(value)); } return hashTable; } class SignonAuthProvider : public AuthProvider { SignonAuthSessionCXX m_authSession; GHashTableCXX m_sessionData; std::string m_mechanism; public: SignonAuthProvider(const SignonAuthSessionCXX &authSession, const GHashTableCXX &sessionData, const std::string &mechanism) : m_authSession(authSession), m_sessionData(sessionData), m_mechanism(mechanism) {} virtual bool methodIsSupported(AuthMethod method) const { return method == AUTH_METHOD_OAUTH2; } virtual Credentials getCredentials() const { SE_THROW("only OAuth2 is supported"); } virtual std::string getOAuth2Bearer(int failedTokens) const { SE_LOG_DEBUG(NULL, "retrieving OAuth2 token, attempt %d", failedTokens); // Retry login if even the refreshed token failed. g_hash_table_insert(m_sessionData, g_strdup("UiPolicy"), g_variant_ref_sink(g_variant_new_uint32(failedTokens >= 2 ? SIGNON_POLICY_REQUEST_PASSWORD : 0))); GVariantCXX resultDataVar; GErrorCXX gerror; // Enforce normal reference counting via _ref_sink. GVariantCXX sessionDataVar(g_variant_ref_sink(HashTable2Variant(m_sessionData))); PlainGStr buffer(g_variant_print(sessionDataVar, true)); SE_LOG_DEBUG(NULL, "asking for OAuth2 token with method %s, mechanism %s and parameters %s", signon_auth_session_get_method(m_authSession), m_mechanism.c_str(), buffer.get()); #define signon_auth_session_process_async_finish signon_auth_session_process_finish SYNCEVO_GLIB_CALL_SYNC(resultDataVar, gerror, signon_auth_session_process_async, m_authSession, sessionDataVar, m_mechanism.c_str(), NULL); buffer.reset(resultDataVar ? g_variant_print(resultDataVar, true) : NULL); SE_LOG_DEBUG(NULL, "OAuth2 token result: %s, %s", buffer.get() ? buffer.get() : "<>", gerror ? gerror->message : "???"); if (!resultDataVar || gerror) { SE_THROW_EXCEPTION_STATUS(StatusException, StringPrintf("could not obtain OAuth2 token: %s", gerror ? gerror->message : "???"), STATUS_FORBIDDEN); } GHashTableCXX resultData(Variant2HashTable(resultDataVar), TRANSFER_REF); GVariant *tokenVar = static_cast(g_hash_table_lookup(resultData, (gpointer)"AccessToken")); if (!tokenVar) { SE_THROW("no AccessToken in OAuth2 response"); } const char *token = g_variant_get_string(tokenVar, NULL); if (!token) { SE_THROW("AccessToken did not contain a string value"); } return token; } virtual std::string getUsername() const { return ""; } }; class StoreIdentityData { public: StoreIdentityData() : m_running(true) {} bool m_running; guint32 m_id; GErrorCXX m_gerror; }; static void StoreIdentityCB(SignonIdentity *self, guint32 id, const GError *error, gpointer userData) { StoreIdentityData *data = reinterpret_cast(userData); data->m_running = false; data->m_id = id; data->m_gerror = error; } boost::shared_ptr createSignonAuthProvider(const InitStateString &username, const InitStateString &password) { boost::shared_ptr provider; // Split username into and . // Be flexible and allow leading/trailing white space. // Comma is optional. static const pcrecpp::RE re("^\\s*(\\d+)\\s*,?\\s*(.*)\\s*$"); AgAccountId accountID; std::string serviceName; if (!re.FullMatch(username, &accountID, &serviceName)) { SE_THROW(StringPrintf("username must have the format gsso:,: %s", username.c_str())); } SE_LOG_DEBUG(NULL, "looking up account ID %d and service '%s'", accountID, serviceName.c_str()); AgManagerCXX manager(ag_manager_new(), TRANSFER_REF); GErrorCXX gerror; AgAccountCXX account(ag_manager_load_account(manager, accountID, gerror), TRANSFER_REF); if (!account) { gerror.throwError(StringPrintf("loading account with ID %d from %s failed", accountID, username.c_str())); } if (!ag_account_get_enabled(account)) { SE_THROW(StringPrintf("account with ID %d from %s is disabled, refusing to use it", accountID, username.c_str())); } AgAccountServiceCXX accountService; if (serviceName.empty()) { accountService = AgAccountServiceCXX::steal(ag_account_service_new(account, NULL)); } else { ServiceListCXX services(ag_account_list_enabled_services(account)); BOOST_FOREACH (AgService *service, services) { const char *name = ag_service_get_name(service); SE_LOG_DEBUG(NULL, "enabled service: %s", name); if (serviceName == name) { accountService = AgAccountServiceCXX::steal(ag_account_service_new(account, service)); // Do *not* select the service for reading/writing properties. // AgAccountService does this internally, and when we create // a new identity below, we want it to be shared by all // services so that the user only needs to log in once. // ag_account_select_service(account, service); break; } } } if (!accountService) { SE_THROW(StringPrintf("service '%s' in account with ID %d not found or not enabled", serviceName.c_str(), accountID)); } AgAuthDataCXX authData(ag_account_service_get_auth_data(accountService), TRANSFER_REF); // SignonAuthServiceCXX authService(signon_auth_service_new(), TRANSFER_REF); guint signonID = ag_auth_data_get_credentials_id(authData); const char *method = ag_auth_data_get_method(authData); const char *mechanism = ag_auth_data_get_mechanism(authData); // Check that the service has a credentials ID. If not, create it and // store its ID permanently. if (!signonID) { SE_LOG_DEBUG(NULL, "have to create signon identity"); SignonIdentityCXX identity(signon_identity_new( #ifdef USE_GSSO NULL #endif ), TRANSFER_REF); boost::shared_ptr identityInfo(signon_identity_info_new(), signon_identity_info_free); signon_identity_info_set_caption(identityInfo.get(), StringPrintf("created by SyncEvolution for account #%d and service %s", accountID, serviceName.empty() ? "<>" : serviceName.c_str()).c_str()); const gchar *mechanisms[] = { mechanism ? mechanism : "*", NULL }; signon_identity_info_set_method(identityInfo.get(), method, mechanisms); StoreIdentityData data; signon_identity_store_credentials_with_info(identity, identityInfo.get(), StoreIdentityCB, &data); GRunWhile(boost::lambda::var(data.m_running)); if (!data.m_id || data.m_gerror) { SE_THROW(StringPrintf("failed to create signon identity: %s", data.m_gerror ? data.m_gerror->message : "???")); } // Now store in account. static const char CREDENTIALS_ID[] = "CredentialsId"; ag_account_set_variant(account, CREDENTIALS_ID, g_variant_new_uint32(data.m_id)); #define ag_account_store_async_finish ag_account_store_finish gboolean res; SYNCEVO_GLIB_CALL_SYNC(res, gerror, ag_account_store_async, account, NULL); if (!res) { gerror.throwError("failed to store account"); } authData = AgAuthDataCXX::steal(ag_account_service_get_auth_data(accountService)); signonID = ag_auth_data_get_credentials_id(authData); if (!signonID) { SE_THROW("still no signonID?!"); } method = ag_auth_data_get_method(authData); mechanism = ag_auth_data_get_mechanism(authData); } GVariantCXX sessionDataVar(ag_auth_data_get_login_parameters(authData, NULL)); GHashTableCXX sessionData(Variant2HashTable(sessionDataVar), TRANSFER_REF); SignonIdentityCXX identity(signon_identity_new_from_db(signonID #ifdef USE_GSSO , NULL #endif ), TRANSFER_REF); SE_LOG_DEBUG(NULL, "using signond identity %d", signonID); SignonAuthSessionCXX authSession(signon_identity_create_session(identity, method, gerror), TRANSFER_REF); // TODO (?): retrieve start URL from account system provider.reset(new SignonAuthProvider(authSession, sessionData, mechanism)); return provider; } #endif // USE_SIGNON SE_END_CXX syncevolution_1.4/src/backends/signon/signon.h000066400000000000000000000022751230021373600216700ustar00rootroot00000000000000/* * Copyright (C) 2013 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_SYNC_EVOLUTION_SIGNON_AUTH_PROVIDER # define INCL_SYNC_EVOLUTION_SIGNON_AUTH_PROVIDER #include #include #include SE_BEGIN_CXX class AuthProvider; boost::shared_ptr createSignonAuthProvider(const InitStateString &username, const InitStateString &password); SE_END_CXX #endif syncevolution_1.4/src/backends/signon/signonRegister.cpp000066400000000000000000000054011230021373600237220ustar00rootroot00000000000000/* * Copyright (C) 2013 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include "signon.h" #include #include SE_BEGIN_CXX #if defined(USE_GSSO) || defined(USE_UOA) static class SignonProvider : public IdentityProvider { public: SignonProvider() : // This uses "gsso" at the moment. The advantage of that is that if // gSSO and UOA were installed in parallel, the user could choose which // one to use. If it turns out that the two will never be installed at the // same time, then this perhaps should be "signon" instead, which then would // pick either a gSSO or UAO backend depending on which is available. #ifdef USE_GSSO IdentityProvider("gsso", "gsso:[,]\n" " Authentication using libgsignond + libaccounts,\n" " using an account created and managed with libaccounts.\n" " The service name is optional. If not given, the\n" " settings from the account will be used.") #elif defined USE_UOA IdentityProvider("uoa", "uoa:[,]\n" " Authentication using libsignon + libaccounts,\n" " using an account created and managed with libaccounts.\n" " The service name is optional. If not given, the\n" " settings from the account will be used.") #endif // USE_GSSO {} virtual boost::shared_ptr create(const InitStateString &username, const InitStateString &password) { // Returning NULL if not enabled... boost::shared_ptr provider; #if (defined USE_GSSO || defined USE_UOA) provider = createSignonAuthProvider(username, password); #endif return provider; } } gsso; #endif // USE_GSSO || USE_UOA SE_END_CXX syncevolution_1.4/src/backends/sqlite/000077500000000000000000000000001230021373600202205ustar00rootroot00000000000000syncevolution_1.4/src/backends/sqlite/README000066400000000000000000000020421230021373600210760ustar00rootroot00000000000000This is a demo backend. It uses a database schema that is similar to the one used on the iPhone, but it is not as complete. To compile this backend as part of SyncEvolution, configure with --enable-sqlite. To compile it against a binary distribution of SyncEvolution, make sure that the SyncEvolution, Synthesis and sqlite developer files are installed and that pkg-config can find syncevolution.pc, synthesis.pc and sqlite.pc. Set your PKG_CONFIG_PATH if necessary. Then the source files can be compiled as follows: g++ -fpic -DPIC -DENABLE_SQLITE -shared -I. \ `pkg-config --cflags --libs syncevolution` `pkg-config --cflags --libs sqlite` *.cpp \ -o /tmp/syncsqlite.so Install /tmp/syncsqlite.so by moving it into the "lib/syncevolution/backends" directory of the SyncEvolution installation. It then should show up as additional choice for the "type" property: syncevolution --source-property type=? ... SQLite Address Book = addressbook = contacts = sqlite-contacts vCard 2.1 (default) = text/x-vcard syncevolution_1.4/src/backends/sqlite/SQLiteContactSource.cpp000066400000000000000000000302661230021373600245710ustar00rootroot00000000000000/* * Copyright (C) 2007-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #ifdef ENABLE_SQLITE #include "SQLiteContactSource.h" #include #include #include #include #include #include #include SE_BEGIN_CXX enum { PERSON_LAST, PERSON_MIDDLE, PERSON_FIRST, PERSON_PREFIX, PERSON_SUFFIX, PERSON_LASTSORT, PERSON_FIRSTSORT, PERSON_ORGANIZATION, PERSON_DEPARTMENT, PERSON_UNIT, PERSON_NOTE, PERSON_BIRTHDAY, PERSON_JOBTITLE, PERSON_TITLE, PERSON_NICKNAME, PERSON_FULLNAME, PERSON_CATEGORIES, PERSON_AIM, PERSON_GROUPWISE, PERSON_ICQ, PERSON_YAHOO, PERSON_FILEAS, PERSON_ANNIVERSARY, PERSON_ASSISTANT, PERSON_MANAGER, PERSON_SPOUSE, PERSON_URL, PERSON_BLOG_URL, PERSON_VIDEO_URL, LAST_COL }; void SQLiteContactSource::open() { static const SQLiteUtil::Mapping mapping[LAST_COL + 1] = { { "Last", "ABPerson", "N_LAST"}, { "Middle", "ABPerson", "N_MIDDLE"}, { "First", "ABPerson", "N_FIRST" }, { "Prefix", "ABPerson", "N_PREFIX" }, { "Suffix", "ABPerson", "N_SUFFIX" }, { "FirstSort", "ABPerson", "" }, { "LastSort", "ABPerson", "" }, { "Organization", "ABPerson", "ORG_NAME" }, { "Department", "ABPerson", "ORG_DIVISION" }, { "Unit", "ABPerson", "ORG_OFFICE" }, //{"Team", "ABPerson", "ORG_TEAM" } { "Note", "ABPerson", "NOTE" }, { "Birthday", "ABPerson", "BDAY" }, { "JobTitle", "ABPerson", "ROLE" }, { "Title", "ABPerson", "TITLE" }, { "Nickname", "ABPerson", "NICKNAME" }, { "CompositeNameFallback", "ABPerson", "FN" }, { "Categories", "ABPerson", "CATEGORIES" }, { "AIM", "ABPerson", "AIM_HANDLE" }, { "Groupwise", "ABPerson", "GROUPWISE_HANDLE" }, { "ICQ", "ABPerson", "ICQ_HANDLE" }, { "Yahoo", "ABPerson", "YAHOO_HANDLE" }, { "FileAs", "ABPerson", "FILE-AS" }, { "Anniversary", "ABPerson", "ANNIVERSARY" }, { "Assistant", "ABPerson", "ASSISTANT" }, { "Manager", "ABPerson", "MANAGER" }, { "Spouse", "ABPerson", "SPOUSE" }, { "URL", "ABPerson", "WEB" }, { "BlogURL", "ABPerson", "BLOGURL" }, { "VideoURL", "ABPerson", "VIDEOURL" }, { NULL } }; static const char *schema = "BEGIN TRANSACTION;" "CREATE TABLE ABPerson (ROWID INTEGER PRIMARY KEY AUTOINCREMENT, " "First TEXT, " "Last TEXT, " "Middle TEXT, " "FirstPhonetic TEXT, " "MiddlePhonetic TEXT, " "LastPhonetic TEXT, " "Organization TEXT, " "Department TEXT, " "Unit TEXT, " "Note TEXT, " "Kind INTEGER, " "Birthday TEXT, " "JobTitle TEXT, " "Title TEXT, " "Nickname TEXT, " "Prefix TEXT, " "Suffix TEXT, " "FirstSort TEXT, " "LastSort TEXT, " "CreationDate INTEGER, " "ModificationDate INTEGER, " "CompositeNameFallback TEXT, " "Categories TEXT, " "AIM TEXT, " "Groupwise TEXT, " "ICQ Text, " "Yahoo TEXT, " "Anniversary TEXT, " "Assistant TEXT, " "Manager TEXT, " "Spouse TEXT, " "URL TEXT, " "BlogURL TEXT, " "VideoURL TEXT, " "FileAs TEXT);" "COMMIT;"; string id = getDatabaseID(); m_sqlite.open(getName(), id.c_str(), mapping, schema); } void SQLiteContactSource::close() { m_sqlite.close(); } void SQLiteContactSource::getSynthesisInfo(SynthesisInfo &info, XMLConfigFragments &fragment) { SourceType sourceType = getSourceType(); string type; if (!sourceType.m_format.empty()) { type = sourceType.m_format; } info.m_native = "vCard21"; info.m_fieldlist = "contacts"; info.m_datatypes = " \n" " \n"; if (type == "text/x-vcard:2.1" || type == "text/x-vcard") { info.m_datatypes = " \n"; if (!sourceType.m_forceFormat) { info.m_datatypes += " \n"; } } else if (type == "text/vcard:3.0" || type == "text/vcard") { info.m_datatypes = " \n"; if (!sourceType.m_forceFormat) { info.m_datatypes += " \n"; } } else { throwError(string("configured MIME type not supported: ") + type); } } SQLiteContactSource::Databases SQLiteContactSource::getDatabases() { Databases result; result.push_back(Database("select database via file path", "file:///")); return result; } bool SQLiteContactSource::isEmpty() { // there are probably more efficient ways to do this, but this is just // a proof-of-concept anyway sqliteptr all(m_sqlite.prepareSQL("SELECT ROWID FROM ABPerson;")); while (m_sqlite.checkSQL(sqlite3_step(all)) == SQLITE_ROW) { return false; } return true; } void SQLiteContactSource::listAllItems(RevisionMap_t &revisions) { sqliteptr all(m_sqlite.prepareSQL("SELECT ROWID, CreationDate, ModificationDate FROM ABPerson;")); while (m_sqlite.checkSQL(sqlite3_step(all)) == SQLITE_ROW) { string uid = m_sqlite.toString(SQLITE3_COLUMN_KEY(all, 0)); string modTime = m_sqlite.time2str(m_sqlite.getTimeColumn(all, 2)); revisions.insert(RevisionMap_t::value_type(uid, modTime)); } } sysync::TSyError SQLiteContactSource::readItemAsKey(sysync::cItemID aID, sysync::KeyH aItemKey) { string uid = aID->item; sqliteptr contact(m_sqlite.prepareSQL("SELECT * FROM ABPerson WHERE ROWID = '%s';", uid.c_str())); if (m_sqlite.checkSQL(sqlite3_step(contact)) != SQLITE_ROW) { throwError(STATUS_NOT_FOUND, string("contact not found: ") + uid); } for (int i = 0; isetValue(aItemKey, field, value.c_str(), value.size()); if (res != sysync::LOCERR_OK) { SE_LOG_WARNING(getDisplayName(), "SQLite backend: set field %s value %s failed", field.c_str(), value.c_str()); } } } return sysync::LOCERR_OK; } sysync::TSyError SQLiteContactSource::insertItemAsKey(sysync::KeyH aItemKey, sysync::cItemID aID, sysync::ItemID newID) { string uid = aID ? aID->item :""; string newuid = uid; string creationTime; string first, last; stringstream cols; stringstream values; // scan-build: value stored to 'numparams' is never read. std::list insValues; for (int i = 0; igetValue (aItemKey, field, data)) { insValues.push_back (string (data.get())); cols << m_sqlite.getMapping(i).colname << ", "; values <<"?, "; if (field == "N_FIRST") { first = insValues.back(); } else if (field == "N_LAST") { last = insValues.back(); } } } // synthesize sort keys: upper case with specific order of first/last name string firstsort = first + " " + last; boost::to_upper(firstsort); string lastsort = last + " " + first; boost::to_upper(lastsort); cols << "FirstSort, LastSort"; values << "?, ?"; insValues.push_back(firstsort); insValues.push_back(lastsort); // optional fixed UID, potentially fixed creation time if (uid.size()) { creationTime = m_sqlite.findColumn("ABPerson", "ROWID", uid.c_str(), "CreationDate", ""); cols << ", ROWID"; values << ", ?"; } cols << ", CreationDate, ModificationDate"; values << ", ?, ?"; // delete complete row so that we can recreate it if (uid.size()) { sqliteptr remove(m_sqlite.prepareSQL("DELETE FROM ABPerson WHERE ROWID == ?;")); m_sqlite.checkSQL(sqlite3_bind_text(remove, 1, uid.c_str(), -1, SQLITE_TRANSIENT)); m_sqlite.checkSQL(sqlite3_step(remove)); } string cols_str = cols.str(); string values_str = values.str(); sqliteptr insert(m_sqlite.prepareSQL("INSERT INTO ABPerson( %s ) VALUES( %s );", cols.str().c_str(), values.str().c_str())); // now bind parameter values in the same order as the columns specification above int param = 1; BOOST_FOREACH (string &value, insValues) { m_sqlite.checkSQL(sqlite3_bind_text(insert, param++, value.c_str(), -1, SQLITE_TRANSIENT)); } if (uid.size()) { m_sqlite.checkSQL(sqlite3_bind_text(insert, param++, uid.c_str(), -1, SQLITE_TRANSIENT)); m_sqlite.checkSQL(sqlite3_bind_text(insert, param++, creationTime.c_str(), -1, SQLITE_TRANSIENT)); } else { m_sqlite.checkSQL(sqlite3_bind_int64(insert, param++, (long long)time(NULL))); } SQLiteUtil::syncml_time_t modificationTime = time(NULL); m_sqlite.checkSQL(sqlite3_bind_int64(insert, param++, modificationTime)); m_sqlite.checkSQL(sqlite3_step(insert)); if (!uid.size()) { // figure out which UID was assigned to the new contact newuid = m_sqlite.findColumn("SQLITE_SEQUENCE", "NAME", "ABPerson", "SEQ", ""); } newID->item = StrAlloc(newuid.c_str()); updateRevision(*m_trackingNode, uid, newuid, m_sqlite.time2str(modificationTime)); return sysync::LOCERR_OK; } void SQLiteContactSource::deleteItem(const string& uid) { sqliteptr del; del.set(m_sqlite.prepareSQL("DELETE FROM ABPerson WHERE " "ABPerson.ROWID = ?;")); m_sqlite.checkSQL(sqlite3_bind_text(del, 1, uid.c_str(), -1, SQLITE_TRANSIENT)); m_sqlite.checkSQL(sqlite3_step(del)); // TODO: throw STATUS_NOT_FOUND exception when nothing was deleted deleteRevision(*m_trackingNode, uid); } void SQLiteContactSource::enableServerMode() { SyncSourceAdmin::init(m_operations, this); SyncSourceBlob::init(m_operations, getCacheDir()); } bool SQLiteContactSource::serverModeEnabled() const { return m_operations.m_loadAdminData; } void SQLiteContactSource::beginSync(const std::string &lastToken, const std::string &resumeToken) { detectChanges(*m_trackingNode, CHANGES_FULL); } std::string SQLiteContactSource::endSync(bool success) { if (success) { m_trackingNode->flush(); } else { // The Synthesis docs say that we should rollback in case of // failure. Cannot do that for data, so lets at least keep // the revision map unchanged. } // no token handling at the moment (not needed for clients) return ""; } SE_END_CXX #endif /* ENABLE_SQLITE */ #ifdef ENABLE_MODULES # include "SQLiteContactSourceRegister.cpp" #endif syncevolution_1.4/src/backends/sqlite/SQLiteContactSource.h000066400000000000000000000107051230021373600242320ustar00rootroot00000000000000/* * Copyright (C) 2007-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_SQLITECONTACTSOURCE #define INCL_SQLITECONTACTSOURCE #include #include #include #include #include #include SE_BEGIN_CXX #ifdef ENABLE_SQLITE /** * Uses SQLiteUtil for contacts with a schema inspired by the one used * by Mac OS X. That schema has hierarchical tables which is not * supported by SQLiteUtil, therefore SQLiteContactSource uses a * simplified schema where each contact consists of one row in the * database table. * * The handling of the "N" and "ORG" property shows how mapping * between one property and multiple different columns works. * * Properties which can occur more than once per contact like address, * email and phone numbers are not supported. They would have to be * stored in additional tables. * * Change tracking is done by implementing a modification date as part * of each contact and using that as the revision string. * The database file is created automatically if the database ID is * file:///. */ class SQLiteContactSource : public SyncSource, virtual public SyncSourceSession, virtual public SyncSourceAdmin, virtual public SyncSourceBlob, virtual public SyncSourceRevisions, virtual public SyncSourceDelete, virtual public SyncSourceLogging, virtual public SyncSourceChanges { public: SQLiteContactSource(const SyncSourceParams ¶ms) : SyncSource(params), m_trackingNode(new PrefixConfigNode("item-", boost::shared_ptr(new SafeConfigNode(params.m_nodes.getTrackingNode())))) { SyncSourceSession::init(m_operations); SyncSourceDelete::init(m_operations); SyncSourceRevisions::init(NULL, NULL, 1, m_operations); SyncSourceChanges::init(m_operations); m_operations.m_isEmpty = boost::bind(&SQLiteContactSource::isEmpty, this); m_operations.m_readItemAsKey = boost::bind(&SQLiteContactSource::readItemAsKey, this, _1, _2); m_operations.m_insertItemAsKey = boost::bind(&SQLiteContactSource::insertItemAsKey, this, _1, (sysync::cItemID)NULL, _2); m_operations.m_updateItemAsKey = boost::bind(&SQLiteContactSource::insertItemAsKey, this, _1, _2, _3); SyncSourceLogging::init(InitList ("N_FIRST")+"N_MIDDLE"+"N_LAST", ", ", m_operations); } protected: /* implementation of SyncSource interface */ virtual void open(); virtual void close(); virtual Databases getDatabases(); virtual void enableServerMode(); virtual bool serverModeEnabled() const; virtual std::string getPeerMimeType() const { return "text/x-vcard"; } /* Methods in SyncSource */ virtual void getSynthesisInfo (SynthesisInfo &info, XMLConfigFragments &fragment); sysync::TSyError readItemAsKey(sysync::cItemID aID, sysync::KeyH aItemKey); sysync::TSyError insertItemAsKey(sysync::KeyH aItemKey, sysync::cItemID aID, sysync::ItemID newID); /* Methods in SyncSourceSession*/ virtual void beginSync(const std::string &lastToken, const std::string &resumeToken); virtual std::string endSync(bool success); /* Methods in SyncSourceDelete*/ virtual void deleteItem(const string &luid); /* Methods in SyncSourceRevisions */ virtual void listAllItems(RevisionMap_t &revisions); private: /** encapsulates access to database */ boost::shared_ptr m_trackingNode; SQLiteUtil m_sqlite; /** implements the m_isEmpty operation */ bool isEmpty(); }; #endif // ENABLE_SQLITE SE_END_CXX #endif // INCL_SQLITECONTACTSOURCE syncevolution_1.4/src/backends/sqlite/SQLiteContactSourceRegister.cpp000066400000000000000000000072251230021373600262750ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "SQLiteContactSource.h" #ifdef ENABLE_UNIT_TESTS # include # include #endif #include SE_BEGIN_CXX static SyncSource *createSource(const SyncSourceParams ¶ms) { SourceType sourceType = SyncSource::getSourceType(params.m_nodes); bool isMe = sourceType.m_backend == "SQLite Address Book"; #ifndef ENABLE_SQLITE return isMe ? RegisterSyncSource::InactiveSource(params) : NULL; #else bool maybeMe = sourceType.m_backend == "addressbook"; if (isMe || maybeMe) { if (sourceType.m_format == "" || sourceType.m_format == "text/x-vcard") { return new SQLiteContactSource(params); } else { return NULL; } } return NULL; #endif } static RegisterSyncSource registerMe("SQLite Address Book", #ifdef ENABLE_SQLITE true, #else false, #endif createSource, "SQLite Address Book = addressbook = contacts = sqlite-contacts\n" " vCard 2.1 (default) = text/x-vcard\n", Values() + (Aliases("SQLite Address Book") + "sqlite-contacts" + "sqlite")); #ifdef ENABLE_SQLITE #ifdef ENABLE_UNIT_TESTS class EvolutionSQLiteContactsTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(EvolutionSQLiteContactsTest); CPPUNIT_TEST(testInstantiate); CPPUNIT_TEST_SUITE_END(); protected: void testInstantiate() { boost::shared_ptr source; source.reset(SyncSource::createTestingSource("contacts", "contacts", true)); source.reset(SyncSource::createTestingSource("contacts", "addressbook", true)); source.reset(SyncSource::createTestingSource("contacts", "sqlite-contacts", true)); source.reset(SyncSource::createTestingSource("contacts", "SQLite Address Book:text/x-vcard", true)); } }; SYNCEVOLUTION_TEST_SUITE_REGISTRATION(EvolutionSQLiteContactsTest); #endif // ENABLE_UNIT_TESTS /*Client-Test requries the backends is an instance of TestingSyncSource * which in turn requires backends support serialized access for the data * which is not supported by SQLiteContactSource.*/ #if 0 #ifdef ENABLE_INTEGRATION_TESTS namespace { #if 0 } #endif static class VCard21Test : public RegisterSyncSourceTest { public: VCard21Test() : RegisterSyncSourceTest("sqlite_vcard21", "vcard21") {} virtual void updateConfig(ClientTestConfig &config) const { config.m_type = "sqlite-contacts:text/x-vcard"; config.m_testcases = "testcases/sqlite_vcard21.vcf"; } } VCard21Test; } #endif // ENABLE_INTEGRATION_TESTS #endif #endif // ENABLE_SQLITE SE_END_CXX syncevolution_1.4/src/backends/sqlite/SQLiteUtil.cpp000066400000000000000000000131771230021373600227340ustar00rootroot00000000000000/* * Copyright (C) 2007-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifdef ENABLE_SQLITE #include "SQLiteUtil.h" #include #include #include #include #include SE_BEGIN_CXX void SQLiteUtil::throwError(const string &operation) { string descr = m_name + ": '" + m_fileid + "': " + operation + " failed"; if (m_db) { const char *error = sqlite3_errmsg(m_db); descr += ": "; descr += error ? error : "unspecified error"; } throw runtime_error(descr); } sqlite3_stmt *SQLiteUtil::prepareSQLWrapper(const char *sql, const char **nextsql) { sqlite3_stmt *stmt = NULL; checkSQL(sqlite3_prepare(m_db, sql, -1, &stmt, nextsql), sql); return stmt; } sqlite3_stmt *SQLiteUtil::prepareSQL(const char *sqlfmt, ...) { va_list ap; va_start(ap, sqlfmt); string s = StringPrintfV (sqlfmt, ap); va_end(ap); return prepareSQLWrapper(s.c_str()); } SQLiteUtil::key_t SQLiteUtil::findKey(const char *database, const char *keyname, const char *key) { sqliteptr query(prepareSQL("SELECT ROWID FROM %s WHERE %s = '%s';", database, keyname, key)); int res = checkSQL(sqlite3_step(query), "getting key"); if (res == SQLITE_ROW) { return sqlite3_column_int64(query, 0); } else { return -1; } } string SQLiteUtil::findColumn(const char *database, const char *keyname, const char *key, const char *column, const char *def) { sqliteptr query(prepareSQL("SELECT %s FROM %s WHERE %s = '%s';", column, database, keyname, key)); int res = checkSQL(sqlite3_step(query), "getting key"); if (res == SQLITE_ROW) { const unsigned char *text = sqlite3_column_text(query, 0); return text ? (const char *)text : def; } else { return def; } } string SQLiteUtil::getTextColumn(sqlite3_stmt *stmt, int col, const char *def) { const unsigned char *text = sqlite3_column_text(stmt, col); return text ? (const char *)text : def; } SQLiteUtil::syncml_time_t SQLiteUtil::getTimeColumn(sqlite3_stmt *stmt, int col) { // assumes that the database stores the result of time() directly return sqlite3_column_int64(stmt, col); } string SQLiteUtil::time2str(SQLiteUtil::syncml_time_t t) { char buffer[128]; sprintf(buffer, "%lu", t); return buffer; } void SQLiteUtil::open(const string &name, const string &fileid, const SQLiteUtil::Mapping *mapping, const char *schema) { close(); m_name = name; m_fileid = fileid; const string prefix("file://"); bool create = fileid.substr(0, prefix.size()) == prefix; string filename = create ? fileid.substr(prefix.size()) : fileid; if (!create && access(filename.c_str(), F_OK)) { throw runtime_error(m_name + ": no such database: '" + filename + "'"); } sqlite3 *db; int res = sqlite3_open(filename.c_str(), &db); m_db = db; checkSQL(res, "opening"); // check whether file is empty = newly created, define schema if that's the case sqliteptr check(prepareSQLWrapper("SELECT * FROM sqlite_master;")); switch (sqlite3_step(check)) { case SQLITE_ROW: // okay break; case SQLITE_DONE: { // empty const char *nextsql = schema; while (nextsql && *nextsql) { const char *sql = nextsql; sqliteptr create(prepareSQLWrapper(sql, &nextsql)); while (true) { int res = sqlite3_step(create); if (res == SQLITE_DONE) { break; } else if (res == SQLITE_ROW) { // continue } else { throwError("creating database");\ } } } break; } default: throwError("checking content"); break; } // query database schema to find columns we need int i; for (i = 0; mapping[i].colname; i++) ; m_mapping.set(new Mapping[i + 1]); sqliteptr query; string tablename; for (i = 0; mapping[i].colname; i++) { m_mapping[i] = mapping[i]; // switching to a different table? if (tablename != m_mapping[i].tablename) { tablename = m_mapping[i].tablename; query.set(prepareSQL("SELECT * FROM %s;", tablename.c_str())); } // search for this column name for (m_mapping[i].colindex = sqlite3_column_count(query) - 1; m_mapping[i].colindex >= 0; m_mapping[i].colindex--) { const char *name = sqlite3_column_name(query, m_mapping[i].colindex); if (name && !strcasecmp(m_mapping[i].colname, name)) { break; } } } memset(&m_mapping[i], 0, sizeof(m_mapping[i])); } void SQLiteUtil::close() { m_db = NULL; } SE_END_CXX #endif /* ENABLE_SQLITE */ syncevolution_1.4/src/backends/sqlite/SQLiteUtil.h000066400000000000000000000117731230021373600224010ustar00rootroot00000000000000/* * Copyright (C) 2007-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_SQLITESYNCSOURCE #define INCL_SQLITESYNCSOURCE #ifdef ENABLE_SQLITE #include #include #include #include #include SE_BEGIN_CXX using namespace std; class SQLiteUnref { public: static void unref(sqlite3 *db) { sqlite3_close(db); } static void unref(sqlite3_stmt *stmt) { sqlite3_finalize(stmt); } }; typedef eptr sqliteptr; /** * This class implements access to SQLite database files: * - opening the database file * - error reporting * - creating a database file * - converting to and from a VObject via a simple property<->column name mapping */ class SQLiteUtil { public: /** information about the database mapping */ struct Mapping { const char *colname; /**< column name in SQL table */ const char *tablename; /**< name of the SQL table which has this column */ const char *fieldname; /**< synthesis internal field name, no corresponding synthesis field if empty*/ int colindex; /**< determined dynamically in open(): index of the column, -1 if not present */ }; const Mapping &getMapping(int i) { return m_mapping[i]; } /** * @param name a name for the data source, used for error messages * @param fileid a descriptor which identifies the file to be opened: * currently valid syntax is file:// followed by path * @param mapping array with database mapping, terminated by NULL colname * @param schema database schema to use when creating new databases, may be NULL */ void open(const string &name, const string &fileid, const Mapping *mapping, const char *schema); void close(); /** * throw error for a specific sqlite3 operation on m_db * @param operation a description of the operation which failed */ void throwError(const string &operation); /** * wrapper around sqlite3_prepare() which operates on the current * database and throws an error if the call fails * * @param sqlfmt printf-style format string for query, followed by parameters for sprintf */ sqlite3_stmt *prepareSQL(const char *sqlfmt, ...); /** * wrapper around sqlite3_prepare() which operates on the current * database and throws an error if the call fails * * @param sql preformatted SQL statement(s) * @param nextsql pointer to next statement in sql */ sqlite3_stmt *prepareSQLWrapper(const char *sql, const char **nextsql = NULL); /** checks the result of an sqlite3 call, throws an error if faulty, otherwise returns the result */ int checkSQL(int res, const char *operation = "SQLite call") { if (res != SQLITE_OK && res != SQLITE_ROW && res != SQLITE_DONE) { throwError(operation); } return res; } /** type used for row keys */ typedef long long key_t; string toString(key_t key) { char buffer[32]; sprintf(buffer, "%lld", key); return buffer; } #define SQLITE3_COLUMN_KEY sqlite3_column_int64 /** return row ID for a certain row */ key_t findKey(const char *database, const char *keyname, const char *key); /** return a specific column for a row identified by a certain key column as text, returns default text if not found */ string findColumn(const char *database, const char *keyname, const char *key, const char *column, const char *def); /** a wrapper for sqlite3_column_test() which will check for NULL and returns default text instead */ string getTextColumn(sqlite3_stmt *stmt, int col, const char *def = ""); typedef unsigned long syncml_time_t; /** transform column to same time base as used by SyncML libary (typically time()) */ syncml_time_t getTimeColumn(sqlite3_stmt *stmt, int col); /** convert time to string */ static string time2str(syncml_time_t t); private: /* copy of open() parameters */ arrayptr m_mapping; string m_name; string m_fileid; /** current database */ eptr m_db; }; SE_END_CXX #endif // ENABLE_SQLITE #endif // INCL_SQLITESYNCSOURCE syncevolution_1.4/src/backends/sqlite/configure-sub.in000066400000000000000000000016301230021373600233200ustar00rootroot00000000000000dnl -*- mode: Autoconf; -*- dnl Invoke autogen.sh to produce a configure script. dnl check for sqlite PKG_CHECK_MODULES(SQLITE, sqlite3, SQLITEFOUND=yes, [SQLITEFOUND=no]) AC_SUBST(SQLITE_CFLAGS) AC_SUBST(SQLITE_LIBS) BACKEND_CPPFLAGS="$BACKEND_CPPFLAGS $SQLITE_CFLAGS" SE_ARG_ENABLE_BACKEND(sqlite, sqlite, [AS_HELP_STRING([--enable-sqlite], [enable access to PIM data stored in SQLite files (experimental, default off)])], [enable_sqlite="$enableval"], [enable_sqlite="no"] ) if test "$enable_sqlite" = "yes"; then test "x${SQLITEFOUND}" = "xyes" || AC_MSG_ERROR([--enable-sqlite requires pkg-config information for sqlite3, which was not found]) AC_DEFINE(ENABLE_SQLITE, 1, [sqlite available]) else # avoid linking against it if not needed SQLITE_CFLAGS= SQLITE_LIBS= fi syncevolution_1.4/src/backends/sqlite/sqlite.am000066400000000000000000000027571230021373600220530ustar00rootroot00000000000000dist_noinst_DATA += \ src/backends/sqlite/configure-sub.in \ src/backends/sqlite/README src_backends_sqlite_lib = src/backends/sqlite/syncsqlite.la MOSTLYCLEANFILES += $(src_backends_sqlite_lib) if ENABLE_MODULES src_backends_sqlite_backenddir = $(BACKENDS_DIRECTORY) src_backends_sqlite_backend_LTLIBRARIES = $(src_backends_sqlite_lib) else noinst_LTLIBRARIES += $(src_backends_sqlite_lib) endif src_backends_sqlite_src = \ src/backends/sqlite/SQLiteUtil.h \ src/backends/sqlite/SQLiteUtil.cpp \ src/backends/sqlite/SQLiteContactSource.h \ src/backends/sqlite/SQLiteContactSource.cpp src_backends_sqlite_syncsqlite_la_SOURCES = $(src_backends_sqlite_src) src_backends_sqlite_syncsqlite_la_LIBADD = $(SQLITE_LIBS) $(SYNCEVOLUTION_LIBS) src_backends_sqlite_syncsqlite_la_LDFLAGS = -module -avoid-version src_backends_sqlite_syncsqlite_la_CXXFLAGS = $(SYNCEVOLUTION_CXXFLAGS) $(SYNCEVO_WFLAGS) src_backends_sqlite_syncsqlite_la_CPPFLAGS = $(SYNCEVOLUTION_CFLAGS) -I$(top_srcdir)/test $(BACKEND_CPPFLAGS) -I$(top_srcdir)/src/backends/sqlite src_backends_sqlite_syncsqlite_la_DEPENDENCIES = src/syncevo/libsyncevolution.la # SQLiteContactSource does not support all fields from Funambol vCard 2.1 # test cases: filter them out before testing #../../testcases/sqlite_vcard21.vcf: $(FUNAMBOL_SUBDIR)/test/test/testcases/vcard21.vcf # mkdir -p ${@D} # perl -e '$$_ = join("", <>); s/^(ADR|TEL|EMAIL|PHOTO).*?(?=^\S)//msg; s/;X-EVOLUTION-UI-SLOT=\d+//g; print;' $< >$@ # all: ../../testcases/sqlite_vcard21.vcf syncevolution_1.4/src/backends/webdav/000077500000000000000000000000001230021373600201675ustar00rootroot00000000000000syncevolution_1.4/src/backends/webdav/CalDAVSource.cpp000066400000000000000000001767051230021373600231260ustar00rootroot00000000000000/* * Copyright (C) 2010 Intel Corporation */ #include "config.h" #ifdef ENABLE_DAV // include first, it sets HANDLE_LIBICAL_MEMORY for us #include #include "CalDAVSource.h" #include #include #include SE_BEGIN_CXX /** * @return "" if subid is empty, otherwise subid */ static std::string SubIDName(const std::string &subid) { return subid.empty() ? "" : subid; } /** remove X-SYNCEVOLUTION-EXDATE-DETACHED from VEVENT */ static void removeSyncEvolutionExdateDetached(icalcomponent *parent) { icalproperty *prop = icalcomponent_get_first_property(parent, ICAL_ANY_PROPERTY); while (prop) { icalproperty *next = icalcomponent_get_next_property(parent, ICAL_ANY_PROPERTY); const char *xname = icalproperty_get_x_name(prop); if (xname && !strcmp(xname, "X-SYNCEVOLUTION-EXDATE-DETACHED")) { icalcomponent_remove_property(parent, prop); icalproperty_free(prop); } prop = next; } } CalDAVSource::CalDAVSource(const SyncSourceParams ¶ms, const boost::shared_ptr &settings) : WebDAVSource(params, settings) { SyncSourceLogging::init(InitList("SUMMARY") + "LOCATION", ", ", m_operations); // override default backup/restore from base class with our own // version m_operations.m_backupData = boost::bind(&CalDAVSource::backupData, this, _1, _2, _3); m_operations.m_restoreData = boost::bind(&CalDAVSource::restoreData, this, _1, _2, _3); } void CalDAVSource::listAllSubItems(SubRevisionMap_t &revisions) { revisions.clear(); const std::string query = "\n" "\n" "\n" "\n" // In practice, peers always return the full data dump // even if asked to return only a subset. Therefore we use this // REPORT to populate our m_cache instead of sending lots of GET // requests later on: faster sync, albeit with higher // memory consumption. // // Because incremental syncs typically don't use listAllSubItems(), // this looks like a good trade-off. #ifdef SHORT_ALL_SUB_ITEMS_DATA "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" #else "\n" #endif "\n" // filter expected by Yahoo! Calendar "\n" "\n" "\n" "\n" "\n" "\n" "\n"; Timespec deadline = createDeadline(); getSession()->startOperation("REPORT 'meta data'", deadline); while (true) { string data; Neon::XMLParser parser; parser.initReportParser(boost::bind(&CalDAVSource::appendItem, this, boost::ref(revisions), _1, _2, boost::ref(data))); m_cache.clear(); m_cache.m_initialized = false; parser.pushHandler(boost::bind(Neon::XMLParser::accept, "urn:ietf:params:xml:ns:caldav", "calendar-data", _2, _3), boost::bind(Neon::XMLParser::append, boost::ref(data), _2, _3)); Neon::Request report(*getSession(), "REPORT", getCalendar().m_path, query, parser); report.addHeader("Depth", "1"); report.addHeader("Content-Type", "application/xml; charset=\"utf-8\""); if (report.run()) { break; } } m_cache.m_initialized = true; } void CalDAVSource::addResource(StringMap &items, const std::string &href, const std::string &etag) { std::string davLUID = path2luid(Neon::URI::parse(href).m_path); items[davLUID] = ETag2Rev(etag); } void CalDAVSource::updateAllSubItems(SubRevisionMap_t &revisions) { // list items to identify new, updated and removed ones const std::string query = "\n" "\n" "\n" "\n" "\n" // filter expected by Yahoo! Calendar "\n" "\n" "\n" "\n" "\n" "\n" "\n"; Timespec deadline = createDeadline(); StringMap items; getSession()->startOperation("updateAllSubItems REPORT 'list items'", deadline); while (true) { string data; Neon::XMLParser parser; items.clear(); parser.initReportParser(boost::bind(&CalDAVSource::addResource, this, boost::ref(items), _1, _2)); Neon::Request report(*getSession(), "REPORT", getCalendar().m_path, query, parser); report.addHeader("Depth", "1"); report.addHeader("Content-Type", "application/xml; charset=\"utf-8\""); if (report.run()) { break; } } // remove obsolete entries SubRevisionMap_t::iterator it = revisions.begin(); while (it != revisions.end()) { SubRevisionMap_t::iterator next = it; ++next; if (items.find(it->first) == items.end()) { revisions.erase(it); } it = next; } // build list of new or updated entries, // copy others to cache m_cache.clear(); m_cache.m_initialized = false; std::list mustRead; BOOST_FOREACH(const StringPair &item, items) { SubRevisionMap_t::iterator it = revisions.find(item.first); if (it == revisions.end() || it->second.m_revision != item.second) { // read current information below SE_LOG_DEBUG(NULL, "updateAllSubItems(): read new or modified item %s", item.first.c_str()); mustRead.push_back(item.first); // The server told us that the item exists. We still need // to deal with the situation that the server might fail // to deliver the item data when we ask for it below. // // There are two reasons when this can happen: either an // item was removed in the meantime or the server is // confused. The latter started to happen reliably with // the Google Calendar server sometime in January/February // 2012. // // In both cases, let's assume that the item is really gone // (and not just unreadable due to that other Google Calendar // bug, see loadItem()+REPORT workaround), and therefore let's // remove the entry from the revisions. if (it != revisions.end()) { revisions.erase(it); } m_cache.erase(item.first); } else { // copy still relevant information SE_LOG_DEBUG(NULL, "updateAllSubItems(): unmodified item %s", it->first.c_str()); addSubItem(it->first, it->second); } } // request dump of these items, add to cache and revisions // // Failures to find or read certain items will be // ignored. appendItem() will only be called for actually // retrieved items. This is partly intentional: Google is known to // have problems with providing all of its data via GET or the // multiget REPORT below. It returns a 404 error for items that a // calendar-query includes (see loadItem()). Such items are // ignored and thus will be silently skipped. This is not // perfect, but better than failing the sync. // // Unfortunately there are other servers (Radicale, I'm looking at // you) which simply return neither data nor errors for the // requested hrefs. To handle that we try the multiget first, // record retrieved or failed responses, then follow up with // individual requests for anything that wasn't mentioned. if (!mustRead.empty()) { std::stringstream buffer; buffer << "\n" "\n" "\n" " \n" " \n" "\n"; BOOST_FOREACH(const std::string &luid, mustRead) { buffer << "" << luid2path(luid) << "\n"; } buffer << ""; std::string query = buffer.str(); std::set results; // LUIDs of all hrefs returned by report getSession()->startOperation("updateAllSubItems REPORT 'multiget new/updated items'", deadline); while (true) { string data; Neon::XMLParser parser; parser.initReportParser(boost::bind(&CalDAVSource::appendMultigetResult, this, boost::ref(revisions), boost::ref(results), _1, _2, boost::ref(data))); parser.pushHandler(boost::bind(Neon::XMLParser::accept, "urn:ietf:params:xml:ns:caldav", "calendar-data", _2, _3), boost::bind(Neon::XMLParser::append, boost::ref(data), _2, _3)); Neon::Request report(*getSession(), "REPORT", getCalendar().m_path, query, parser); report.addHeader("Depth", "1"); report.addHeader("Content-Type", "application/xml; charset=\"utf-8\""); if (report.run()) { break; } } // Workaround for Radicale 0.6.4: it simply returns nothing (no error, no data). // Fall back to GET of items with no response. BOOST_FOREACH(const std::string &luid, mustRead) { if (results.find(luid) == results.end()) { getSession()->startOperation(StringPrintf("GET item %s not returned by 'multiget new/updated items'", luid.c_str()), deadline); std::string path = luid2path(luid); std::string data; std::string etag; while (true) { data.clear(); Neon::Request req(*getSession(), "GET", path, "", data); req.addHeader("Accept", contentType()); if (req.run()) { etag = getETag(req); break; } } appendItem(revisions, path, etag, data); } } } } int CalDAVSource::appendMultigetResult(SubRevisionMap_t &revisions, std::set &luids, const std::string &href, const std::string &etag, std::string &data) { // record which items were seen in the response... luids.insert(path2luid(href)); // and store information about them return appendItem(revisions, href, etag, data); } int CalDAVSource::appendItem(SubRevisionMap_t &revisions, const std::string &href, const std::string &etag, std::string &data) { // Ignore responses with no data: this is not perfect (should better // try to figure out why there is no data), but better than // failing. // // One situation is the response for the collection itself, // which comes with a 404 status and no data with Google Calendar. if (data.empty()) { return 0; } Event::unescapeRecurrenceID(data); eptr calendar(icalcomponent_new_from_string((char *)data.c_str()), // cast is a hack for broken definition in old libical "iCalendar 2.0"); Event::fixIncomingCalendar(calendar.get()); std::string davLUID = path2luid(Neon::URI::parse(href).m_path); SubRevisionEntry &entry = revisions[davLUID]; entry.m_revision = ETag2Rev(etag); long maxSequence = 0; std::string uid; entry.m_subids.clear(); for (icalcomponent *comp = icalcomponent_get_first_component(calendar, ICAL_VEVENT_COMPONENT); comp; comp = icalcomponent_get_next_component(calendar, ICAL_VEVENT_COMPONENT)) { std::string subid = Event::getSubID(comp); uid = Event::getUID(comp); long sequence = Event::getSequence(comp); if (sequence > maxSequence) { maxSequence = sequence; } entry.m_subids.insert(subid); } entry.m_uid = uid; // Ignore items which contain no VEVENT. Happens with Google Calendar // after using it for a while. Deleting them via DELETE doesn't seem // to have an effect either, so all we really can do is ignore them. if (entry.m_subids.empty()) { SE_LOG_DEBUG(NULL, "ignoring broken item %s (is empty)", davLUID.c_str()); revisions.erase(davLUID); m_cache.erase(davLUID); data.clear(); return 0; } if (!m_cache.m_initialized) { boost::shared_ptr event(new Event); event->m_DAVluid = davLUID; event->m_UID = uid; event->m_etag = entry.m_revision; event->m_subids = entry.m_subids; event->m_sequence = maxSequence; #ifndef SHORT_ALL_SUB_ITEMS_DATA // we got a full data dump, use it for (icalcomponent *comp = icalcomponent_get_first_component(calendar, ICAL_VEVENT_COMPONENT); comp; comp = icalcomponent_get_next_component(calendar, ICAL_VEVENT_COMPONENT)) { } event->m_calendar = calendar; #endif m_cache.insert(make_pair(davLUID, event)); } // reset data for next item data.clear(); return 0; } void CalDAVSource::addSubItem(const std::string &luid, const SubRevisionEntry &entry) { boost::shared_ptr &event = m_cache[luid]; event.reset(new Event); event->m_DAVluid = luid; event->m_etag = entry.m_revision; event->m_UID = entry.m_uid; // We don't know sequence and last-modified. This // information will have to be filled in by loadItem() // when some operation on this event needs it. event->m_subids = entry.m_subids; } void CalDAVSource::setAllSubItems(const SubRevisionMap_t &revisions) { if (!m_cache.m_initialized) { // populate our cache (without data) from the information cached // for us BOOST_FOREACH(const SubRevisionMap_t::value_type &subentry, revisions) { addSubItem(subentry.first, subentry.second); } m_cache.m_initialized = true; } } SubSyncSource::SubItemResult CalDAVSource::insertSubItem(const std::string &luid, const std::string &callerSubID, const std::string &item) { SubItemResult subres; // parse new event boost::shared_ptr newEvent(new Event); newEvent->m_calendar.set(icalcomponent_new_from_string((char *)item.c_str()), // hack for old libical "parsing iCalendar 2.0"); struct icaltimetype lastmodtime = icaltime_null_time(); icalcomponent *firstcomp = NULL; for (icalcomponent *comp = firstcomp = icalcomponent_get_first_component(newEvent->m_calendar, ICAL_VEVENT_COMPONENT); comp; comp = icalcomponent_get_next_component(newEvent->m_calendar, ICAL_VEVENT_COMPONENT)) { std::string subid = Event::getSubID(comp); EventCache::iterator it; // remove X-SYNCEVOLUTION-EXDATE-DETACHED, could be added by // the engine's read/modify/write cycle when resolving a // conflict removeSyncEvolutionExdateDetached(comp); if (!luid.empty() && (it = m_cache.find(luid)) != m_cache.end()) { // Additional sanity check: ensure that the expected UID is set. // Necessary if the peer we synchronize with (aka the local // data storage) doesn't support foreign UIDs. Maemo 5 calendar // backend is one example. Event::setUID(comp, it->second->m_UID); newEvent->m_UID = it->second->m_UID; } else { newEvent->m_UID = Event::getUID(comp); if (newEvent->m_UID.empty()) { // create new UID newEvent->m_UID = UUID(); Event::setUID(comp, newEvent->m_UID); } } newEvent->m_sequence = Event::getSequence(comp); newEvent->m_subids.insert(subid); // set DTSTAMP to LAST-MODIFIED in replacement // // Needed because Google insists on replacing the original // DTSTAMP and checks it (409, "Can only store an event with // a newer DTSTAMP"). // // According to RFC 2445, the property is set once when the // event is created for the first time. RFC 5545 extends this // and states that without a METHOD property (the case with // CalDAV), DTSTAMP is identical to LAST-MODIFIED, so Google // is right. icalproperty *dtstamp = icalcomponent_get_first_property(comp, ICAL_DTSTAMP_PROPERTY); if (dtstamp) { icalproperty *lastmod = icalcomponent_get_first_property(comp, ICAL_LASTMODIFIED_PROPERTY); if (lastmod) { lastmodtime = icalproperty_get_lastmodified(lastmod); icalproperty_set_dtstamp(dtstamp, lastmodtime); } } } if (newEvent->m_subids.size() != 1) { SE_THROW("new CalDAV item did not contain exactly one VEVENT"); } std::string subid = *newEvent->m_subids.begin(); // Determine whether we already know the merged item even though // our caller didn't. std::string davLUID = luid; std::string knownSubID = callerSubID; if (davLUID.empty()) { EventCache::iterator it = m_cache.findByUID(newEvent->m_UID); if (it != m_cache.end()) { davLUID = it->first; knownSubID = subid; } } if (davLUID.empty()) { // New VEVENT; should not be part of an existing merged item // ("meeting series"). // // If another app created a resource with the same UID, // then two things can happen: // 1. server merges the data (Google) // 2. adding the item is rejected (standard compliant CalDAV server) // // If the UID is truely new, then // 3. the server may rename the item // // The following code deals with case 3 and also covers case // 1, but our usual Google workarounds (for example, no // patching of SEQUENCE) were not applied and thus sending the // item might fail. // // Case 2 is not currently handled and causes the sync to fail. // This is in line with the current design ("concurrency detected, // causes error, fixed by trying again in slow sync"). InsertItemResult res; // Yahoo expects resource names to match UID + ".ics". std::string name = newEvent->m_UID + ".ics"; std::string buffer; const std::string *data; if (!settings().googleChildHack() || subid.empty()) { // avoid re-encoding item data data = &item; } else { // sanitize item first: when adding child event without parent, // then the RECURRENCE-ID confuses Google eptr icalstr(ical_strdup(icalcomponent_as_ical_string(newEvent->m_calendar))); buffer = icalstr.get(); Event::escapeRecurrenceID(buffer); data = &buffer; } SE_LOG_DEBUG(getDisplayName(), "inserting new VEVENT"); res = insertItem(name, *data, true); subres.m_mainid = res.m_luid; subres.m_uid = newEvent->m_UID; subres.m_subid = subid; subres.m_revision = res.m_revision; EventCache::iterator it = m_cache.find(res.m_luid); if (it != m_cache.end()) { // merge into existing Event Event &event = loadItem(*it->second); event.m_etag = res.m_revision; if (event.m_subids.find(subid) != event.m_subids.end()) { // was already in that item but caller didn't seem to know, // and now we replaced the data on the CalDAV server subres.m_state = ITEM_REPLACED; } else { // add to merged item event.m_subids.insert(subid); } icalcomponent_merge_component(event.m_calendar, newEvent->m_calendar.release()); // function destroys merged calendar } else { // Google Calendar adds a default alarm each time a VEVENT is added // anew. Avoid that by resending our data if necessary (= no alarm set). if (settings().googleAlarmHack() && !icalcomponent_get_first_component(firstcomp, ICAL_VALARM_COMPONENT)) { // add to cache, then update it newEvent->m_DAVluid = res.m_luid; newEvent->m_etag = res.m_revision; m_cache[newEvent->m_DAVluid] = newEvent; // potentially need to know sequence and mod time on server: // keep pointer (clears pointer in newEvent), // then get and parse new copy from server eptr calendar = newEvent->m_calendar; if (settings().googleUpdateHack()) { loadItem(*newEvent); // increment in original data newEvent->m_sequence++; newEvent->m_lastmodtime++; Event::setSequence(firstcomp, newEvent->m_sequence); icalproperty *lastmod = icalcomponent_get_first_property(firstcomp, ICAL_LASTMODIFIED_PROPERTY); if (lastmod) { lastmodtime = icaltime_from_timet(newEvent->m_lastmodtime, false); lastmodtime.is_utc = 1; icalproperty_set_lastmodified(lastmod, lastmodtime); } icalproperty *dtstamp = icalcomponent_get_first_property(firstcomp, ICAL_DTSTAMP_PROPERTY); if (dtstamp) { icalproperty_set_dtstamp(dtstamp, lastmodtime); } // re-encode below data = &buffer; } bool mangleRecurrenceID = settings().googleChildHack() && !subid.empty(); if (data == &buffer || mangleRecurrenceID) { eptr icalstr(ical_strdup(icalcomponent_as_ical_string(calendar))); buffer = icalstr.get(); } if (mangleRecurrenceID) { Event::escapeRecurrenceID(buffer); } SE_LOG_DEBUG(NULL, "resending VEVENT to get rid of VALARM"); res = insertItem(name, *data, true); newEvent->m_etag = subres.m_revision = res.m_revision; newEvent->m_calendar = calendar; } else { // add to cache without further changes newEvent->m_DAVluid = res.m_luid; newEvent->m_etag = res.m_revision; m_cache[newEvent->m_DAVluid] = newEvent; } } } else { if (!subid.empty() && subid != knownSubID) { SE_THROW(StringPrintf("new CalDAV item does not have right RECURRENCE-ID: item %s != expected %s", subid.c_str(), knownSubID.c_str())); } Event &event = loadItem(davLUID); if (subid.empty() && subid != knownSubID) { // fix incomplete iCalendar 2.0 item: should have had a RECURRENCE-ID icalcomponent *newcomp = icalcomponent_get_first_component(newEvent->m_calendar, ICAL_VEVENT_COMPONENT); icalproperty *prop = icalcomponent_get_first_property(newcomp, ICAL_RECURRENCEID_PROPERTY); if (prop) { icalcomponent_remove_property(newcomp, prop); icalproperty_free(prop); } // reconstruct RECURRENCE-ID with known value and TZID from start time of // the parent event or (if not found) the current event eptr rid(icalproperty_new_recurrenceid(icaltime_from_string(knownSubID.c_str())), "new rid"); icalproperty *dtstart = NULL; icalcomponent *comp; // look for parent first for (comp = icalcomponent_get_first_component(event.m_calendar, ICAL_VEVENT_COMPONENT); comp && !dtstart; comp = icalcomponent_get_next_component(event.m_calendar, ICAL_VEVENT_COMPONENT)) { if (!icalcomponent_get_first_property(comp, ICAL_RECURRENCEID_PROPERTY)) { dtstart = icalcomponent_get_first_property(comp, ICAL_DTSTART_PROPERTY); } } // fall back to current event if (!dtstart) { dtstart = icalcomponent_get_first_property(newcomp, ICAL_DTSTART_PROPERTY); } // ignore missing TZID if (dtstart) { icalparameter *tzid = icalproperty_get_first_parameter(dtstart, ICAL_TZID_PARAMETER); if (tzid) { icalproperty_set_parameter(rid, icalparameter_new_clone(tzid)); } } // finally add RECURRENCE-ID and fix newEvent's meta information icalcomponent_add_property(newcomp, rid.release()); subid = knownSubID; newEvent->m_subids.erase(""); newEvent->m_subids.insert(subid); } // no changes expected yet, copy previous attributes subres.m_mainid = davLUID; subres.m_uid = event.m_UID; subres.m_subid = subid; subres.m_revision = event.m_etag; // Google hack: increase sequence number if smaller or equal to // sequence on server. Server rejects update otherwise. // See http://code.google.com/p/google-caldav-issues/issues/detail?id=26 if (settings().googleUpdateHack()) { // always bump SEQ by one before PUT event.m_sequence++; if (newEvent->m_sequence < event.m_sequence) { // override in new event, existing ones will be updated below Event::setSequence(firstcomp, event.m_sequence); } else { // new event sequence is equal or higher, use that event.m_sequence = newEvent->m_sequence; } } // update cache: find old VEVENT and remove it before adding new one, // update last modified time of all other components icalcomponent *removeme = NULL; for (icalcomponent *comp = icalcomponent_get_first_component(event.m_calendar, ICAL_VEVENT_COMPONENT); comp; comp = icalcomponent_get_next_component(event.m_calendar, ICAL_VEVENT_COMPONENT)) { if (Event::getSubID(comp) == subid) { removeme = comp; } else if (settings().googleUpdateHack()) { // increase modification time stamps to that of the new item, // Google rejects the whole update otherwise if (!icaltime_is_null_time(lastmodtime)) { icalproperty *dtstamp = icalcomponent_get_first_property(comp, ICAL_DTSTAMP_PROPERTY); if (dtstamp) { icalproperty_set_dtstamp(dtstamp, lastmodtime); } icalproperty *lastmod = icalcomponent_get_first_property(comp, ICAL_LASTMODIFIED_PROPERTY); if (lastmod) { icalproperty_set_lastmodified(lastmod, lastmodtime); } } // set SEQ to the one increased above Event::setSequence(comp, event.m_sequence); } } if (davLUID != luid) { // caller didn't know final UID: if found, then tell him to // merge the data and try again if (removeme) { subres.m_state = ITEM_NEEDS_MERGE; goto done; } else { event.m_subids.insert(subid); } } else { if (removeme) { // this is what we expect when the caller mentions the DAV LUID icalcomponent_remove_component(event.m_calendar, removeme); icalcomponent_free(removeme); } else { // caller confused?! SE_THROW("event not found"); } } icalcomponent_merge_component(event.m_calendar, newEvent->m_calendar.release()); // function destroys merged calendar eptr icalstr(ical_strdup(icalcomponent_as_ical_string(event.m_calendar))); std::string data = icalstr.get(); // Google gets confused when adding a child without parent, // replace in that case. bool haveParent = event.m_subids.find("") != event.m_subids.end(); if (settings().googleChildHack() && !haveParent) { Event::escapeRecurrenceID(data); } // TODO: avoid updating item on server immediately? try { SE_LOG_DEBUG(getDisplayName(), "updating VEVENT"); InsertItemResult res = insertItem(event.m_DAVluid, data, true); if (res.m_state != ITEM_OKAY || res.m_luid != event.m_DAVluid) { // should not merge with anything, if so, our cache was invalid SE_THROW("CalDAV item not updated as expected"); } event.m_etag = res.m_revision; subres.m_revision = event.m_etag; } catch (const TransportStatusException &ex) { if (ex.syncMLStatus() == 403 && strstr(ex.what(), "You don't have access to change that event")) { // Google Calendar sometimes refuses writes for specific items, // typically meetings organized by someone else. #if 1 // Treat like a temporary, per item error to avoid aborting the // whole sync session. Doesn't really solve the problem (client // and server remain out of sync and will run into this again and // again), but better than giving up on all items or ignoring the // problem. SE_THROW_EXCEPTION_STATUS(StatusException, "CalDAV peer rejected updated with 403, keep trying", SyncMLStatus(417)); #else // Assume that the item hasn't changed and mark it as "merged". // This is incorrect. The 403 error has been seen in cases where // a detached recurrence had to be added to an existing meeting // series. Ignoring the problem means would keep the detached // recurrence out of the server permanently. SE_LOG_INFO(getDisplayName(), "%s: not updated because CalDAV server refused write access for it", getSubDescription(event, subid).c_str()); subres.m_merged = true; subres.m_revision = event.m_etag; #endif } else if (ex.syncMLStatus() == 409 && strstr(ex.what(), "Can only store an event with a newer DTSTAMP")) { SE_LOG_DEBUG(NULL, "resending VEVENT with updated SEQUENCE/LAST-MODIFIED/DTSTAMP to work around 409"); // Sometimes a PUT of two linked events updates one of them on the server // (visible in modified SEQUENCE and LAST-MODIFIED values) and then // fails with 409 because, presumably, the other item now has // too low SEQUENCE/LAST-MODIFIED/DTSTAMP values. // // An attempt with splitting the PUT in advance worked for some cases, // but then it still happened for others. So let's use brute force and // try again once more after reading the updated event anew. eptr fullcal = event.m_calendar; loadItem(event); event.m_sequence++; lastmodtime = icaltime_from_timet(event.m_lastmodtime, false); lastmodtime.is_utc = 1; event.m_calendar = fullcal; for (icalcomponent *comp = icalcomponent_get_first_component(event.m_calendar, ICAL_VEVENT_COMPONENT); comp; comp = icalcomponent_get_next_component(event.m_calendar, ICAL_VEVENT_COMPONENT)) { if (!icaltime_is_null_time(lastmodtime)) { icalproperty *dtstamp = icalcomponent_get_first_property(comp, ICAL_DTSTAMP_PROPERTY); if (dtstamp) { icalproperty_set_dtstamp(dtstamp, lastmodtime); } icalproperty *lastmod = icalcomponent_get_first_property(comp, ICAL_LASTMODIFIED_PROPERTY); if (lastmod) { icalproperty_set_lastmodified(lastmod, lastmodtime); } } Event::setSequence(comp, event.m_sequence); } eptr icalstr(ical_strdup(icalcomponent_as_ical_string(event.m_calendar))); std::string data = icalstr.get(); InsertItemResult res = insertItem(event.m_DAVluid, data, true); if (res.m_state != ITEM_OKAY || res.m_luid != event.m_DAVluid) { // should not merge with anything, if so, our cache was invalid SE_THROW("CalDAV item not updated as expected"); } event.m_etag = res.m_revision; subres.m_revision = event.m_etag; } else { throw; } } } done: return subres; } void CalDAVSource::readSubItem(const std::string &davLUID, const std::string &subid, std::string &item) { Event &event = loadItem(davLUID); if (event.m_subids.size() == 1) { // simple case: convert existing VCALENDAR if (*event.m_subids.begin() == subid) { eptr icalstr(ical_strdup(icalcomponent_as_ical_string(event.m_calendar))); item = icalstr.get(); } else { SE_THROW("event not found"); } } else { // complex case: create VCALENDAR with just the VTIMEZONE definition(s) // and the one event, then convert that eptr calendar(icalcomponent_new(ICAL_VCALENDAR_COMPONENT), "VCALENDAR"); for (icalcomponent *tz = icalcomponent_get_first_component(event.m_calendar, ICAL_VTIMEZONE_COMPONENT); tz; tz = icalcomponent_get_next_component(event.m_calendar, ICAL_VTIMEZONE_COMPONENT)) { eptr clone(icalcomponent_new_clone(tz), "VTIMEZONE"); icalcomponent_add_component(calendar, clone.release()); } bool found = false; icalcomponent *parent = NULL; for (icalcomponent *comp = icalcomponent_get_first_component(event.m_calendar, ICAL_VEVENT_COMPONENT); comp; comp = icalcomponent_get_next_component(event.m_calendar, ICAL_VEVENT_COMPONENT)) { if (Event::getSubID(comp) == subid) { eptr clone(icalcomponent_new_clone(comp), "VEVENT"); if (subid.empty()) { parent = clone.get(); } icalcomponent_add_component(calendar, clone.release()); found = true; break; } } if (!found) { SE_THROW("event not found"); } // tell engine and peers about EXDATEs implied by // RECURRENCE-IDs in detached recurrences by creating // X-SYNCEVOLUTION-EXDATE-DETACHED in the parent if (parent && event.m_subids.size() > 1) { // remove all old X-SYNCEVOLUTION-EXDATE-DETACHED (just in case) removeSyncEvolutionExdateDetached(parent); // now populate with RECURRENCE-IDs of detached recurrences for (icalcomponent *comp = icalcomponent_get_first_component(event.m_calendar, ICAL_VEVENT_COMPONENT); comp; comp = icalcomponent_get_next_component(event.m_calendar, ICAL_VEVENT_COMPONENT)) { icalproperty *prop = icalcomponent_get_first_property(comp, ICAL_RECURRENCEID_PROPERTY); if (prop) { eptr rid(ical_strdup(icalproperty_get_value_as_string(prop))); icalproperty *exdate = icalproperty_new_from_string(StringPrintf("X-SYNCEVOLUTION-EXDATE-DETACHED:%s", rid.get()).c_str()); if (exdate) { icalparameter *tzid = icalproperty_get_first_parameter(prop, ICAL_TZID_PARAMETER); if (tzid) { icalproperty_add_parameter(exdate, icalparameter_new_clone(tzid)); } icalcomponent_add_property(parent, exdate); } } } } eptr icalstr(ical_strdup(icalcomponent_as_ical_string(calendar))); item = icalstr.get(); } } void CalDAVSource::Event::escapeRecurrenceID(std::string &data) { boost::replace_all(data, "\nRECURRENCE-ID", "\nX-SYNCEVOLUTION-RECURRENCE-ID"); } void CalDAVSource::Event::unescapeRecurrenceID(std::string &data) { boost::replace_all(data, "\nX-SYNCEVOLUTION-RECURRENCE-ID", "\nRECURRENCE-ID"); } std::string CalDAVSource::removeSubItem(const string &davLUID, const std::string &subid) { EventCache::iterator it = m_cache.find(davLUID); if (it == m_cache.end()) { // gone already throwError(STATUS_NOT_FOUND, "deleting item: " + davLUID); return ""; } // use item as it is, load only if it is not going to be removed entirely Event &event = *it->second; if (event.m_subids.size() == 1) { // remove entire merged item, nothing will be left after removal if (*event.m_subids.begin() != subid) { SE_LOG_DEBUG(getDisplayName(), "%s: request to remove the %s recurrence: only the %s recurrence exists", davLUID.c_str(), SubIDName(subid).c_str(), SubIDName(*event.m_subids.begin()).c_str()); throwError(STATUS_NOT_FOUND, "remove sub-item: " + SubIDName(subid) + " in " + davLUID); return event.m_etag; } else { try { removeItem(event.m_DAVluid); } catch (const TransportStatusException &ex) { if (ex.syncMLStatus() == 409 && strstr(ex.what(), "Can't delete a recurring event")) { // Google CalDAV: // HTTP/1.1 409 Can't delete a recurring event except on its organizer's calendar // // Workaround: remove RRULE and EXDATE before deleting bool updated = false; icalcomponent *comp = icalcomponent_get_first_component(event.m_calendar, ICAL_VEVENT_COMPONENT); if (comp) { icalproperty *prop; while ((prop = icalcomponent_get_first_property(comp, ICAL_RRULE_PROPERTY)) != NULL) { icalcomponent_remove_property(comp, prop); icalproperty_free(prop); updated = true; } while ((prop = icalcomponent_get_first_property(comp, ICAL_EXDATE_PROPERTY)) != NULL) { icalcomponent_remove_property(comp, prop); icalproperty_free(prop); updated = true; } } if (updated) { SE_LOG_DEBUG(getDisplayName(), "Google recurring event delete hack: remove RRULE before deleting"); eptr icalstr(ical_strdup(icalcomponent_as_ical_string(event.m_calendar))); insertSubItem(davLUID, subid, icalstr.get()); // It has been observed that trying the DELETE immediately // failed again with the same "Can't delete a recurring event" // error although the event no longer has an RRULE. Seems // that the Google server sometimes need a bit of time until // changes really trickle through all databases. Let's // try a few times before giving up. for (int retry = 0; retry < 5; retry++) { try { SE_LOG_DEBUG(getDisplayName(), "Google recurring event delete hack: remove event, attempt #%d", retry); removeSubItem(davLUID, subid); break; } catch (const TransportStatusException &ex2) { if (ex2.syncMLStatus() == 409 && strstr(ex2.what(), "Can't delete a recurring event")) { SE_LOG_DEBUG(getDisplayName(), "Google recurring event delete hack: try again in a second"); Sleep(1); } else { throw; } } } } else { SE_LOG_DEBUG(getDisplayName(), "Google recurring event delete hack not applicable, giving up"); throw; } } else { throw; } } } m_cache.erase(davLUID); return ""; } else { loadItem(event); bool found = false; bool parentRemoved = false; for (icalcomponent *comp = icalcomponent_get_first_component(event.m_calendar, ICAL_VEVENT_COMPONENT); comp; comp = icalcomponent_get_next_component(event.m_calendar, ICAL_VEVENT_COMPONENT)) { if (Event::getSubID(comp) == subid) { icalcomponent_remove_component(event.m_calendar, comp); icalcomponent_free(comp); found = true; if (subid.empty()) { parentRemoved = true; } } } if (!found) { throwError(STATUS_NOT_FOUND, "remove sub-item: " + SubIDName(subid) + " in " + davLUID); return event.m_etag; } event.m_subids.erase(subid); // TODO: avoid updating the item immediately eptr icalstr(ical_strdup(icalcomponent_as_ical_string(event.m_calendar))); InsertItemResult res; if (parentRemoved && settings().googleChildHack()) { // Must avoid VEVENTs with RECURRENCE-ID in // event.m_calendar and the PUT request. Brute-force // approach here is to encode as string, escape, and parse // again. string item = icalstr.get(); Event::escapeRecurrenceID(item); event.m_calendar.set(icalcomponent_new_from_string((char *)item.c_str()), // hack for old libical "parsing iCalendar 2.0"); res = insertItem(davLUID, item, true); } else { res = insertItem(davLUID, icalstr.get(), true); } if (res.m_state != ITEM_OKAY || res.m_luid != davLUID) { SE_THROW("unexpected result of removing sub event"); } event.m_etag = res.m_revision; return event.m_etag; } } void CalDAVSource::removeMergedItem(const std::string &davLUID) { EventCache::iterator it = m_cache.find(davLUID); if (it == m_cache.end()) { // gone already, no need to do anything SE_LOG_DEBUG(getDisplayName(), "%s: ignoring request to delete non-existent item", davLUID.c_str()); return; } // use item as it is, load only if it is not going to be removed entirely Event &event = *it->second; // remove entire merged item, nothing will be left after removal try { removeItem(event.m_DAVluid); } catch (const TransportStatusException &ex) { if (ex.syncMLStatus() == 409 && strstr(ex.what(), "Can't delete a recurring event")) { // Google CalDAV: // HTTP/1.1 409 Can't delete a recurring event except on its organizer's calendar // // Workaround: use the workarounds from removeSubItem() std::set subids = event.m_subids; for (std::set::reverse_iterator it = subids.rbegin(); it != subids.rend(); ++it) { removeSubItem(davLUID, *it); } } else { throw; } } m_cache.erase(davLUID); } void CalDAVSource::flushItem(const string &davLUID) { // TODO: currently we always flush immediately, so no need to send data here EventCache::iterator it = m_cache.find(davLUID); if (it != m_cache.end()) { it->second->m_calendar.set(NULL); } } std::string CalDAVSource::getSubDescription(const string &davLUID, const string &subid) { EventCache::iterator it = m_cache.find(davLUID); if (it == m_cache.end()) { // unknown item, return empty string for fallback return ""; } else { return getSubDescription(*it->second, subid); } } std::string CalDAVSource::getSubDescription(Event &event, const string &subid) { if (!event.m_calendar) { // Don't load (expensive!) only to provide the description. // Returning an empty string will trigger the fallback (logging the ID). return ""; } for (icalcomponent *comp = icalcomponent_get_first_component(event.m_calendar, ICAL_VEVENT_COMPONENT); comp; comp = icalcomponent_get_next_component(event.m_calendar, ICAL_VEVENT_COMPONENT)) { if (Event::getSubID(comp) == subid) { std::string descr; const char *summary = icalcomponent_get_summary(comp); if (summary && summary[0]) { descr += summary; } if (true /* is event */) { const char *location = icalcomponent_get_location(comp); if (location && location[0]) { if (!descr.empty()) { descr += ", "; } descr += location; } } // TODO: other item types return descr; } } return ""; } std::string CalDAVSource::getDescription(const string &luid) { StringPair ids = MapSyncSource::splitLUID(luid); return getSubDescription(ids.first, ids.second); } CalDAVSource::Event &CalDAVSource::findItem(const std::string &davLUID) { EventCache::iterator it = m_cache.find(davLUID); if (it == m_cache.end()) { throwError(STATUS_NOT_FOUND, "finding item: " + davLUID); } return *it->second; } CalDAVSource::Event &CalDAVSource::loadItem(const std::string &davLUID) { Event &event = findItem(davLUID); return loadItem(event); } int CalDAVSource::storeItem(const std::string &wantedLuid, std::string &item, std::string &data, const std::string &href) { std::string luid = path2luid(Neon::URI::parse(href).m_path); if (luid == wantedLuid) { SE_LOG_DEBUG(NULL, "got item %s via REPORT fallback", luid.c_str()); item = data; } data.clear(); return 0; } CalDAVSource::Event &CalDAVSource::loadItem(Event &event) { if (!event.m_calendar) { std::string item; try { readItem(event.m_DAVluid, item, true); } catch (const TransportStatusException &ex) { if (ex.syncMLStatus() == 404) { // Someone must have created a detached recurrence on // the server without the master event. We avoid that // with the "Google Child Hack", but have no control // over other clients. So let's deal with this problem // after logging it. Exception::log(); // We know about the event because it showed up in a REPORT. // So let's use such a REPORT to retrieve the desired item. // Not as efficient as a GET (and thus not the default), but // so be it. #if 0 // This would be fairly efficient, but runs into the same 404 error as a GET. std::string query = StringPrintf("\n" "\n" "\n" " \n" "\n" "<[CDATA[%s]]>\n" "", event.m_DAVluid.c_str()); Neon::XMLParser parser; std::string href, etag; item = ""; parser.initReportParser(href, etag); parser.pushHandler(boost::bind(Neon::XMLParser::accept, "urn:ietf:params:xml:ns:caldav", "calendar-data", _2, _3), boost::bind(Neon::XMLParser::append, boost::ref(item), _2, _3)); Neon::Request report(*getSession(), "REPORT", getCalendar().m_path, query, parser); report.addHeader("Content-Type", "application/xml; charset=\"utf-8\""); report.run(); #else std::string query = StringPrintf("\n" "\n" "\n" "\n" "\n" "\n" // filter expected by Yahoo! Calendar "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n", event.m_UID.c_str()); Timespec deadline = createDeadline(); getSession()->startOperation("REPORT 'single item'", deadline); while (true) { Neon::XMLParser parser; std::string data; parser.initReportParser(boost::bind(&CalDAVSource::storeItem, this, boost::ref(event.m_DAVluid), boost::ref(item), boost::ref(data), _1)); parser.pushHandler(boost::bind(Neon::XMLParser::accept, "urn:ietf:params:xml:ns:caldav", "calendar-data", _2, _3), boost::bind(Neon::XMLParser::append, boost::ref(data), _2, _3)); Neon::Request report(*getSession(), "REPORT", getCalendar().m_path, query, parser); report.addHeader("Depth", "1"); report.addHeader("Content-Type", "application/xml; charset=\"utf-8\""); if (report.run()) { break; } } #endif } else { throw; } } Event::unescapeRecurrenceID(item); event.m_calendar.set(icalcomponent_new_from_string((char *)item.c_str()), // hack for old libical "parsing iCalendar 2.0"); Event::fixIncomingCalendar(event.m_calendar.get()); // Sequence number/last-modified might have been increased by last save. // Or the cache was populated by setAllSubItems(), which doesn't give // us the information. In that case, UID might also still be unknown. // Either way, check it again. for (icalcomponent *comp = icalcomponent_get_first_component(event.m_calendar, ICAL_VEVENT_COMPONENT); comp; comp = icalcomponent_get_next_component(event.m_calendar, ICAL_VEVENT_COMPONENT)) { if (event.m_UID.empty()) { event.m_UID = Event::getUID(comp); } long sequence = Event::getSequence(comp); if (sequence > event.m_sequence) { event.m_sequence = sequence; } icalproperty *lastmod = icalcomponent_get_first_property(comp, ICAL_LASTMODIFIED_PROPERTY); if (lastmod) { icaltimetype lastmodtime = icalproperty_get_lastmodified(lastmod); time_t mod = icaltime_as_timet(lastmodtime); if (mod > event.m_lastmodtime) { event.m_lastmodtime = mod; } } } } return event; } void CalDAVSource::Event::fixIncomingCalendar(icalcomponent *calendar) { // Evolution has a problem when the parent event uses a time // zone and the RECURRENCE-ID uses UTC (can happen in Exchange // meeting invitations): then Evolution and/or libical do not // recognize that the detached recurrence overrides the // regular recurrence and display both. // // As a workaround, remember time zone of DTSTART in parent event // in the first loop iteration. Then below transform the RECURRENCE-ID // time. bool ridInUTC = false; const icaltimezone *zone = NULL; for (icalcomponent *comp = icalcomponent_get_first_component(calendar, ICAL_VEVENT_COMPONENT); comp; comp = icalcomponent_get_next_component(calendar, ICAL_VEVENT_COMPONENT)) { // remember whether we need to convert RECURRENCE-ID struct icaltimetype rid = icalcomponent_get_recurrenceid(comp); if (icaltime_is_utc(rid)) { ridInUTC = true; } // is parent event? -> remember time zone unless it is UTC static const struct icaltimetype null = { 0 }; if (!memcmp(&rid, &null, sizeof(null))) { struct icaltimetype dtstart = icalcomponent_get_dtstart(comp); if (!icaltime_is_utc(dtstart)) { zone = icaltime_get_timezone(dtstart); } } // remove useless X-LIC-ERROR icalproperty *prop = icalcomponent_get_first_property(comp, ICAL_ANY_PROPERTY); while (prop) { icalproperty *next = icalcomponent_get_next_property(comp, ICAL_ANY_PROPERTY); const char *name = icalproperty_get_property_name(prop); if (name && !strcmp("X-LIC-ERROR", name)) { icalcomponent_remove_property(comp, prop); icalproperty_free(prop); } prop = next; } } // now update RECURRENCE-ID? if (zone && ridInUTC) { for (icalcomponent *comp = icalcomponent_get_first_component(calendar, ICAL_VEVENT_COMPONENT); comp; comp = icalcomponent_get_next_component(calendar, ICAL_VEVENT_COMPONENT)) { icalproperty *prop = icalcomponent_get_first_property(comp, ICAL_RECURRENCEID_PROPERTY); if (prop) { struct icaltimetype rid = icalproperty_get_recurrenceid(prop); if (icaltime_is_utc(rid)) { rid = icaltime_convert_to_zone(rid, const_cast(zone)); // icaltime_convert_to_zone should take a "const timezone" but doesn't icalproperty_set_recurrenceid(prop, rid); icalproperty_remove_parameter_by_kind(prop, ICAL_TZID_PARAMETER); icalparameter *param = icalparameter_new_from_value_string(ICAL_TZID_PARAMETER, icaltimezone_get_tzid(const_cast(zone))); icalproperty_set_parameter(prop, param); } } } } } std::string CalDAVSource::Event::icalTime2Str(const icaltimetype &tt) { static const struct icaltimetype null = { 0 }; if (!memcmp(&tt, &null, sizeof(null))) { return ""; } else { eptr timestr(ical_strdup(icaltime_as_ical_string(tt))); if (!timestr) { SE_THROW("cannot convert to time string"); } return timestr.get(); } } std::string CalDAVSource::Event::getSubID(icalcomponent *comp) { struct icaltimetype rid = icalcomponent_get_recurrenceid(comp); return icalTime2Str(rid); } std::string CalDAVSource::Event::getUID(icalcomponent *comp) { std::string uid; icalproperty *prop = icalcomponent_get_first_property(comp, ICAL_UID_PROPERTY); if (prop) { uid = icalproperty_get_uid(prop); } return uid; } void CalDAVSource::Event::setUID(icalcomponent *comp, const std::string &uid) { icalproperty *prop = icalcomponent_get_first_property(comp, ICAL_UID_PROPERTY); if (prop) { icalproperty_set_uid(prop, uid.c_str()); } else { icalcomponent_add_property(comp, icalproperty_new_uid(uid.c_str())); } } int CalDAVSource::Event::getSequence(icalcomponent *comp) { int sequence = 0; icalproperty *prop = icalcomponent_get_first_property(comp, ICAL_SEQUENCE_PROPERTY); if (prop) { sequence = icalproperty_get_sequence(prop); } return sequence; } void CalDAVSource::Event::setSequence(icalcomponent *comp, int sequence) { icalproperty *prop = icalcomponent_get_first_property(comp, ICAL_SEQUENCE_PROPERTY); if (prop) { icalproperty_set_sequence(prop, sequence); } else { icalcomponent_add_property(comp, icalproperty_new_sequence(sequence)); } } CalDAVSource::EventCache::iterator CalDAVSource::EventCache::findByUID(const std::string &uid) { for (iterator it = begin(); it != end(); ++it) { if (it->second->m_UID == uid) { return it; } } return end(); } void CalDAVSource::backupData(const SyncSource::Operations::ConstBackupInfo &oldBackup, const SyncSource::Operations::BackupInfo &newBackup, BackupReport &backupReport) { contactServer(); // If this runs as part of the sync preparations, then we might // use the result to populate our m_cache. But because dumping // data is typically disabled, this optimization isn't really // worth that much. ItemCache cache; cache.init(oldBackup, newBackup, false); // stream directly from REPORT with full data into backup const std::string query = "\n" "\n" "\n" "\n" "\n" "\n" // filter expected by Yahoo! Calendar "\n" "\n" "\n" "\n" "\n" "\n" "\n"; string data; Neon::XMLParser parser; parser.initReportParser(boost::bind(&CalDAVSource::backupItem, this, boost::ref(cache), _1, _2, boost::ref(data))); parser.pushHandler(boost::bind(Neon::XMLParser::accept, "urn:ietf:params:xml:ns:caldav", "calendar-data", _2, _3), boost::bind(Neon::XMLParser::append, boost::ref(data), _2, _3)); Timespec deadline = createDeadline(); getSession()->startOperation("REPORT 'full calendar'", deadline); while (true) { Neon::Request report(*getSession(), "REPORT", getCalendar().m_path, query, parser); report.addHeader("Depth", "1"); report.addHeader("Content-Type", "application/xml; charset=\"utf-8\""); if (report.run()) { break; } cache.reset(); } cache.finalize(backupReport); } int CalDAVSource::backupItem(ItemCache &cache, const std::string &href, const std::string &etag, std::string &data) { // detect and ignore empty items, like we do in appendItem() eptr calendar(icalcomponent_new_from_string((char *)data.c_str()), // cast is a hack for broken definition in old libical "iCalendar 2.0"); if (icalcomponent_get_first_component(calendar, ICAL_VEVENT_COMPONENT)) { Event::unescapeRecurrenceID(data); std::string luid = path2luid(Neon::URI::parse(href).m_path); std::string rev = ETag2Rev(etag); cache.backupItem(data, luid, rev); } else { SE_LOG_DEBUG(NULL, "ignoring broken item %s during backup (is empty)", href.c_str()); } // reset data for next item data.clear(); return 0; } void CalDAVSource::restoreData(const SyncSource::Operations::ConstBackupInfo &oldBackup, bool dryrun, SyncSourceReport &report) { // TODO: implement restore throw("not implemented"); } bool CalDAVSource::typeMatches(const StringMap &props) const { StringMap::const_iterator it = props.find("urn:ietf:params:xml:ns:caldav:supported-calendar-component-set"); if (it != props.end() && it->second.find("") != std::string::npos) { return true; } else { return false; } } SE_END_CXX #endif // ENABLE_DAV syncevolution_1.4/src/backends/webdav/CalDAVSource.h000066400000000000000000000174011230021373600225560ustar00rootroot00000000000000/* * Copyright (C) 2010 Intel Corporation */ #ifndef INCL_CALDAVSOURCE #define INCL_CALDAVSOURCE #include #ifdef ENABLE_DAV #include "WebDAVSource.h" #include #include #include #include #include #include SE_BEGIN_CXX class CalDAVSource : public WebDAVSource, public SubSyncSource, public SyncSourceLogging { public: CalDAVSource(const SyncSourceParams ¶ms, const boost::shared_ptr &settings); /* implementation of SyncSourceSerialize interface */ virtual std::string getMimeType() const { return "text/calendar"; } virtual std::string getMimeVersion() const { return "2.0"; } /* implementation of SubSyncSource interface */ virtual void begin() { contactServer(); } virtual void endSubSync(bool success) { if (success) { storeServerInfos(); } } virtual std::string subDatabaseRevision() { return databaseRevision(); } virtual void listAllSubItems(SubRevisionMap_t &revisions); virtual void updateAllSubItems(SubRevisionMap_t &revisions); virtual void setAllSubItems(const SubRevisionMap_t &revisions); virtual SubItemResult insertSubItem(const std::string &uid, const std::string &subid, const std::string &item); virtual void readSubItem(const std::string &uid, const std::string &subid, std::string &item); virtual std::string removeSubItem(const string &uid, const std::string &subid); virtual void removeMergedItem(const std::string &luid); virtual void flushItem(const string &uid); virtual std::string getSubDescription(const string &uid, const string &subid); // implementation of SyncSourceLogging callback virtual std::string getDescription(const string &luid); /** * Dump each resource item unmodified into the given directory. * The ConfigNode stores the luid/etag mapping. */ void backupData(const SyncSource::Operations::ConstBackupInfo &oldBackup, const SyncSource::Operations::BackupInfo &newBackup, BackupReport &report); /** * Restore database from data stored in backupData(). Will be * called inside open()/close() pair. beginSync() is *not* called. */ void restoreData(const SyncSource::Operations::ConstBackupInfo &oldBackup, bool dryrun, SyncSourceReport &report); // disambiguate getSynthesisAPI() SDKInterface *getSynthesisAPI() const { return SubSyncSource::getSynthesisAPI(); } protected: // implementation of WebDAVSource callbacks virtual std::string serviceType() const { return "caldav"; } virtual bool typeMatches(const StringMap &props) const; virtual std::string homeSetProp() const { return "urn:ietf:params:xml:ns:caldav:calendar-home-set"; } virtual std::string wellKnownURL() const { return "/.well-known/caldav"; } virtual std::string contentType() const { return "text/calendar; charset=utf-8"; } virtual std::string suffix() const { return ".ics"; } virtual std::string getContent() const { return "VEVENT"; } virtual bool getContentMixed() const { return true; } private: /** * Information about each merged item. */ class Event : boost::noncopyable { public: Event() : m_sequence(0), m_lastmodtime(0) {} /** the ID used by WebDAVSource */ std::string m_DAVluid; /** the iCalendar 2.0 UID */ std::string m_UID; /** revision string in WebDAVSource */ std::string m_etag; /** maximum sequence number of any sub item */ long m_sequence; /** maximum modification time of any sub item */ time_t m_lastmodtime; /** * the list of simplified RECURRENCE-IDs (without time zone, * see icalTime2Str()), empty string for VEVENT without * RECURRENCE-ID */ std::set m_subids; /** * parsed VCALENDAR component representing the current * state of the item as it exists on the WebDAV server, * must be kept up-to-date as we make changes, may be NULL */ eptr m_calendar; /** * clean up calendar directly after receiving it from peer: * RECURRENCE-ID in UTC, remove X-LIC-ERROR */ static void fixIncomingCalendar(icalcomponent *calendar); /** date-time as string, without time zone */ static std::string icalTime2Str(const icaltimetype &tt); /** RECURRENCE-ID, empty if none */ static std::string getSubID(icalcomponent *icomp); /** SEQUENCE number, 0 if none */ static int getSequence(icalcomponent *icomp); static void setSequence(icalcomponent *icomp, int sequenceval); /** UID, empty if none */ static std::string getUID(icalcomponent *icomp); static void setUID(icalcomponent *icomp, const std::string &uid); /** rename RECURRENCE-ID to X-SYNCEVOLUTION-RECURRENCE-ID and vice versa */ static void escapeRecurrenceID(std::string &data); static void unescapeRecurrenceID(std::string &data); }; /** * A cache of information about each merged item. Maps from * WebDAVSource local ID to Event. Items in the cache are in the * format as expected by the local side, with RECURRENCE-ID. * * This is not necessarily how the data is sent to the server: * - RECURRENCE-ID in an item which has no master event * is replaced by X-SYNCEVOLUTION-RECURRENCE-ID because * Google gets confused by a single detached event without * parent (Event::escapeRecurrenceID()). * * When retrieving an EVENT from the server this is substituted * again before parsing (depends on server preserving X- * extensions, see Event::unescapeRecurrenceID()). */ class EventCache : public std::map > { public: EventCache() : m_initialized(false) {} bool m_initialized; iterator findByUID(const std::string &uid); } m_cache; Event &findItem(const std::string &davLUID); Event &loadItem(const std::string &davLUID); Event &loadItem(Event &event); std::string getSubDescription(Event &event, const string &subid); /** calback for multiget: same as appendItem, but also records luid of all responses */ int appendMultigetResult(SubRevisionMap_t &revisions, std::set &luids, const std::string &href, const std::string &etag, std::string &data); /** callback for listAllSubItems: parse and add new item */ int appendItem(SubRevisionMap_t &revisions, const std::string &href, const std::string &etag, std::string &data); /** callback for backupData(): dump into backup */ int backupItem(ItemCache &cache, const std::string &href, const std::string &etag, std::string &data); /** callback for loadItem(): store right item from REPORT */ int storeItem(const std::string &wantedLuid, std::string &item, std::string &data, const std::string &href); /** add to m_cache */ void addSubItem(const std::string &luid, const SubRevisionEntry &entry); /** store as luid + revision */ void addResource(StringMap &items, const std::string &href, const std::string &etag); }; SE_END_CXX #endif // ENABLE_DAV #endif // INCL_CALDAVSOURCE syncevolution_1.4/src/backends/webdav/CalDAVVxxSource.cpp000066400000000000000000000024031230021373600236130ustar00rootroot00000000000000/* * Copyright (C) 2010 Intel Corporation */ #include "CalDAVVxxSource.h" #ifdef ENABLE_DAV #include SE_BEGIN_CXX // TODO: use EDS backend icalstrdup.c #define ical_strdup(_x) (_x) CalDAVVxxSource::CalDAVVxxSource(const std::string &content, const SyncSourceParams ¶ms, const boost::shared_ptr &settings) : WebDAVSource(params, settings), m_content(content) { SyncSourceLogging::init(InitList("SUMMARY") + "LOCATION", " ", m_operations); } std::string CalDAVVxxSource::getDescription(const string &luid) { // TODO return ""; } bool CalDAVVxxSource::typeMatches(const StringMap &props) const { std::string davcomp = StringPrintf("", m_content.c_str()); StringMap::const_iterator it = props.find("urn:ietf:params:xml:ns:caldav:supported-calendar-component-set"); if (it != props.end() && it->second.find(davcomp) != std::string::npos) { return true; } else { return false; } } SE_END_CXX #endif // ENABLE_DAV syncevolution_1.4/src/backends/webdav/CalDAVVxxSource.h000066400000000000000000000041521230021373600232630ustar00rootroot00000000000000/* * Copyright (C) 2010 Intel Corporation */ #ifndef INCL_CALDAVVXXSOURCE #define INCL_CALDAVVXXSOURCE #include #ifdef ENABLE_DAV #include "WebDAVSource.h" #include #include #include #include #include #include SE_BEGIN_CXX /** * Supports VJOURNAL and VTODO via CalDAV. * * In contrast to CalDAVSource, no complex handling * of UID/RECURRENCE-ID is necessary because those do not * apply to VJOURNAL and VTODO. * * Therefore CalDAVVxxSource is much closer to CardDAVSource, * except that it uses CalDAV. */ class CalDAVVxxSource : public WebDAVSource, public SyncSourceLogging { public: /** * @param content "VJOURNAL" or "VTODO" */ CalDAVVxxSource(const std::string &content, const SyncSourceParams ¶ms, const boost::shared_ptr &settings); /* implementation of SyncSourceSerialize interface */ virtual std::string getMimeType() const { return m_content == "VJOURNAL" ? "text/calendar+plain" : "text/calendar"; } virtual std::string getMimeVersion() const { return "2.0"; } // implementation of SyncSourceLogging callback virtual std::string getDescription(const string &luid); protected: // implementation of WebDAVSource callbacks virtual std::string serviceType() const { return "caldav"; } virtual bool typeMatches(const StringMap &props) const; virtual std::string homeSetProp() const { return "urn:ietf:params:xml:ns:caldav:calendar-home-set"; } virtual std::string wellKnownURL() const { return "/.well-known/caldav"; } virtual std::string contentType() const { return "text/calendar; charset=utf-8"; } virtual std::string suffix() const { return ".ics"; } virtual std::string getContent() const { return m_content; } virtual bool getContentMixed() const { return true; } private: const std::string m_content; }; SE_END_CXX #endif // ENABLE_DAV #endif // INCL_CALDAVVXXSOURCE syncevolution_1.4/src/backends/webdav/CardDAVSource.cpp000066400000000000000000000041441230021373600232630ustar00rootroot00000000000000/* * Copyright (C) 2010 Intel Corporation */ #include "CardDAVSource.h" #ifdef ENABLE_DAV #include SE_BEGIN_CXX // TODO: use EDS backend icalstrdup.c #define ical_strdup(_x) (_x) CardDAVSource::CardDAVSource(const SyncSourceParams ¶ms, const boost::shared_ptr &settings) : WebDAVSource(params, settings) { SyncSourceLogging::init(InitList("N_FIRST") + "N_MIDDLE" + "N_LAST", " ", m_operations); } std::string CardDAVSource::getDescription(const string &luid) { // TODO return ""; } void CardDAVSource::readItem(const std::string &luid, std::string &item, bool raw) { WebDAVSource::readItem(luid, item, raw); // Workaround for Yahoo! Contacts: it encodes // backslash \ single quote ' double quote " // as // NOTE;CHARSET=utf-8;ENCODING=QUOTED-PRINTABLE: = // backslash &#92; single quote ' double quote " // // This is just plain wrong. The backslash even seems to be // encoded twice: \ -> \ -> &#92; // // I don't see any way to detect this broken encoding reliably // at runtime. In the meantime deal with it by always replacing // HTML enties until none are left. Obviously that means that // it is impossible to put HTML entities into a contact value. // TODO: better detection of this server bug. if (false) { replaceHTMLEntities(item); } } bool CardDAVSource::typeMatches(const StringMap &props) const { StringMap::const_iterator it = props.find("DAV::resourcetype"); if (it != props.end()) { const std::string &type = it->second; // allow parameters (no closing bracket) // and allow also "carddavaddressbook" (caused by invalid Neon // string concatenation?!) if (type.find(" #ifdef ENABLE_DAV #include "WebDAVSource.h" #include #include #include #include #include #include SE_BEGIN_CXX class CardDAVSource : public WebDAVSource, public SyncSourceLogging { public: CardDAVSource(const SyncSourceParams ¶ms, const boost::shared_ptr &settings); /* implementation of SyncSourceSerialize interface */ virtual std::string getMimeType() const { return "text/vcard"; } virtual std::string getMimeVersion() const { return "3.0"; } // implementation of SyncSourceLogging callback virtual std::string getDescription(const string &luid); // implements vCard specific conversions on top of generic WebDAV readItem() void readItem(const std::string &luid, std::string &item, bool raw); protected: // implementation of WebDAVSource callbacks virtual std::string serviceType() const { return "carddav"; } virtual bool typeMatches(const StringMap &props) const; virtual std::string homeSetProp() const { return "urn:ietf:params:xml:ns:carddav:addressbook-home-set"; } virtual std::string wellKnownURL() const { return "/.well-known/carddav"; } virtual std::string contentType() const { return "text/vcard; charset=utf-8"; } virtual std::string getContent() const { return "VCARD"; } virtual bool getContentMixed() const { return false; } }; SE_END_CXX #endif // ENABLE_DAV #endif // INCL_CARDDAVSOURCE syncevolution_1.4/src/backends/webdav/NeonCXX.cpp000066400000000000000000001030011230021373600221500ustar00rootroot00000000000000/* * Copyright (C) 2010 Patrick Ohly */ #include #ifdef ENABLE_DAV #include "NeonCXX.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include SE_BEGIN_CXX namespace Neon { #if 0 } #endif std::string features() { std::list res; if (ne_has_support(NE_FEATURE_SSL)) { res.push_back("SSL"); } if (ne_has_support(NE_FEATURE_ZLIB)) { res.push_back("ZLIB"); } if (ne_has_support(NE_FEATURE_IPV6)) { res.push_back("IPV6"); } if (ne_has_support(NE_FEATURE_LFS)) { res.push_back("LFS"); } if (ne_has_support(NE_FEATURE_SOCKS)) { res.push_back("SOCKS"); } if (ne_has_support(NE_FEATURE_TS_SSL)) { res.push_back("TS_SSL"); } if (ne_has_support(NE_FEATURE_I18N)) { res.push_back("I18N"); } return boost::join(res, ", "); } URI URI::parse(const std::string &url, bool collection) { ne_uri uri; int error = ne_uri_parse(url.c_str(), &uri); URI res = fromNeon(uri, collection); if (!res.m_port) { res.m_port = ne_uri_defaultport(res.m_scheme.c_str()); } ne_uri_free(&uri); if (error) { SE_THROW_EXCEPTION(TransportException, StringPrintf("invalid URL '%s' (parsed as '%s')", url.c_str(), res.toURL().c_str())); } return res; } URI URI::fromNeon(const ne_uri &uri, bool collection) { URI res; if (uri.scheme) { res.m_scheme = uri.scheme; } if (uri.host) { res.m_host = uri.host; } if (uri.userinfo) { res.m_userinfo = uri.userinfo; } if (uri.path) { res.m_path = normalizePath(uri.path, collection); } if (uri.query) { res.m_query = uri.query; } if (uri.fragment) { res.m_fragment = uri.fragment; } res.m_port = uri.port; return res; } URI URI::resolve(const std::string &path) const { ne_uri tmp[2]; ne_uri full; memset(tmp, 0, sizeof(tmp)); tmp[0].path = const_cast(m_path.c_str()); tmp[1].path = const_cast(path.c_str()); ne_uri_resolve(tmp + 0, tmp + 1, &full); URI res(*this); res.m_path = full.path; ne_uri_free(&full); return res; } std::string URI::toURL() const { std::ostringstream buffer; buffer << m_scheme << "://"; if (!m_userinfo.empty()) { buffer << m_userinfo << "@"; } buffer << m_host; if (m_port) { buffer << ":" << m_port; } buffer << m_path; if (!m_query.empty()) { buffer << "?" << m_query; } if (!m_fragment.empty()) { buffer << "#" << m_fragment; } return buffer.str(); } std::string URI::escape(const std::string &text) { SmartPtr tmp(ne_path_escape(text.c_str())); // Fail gracefully. I have observed ne_path_escape returning NULL // a couple of times, with input "%u". It makes sense, if the // escaping fails, to just return the same string, because, well, // it couldn't be escaped. return tmp ? tmp.get() : text; } std::string URI::unescape(const std::string &text) { SmartPtr tmp(ne_path_unescape(text.c_str())); // Fail gracefully. See also the similar comment for the escape() method. return tmp ? tmp.get() : text; } std::string URI::normalizePath(const std::string &path, bool collection) { std::string res; res.reserve(path.size() * 150 / 100); // always start with one leading slash res = "/"; typedef boost::split_iterator string_split_iterator; string_split_iterator it = boost::make_split_iterator(path, boost::first_finder("/", boost::is_iequal())); while (!it.eof()) { if (it->begin() == it->end()) { // avoid adding empty path components ++it; } else { std::string split(it->begin(), it->end()); // Let's have an exception here for "%u", since we use that to replace the // actual username into the path. It's safe to ignore "%u" because it // couldn't be in a valid URI anyway. // TODO: we should find a neat way to remove the awareness of "%u" from // NeonCXX. std::string normalizedSplit = split; if (split != "%u") { normalizedSplit = escape(unescape(split)); } res += normalizedSplit; ++it; if (!it.eof()) { res += '/'; } } } if (collection && !boost::ends_with(res, "/")) { res += '/'; } return res; } std::string Status2String(const ne_status *status) { if (!status) { return ""; } return StringPrintf("", status->major_version, status->minor_version, status->code, status->klass, status->reason_phrase ? status->reason_phrase : "\"\""); } Session::Session(const boost::shared_ptr &settings) : m_forceAuthorizationOnce(false), m_credentialsSent(false), m_oauthTokenRejections(0), m_settings(settings), m_debugging(false), m_session(NULL), m_attempt(0) { int logLevel = m_settings->logLevel(); if (logLevel >= 3) { ne_debug_init(stderr, NE_DBG_FLUSH|NE_DBG_HTTP|NE_DBG_HTTPAUTH| (logLevel >= 4 ? NE_DBG_HTTPBODY : 0) | (logLevel >= 5 ? (NE_DBG_LOCKS|NE_DBG_SSL) : 0)| (logLevel >= 6 ? (NE_DBG_XML|NE_DBG_XMLPARSE) : 0)| (logLevel >= 11 ? (NE_DBG_HTTPPLAIN) : 0)); m_debugging = true; } else { ne_debug_init(NULL, 0); } ne_sock_init(); m_uri = URI::parse(settings->getURL()); m_session = ne_session_create(m_uri.m_scheme.c_str(), m_uri.m_host.c_str(), m_uri.m_port); ne_set_server_auth(m_session, getCredentials, this); if (m_uri.m_scheme == "https") { // neon only initializes session->ssl_context if // using https and segfaults in ne_ssl_trust_default_ca() // of ne_gnutls.c if ne_ssl_trust_default_ca() // is called for non-https. So better call these // functions only when needed. ne_ssl_set_verify(m_session, sslVerify, this); ne_ssl_trust_default_ca(m_session); // hack for Yahoo: need a client certificate ne_ssl_client_cert *cert = ne_ssl_clicert_read("client.p12"); SE_LOG_DEBUG(NULL, "client cert is %s", !cert ? "missing" : ne_ssl_clicert_encrypted(cert) ? "encrypted" : "unencrypted"); if (cert) { if (ne_ssl_clicert_encrypted(cert)) { if (ne_ssl_clicert_decrypt(cert, "meego")) { SE_LOG_DEBUG(NULL, "decryption failed"); } } ne_ssl_set_clicert(m_session, cert); } } m_proxyURL = settings->proxy(); if (m_proxyURL.empty()) { #ifdef HAVE_LIBNEON_SYSTEM_PROXY // hard compile-time dependency ne_session_system_proxy(m_session, 0); #else // compiled against older libneon, but might run with more recent neon typedef void (*session_system_proxy_t)(ne_session *sess, unsigned int flags); session_system_proxy_t session_system_proxy = (session_system_proxy_t)dlsym(RTLD_DEFAULT, "ne_session_system_proxy"); if (session_system_proxy) { session_system_proxy(m_session, 0); } #endif } else { URI proxyuri = URI::parse(m_proxyURL); ne_session_proxy(m_session, proxyuri.m_host.c_str(), proxyuri.m_port); } int seconds = settings->timeoutSeconds(); if (seconds < 0) { seconds = 5 * 60; } ne_set_read_timeout(m_session, seconds); ne_set_connect_timeout(m_session, seconds); ne_hook_pre_send(m_session, preSendHook, this); } Session::~Session() { if (m_session) { ne_session_destroy(m_session); } ne_sock_exit(); } boost::shared_ptr Session::m_cachedSession; boost::shared_ptr Session::create(const boost::shared_ptr &settings) { URI uri = URI::parse(settings->getURL()); if (m_cachedSession && m_cachedSession->m_uri == uri && m_cachedSession->m_proxyURL == settings->proxy()) { // reuse existing session with new settings pointer m_cachedSession->m_settings = settings; return m_cachedSession; } // create new session m_cachedSession.reset(new Session(settings)); return m_cachedSession; } int Session::getCredentials(void *userdata, const char *realm, int attempt, char *username, char *password) throw() { try { Session *session = static_cast(userdata); boost::shared_ptr authProvider = session->m_settings->getAuthProvider(); if (authProvider && authProvider->methodIsSupported(AuthProvider::AUTH_METHOD_OAUTH2)) { // We have to fail here because we cannot provide neon // with a username/password combination. Instead we rely // on the "retry request" mechanism to resend the request // with a fresh token. SE_LOG_DEBUG(NULL, "giving up on request, try again with new OAuth2 token"); return 1; } else if (!attempt) { // try again with credentials std::string user, pw; session->m_settings->getCredentials(realm, user, pw); SyncEvo::Strncpy(username, user.c_str(), NE_ABUFSIZ); SyncEvo::Strncpy(password, pw.c_str(), NE_ABUFSIZ); session->m_credentialsSent = true; SE_LOG_DEBUG(NULL, "retry request with credentials"); return 0; } else { // give up return 1; } } catch (...) { Exception::handle(); SE_LOG_ERROR(NULL, "no credentials for %s", realm); return 1; } } void Session::forceAuthorization(const boost::shared_ptr &authProvider) { m_forceAuthorizationOnce = true; m_authProvider = authProvider; } void Session::preSendHook(ne_request *req, void *userdata, ne_buffer *header) throw() { try { static_cast(userdata)->preSend(req, header); } catch (...) { Exception::handle(); } } void Session::preSend(ne_request *req, ne_buffer *header) { // sanity check: startOperation must have been called if (m_operation.empty()) { SE_THROW("internal error: startOperation() not called"); } // Only do this once when using normal username/password. // Always do it when using OAuth2. bool useOAuth2 = m_authProvider && m_authProvider->methodIsSupported(AuthProvider::AUTH_METHOD_OAUTH2); if (m_forceAuthorizationOnce || useOAuth2) { m_forceAuthorizationOnce = false; bool haveAuthorizationHeader = boost::starts_with(header->data, "Authorization:") || strstr(header->data, "\nAuthorization:"); if (useOAuth2) { if (haveAuthorizationHeader) { SE_THROW("internal error: already have Authorization header when about to add OAuth2"); } // Token was obtained by Session::run(). SE_LOG_DEBUG(NULL, "using OAuth2 token '%s' to authenticate", m_oauth2Bearer.c_str()); m_credentialsSent = true; // SmartPtr blob(ne_base64((const unsigned char *)m_oauth2Bearer.c_str(), m_oauth2Bearer.size())); ne_buffer_concat(header, "Authorization: Bearer ", m_oauth2Bearer.c_str() /* blob.get() */, "\r\n", NULL); } else if (m_uri.m_scheme == "https") { // append "Authorization: Basic" header if not present already if (!haveAuthorizationHeader) { Credentials creds = m_authProvider->getCredentials(); std::string credentials = creds.m_username + ":" + creds.m_password; SmartPtr blob(ne_base64((const unsigned char *)credentials.c_str(), credentials.size())); ne_buffer_concat(header, "Authorization: Basic ", blob.get(), "\r\n", NULL); } // check for acceptance of credentials later m_credentialsSent = true; SE_LOG_DEBUG(NULL, "forced sending credentials"); } else { SE_LOG_DEBUG(NULL, "skipping forced sending credentials because not using https"); } } } int Session::sslVerify(void *userdata, int failures, const ne_ssl_certificate *cert) throw() { try { Session *session = static_cast(userdata); static const Flag descr[] = { { NE_SSL_NOTYETVALID, "certificate not yet valid" }, { NE_SSL_EXPIRED, "certificate has expired" }, { NE_SSL_IDMISMATCH, "hostname mismatch" }, { NE_SSL_UNTRUSTED, "untrusted certificate" }, { 0, NULL } }; SE_LOG_DEBUG(NULL, "%s: SSL verification problem: %s", session->getURL().c_str(), Flags2String(failures, descr).c_str()); if (!session->m_settings->verifySSLCertificate()) { SE_LOG_DEBUG(NULL, "ignoring bad certificate"); return 0; } if (failures == NE_SSL_IDMISMATCH && !session->m_settings->verifySSLHost()) { SE_LOG_DEBUG(NULL, "ignoring hostname mismatch"); return 0; } return 1; } catch (...) { Exception::handle(); return 1; } } #ifdef HAVE_LIBNEON_OPTIONS unsigned int Session::options(const std::string &path) { unsigned int caps; checkError(ne_options2(m_session, path.c_str(), &caps)); return caps; } #endif // HAVE_LIBNEON_OPTIONS class PropFindDeleter { public: void operator () (ne_propfind_handler *handler) { if (handler) { ne_propfind_destroy(handler); } } }; void Session::propfindURI(const std::string &path, int depth, const ne_propname *props, const PropfindURICallback_t &callback, const Timespec &deadline) { startOperation("PROPFIND", deadline); retry: boost::shared_ptr handler; int error; checkAuthorization(); handler = boost::shared_ptr(ne_propfind_create(m_session, path.c_str(), depth), PropFindDeleter()); if (props != NULL) { error = ne_propfind_named(handler.get(), props, propsResult, const_cast(static_cast(&callback))); } else { error = ne_propfind_allprop(handler.get(), propsResult, const_cast(static_cast(&callback))); } // remain valid as long as "handler" is valid ne_request *req = ne_propfind_get_request(handler.get()); const ne_status *status = ne_get_status(req); const char *tmp = ne_get_response_header(req, "Location"); std::string location(tmp ? tmp : ""); if (!checkError(error, status->code, status, location)) { goto retry; } } void Session::propsResult(void *userdata, const ne_uri *uri, const ne_prop_result_set *results) throw() { try { PropfindURICallback_t *callback = static_cast(userdata); (*callback)(URI::fromNeon(*uri), results); } catch (...) { Exception::handle(); } } void Session::propfindProp(const std::string &path, int depth, const ne_propname *props, const PropfindPropCallback_t &callback, const Timespec &deadline) { propfindURI(path, depth, props, boost::bind(&Session::propsIterate, _1, _2, boost::cref(callback)), deadline); } void Session::propsIterate(const URI &uri, const ne_prop_result_set *results, const PropfindPropCallback_t &callback) { PropIteratorUserdata_t data(&uri, &callback); ne_propset_iterate(results, propIterator, &data); } int Session::propIterator(void *userdata, const ne_propname *pname, const char *value, const ne_status *status) throw() { try { const PropIteratorUserdata_t *data = static_cast(userdata); (*data->second)(*data->first, pname, value, status); return 0; } catch (...) { Exception::handle(); return 1; // abort iterating } } void Session::startOperation(const string &operation, const Timespec &deadline) { SE_LOG_DEBUG(NULL, "starting %s, credentials %s, %s", operation.c_str(), m_settings->getCredentialsOkay() ? "okay" : "unverified", deadline ? StringPrintf("deadline in %.1lfs", (deadline - Timespec::monotonic()).duration()).c_str() : "no deadline"); // now is a good time to check for user abort SuspendFlags::getSuspendFlags().checkForNormal(); // remember current operation attributes m_operation = operation; m_deadline = deadline; // no credentials set yet for next request m_credentialsSent = false; // first attempt at request m_attempt = 0; } void Session::flush() { if (m_debugging && LogRedirect::redirectingStderr()) { // flush stderr and wait a bit: this might help to get // the redirected output via LogRedirect fflush(stderr); Sleep(0.001); } } bool Session::checkError(int error, int code, const ne_status *status, const string &location, const std::set *expectedCodes) { flush(); SuspendFlags &s = SuspendFlags::getSuspendFlags(); // unset operation, set it again only if the same operation is going to be retried string operation = m_operation; m_operation = ""; // determine error description, may be made more specific below string descr; if (code) { descr = StringPrintf("%s: Neon error code %d, HTTP status %d: %s", operation.c_str(), error, code, ne_get_error(m_session)); } else { descr = StringPrintf("%s: Neon error code %d, no HTTP status: %s", operation.c_str(), error, ne_get_error(m_session)); } // true for specific errors which might go away after a retry bool retry = false; // detect redirect if ((error == NE_ERROR || error == NE_OK) && (code >= 300 && code <= 399)) { // special case Google: detect redirect to temporary error page // and retry; same for redirect to login page if (boost::starts_with(location, "http://www.google.com/googlecalendar/unavailable.html") || boost::starts_with(location, "https://www.google.com/googlecalendar/unavailable.html") || boost::starts_with(location, "https://accounts.google.com/ServiceLogin")) { retry = true; } else { SE_THROW_EXCEPTION_2(RedirectException, StringPrintf("%s: %d status: redirected to %s", operation.c_str(), code, location.c_str()), code, location); } } switch (error) { case NE_OK: // request itself completed, but might still have resulted in bad status if (expectedCodes && expectedCodes->find(code) != expectedCodes->end()) { // return to caller immediately as if we had succeeded, // without throwing an exception and without retrying return true; } if (code && (code < 200 || code >= 300)) { if (status) { descr = StringPrintf("%s: bad HTTP status: %s", operation.c_str(), Status2String(status).c_str()); } else { descr = StringPrintf("%s: bad HTTP status: %d", operation.c_str(), code); } if (code >= 500 && code <= 599) { // potentially temporary server failure, may try again retry = true; } } else { // all fine, no retry necessary: clean up // remember completion time of request m_lastRequestEnd = Timespec::monotonic(); // assume that credentials were valid, if sent if (m_credentialsSent) { SE_LOG_DEBUG(NULL, "credentials accepted"); m_settings->setCredentialsOkay(true); } m_oauthTokenRejections = 0; return true; } break; case NE_AUTH: { // Retry OAuth2-based request if we still have a valid token. bool useOAuth2 = m_authProvider && m_authProvider->methodIsSupported(AuthProvider::AUTH_METHOD_OAUTH2); if (useOAuth2) { // Try again with new token? Need to restore the counter, // because it is relevant for getOAuth2Bearer() in preSend(). if (m_oauthTokenRejections < 2) { if (!m_oauth2Bearer.empty() && m_credentialsSent) { SE_LOG_DEBUG(NULL, "discarding used and rejected OAuth2 token '%s'", m_oauth2Bearer.c_str()); m_oauthTokenRejections++; m_oauth2Bearer.clear(); } else { SE_LOG_DEBUG(NULL, "OAuth2 token '%s' not used?!", m_oauth2Bearer.c_str()); } retry = true; SE_LOG_DEBUG(NULL, "OAuth2 retry after %d failed tokens", m_oauthTokenRejections); } else { SE_LOG_DEBUG(NULL, "too many failed OAuth2 tokens, giving up"); } } // tell caller what kind of transport error occurred code = STATUS_UNAUTHORIZED; descr = StringPrintf("%s: Neon error code %d = NE_AUTH, HTTP status %d: %s", operation.c_str(), error, code, ne_get_error(m_session)); break; } case NE_ERROR: if (code) { descr = StringPrintf("%s: Neon error code %d: %s", operation.c_str(), error, ne_get_error(m_session)); if (code >= 500 && code <= 599) { // potentially temporary server failure, may try again retry = true; } } else if (descr.find("Secure connection truncated") != descr.npos || descr.find("decryption failed or bad record mac") != descr.npos) { // occasionally seen with Google server; let's retry // For example: "Could not read status line: SSL error: decryption failed or bad record mac" retry = true; } break; case NE_LOOKUP: case NE_TIMEOUT: case NE_CONNECT: retry = true; break; } if (code == 401) { if (m_settings->getCredentialsOkay()) { SE_LOG_DEBUG(NULL, "credential error due to throttling (?), retry"); retry = true; } else { // give up without retrying SE_LOG_DEBUG(NULL, "credential error, no success with them before => report it"); } } SE_LOG_DEBUG(NULL, "%s, %s", descr.c_str(), retry ? "might retry" : "must not retry"); if (retry) { m_attempt++; if (!m_deadline) { SE_LOG_DEBUG(NULL, "retrying not allowed for %s (no deadline)", operation.c_str()); } else { Timespec now = Timespec::monotonic(); if (now < m_deadline) { int retrySeconds = m_settings->retrySeconds(); if (retrySeconds >= 0) { Timespec last = m_lastRequestEnd; Timespec now = Timespec::monotonic(); if (!last) { last = now; } int delay = retrySeconds * (1 << (m_attempt - 1)); Timespec next = last + delay; if (next > m_deadline) { // no point in waiting (potentially much) until after our // deadline, do final attempt at that time next = m_deadline; } if (next > now) { double duration = (next - now).duration(); SE_LOG_DEBUG(NULL, "retry %s in %.1lfs, attempt #%d", operation.c_str(), duration, m_attempt); // Inform the user, because this will take a // while and we don't want to give the // impression of being stuck. SE_LOG_INFO(NULL, "operation temporarily (?) failed, going to retry in %.1lfs before giving up in %.1lfs: %s", duration, (m_deadline - now).duration(), descr.c_str()); Sleep(duration); } else { SE_LOG_DEBUG(NULL, "retry %s immediately (due already), attempt #%d", operation.c_str(), m_attempt); } } else { SE_LOG_DEBUG(NULL, "retry %s immediately (retry interval <= 0), attempt #%d", operation.c_str(), m_attempt); } // try same operation again? if (s.getState() == SuspendFlags::NORMAL) { m_operation = operation; return false; } } else { SE_LOG_DEBUG(NULL, "retry %s would exceed deadline, bailing out", m_operation.c_str()); } } } if (code == 401) { // fatal credential error, remember that SE_LOG_DEBUG(NULL, "credentials rejected"); m_settings->setCredentialsOkay(false); } if (code) { // copy error code into exception SE_THROW_EXCEPTION_STATUS(TransportStatusException, descr, SyncMLStatus(code)); } else { SE_THROW_EXCEPTION(TransportException, descr); } } XMLParser::XMLParser() { m_parser = ne_xml_create(); } XMLParser::~XMLParser() { ne_xml_destroy(m_parser); } XMLParser &XMLParser::pushHandler(const StartCB_t &start, const DataCB_t &data, const EndCB_t &end) { m_stack.push_back(Callbacks(start, data, end)); Callbacks &cb = m_stack.back(); ne_xml_push_handler(m_parser, startCB, dataCB, endCB, &cb); return *this; } int XMLParser::startCB(void *userdata, int parent, const char *nspace, const char *name, const char **atts) { Callbacks *cb = static_cast(userdata); try { return cb->m_start(parent, nspace, name, atts); } catch (...) { Exception::handle(); SE_LOG_ERROR(NULL, "startCB %s %s failed", nspace, name); return -1; } } int XMLParser::dataCB(void *userdata, int state, const char *cdata, size_t len) { Callbacks *cb = static_cast(userdata); try { return cb->m_data ? cb->m_data(state, cdata, len) : 0; } catch (...) { Exception::handle(); SE_LOG_ERROR(NULL, "dataCB failed"); return -1; } } int XMLParser::endCB(void *userdata, int state, const char *nspace, const char *name) { Callbacks *cb = static_cast(userdata); try { return cb->m_end ? cb->m_end(state, nspace, name) : 0; } catch (...) { Exception::handle(); SE_LOG_ERROR(NULL, "endCB %s %s failed", nspace, name); return -1; } } int XMLParser::accept(const std::string &nspaceExpected, const std::string &nameExpected, const char *nspace, const char *name) { if (nspace && nspaceExpected == nspace && name && nameExpected == name) { return 1; } else { return 0; } } int XMLParser::append(std::string &buffer, const char *data, size_t len) { buffer.append(data, len); return 0; } int XMLParser::reset(std::string &buffer) { buffer.clear(); return 0; } void XMLParser::initReportParser(const ResponseEndCB_t &responseEnd) { pushHandler(boost::bind(Neon::XMLParser::accept, "DAV:", "multistatus", _2, _3)); pushHandler(boost::bind(Neon::XMLParser::accept, "DAV:", "response", _2, _3), Neon::XMLParser::DataCB_t(), boost::bind(&Neon::XMLParser::doResponseEnd, this, responseEnd)); pushHandler(boost::bind(Neon::XMLParser::accept, "DAV:", "href", _2, _3), boost::bind(Neon::XMLParser::append, boost::ref(m_href), _2, _3)); pushHandler(boost::bind(Neon::XMLParser::accept, "DAV:", "propstat", _2, _3)); pushHandler(boost::bind(Neon::XMLParser::accept, "DAV:", "status", _2, _3) /* check status? */); pushHandler(boost::bind(Neon::XMLParser::accept, "DAV:", "prop", _2, _3)); pushHandler(boost::bind(Neon::XMLParser::accept, "DAV:", "getetag", _2, _3), boost::bind(Neon::XMLParser::append, boost::ref(m_etag), _2, _3)); } Request::Request(Session &session, const std::string &method, const std::string &path, const std::string &body, std::string &result) : m_method(method), m_session(session), m_result(&result), m_parser(NULL) { m_req = ne_request_create(session.getSession(), m_method.c_str(), path.c_str()); ne_set_request_body_buffer(m_req, body.c_str(), body.size()); } Request::Request(Session &session, const std::string &method, const std::string &path, const std::string &body, XMLParser &parser) : m_method(method), m_session(session), m_result(NULL), m_parser(&parser) { m_req = ne_request_create(session.getSession(), m_method.c_str(), path.c_str()); ne_set_request_body_buffer(m_req, body.c_str(), body.size()); } Request::~Request() { ne_request_destroy(m_req); } #ifdef NEON_COMPATIBILITY /** * wrapper needed to allow lazy resolution of the ne_accept_2xx() function when needed * instead of when loaded */ static int ne_accept_2xx(void *userdata, ne_request *req, const ne_status *st) { return ::ne_accept_2xx(userdata, req, st); } #endif void Session::checkAuthorization() { bool useOAuth2 = m_authProvider && m_authProvider->methodIsSupported(AuthProvider::AUTH_METHOD_OAUTH2); if (useOAuth2 && m_oauth2Bearer.empty()) { // Count the number of times we asked for new tokens. This helps // the provider determine whether the token that it returns are valid. try { m_oauth2Bearer = m_authProvider->getOAuth2Bearer(m_oauthTokenRejections); SE_LOG_DEBUG(NULL, "got new OAuth2 token '%s' for next request", m_oauth2Bearer.c_str()); } catch (...) { std::string explanation; Exception::handle(explanation); // Treat all errors as fatal authentication errors. // Our caller will abort immediately. SE_THROW_EXCEPTION_STATUS(FatalException, StringPrintf("logging into remote service failed: %s", explanation.c_str()), STATUS_FORBIDDEN); } } } bool Session::run(Request &request, const std::set *expectedCodes) { int error; // Check for authorization while we still can. checkAuthorization(); std::string *result = request.getResult(); ne_request *req = request.getRequest(); if (result) { result->clear(); ne_add_response_body_reader(req, ne_accept_2xx, Request::addResultData, &request); error = ne_request_dispatch(req); } else { error = ne_xml_dispatch_request(req, request.getParser()->get()); } return checkError(error, request.getStatus()->code, request.getStatus(), request.getResponseHeader("Location"), expectedCodes); } int Request::addResultData(void *userdata, const char *buf, size_t len) { Request *me = static_cast(userdata); me->m_result->append(buf, len); return 0; } } SE_END_CXX #endif // ENABLE_DAV syncevolution_1.4/src/backends/webdav/NeonCXX.h000066400000000000000000000471541230021373600216350ustar00rootroot00000000000000/* * Copyright (C) 2010 Patrick Ohly */ /** * Simplifies usage of neon in C++ by wrapping some calls in C++ * classes. Includes all neon header files relevant for the backend. */ #ifndef INCL_NEONCXX #define INCL_NEONCXX #include #include #include #include #include #include #include // TODO: remove this again using namespace std; #include #include #include #include SE_BEGIN_CXX class AuthProvider; namespace Neon { #if 0 } #endif /** comma separated list of features supported by libneon in use */ std::string features(); class Request; /** * Throwing this will stop all further attempts to use the * remote service. */ class FatalException : public StatusException { public: FatalException(const std::string &file, int line, const std::string &what, SyncMLStatus status) : StatusException(file, line, what, status) {} }; class Settings { public: /** * base URL for WebDAV service */ virtual std::string getURL() = 0; /** * host name must match for SSL? */ virtual bool verifySSLHost() = 0; /** * SSL certificate must be valid? */ virtual bool verifySSLCertificate() = 0; /** * proxy URL, empty for system default */ virtual std::string proxy() = 0; /** * fill in username and password for specified realm (URL?), * throw error if not available */ virtual void getCredentials(const std::string &realm, std::string &username, std::string &password) = 0; /** * Grant access to AuthProvider. In addition to plain username/password * in getCredentials, this one here might also be used for OAuth2. */ virtual boost::shared_ptr getAuthProvider() = 0; /** * Google returns a 401 error even if the credentials * are valid. It seems to use that to throttle request * rates. This read/write setting remembers whether the * credentials were used successfully in the past, * in which case we try harder to get a failed request * executed. Otherwise we give up immediately. */ virtual bool getCredentialsOkay() = 0; virtual void setCredentialsOkay(bool okay) = 0; /** * standard SyncEvolution log level, see * Session::Session() how that is mapped to neon debugging */ virtual int logLevel() = 0; /** * if true, then manipulate SEQUENCE and LAST-MODIFIED properties * so that Google CalDAV server accepts updates */ virtual bool googleUpdateHack() const = 0; /** * if true, then avoid RECURRENCE-ID in sub items without * corresponding parent by replacing it with * X-SYNCEVOLUTION-RECURRENCE-ID */ virtual bool googleChildHack() const = 0; /** * if true, then check whether server has added an unwanted alarm * and resend to get rid of it */ virtual bool googleAlarmHack() const = 0; /** * duration in seconds after which communication with a server * fails with a timeout error; <= 0 picks a large default value */ virtual int timeoutSeconds() const = 0; /** * for network operations which fail before reaching timeoutSeconds() * and can/should be retried: try again if > 0 */ virtual int retrySeconds() const = 0; /** * use this to create a boost_shared pointer for a * Settings instance which needs to be freed differently */ struct NullDeleter { void operator()(Settings *) const {} }; }; struct URI { std::string m_scheme; std::string m_host; std::string m_userinfo; unsigned int m_port; std::string m_path; std::string m_query; std::string m_fragment; URI() : m_port(0) {} /** * Split URL into parts. Throws TransportAgentException on * invalid url. Port will be set to default for scheme if not set * in URL. Path is normalized. * * @param collection set to true if the normalized path is for * a collection and shall have a trailing sl */ static URI parse(const std::string &url, bool collection=false); static URI fromNeon(const ne_uri &other, bool collection=false); /** * produce new URI from current path and new one (may be absolute * and relative) */ URI resolve(const std::string &path) const; /** compose URL from parts */ std::string toURL() const; /** * URL-escape string */ static std::string escape(const std::string &text); static std::string unescape(const std::string &text); /** * Removes differences caused by escaping different characters. * Appends slash if path is a collection (or meant to be one) and * doesn't have a trailing slash. Removes double slashes. * * @param path an absolute path (leading slash) */ static std::string normalizePath(const std::string &path, bool collection); bool operator == (const URI &other) { return m_scheme == other.m_scheme && m_host == other.m_host && m_userinfo == other.m_userinfo && m_port == other.m_port && m_path == other.m_path && m_query == other.m_query && m_fragment == other.m_fragment; } bool empty() { return m_scheme.empty() && m_host.empty() && m_userinfo.empty() && m_port == 0 && m_path.empty() && m_query.empty() && m_fragment.empty(); } }; /** produce debug string for status, which may be NULL */ std::string Status2String(const ne_status *status); /** * Wraps all session related activities. * Throws transport errors for fatal problems. */ class Session { /** * @param settings must provide information about settings on demand */ Session(const boost::shared_ptr &settings); static boost::shared_ptr m_cachedSession; bool m_forceAuthorizationOnce; boost::shared_ptr m_authProvider; /** * Count how often a request was sent with credentials. * If the request succeeds, we assume that the credentials * were okay. A bit fuzzy because forcing authorization * might succeed despite invalid credentials if the * server doesn't check them. */ bool m_credentialsSent; /** * Count the number of consecutive times that an OAuth2 token * failed to get accepted. This can happen when the current one * expired and needs to be refreshed or we need re-authorization * by the user. */ int m_oauthTokenRejections; /** * Cached token for OAuth2. Obtained before starting the request in run(), * used in preSend(), invalidated when it caused an authentication error * in checkError(). */ std::string m_oauth2Bearer; /** * current operation; used for debugging output */ string m_operation; /** * current deadline for operation */ Timespec m_deadline; public: /** * Create or reuse Session instance. * * One Session instance is kept alive throughout the life of the process, * to reuse proxy information (libproxy has a considerably delay during * initialization) and HTTP connection/authentication. */ static boost::shared_ptr create(const boost::shared_ptr &settings); ~Session(); #ifdef HAVE_LIBNEON_OPTIONS /** ne_options2() for a specific path*/ unsigned int options(const std::string &path); #endif /** * called with URI and complete result set; exceptions are logged, but ignored */ typedef boost::function PropfindURICallback_t; /** * called with URI and specific property, value string may be NULL (error case); * exceptions are logged and abort iterating over properties (but not URIs) */ typedef boost::function PropfindPropCallback_t; /** ne_simple_propfind(): invoke callback for each URI */ void propfindURI(const std::string &path, int depth, const ne_propname *props, const PropfindURICallback_t &callback, const Timespec &deadline); /** * ne_simple_propfind(): invoke callback for each property of each URI * @param deadline stop resending after that point in time, * zero disables resending * @param retrySeconds number of seconds to wait between resending, * must not be negative */ void propfindProp(const std::string &path, int depth, const ne_propname *props, const PropfindPropCallback_t &callback, const Timespec &deadline); /** URL which is in use */ std::string getURL() const { return m_uri.toURL(); } /** same as getURL() split into parts */ const URI &getURI() const { return m_uri; } /** * to be called *once* before executing a request * or retrying it * * call sequence is this: * - startOperation() * - repeat until success or final failure: create request, run(), checkError() * * @param operation internal descriptor for debugging (for example, PROPFIND) * @param deadline time at which the operation must be completed, otherwise it'll be considered failed; * empty if the operation is only meant to be attempted once */ void startOperation(const string &operation, const Timespec &deadline); /** * Run one attempt to execute the request. May be called multiple times. * * Uses checkError() underneath to detect fatal errors and throw * exceptions. * * @return result of Session::checkError() */ bool run(Request &request, const std::set *expectedCodes); /** * to be called after each operation which might have produced debugging output by neon; * automatically called by checkError() */ void flush(); ne_session *getSession() const { return m_session; } /** * Force next request in this session to have Basic authorization * (when username/password are provided by AuthProvider) or all * requests to use OAuth2 authentication. */ void forceAuthorization(const boost::shared_ptr &authProvider); private: boost::shared_ptr m_settings; bool m_debugging; ne_session *m_session; URI m_uri; std::string m_proxyURL; /** time when last successul request completed, maintained by checkError() */ Timespec m_lastRequestEnd; /** number of times a request was sent, maintained by startOperation(), the credentials callback, and checkError() */ int m_attempt; void checkAuthorization(); /** * throw error if error code indicates failure; * pass additional status code from a request whenever possible * * @param error return code from Neon API call * @param code HTTP status code * @param status optional ne_status pointer, non-NULL for all requests * @param location optional "Location" header value * @param expectedCodes set of codes which are normal and must not result * in retrying or an exception (returns true, as if the * operation succeeded) * * @return true for success, false if retry needed (only if deadline not empty); * errors reported via exceptions */ bool checkError(int error, int code = 0, const ne_status *status = NULL, const string &location = "", const std::set *expectedCodes = NULL); /** ne_set_server_auth() callback */ static int getCredentials(void *userdata, const char *realm, int attempt, char *username, char *password) throw(); /** ne_ssl_set_verify() callback */ static int sslVerify(void *userdata, int failures, const ne_ssl_certificate *cert) throw(); /** ne_props_result callback which invokes a PropfindURICallback_t as user data */ static void propsResult(void *userdata, const ne_uri *uri, const ne_prop_result_set *results) throw(); /** iterate over properties in result set */ static void propsIterate(const URI &uri, const ne_prop_result_set *results, const PropfindPropCallback_t &callback); /** ne_propset_iterator callback which invokes pair */ static int propIterator(void *userdata, const ne_propname *pname, const char *value, const ne_status *status) throw(); // use pointers here, g++ 4.2.3 has issues with references (which was used before) typedef std::pair PropIteratorUserdata_t; /** Neon callback for preSend() */ static void preSendHook(ne_request *req, void *userdata, ne_buffer *header) throw(); /** implements forced Basic authentication, if requested */ void preSend(ne_request *req, ne_buffer *header); }; /** * encapsulates a ne_xml_parser */ class XMLParser { public: XMLParser(); ~XMLParser(); ne_xml_parser *get() const { return m_parser; } /** * See ne_xml_startelm_cb: * arguments are parent state, namespace, name, attributes (NULL terminated) * @return < 0 abort, 0 decline, > 0 accept */ typedef boost::function StartCB_t; /** * See ne_xml_cdata_cb: * arguments are state of element, data and data len * May be NULL. * @return != 0 to abort */ typedef boost::function DataCB_t; /** * See ne_xml_endelm_cb: * arguments are state of element, namespace, name * May be NULL. * @return != 0 to abort */ typedef boost::function EndCB_t; /** * add new handler, see ne_xml_push_handler() */ XMLParser &pushHandler(const StartCB_t &start, const DataCB_t &data = DataCB_t(), const EndCB_t &end = EndCB_t()); /** * StartCB_t: accepts a new element if namespace and name match */ static int accept(const std::string &nspaceExpected, const std::string &nameExpected, const char *nspace, const char *name); /** * DataCB_t: append to std::string */ static int append(std::string &buffer, const char *data, size_t len); /** * EndCB_t: clear std::string */ static int reset(std::string &buffer); /** * called once a response is completely parse * * @param href the path for which the response was sent * @param etag it's etag, empty if not requested or available */ typedef boost::function ResponseEndCB_t; /** * Setup parser for handling REPORT result. * Already deals with href and etag, caching it * for each response and passing it to the "response * complete" callback. * * Caller still needs to push a handler for * "urn:ietf:params:xml:ns:caldav", "calendar-data", * or any other elements that it wants to know about. * * @param responseEnd called at the end of processing each response; * this is the only time when all relevant * parts of the response are guaranteed to have been seen; * when expecting only one response, the callback * is not needed */ void initReportParser(const ResponseEndCB_t &responseEnd = ResponseEndCB_t()); private: ne_xml_parser *m_parser; struct Callbacks { Callbacks(const StartCB_t &start, const DataCB_t &data = DataCB_t(), const EndCB_t &end = EndCB_t()) : m_start(start), m_data(data), m_end(end) {} StartCB_t m_start; DataCB_t m_data; EndCB_t m_end; }; std::list m_stack; /** buffers for initReportParser() */ std::string m_href, m_etag; int doResponseEnd(const ResponseEndCB_t &responseEnd) { if (responseEnd) { responseEnd(m_href, m_etag); } // clean up for next response m_href.clear(); m_etag.clear(); return 0; } static int startCB(void *userdata, int parent, const char *nspace, const char *name, const char **atts); static int dataCB(void *userdata, int state, const char *cdata, size_t len); static int endCB(void *userdata, int state, const char *nspace, const char *name); }; /** * encapsulates a ne_request, with std::string as read and write buffer */ class Request { public: /** * read and write buffers owned by caller */ Request(Session &session, const std::string &method, const std::string &path, const std::string &body, std::string &result); /** * read from buffer (owned by caller!) and * parse result as XML */ Request(Session &session, const std::string &method, const std::string &path, const std::string &body, XMLParser &parser); ~Request(); void setFlag(ne_request_flag flag, int value) { ne_set_request_flag(m_req, flag, value); } void addHeader(const std::string &name, const std::string &value) { ne_add_request_header(m_req, name.c_str(), value.c_str()); } /** * Execute the request. See Session::run(). */ bool run(const std::set *expectedCodes = NULL) { return m_session.run(*this, expectedCodes); } std::string getResponseHeader(const std::string &name) { const char *value = ne_get_response_header(m_req, name.c_str()); return value ? value : ""; } int getStatusCode() { return ne_get_status(m_req)->code; } const ne_status *getStatus() { return ne_get_status(m_req); } ne_request *getRequest() const { return m_req; } std::string *getResult() const { return m_result; } XMLParser *getParser() const { return m_parser; } /** ne_block_reader implementation */ static int addResultData(void *userdata, const char *buf, size_t len); private: // buffers for string (copied by ne_request_create(), // but due to a bug in neon, our method string is still used // for credentials) std::string m_method; Session &m_session; ne_request *m_req; std::string *m_result; XMLParser *m_parser; }; /** thrown for 301 HTTP status */ class RedirectException : public TransportException { const int m_code; const std::string m_url; public: RedirectException(const std::string &file, int line, const std::string &what, int code, const std::string &url) : TransportException(file, line, what), m_code(code), m_url(url) {} ~RedirectException() throw() {} /** returns exact HTTP status code (301, 302, ...) */ int getCode() const { return m_code; } /** returns URL to where the request was redirected */ std::string getLocation() const { return m_url; } }; } SE_END_CXX #endif // INCL_NEONCXX syncevolution_1.4/src/backends/webdav/README000066400000000000000000000327651230021373600210640ustar00rootroot00000000000000Usage ===== The "webdav" backend provides sync sources for CalDAV and CardDAV. They can be selected with backend=CalDAV resp. backend=CardDAV. In contrast to other backends, these sources need additional information about a peer: * syncURL: Specifies URL of calendar or contacts collection. %u gets replaced with the username. A ?SyncEvolution=,,... parameter provides further control. Currently implemented keywords: UpdateHack = work around Google's REV CalDAV quirks ChildHack = avoid storing calendar items which contain VEVENTs with RECURRENCE-ID and no corresponding VEVENT with RRULE; Google Calendar can store such items but reading them fails AlarmHack = when storing a VEVENT without VALARM, store it multiple times, because otherwise Google Calendar adds a default alarm Google = enables all hacks needed for Google Specifying a syncURL is optional. If not given, then DNS SRV lookups based on the domain name in the username are used to find the right host. In theory, .well-known URIs are then used to find the right path on that host, but in practice none of the peers that were tested (Google and Yahoo) seemed to support that. Therefore the code contains a fallback for well-known Yahoo paths when .well-known URIs fail. * username/password: credentials, if available use the email address (needed for auto discovery of CalDAV or CardDAV server) They also use: * loglevel: >= 3: basic logging of HTTP traffic >= 4: also logging of HTTP body >= 5: also SSL and WebDAV lock handling >= 6: detailed information about XML parsing >= 11: plaintext HTTP authentication The recommended way of using the CalDAV and CardDAV backends is to configure a context with the "target-config" peer inside it. Such a context then can be used as part of a local sync with other sources. See the Google section below for examples. Multiple calendars/address books ================================ This is not yet implemented. The code always ends up picking the default collection. Eventually the goal is to enable: 1. listing of available collections 2. selecting them via the "database" property Only the second point is currently implemented. The "database" property now can hold the final URL of the collection (aka database) on the WebDAV server which is used for the source. Setting it skips the entire auto-discovery process, which makes access quite a bit faster and more reliable. Because most UIs won't know initially how to find these URLs (listing them not supported by the core SyncEvolution) and/or won't have the necessary user dialogs, the property is set automatically after a successful sync. This avoids the accidental switching between databases when the user adds or removes databases on the server (which can lead to different results of the auto discovery). There's still no guarantee that the database picked by default is the "right" one. That can only be solved in the UI because servers typically don't have a "default" or "personal" flag for their collections. Concurrency =========== The original plan was to lock the server's collection while manipulating it during a sync. But Google Calendar does not support locks, so that was not pursued further. An alternative would be to run a sync without locking and detect concurrent modifications, but that is not implemented yet. Therefore it is possible (although not likely) that changes get lost when some other client changes an item after SyncEvolution started a sync and before that same item gets modified by SyncEvolution as part of that sync. Change tracking itself copes with changes made while a sync runs. They'll be synchronized as part of the next sync. Google ====== Google supports CalDAV access to its calendars. Several quirks were encountered, which SyncEvolution tries to work around as good as possible. Use syncURL=https://www.google.com/calendar/dav/%u/user/?SyncEvolution=Google to enable these workarounds. # create context for accessing Google CalDAV server, # not visible in sync UI (consumerReady = 0): syncevolution --configure \ --template SyncEvolution \ backend=CalDAV \ syncURL=https://www.google.com/calendar/dav/%u/user/?SyncEvolution=Google \ username= \ password= \ consumerReady=0 \ target-config@google calendar # list events in the server syncevolution --print-items target-config@google calendar # show the content of these events on stdout syncevolution --export - target-config@google calendar # set up synchronization, using "google-calendar" as peer name # because "google" is typically used for the SyncML-based # contact sync, which uses a different syncURL; # username/password can be left empty to use the credentials # configured above syncevolution --configure \ --template "SyncEvolution Client" \ syncURL=local://@google \ consumerReady=1 \ username= \ password= \ google-calendar calendar # run a sync for the first time (allow slow sync) syncevolution --sync slow google-calendar # normal sync, also possible via sync UI syncevolution google-calendar Yahoo ===== Yahoo supports both CalDAV and CardDAV. CalDAV is available at this time (beginning 2011) if and only if the user has switched to the "Yahoo! Calendar Beta". It is stable enough to be useful. CardDAV works in read/write mode, but has severe limitations: * Contacts sent by Yahoo encode special characters with HTML/XML entities, in one case even multiple times (\ -> \ -> &#92;). At the moment SyncEvolution unconditionally works around that by replacing these entities, which is wrong for servers working correctly. * Contacts are sent with UTF-8 encoding by Yahoo, but uploading such contacts leads to non-ASCII characters being replaced with a question mark on the server. Both of these issues can be reproduced with iOS 4 when configuring Yahoo as generic CardDAV server. Yahoo uses different hosts for CalDAV and CardDAV. Leave the syncURL empty and use the @yahoo email address as username to let the backend do the host lookup. # configure CalDAV and CardDAV with Yahoo, # with host lookup via the domain in the username syncevolution --configure \ --template SyncEvolution \ calendar/backend=CalDAV \ addressbook/backend=CardDAV \ syncURL= \ username= \ password= \ consumerReady=0 \ target-config@yahoo addressbook calendar # list items, use "--export -" to see content syncevolution --print-items target-config@yahoo calendar syncevolution --print-items target-config@yahoo addressbook # configure synchronization, with addressbook not enabled syncevolution --configure \ --template "SyncEvolution Client" \ addressbook/sync=disabled \ syncURL=local://@yahoo \ username= \ password= \ yahoo addressbook calendar Debugging/Troubleshooting ========================= Run the commands above with "loglevel=4" or higher. Set SYNCEVOLUTION_DEBUG=1 to see the full neon output directly (instead of having it filtered and, if present, written into a logfile) and run the operation locally with --daemon=no (instead of inside the syncevo-dbus-server). Example: SYNCEVOLUTION_DEBUG=1 syncevolution --daemon=no loglevel=4 --print-items @google calendar Each sync produces two log files, one for the main config ("google-calendar"), one for the target config ("target-config@google"). It is possible to choose different log levels: syncevolution --run \ loglevel=1 \ loglevel@google=11 \ google-calendar Occassionally Google Calendar gets confused and reports VEVENTs in response to a REPORT which then are not returned for a GET. Symptoms of that are: [ERROR @google] @google/calendar: event not found SyncEvolution tries hard to avoid such situations, but some calendars already seem to be in such a state to begin with. Deleting offending events via the web interface helps in such cases. When running under debugger, then beware that the WebDAV part runs inside a forked process. Use gdb's "set follow-fork-mode child" to enter the child. Make sure that you don't accidentally follow execution into one of the external shell commands: $ gdb ./syncevolution (gdb) set follow-fork-mode child (gdb) b SyncEvo::LocalTransportAgent::run Breakpoint 1 at 0x846755: file /home/pohly/syncevolution/syncevolution/src/syncevo/LocalTransportAgent.cpp, line 159. (gdb) run --daemon=no --run google-calendar Starting program: /home/pohly/work/syncevolution/src/syncevolution --daemon=no --run google-calendar [Thread debugging using libthread_db enabled] [INFO] @default/addressbook: inactive [INFO] @default/calendar+todo: inactive [INFO] @default/memo: inactive [INFO] @default/todo: inactive [New process 4655] [Thread debugging using libthread_db enabled] [Switching to Thread 0x7ffff7fc27e0 (LWP 4655)] Breakpoint 1, SyncEvo::LocalTransportAgent::run (this=0xc2a310) at /home/pohly/syncevolution/syncevolution/src/syncevo/LocalTransportAgent.cpp:159 159 const char *delay = getenv("SYNCEVOLUTION_LOCAL_CHILD_DELAY"); (gdb) set follow-fork-mode parent (gdb) b SyncEvo::CalDAVSource::readSubItem Breakpoint 2 at 0x69d47c: file /home/pohly/syncevolution/syncevolution/src/backends/webdav/CalDAVSource.cpp, line 382. (gdb) c Continuing. [...] Breakpoint 2, SyncEvo::CalDAVSource::readSubItem (this=0xec7430, davLUID=..., subid=..., item=...) at /home/pohly/syncevolution/syncevolution/src/backends/webdav/CalDAVSource.cpp:382 382 Event &event = loadItem(davLUID); (gdb) ... Alternatively, setting SYNCEVOLUTION_LOCAL_CHILD_DELAY= in the environment and the attaching to the second "syncevolution" process also works. Implementation ============== The neon library is used for HTTP/WebDAV. CalDAV and CardDAV queries can be done with it, although not supported directly. SyncEvolution's NeonCXX provides a C++ API on top of neon, with exceptions thrown for errors, Boost function pointers for callbacks and handles that track the underlying pointers. WebDAVSource contains the main WebDAV logic. It is customized via virtual methods by derived CalDAVSource and CardDAVSource whenever necessary. These sources use the resource name/ETag pair to detect changes. So for CardDAV retrieving the meta information is sufficient to detect changes. With CalDAV the situation is a bit more complex. A CalDAV item contains multiple VEVENTs, whereas SyncEvolution works on one VEVENT at a time during synchronization. The MapSyncSource uses a CalDAVSource and exposes calendar data with one VEVENT per item. MapSyncSource is generic enough to be used by sources which implement the SubSyncSource interface. The downside is that the number of VEVENTs inside each CalDAV item and their UID/RECURRENCE-ID properties need to be known before a sync. In the default mode of SyncEvolution (with automatic data backups before and after a sync), this additional information is collected as part of creating the backup, so no additional overhead is incurred, but when disabling backups, a sync still needs to download the calendar data first. In theory, CalDAV can report just the necessary UID/RECURRENCE-ID properties and SyncEvolution uses that, but in practice, servers return the full data. DNS SRV lookup is implemented using the syncevo-webdav-lookup utility script which calls "host", "adnshost" or "nslookup", depending on what is available. This approach avoids a hard dependency on a specfic resolver library and (for some of these) writing quite a bit of additional code. Testing ======= Testing via client-test is possible for the Client::Source tests. They cover importing/exporting of items and change tracking. client-test needs to be told in the CLIENT_TEST_WEBDAV env variable which CalDAV or CardDAV servers are available and the sync properties for them. It then creates a suitable "target-config@client-test-" config with "caldav" and/or "carddav" sources. Sync properties can be specified; if not given, the relevant ones must be set inside that config already. In addition to sync properties, the following properties can be used to influence the test setup itself: testconfig = name of a test config known to ClientTest::getTestData(), default is "eds_contact" (for CardDAV) and "eds_event" (for CalDAV) testcases = name of file holding data for testImport (eds_contact.vcf and eds_event.ics) The format of CLIENT_TEST_WEBDAV is: [caldav] [carddav] [] = ...; ... = sync property or [caldav/carddav/]/testcases Example: env PATH=backends/webdav/:$PATH \ "CLIENT_TEST_WEBDAV= yahoo caldav carddav username= password= caldav/testcases=testcases/yahoo_event.ics carddav/testcases=testcases/yahoo_contact.vcf ; google caldav username= password= testcases=testcases/google_event.ics syncURL=https://www.google.com/calendar/dav/%u/user/?SyncEvolution=Google" \ ./client-test \ Client::Source::yahoo_caldav::testOpen \ Client::Source::yahoo_carddav::testOpen \ Client::Source::google_caldav::testOpen Note that this assumes that the test is run inside the "src" directory without installing it, so the PATH must include the backend dir which contains syncevo-webdav-lookup. syncevolution_1.4/src/backends/webdav/WebDAVSource.cpp000066400000000000000000002270251230021373600231340ustar00rootroot00000000000000/* * Copyright (C) 2010 Intel Corporation */ #include "WebDAVSource.h" #include #include #include #include #include #include #include #include #include #include #include SE_BEGIN_CXX BoolConfigProperty &WebDAVCredentialsOkay() { static BoolConfigProperty okay("webDAVCredentialsOkay", "credentials were accepted before"); return okay; } #ifdef ENABLE_DAV /** * Retrieve settings from SyncConfig. * NULL pointer for config is allowed. */ class ContextSettings : public Neon::Settings { boost::shared_ptr m_context; SyncSourceConfig *m_sourceConfig; std::string m_url; /** do change tracking without relying on CTag */ bool m_noCTag; bool m_googleUpdateHack; bool m_googleChildHack; bool m_googleAlarmHack; // credentials were valid in the past: stored persistently in tracking node bool m_credentialsOkay; public: ContextSettings(const boost::shared_ptr &context, SyncSourceConfig *sourceConfig) : m_context(context), m_sourceConfig(sourceConfig), m_noCTag(false), m_googleUpdateHack(false), m_googleChildHack(false), m_googleAlarmHack(false), m_credentialsOkay(false) { std::string url; // check source config first if (m_sourceConfig) { url = m_sourceConfig->getDatabaseID(); if (url.find("%u") != url.npos) { std::string username = getUsername(); boost::replace_all(url, "%u", Neon::URI::escape(username)); } } // fall back to sync context if (url.empty() && m_context) { vector urls = m_context->getSyncURL(); if (!urls.empty()) { url = urls.front(); if (url.find("%u") != url.npos) { std::string username = getUsername(); boost::replace_all(url, "%u", Neon::URI::escape(username)); } } } // remember result and set flags setURL(url); // m_credentialsOkay: no corresponding setting when using // credentials + URL from source config, in which case we // never know that credentials should work (bad for Google, // with its temporary authentication errors) if (m_context) { boost::shared_ptr node = m_context->getNode(WebDAVCredentialsOkay()); m_credentialsOkay = WebDAVCredentialsOkay().getPropertyValue(*node); } } void setURL(const std::string &url) { initializeFlags(url); m_url = url; } virtual std::string getURL() { return m_url; } virtual bool verifySSLHost() { return !m_context || m_context->getSSLVerifyHost(); } virtual bool verifySSLCertificate() { return !m_context || m_context->getSSLVerifyServer(); } virtual std::string proxy() { if (!m_context || !m_context->getUseProxy()) { return ""; } else { return m_context->getProxyHost(); } } bool noCTag() const { return m_noCTag; } virtual bool googleUpdateHack() const { return m_googleUpdateHack; } virtual bool googleChildHack() const { return m_googleChildHack; } virtual bool googleAlarmHack() const { return m_googleChildHack; } virtual int timeoutSeconds() const { return m_context->getRetryDuration(); } virtual int retrySeconds() const { int seconds = m_context->getRetryInterval(); if (seconds >= 0) { seconds /= (120 / 5); // default: 2min => 5s } return seconds; } virtual void getCredentials(const std::string &realm, std::string &username, std::string &password); virtual boost::shared_ptr getAuthProvider(); std::string getUsername() { lookupAuthProvider(); return m_authProvider->getUsername(); } virtual bool getCredentialsOkay() { return m_credentialsOkay; } virtual void setCredentialsOkay(bool okay) { if (m_credentialsOkay != okay && m_context) { boost::shared_ptr node = m_context->getNode(WebDAVCredentialsOkay()); if (!node->isReadOnly()) { WebDAVCredentialsOkay().setProperty(*node, okay); node->flush(); } m_credentialsOkay = okay; } } virtual int logLevel() { return m_context ? m_context->getLogLevel().get() : Logger::instance().getLevel(); } private: void initializeFlags(const std::string &url); boost::shared_ptr m_authProvider; void lookupAuthProvider(); }; void ContextSettings::getCredentials(const std::string &realm, std::string &username, std::string &password) { lookupAuthProvider(); Credentials creds = m_authProvider->getCredentials(); username = creds.m_username; password = creds.m_password; } boost::shared_ptr ContextSettings::getAuthProvider() { lookupAuthProvider(); return m_authProvider; } void ContextSettings::lookupAuthProvider() { if (m_authProvider) { return; } UserIdentity identity; InitStateString password; // prefer source config if anything is set there const char *credentialsFrom = "undefined"; if (m_sourceConfig) { identity = m_sourceConfig->getUser(); password = m_sourceConfig->getPassword(); credentialsFrom = "source config"; } // fall back to context if (m_context && !identity.wasSet() && !password.wasSet()) { identity = m_context->getSyncUser(); password = m_context->getSyncPassword(); credentialsFrom = "source context"; } SE_LOG_DEBUG(NULL, "using username '%s' from %s for WebDAV, password %s", identity.toString().c_str(), credentialsFrom, password.wasSet() ? "was set" : "not set"); // lookup actual authentication method instead of assuming username/password m_authProvider = AuthProvider::create(identity, password); } void ContextSettings::initializeFlags(const std::string &url) { bool googleUpdate = false, googleChild = false, googleAlarm = false, noCTag = false; Neon::URI uri = Neon::URI::parse(url); typedef boost::split_iterator string_split_iterator; for (string_split_iterator arg = boost::make_split_iterator(uri.m_query, boost::first_finder("&", boost::is_iequal())); arg != string_split_iterator(); ++arg) { static const std::string keyword = "SyncEvolution="; if (boost::istarts_with(*arg, keyword)) { std::string params(arg->begin() + keyword.size(), arg->end()); for (string_split_iterator flag = boost::make_split_iterator(params, boost::first_finder(",", boost::is_iequal())); flag != string_split_iterator(); ++flag) { if (boost::iequals(*flag, "UpdateHack")) { googleUpdate = true; } else if (boost::iequals(*flag, "ChildHack")) { googleChild = true; } else if (boost::iequals(*flag, "AlarmHack")) { googleAlarm = true; } else if (boost::iequals(*flag, "Google")) { googleUpdate = googleChild = googleAlarm = true; } else if (boost::iequals(*flag, "NoCTag")) { noCTag = true; } else { SE_THROW(StringPrintf("unknown SyncEvolution flag %s in URL %s", std::string(flag->begin(), flag->end()).c_str(), url.c_str())); } } } else if (arg->end() != arg->begin()) { SE_THROW(StringPrintf("unknown parameter %s in URL %s", std::string(arg->begin(), arg->end()).c_str(), url.c_str())); } } // store final result m_googleUpdateHack = googleUpdate; m_googleChildHack = googleChild; m_googleAlarmHack = googleAlarm; m_noCTag = noCTag; } WebDAVSource::WebDAVSource(const SyncSourceParams ¶ms, const boost::shared_ptr &settings) : TrackingSyncSource(params), m_settings(settings) { if (!m_settings) { m_contextSettings.reset(new ContextSettings(params.m_context, this)); m_settings = m_contextSettings; } /* insert contactServer() into BackupData_t and RestoreData_t (implemented by SyncSourceRevisions) */ m_operations.m_backupData = boost::bind(&WebDAVSource::backupData, this, m_operations.m_backupData, _1, _2, _3); m_operations.m_restoreData = boost::bind(&WebDAVSource::restoreData, this, m_operations.m_restoreData, _1, _2, _3); // ignore the "Request ends, status 207 class 2xx, error line:" printed by neon LogRedirect::addIgnoreError(", error line:"); // ignore error messages in returned data LogRedirect::addIgnoreError("Read block ("); } static const std::string UID("\nUID:"); const std::string *WebDAVSource::createResourceName(const std::string &item, std::string &buffer, std::string &luid) { luid = extractUID(item); std::string suffix = getSuffix(); if (luid.empty()) { // must modify item luid = UUID(); buffer = item; size_t start = buffer.find("\nEND:" + getContent()); if (start != buffer.npos) { start++; buffer.insert(start, StringPrintf("UID:%s\r\n", luid.c_str())); } luid += suffix; return &buffer; } else { luid += suffix; return &item; } } const std::string *WebDAVSource::setResourceName(const std::string &item, std::string &buffer, const std::string &luid) { std::string olduid = luid; std::string suffix = getSuffix(); if (boost::ends_with(olduid, suffix)) { olduid.resize(olduid.size() - suffix.size()); } // first check if the item already contains the right UID size_t start, end; std::string uid = extractUID(item, &start, &end); if (uid == olduid) { return &item; } // insert or overwrite buffer = item; if (start != std::string::npos) { // overwrite buffer.replace(start, end - start, olduid); } else { // insert start = buffer.find("\nEND:" + getContent()); if (start != buffer.npos) { start++; buffer.insert(start, StringPrintf("UID:%s\n", olduid.c_str())); } } return &buffer; } std::string WebDAVSource::extractUID(const std::string &item, size_t *startp, size_t *endp) { std::string luid; if (startp) { *startp = std::string::npos; } if (endp) { *endp = std::string::npos; } // find UID, use that plus ".vcf" as resource name (expected by Yahoo Contacts) size_t start = item.find(UID); if (start != item.npos) { start += UID.size(); size_t end = item.find("\n", start); if (end != item.npos) { if (startp) { *startp = start; } luid = item.substr(start, end - start); if (boost::ends_with(luid, "\r")) { luid.resize(luid.size() - 1); } // keep checking for more lines because of folding while (end + 1 < item.size() && item[end + 1] == ' ') { start = end + 1; end = item.find("\n", start); if (end == item.npos) { // incomplete, abort luid = ""; if (startp) { *startp = std::string::npos; } break; } luid += item.substr(start, end - start); if (boost::ends_with(luid, "\r")) { luid.resize(luid.size() - 1); } } // success, return all information if (endp) { // don't include \r or \n *endp = item[end - 1] == '\r' ? end - 1 : end; } } } return luid; } std::string WebDAVSource::getSuffix() const { return getContent() == "VCARD" ? ".vcf" : ".ics"; } void WebDAVSource::replaceHTMLEntities(std::string &item) { while (true) { bool found = false; std::string decoded; size_t last = 0; // last character copied size_t next = 0; // next character to be looked at while (true) { next = item.find('&', next); size_t start = next; if (next == item.npos) { // finish decoding if (found) { decoded.append(item, last, item.size() - last); } break; } next++; size_t end = next; while (end != item.size()) { char c = item[end]; if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c == '#')) { end++; } else { break; } } if (end == item.size() || item[end] != ';') { // Invalid character between & and ; or no // proper termination? No entity, continue // decoding in next loop iteration. next = end; continue; } unsigned char c = 0; if (next < end) { if (item[next] == '#') { // decimal or hexadecimal number next++; if (next < end) { int base; if (item[next] == 'x') { // hex base = 16; next++; } else { base = 10; } while (next < end) { unsigned char v = tolower(item[next]); if (v >= '0' && v <= '9') { next++; c = c * base + (v - '0'); } else if (base == 16 && v >= 'a' && v <= 'f') { next++; c = c * base + (v - 'a') + 10; } else { // invalid character, abort scanning of this entity break; } } } } else { // check for entities struct { const char *m_name; unsigned char m_character; } entities[] = { // core entries, extend as needed... { "quot", '"' }, { "amp", '&' }, { "apos", '\'' }, { "lt", '<' }, { "gt", '>' }, { NULL, 0 } }; int i = 0; while (true) { const char *name = entities[i].m_name; if (!name) { break; } if (!item.compare(next, end - next, name)) { c = entities[i].m_character; next += strlen(name); break; } i++; } } if (next == end) { // swallowed all characters in entity, must be valid: // copy all uncopied characters plus the new one found = true; decoded.reserve(item.size()); decoded.append(item, last, start - last); decoded.append(1, c); last = end + 1; } } next = end + 1; } if (found) { item = decoded; } else { break; } } } void WebDAVSource::open() { // Nothing to do here, expensive initialization is in contactServer(). } static bool setFirstURL(Neon::URI &result, const std::string &name, const Neon::URI &uri) { result = uri; // stop return false; } void WebDAVSource::contactServer() { if (!m_calendar.empty() && m_session) { // we have done this work before, no need to repeat it } SE_LOG_DEBUG(NULL, "using libneon %s with %s", ne_version_string(), Neon::features().c_str()); // Can we skip auto-detection because a full resource URL is set? std::string database = getDatabaseID(); if (!database.empty() && m_contextSettings) { m_calendar = Neon::URI::parse(database, true); // m_contextSettings = m_settings, so this sets m_settings->getURL() m_contextSettings->setURL(database); // start talking to host defined by m_settings->getURL() m_session = Neon::Session::create(m_settings); // force authentication via username/password or OAuth2 m_session->forceAuthorization(m_settings->getAuthProvider()); return; } // Create session and find first collection (the default). m_calendar = Neon::URI(); findCollections(boost::bind(setFirstURL, boost::ref(m_calendar), _1, _2)); if (m_calendar.empty()) { throwError("no database found"); } SE_LOG_DEBUG(NULL, "picked final path %s", m_calendar.m_path.c_str()); // Check some server capabilities. Purely informational at this // point, doesn't have to succeed either (Google 401 throttling // workaround not active here, so it may really fail!). #ifdef HAVE_LIBNEON_OPTIONS if (Logger::instance().getLevel() >= Logger::DEV) { try { SE_LOG_DEBUG(NULL, "read capabilities of %s", m_calendar.toURL().c_str()); m_session->startOperation("OPTIONS", Timespec()); int caps = m_session->options(m_calendar.m_path); static const Flag descr[] = { { NE_CAP_DAV_CLASS1, "Class 1 WebDAV (RFC 2518)" }, { NE_CAP_DAV_CLASS2, "Class 2 WebDAV (RFC 2518)" }, { NE_CAP_DAV_CLASS3, "Class 3 WebDAV (RFC 4918)" }, { NE_CAP_MODDAV_EXEC, "mod_dav 'executable' property" }, { NE_CAP_DAV_ACL, "WebDAV ACL (RFC 3744)" }, { NE_CAP_VER_CONTROL, "DeltaV version-control" }, { NE_CAP_CO_IN_PLACE, "DeltaV checkout-in-place" }, { NE_CAP_VER_HISTORY, "DeltaV version-history" }, { NE_CAP_WORKSPACE, "DeltaV workspace" }, { NE_CAP_UPDATE, "DeltaV update" }, { NE_CAP_LABEL, "DeltaV label" }, { NE_CAP_WORK_RESOURCE, "DeltaV working-resouce" }, { NE_CAP_MERGE, "DeltaV merge" }, { NE_CAP_BASELINE, "DeltaV baseline" }, { NE_CAP_ACTIVITY, "DeltaV activity" }, { NE_CAP_VC_COLLECTION, "DeltaV version-controlled-collection" }, { 0, NULL } }; SE_LOG_DEBUG(NULL, "%s WebDAV capabilities: %s", m_session->getURL().c_str(), Flags2String(caps, descr).c_str()); } catch (const Neon::FatalException &ex) { throw; } catch (...) { Exception::handle(); } } #endif // HAVE_LIBNEON_OPTIONS } bool WebDAVSource::findCollections(const boost::function &storeResult) { bool res = true; // completed int timeoutSeconds = m_settings->timeoutSeconds(); int retrySeconds = m_settings->retrySeconds(); SE_LOG_DEBUG(getDisplayName(), "timout %ds, retry %ds => %s", timeoutSeconds, retrySeconds, (timeoutSeconds <= 0 || retrySeconds <= 0) ? "resending disabled" : "resending allowed"); boost::shared_ptr authProvider = m_contextSettings->getAuthProvider(); std::string username = authProvider->getUsername(); // If no URL was configured, then try DNS SRV lookup. // syncevo-webdav-lookup and at least one of the tools // it depends on (host, nslookup, adnshost, ...) must // be in the shell search path. // // Only our own m_contextSettings allows overriding the // URL. Not an issue, in practice it is always used. std::string url = m_settings->getURL(); if (url.empty() && m_contextSettings) { size_t pos = username.find('@'); if (pos == username.npos) { // throw authentication error to indicate that the credentials are wrong throwError(STATUS_UNAUTHORIZED, StringPrintf("syncURL not configured and username %s does not contain a domain", username.c_str())); } std::string domain = username.substr(pos + 1); FILE *in = NULL; try { Timespec startTime = Timespec::monotonic(); retry: in = popen(StringPrintf("syncevo-webdav-lookup '%s' '%s'", serviceType().c_str(), domain.c_str()).c_str(), "r"); if (!in) { throwError("syncURL not configured and starting syncevo-webdav-lookup for DNS SRV lookup failed", errno); } // ridicuously long URLs are truncated... char buffer[1024]; size_t read = fread(buffer, 1, sizeof(buffer) - 1, in); buffer[read] = 0; if (read > 0 && buffer[read - 1] == '\n') { read--; } buffer[read] = 0; m_contextSettings->setURL(buffer); SE_LOG_DEBUG(getDisplayName(), "found syncURL '%s' via DNS SRV", buffer); int res = pclose(in); in = NULL; switch (res) { case 0: break; case 2: throwError(StringPrintf("syncURL not configured and syncevo-webdav-lookup did not find a DNS utility to search for %s in %s", serviceType().c_str(), domain.c_str())); break; case 3: throwError(StringPrintf("syncURL not configured and DNS SRV search for %s in %s did not find the service", serviceType().c_str(), domain.c_str())); break; default: { Timespec now = Timespec::monotonic(); if (retrySeconds > 0 && timeoutSeconds > 0) { if (now < startTime + timeoutSeconds) { SE_LOG_DEBUG(getDisplayName(), "DNS SRV search failed due to network issues, retry in %d seconds", retrySeconds); Sleep(retrySeconds); goto retry; } else { SE_LOG_INFO(getDisplayName(), "DNS SRV search timed out after %d seconds", timeoutSeconds); } } // probably network problem throwError(STATUS_TRANSPORT_FAILURE, StringPrintf("syncURL not configured and DNS SRV search for %s in %s failed", serviceType().c_str(), domain.c_str())); break; } } } catch (...) { if (in) { pclose(in); } throw; } } // start talking to host defined by m_settings->getURL() m_session = Neon::Session::create(m_settings); // Find default calendar. Same for address book, with slightly // different parameters. // // Stops when: // - current path is calendar collection (= contains VEVENTs) // Gives up: // - when running in circles // - nothing else to try out // - tried 10 times // Follows: // - current-user-principal // - CalDAV calendar-home-set // - collections // // TODO: hrefs and redirects are assumed to be on the same host - support switching host // TODO: support more than one calendar. Instead of stopping at the first one, // scan more throroughly, then decide deterministically. int counter = 0; const int limit = 1000; // Keeps track of paths to look at and those // which were already tested. class Tried : public std::set { std::list m_candidates; bool m_found; public: Tried() : m_found(false) {} /** Was path not tested yet? */ bool isNew(const std::string &path) { return find(Neon::URI::normalizePath(path, true)) == end(); } /** Hand over next candidate to caller, empty if none available. */ std::string getNextCandidate() { if (!m_candidates.empty() ) { std::string candidate = m_candidates.front(); m_candidates.pop_front(); return candidate; } else { return ""; } } /** remember that path was tested */ std::string insert(const std::string &path) { std::string normal = Neon::URI::normalizePath(path, true); std::set::insert(normal); m_candidates.remove(normal); return normal; } enum Position { FRONT, BACK }; void addCandidate(const std::string &path, Position position) { std::string normal = Neon::URI::normalizePath(path, true); if (isNew(normal)) { if (position == FRONT) { m_candidates.push_front(normal); } else { m_candidates.push_back(normal); } } } void foundResult() { m_found = true; } /** Nothing left to try and nothing found => bail out with error for last candidate. */ bool errorIsFatal() { return m_candidates.empty() && !m_found; } } tried; std::string path = m_session->getURI().m_path; Neon::Session::PropfindPropCallback_t callback = boost::bind(&WebDAVSource::openPropCallback, this, _1, _2, _3, _4); // With Yahoo! the initial connection often failed with 50x // errors. Retrying individual requests is error prone because at // least one (asking for .well-known/[caldav|carddav]) always // results in 502. Let the PROPFIND requests be resent, but in // such a way that the overall discovery will never take longer // than the total configured timeout period. // // The PROPFIND with openPropCallback is idempotent, because it // will just overwrite previously found information in m_davProps. // Therefore resending is okay. Timespec finalDeadline = createDeadline(); // no resending if left empty // Add well-known URL as fallback to be tried if configured // path was empty. eGroupware also replies with a redirect for the // empty path, but relying on that alone is risky because it isn't // specified. if (path.empty() || path == "/") { std::string wellknown = wellKnownURL(); if (!wellknown.empty()) { tried.addCandidate(wellknown, Tried::BACK); } } while (true) { bool usernameInserted = false; std::string next; // Replace %u with the username, if the %u is found. Also, keep track // of this event happening, because if we later on get a 404 error, // we will convert it to 401 only if the path contains the username // and it was indeed us who put the username there (not the server). if (boost::find_first(path, "%u")) { boost::replace_all(path, "%u", Neon::URI::escape(username)); usernameInserted = true; } // must normalize so that we can compare against results from server path = tried.insert(path); SE_LOG_DEBUG(NULL, "testing %s", path.c_str()); // Accessing the well-known URIs should lead to a redirect, but // with Yahoo! Calendar all I got was a 502 "connection refused". // Yahoo! Contacts also doesn't redirect. Instead on ends with // a Principal resource - perhaps reading that would lead further. // // So anyway, let's try the well-known URI first, but also add // the root path as fallback. if (path == "/.well-known/caldav/" || path == "/.well-known/carddav/") { // remove trailing slash added by normalization, to be aligned with draft-daboo-srv-caldav-10 path.resize(path.size() - 1); // Yahoo! Calendar returns no redirect. According to rfc4918 appendix-E, // a client may simply try the root path in case of such a failure, // which happens to work for Yahoo. tried.addCandidate("/", Tried::BACK); // TODO: Google Calendar, with workarounds // candidates.push_back(StringPrintf("/calendar/dav/%s/user/", Neon::URI::escape(username).c_str())); } bool success = false; try { // disable resending for some known cases where it never succeeds Timespec deadline = finalDeadline; if (boost::starts_with(path, "/.well-known") && m_settings->getURL().find("yahoo.com") != string::npos) { deadline = Timespec(); } if (Logger::instance().getLevel() >= Logger::DEV) { // First dump WebDAV "allprops" properties (does not contain // properties which must be asked for explicitly!). Only // relevant for debugging. try { SE_LOG_DEBUG(NULL, "debugging: read all WebDAV properties of %s", path.c_str()); // Use OAuth2, if available. boost::shared_ptr authProvider = m_settings->getAuthProvider(); if (authProvider->methodIsSupported(AuthProvider::AUTH_METHOD_OAUTH2)) { m_session->forceAuthorization(authProvider); } Neon::Session::PropfindPropCallback_t callback = boost::bind(&WebDAVSource::openPropCallback, this, _1, _2, _3, _4); m_session->propfindProp(path, 0, NULL, callback, Timespec()); } catch (const Neon::FatalException &ex) { throw; } catch (...) { handleException(HANDLE_EXCEPTION_NO_ERROR); } } // Now ask for some specific properties of interest for us. // Using CALDAV:allprop would be nice, but doesn't seem to // be possible with Neon. // // The "current-user-principal" is particularly relevant, // because it leads us from // "/.well-known/[carddav/caldav]" (or whatever that // redirected to) to the current user and its // "[calendar/addressbook]-home-set". // // Apple Calendar Server only returns that information if // we force authorization to be used. Otherwise it returns // // // // // We send valid credentials here, using Basic authorization, // if configured to use credentials instead of something like OAuth2. // The rationale is that this cuts down on the number of // requests for https while still being secure. For // http, our Neon wrapper is smart enough to ignore our request. // // See also: // http://tools.ietf.org/html/rfc4918#appendix-E // http://lists.w3.org/Archives/Public/w3c-dist-auth/2005OctDec/0243.html // http://thread.gmane.org/gmane.comp.web.webdav.neon.general/717/focus=719 m_session->forceAuthorization(m_settings->getAuthProvider()); m_davProps.clear(); // Avoid asking for CardDAV properties when only using CalDAV // and vice versa, to avoid breaking both when the server is only // broken for one of them (like Google, which (temporarily?) sent // invalid CardDAV properties). static const ne_propname caldav[] = { // WebDAV ACL { "DAV:", "alternate-URI-set" }, { "DAV:", "principal-URL" }, { "DAV:", "current-user-principal" }, { "DAV:", "group-member-set" }, { "DAV:", "group-membership" }, { "DAV:", "displayname" }, { "DAV:", "resourcetype" }, // CalDAV { "urn:ietf:params:xml:ns:caldav", "calendar-home-set" }, { "urn:ietf:params:xml:ns:caldav", "calendar-description" }, { "urn:ietf:params:xml:ns:caldav", "calendar-timezone" }, { "urn:ietf:params:xml:ns:caldav", "supported-calendar-component-set" }, { "urn:ietf:params:xml:ns:caldav", "supported-calendar-data" }, { "urn:ietf:params:xml:ns:caldav", "max-resource-size" }, { "urn:ietf:params:xml:ns:caldav", "min-date-time" }, { "urn:ietf:params:xml:ns:caldav", "max-date-time" }, { "urn:ietf:params:xml:ns:caldav", "max-instances" }, { "urn:ietf:params:xml:ns:caldav", "max-attendees-per-instance" }, { NULL, NULL } }; static const ne_propname carddav[] = { // WebDAV ACL { "DAV:", "alternate-URI-set" }, { "DAV:", "principal-URL" }, { "DAV:", "current-user-principal" }, { "DAV:", "group-member-set" }, { "DAV:", "group-membership" }, { "DAV:", "displayname" }, { "DAV:", "resourcetype" }, // CardDAV { "urn:ietf:params:xml:ns:carddav", "addressbook-home-set" }, { "urn:ietf:params:xml:ns:carddav", "principal-address" }, { "urn:ietf:params:xml:ns:carddav", "addressbook-description" }, { "urn:ietf:params:xml:ns:carddav", "supported-address-data" }, { "urn:ietf:params:xml:ns:carddav", "max-resource-size" }, { NULL, NULL } }; SE_LOG_DEBUG(NULL, "read relevant properties of %s", path.c_str()); m_session->propfindProp(path, 0, getContent() == "VCARD" ? carddav : caldav, callback, deadline); success = true; } catch (const Neon::FatalException &ex) { throw; } catch (const Neon::RedirectException &ex) { // follow to new location Neon::URI next = Neon::URI::parse(ex.getLocation(), true); Neon::URI old = m_session->getURI(); // keep old host + scheme + port if not set in next location if (next.m_scheme.empty()) { next.m_scheme = old.m_scheme; } if (next.m_host.empty()) { next.m_host = old.m_host; } if (!next.m_port) { next.m_port = old.m_port; } if (next.m_scheme != old.m_scheme || next.m_host != old.m_host || next.m_port != old.m_port) { SE_LOG_DEBUG(NULL, "ignore redirection to different server (not implemented): %s", ex.getLocation().c_str()); if (tried.errorIsFatal()) { throw; } } else if (tried.isNew(next.m_path)) { SE_LOG_DEBUG(NULL, "new candidate from %s -> %s redirect", old.m_path.c_str(), next.m_path.c_str()); tried.addCandidate(next.m_path, Tried::FRONT); } else { SE_LOG_DEBUG(NULL, "already known candidate from %s -> %s redirect", old.m_path.c_str(), next.m_path.c_str()); } } catch (const TransportStatusException &ex) { SE_LOG_DEBUG(NULL, "TransportStatusException: %s", ex.what()); if (ex.syncMLStatus() == 404 && boost::find_first(path, username) && usernameInserted) { // We're actually looking at an authentication error: the path to the calendar has // not been found, so the username was wrong. Let's hijack the error message and // code of the exception by throwing a new one. string descr = StringPrintf("Path not found: %s. Is the username '%s' correct?", path.c_str(), username.c_str()); int code = 401; SE_THROW_EXCEPTION_STATUS(TransportStatusException, descr, SyncMLStatus(code)); } else { if (tried.errorIsFatal()) { throw; } else { // ignore the error (whatever it was!), try next // candidate; needed to handle 502 "Connection // refused" for /.well-known/caldav/ from Yahoo! // Calendar SE_LOG_DEBUG(NULL, "ignore error for URI candidate: %s", ex.what()); } } } catch (const Exception &ex) { if (tried.errorIsFatal()) { throw; } else { // ignore the error (whatever it was!), try next // candidate; needed to handle 502 "Connection // refused" for /.well-known/caldav/ from Yahoo! // Calendar SE_LOG_DEBUG(NULL, "ignore error for URI candidate: %s", ex.what()); } } if (success) { Props_t::iterator pathProps = m_davProps.find(path); if (pathProps == m_davProps.end()) { // No reply for requested path? Happens with Yahoo Calendar server, // which returns information about "/dav" when asked about "/". // Move to that path. if (!m_davProps.empty()) { pathProps = m_davProps.begin(); string newpath = pathProps->first; SE_LOG_DEBUG(NULL, "use properties for '%s' instead of '%s'", newpath.c_str(), path.c_str()); path = newpath; } } StringMap *props = pathProps == m_davProps.end() ? NULL : &pathProps->second; bool isResult = false; if (props && typeMatches(*props)) { isResult = true; StringMap::const_iterator it; // TODO: filter out CalDAV collections which do // not contain the right components // (urn:ietf:params:xml:ns:caldav:supported-calendar-component-set) // found something tried.foundResult(); it = props->find("DAV::displayname"); Neon::URI uri = m_session->getURI(); uri.m_path = path; std::string name; if (it != props->end()) { name = it->second; } SE_LOG_DEBUG(NULL, "found %s = %s", name.c_str(), uri.toURL().c_str()); res = storeResult(name, uri); if (!res) { // done break; } } // find next path: // prefer CardDAV:calendar-home-set or CalDAV:addressbook-home-set std::list homes; if (props) { homes = extractHREFs((*props)[homeSetProp()]); } BOOST_FOREACH(const std::string &home, homes) { if (!home.empty() && tried.isNew(home)) { if (next.empty()) { SE_LOG_DEBUG(NULL, "follow home-set property to %s", home.c_str()); next = home; } else { SE_LOG_DEBUG(NULL, "new candidate from home-set property %s", home.c_str()); tried.addCandidate(home, Tried::FRONT); } } } // alternatively, follow principal URL if (next.empty()) { std::string principal; if (props) { principal = extractHREF((*props)["DAV::current-user-principal"]); } // TODO: // xmlns:d="DAV:" // /m8/carddav/principals/__uids__/patrick.ohly@googlemail.com/ if (!principal.empty() && tried.isNew(principal)) { next = principal; SE_LOG_DEBUG(NULL, "follow current-user-prinicipal to %s", next.c_str()); } } // finally, recursively descend into collections, // unless we identified it as a result (because those // cannot be recursive) if (next.empty() && !isResult) { std::string type; if (props) { type = (*props)["DAV::resourcetype"]; } if (type.find("") != type.npos) { // List members and find new candidates. // Yahoo! Calendar does not return resources contained in /dav//Calendar/ // if is used. Properties must be requested explicitly. SE_LOG_DEBUG(NULL, "list items in %s", path.c_str()); // See findCollections() for the reason why we are not mixing CalDAV and CardDAV // properties. static const ne_propname caldav[] = { { "DAV:", "displayname" }, { "DAV:", "resourcetype" }, { "urn:ietf:params:xml:ns:caldav", "calendar-home-set" }, { "urn:ietf:params:xml:ns:caldav", "calendar-description" }, { "urn:ietf:params:xml:ns:caldav", "calendar-timezone" }, { "urn:ietf:params:xml:ns:caldav", "supported-calendar-component-set" }, { NULL, NULL } }; static const ne_propname carddav[] = { { "DAV:", "displayname" }, { "DAV:", "resourcetype" }, { "urn:ietf:params:xml:ns:carddav", "addressbook-home-set" }, { "urn:ietf:params:xml:ns:carddav", "addressbook-description" }, { "urn:ietf:params:xml:ns:carddav", "supported-address-data" }, { NULL, NULL } }; m_davProps.clear(); m_session->propfindProp(path, 1, getContent() == "VCARD" ? carddav : caldav, callback, finalDeadline); std::set subs; BOOST_FOREACH(Props_t::value_type &entry, m_davProps) { const std::string &sub = entry.first; const std::string &subType = entry.second["DAV::resourcetype"]; // new candidates are: // - untested // - not already a candidate // - a resource, but not the CalDAV schedule-inbox/outbox // - not shared ("global-addressbook" in Apple Calendar Server), // because these are unlikely to be the right "default" collection // // Trying to prune away collections here which are not of the // right type *and* cannot contain collections of the right // type (example: Apple Calendar Server "inbox" under // calendar-home-set URL with type "CALDAV:schedule-inbox") requires // knowledge not current provided by derived classes. TODO (?). if (tried.isNew(sub) && subType.find("") != subType.npos && subType.find(" limit) { throwError(StringPrintf("giving up search for collection after %d attempts", limit)); } path = next; } return res; } std::string WebDAVSource::extractHREF(const std::string &propval) { // all additional parameters after opening resp. closing tag static const std::string hrefStart = "', start); if (start != propval.npos) { start++; size_t end = propval.find(hrefEnd, start); if (end != propval.npos) { return propval.substr(start, end - start); } } return ""; } std::list WebDAVSource::extractHREFs(const std::string &propval) { std::list res; // all additional parameters after opening resp. closing tag static const std::string hrefStart = "', start); if (start != propval.npos) { start++; size_t end = propval.find(hrefEnd, start); if (end != propval.npos) { res.push_back(propval.substr(start, end - start)); current = end; } else { break; } } else { break; } } return res; } void WebDAVSource::openPropCallback(const Neon::URI &uri, const ne_propname *prop, const char *value, const ne_status *status) { // TODO: recognize CALDAV:calendar-timezone and use it for local time conversion of events std::string name; if (prop->nspace) { name = prop->nspace; } name += ":"; name += prop->name; if (value) { m_davProps[uri.m_path][name] = value; boost::trim_if(m_davProps[uri.m_path][name], boost::is_space()); } } bool WebDAVSource::isEmpty() { contactServer(); // listing all items is relatively efficient, let's use that // TODO: use truncated result search RevisionMap_t revisions; listAllItems(revisions); return revisions.empty(); } void WebDAVSource::close() { m_session.reset(); } static bool storeCollection(SyncSource::Databases &result, const std::string &name, const Neon::URI &uri) { std::string url = uri.toURL(); // avoid duplicates BOOST_FOREACH(const SyncSource::Database &entry, result) { if (entry.m_uri == url) { // already found before return true; } } result.push_back(SyncSource::Database(name, url)); return true; } WebDAVSource::Databases WebDAVSource::getDatabases() { Databases result; // do a scan if some kind of credentials were set if (m_contextSettings->getAuthProvider()->wasConfigured()) { findCollections(boost::bind(storeCollection, boost::ref(result), _1, _2)); if (!result.empty()) { result.front().m_isDefault = true; } } else { result.push_back(Database("select database via absolute URL, set username/password to scan, set syncURL to base URL if server does not support auto-discovery", "")); } return result; } void WebDAVSource::getSynthesisInfo(SynthesisInfo &info, XMLConfigFragments &fragments) { TrackingSyncSource::getSynthesisInfo(info, fragments); // only CalDAV enforces unique UID std::string content = getContent(); if (content == "VEVENT" || content == "VTODO" || content == "VJOURNAL") { info.m_globalIDs = true; } if (content == "VEVENT") { info.m_backendRule = "HAVE-SYNCEVOLUTION-EXDATE-DETACHED"; } // TODO: instead of identifying the peer based on the // session URI, use some information gathered about // it during open() if (m_session) { string host = m_session->getURI().m_host; if (host.find("google") != host.npos) { info.m_backendRule = "GOOGLE"; fragments.m_remoterules["GOOGLE"] = " \n" " none\n" // enable extensions, just in case (not relevant yet for calendar) " \n" " "; } else if (host.find("yahoo") != host.npos) { info.m_backendRule = "YAHOO"; fragments.m_remoterules["YAHOO"] = " \n" " none\n" // Yahoo! Contacts reacts with a "500 - internal server error" // to an empty X-GENDER property. In general, empty properties // should never be necessary in CardDAV and CalDAV, because // sent items conceptually replace the one on the server, so // disable them all. " yes\n" // BDAY is ignored if it has the compact 19991231 instead of // 1999-12-31, although both are valid. " \n" // Yahoo accepts extensions, so send them. However, it // doesn't seem to store the X-EVOLUTION-UI-SLOT parameter // extensions. " \n" " "; } else { // fallback: generic CalDAV/CardDAV, with all properties // enabled (for example, X-EVOLUTION-UI-SLOT) info.m_backendRule = "WEBDAV"; fragments.m_remoterules["WEBDAV"] = " \n" " none\n" " \n" " "; } SE_LOG_DEBUG(getDisplayName(), "using data conversion rules for '%s'", info.m_backendRule.c_str()); } } void WebDAVSource::storeServerInfos() { if (getDatabaseID().empty()) { // user did not select resource, remember the one used for the // next sync setDatabaseID(m_calendar.toURL()); getProperties()->flush(); } } /** * See https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-ctag.txt */ static const ne_propname getctag[] = { { "http://calendarserver.org/ns/", "getctag" }, { NULL, NULL } }; std::string WebDAVSource::databaseRevision() { if (m_contextSettings && m_contextSettings->noCTag()) { // return empty string to disable usage of CTag return ""; } contactServer(); Timespec deadline = createDeadline(); Neon::Session::PropfindPropCallback_t callback = boost::bind(&WebDAVSource::openPropCallback, this, _1, _2, _3, _4); m_davProps[m_calendar.m_path]["http://calendarserver.org/ns/:getctag"] = ""; m_session->propfindProp(m_calendar.m_path, 0, getctag, callback, deadline); // Fatal communication problems will be reported via exceptions. // Once we get here, invalid or incomplete results can be // treated as "don't have revision string". string ctag = m_davProps[m_calendar.m_path]["http://calendarserver.org/ns/:getctag"]; return ctag; } static const ne_propname getetag[] = { { "DAV:", "getetag" }, { "DAV:", "resourcetype" }, { NULL, NULL } }; void WebDAVSource::listAllItems(RevisionMap_t &revisions) { contactServer(); if (!getContentMixed()) { // Can use simple PROPFIND because we do not have to // double-check that each item really contains the right data. bool failed = false; Timespec deadline = createDeadline(); m_session->propfindURI(m_calendar.m_path, 1, getetag, boost::bind(&WebDAVSource::listAllItemsCallback, this, _1, _2, boost::ref(revisions), boost::ref(failed)), deadline); if (failed) { SE_THROW("incomplete listing of all items"); } } else { // We have to read item data and verify that it really is // something we have to (and may) work on. Currently only // happens for CalDAV, CardDAV items are uniform. The CalDAV // comp-filter alone should the trick, but some servers (for // example Radicale 0.7) ignore it and thus we could end up // deleting items we were not meant to touch. const std::string query = "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" // filter expected by Yahoo! Calendar "\n" "\n" "\n" "\n" "\n" "\n" "\n"; Timespec deadline = createDeadline(); getSession()->startOperation("REPORT 'meta data'", deadline); while (true) { string data; Neon::XMLParser parser; parser.initReportParser(boost::bind(&WebDAVSource::checkItem, this, boost::ref(revisions), _1, _2, &data)); parser.pushHandler(boost::bind(Neon::XMLParser::accept, "urn:ietf:params:xml:ns:caldav", "calendar-data", _2, _3), boost::bind(Neon::XMLParser::append, boost::ref(data), _2, _3)); Neon::Request report(*getSession(), "REPORT", getCalendar().m_path, query, parser); report.addHeader("Depth", "1"); report.addHeader("Content-Type", "application/xml; charset=\"utf-8\""); if (report.run()) { break; } } } } std::string WebDAVSource::findByUID(const std::string &uid, const Timespec &deadline) { RevisionMap_t revisions; std::string query; if (getContent() == "VCARD") { // use CardDAV query = "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "" + uid + "\n" "\n" "\n" "\n" "\n"; } else { // use CalDAV query = "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" // Collation from RFC 4791, not supported yet by all servers. // Apple Calendar Server did not like CDATA. // TODO: escape special characters. "" /* <[CDATA[ */ + uid + /* ]]> */ "\n" "\n" "\n" "\n" "\n" "\n"; } getSession()->startOperation("REPORT 'UID lookup'", deadline); while (true) { Neon::XMLParser parser; parser.initReportParser(boost::bind(&WebDAVSource::checkItem, this, boost::ref(revisions), _1, _2, (std::string *)0)); Neon::Request report(*getSession(), "REPORT", getCalendar().m_path, query, parser); report.addHeader("Depth", "1"); report.addHeader("Content-Type", "application/xml; charset=\"utf-8\""); if (report.run()) { break; } } switch (revisions.size()) { case 0: SE_THROW_EXCEPTION_STATUS(TransportStatusException, "object not found", SyncMLStatus(404)); break; case 1: return revisions.begin()->first; break; default: // should not happen SE_THROW(StringPrintf("UID %s not unique?!", uid.c_str())); } // not reached return ""; } void WebDAVSource::listAllItemsCallback(const Neon::URI &uri, const ne_prop_result_set *results, RevisionMap_t &revisions, bool &failed) { static const ne_propname prop = { "DAV:", "getetag" }; static const ne_propname resourcetype = { "DAV:", "resourcetype" }; const char *type = ne_propset_value(results, &resourcetype); if (type && strstr(type, "")) { // skip collections return; } std::string uid = path2luid(uri.m_path); if (uid.empty()) { // skip collection itself (should have been detected as collection already) return; } const char *etag = ne_propset_value(results, &prop); if (etag) { std::string rev = ETag2Rev(etag); SE_LOG_DEBUG(NULL, "item %s = rev %s", uid.c_str(), rev.c_str()); revisions[uid] = rev; } else { failed = true; SE_LOG_ERROR(NULL, "%s: %s", uri.toURL().c_str(), Neon::Status2String(ne_propset_status(results, &prop)).c_str()); } } int WebDAVSource::checkItem(RevisionMap_t &revisions, const std::string &href, const std::string &etag, std::string *data) { // Ignore responses with no data: this is not perfect (should better // try to figure out why there is no data), but better than // failing. // // One situation is the response for the collection itself, // which comes with a 404 status and no data with Google Calendar. if (data && data->empty()) { return 0; } // No need to parse, user content cannot start at start of line in // iCalendar 2.0. if (!data || data->find("\nBEGIN:" + getContent()) != data->npos) { std::string davLUID = path2luid(Neon::URI::parse(href).m_path); std::string rev = ETag2Rev(etag); revisions[davLUID] = rev; } // reset data for next item if (data) { data->clear(); } return 0; } std::string WebDAVSource::path2luid(const std::string &path) { // m_calendar.m_path is normalized, path is not. // Before comparing, normalize it. std::string res = Neon::URI::normalizePath(path, false); if (boost::starts_with(res, m_calendar.m_path)) { res = Neon::URI::unescape(res.substr(m_calendar.m_path.size())); } else { // keep full, absolute path as LUID } return res; } std::string WebDAVSource::luid2path(const std::string &luid) { if (boost::starts_with(luid, "/")) { return luid; } else { return m_calendar.resolve(Neon::URI::escape(luid)).m_path; } } void WebDAVSource::readItem(const string &uid, std::string &item, bool raw) { Timespec deadline = createDeadline(); m_session->startOperation("GET", deadline); while (true) { item.clear(); Neon::Request req(*m_session, "GET", luid2path(uid), "", item); // useful with CardDAV: server might support more than vCard 3.0, but we don't req.addHeader("Accept", contentType()); try { if (req.run()) { break; } } catch (const TransportStatusException &ex) { if (ex.syncMLStatus() == 410) { // Radicale reports 410 'Gone'. Hmm, okay. // Let's map it to the expected 404. SE_THROW_EXCEPTION_STATUS(TransportStatusException, "object not found (was 410 'Gone')", SyncMLStatus(404)); } throw; } } } TrackingSyncSource::InsertItemResult WebDAVSource::insertItem(const string &uid, const std::string &item, bool raw) { std::string new_uid; std::string rev; InsertItemResultState state = ITEM_OKAY; Timespec deadline = createDeadline(); // no resending if left empty m_session->startOperation("PUT", deadline); std::string result; int counter = 0; retry: counter++; result = ""; if (uid.empty()) { // Pick a resource name (done by derived classes, by default random), // catch unexpected conflicts via If-None-Match: *. std::string buffer; const std::string *data = createResourceName(item, buffer, new_uid); Neon::Request req(*m_session, "PUT", luid2path(new_uid), *data, result); // Clearing the idempotent flag would allow us to clearly // distinguish between a connection error (no changes made // on server) and a server failure (may or may not have // changed something) because it'll close the connection // first. // // But because we are going to try resending // the PUT anyway in case of 5xx errors we might as well // treat it like an idempotent request (which it is, // in a way, because we'll try to get our data onto // the server no matter what) and keep reusing an // existing connection. // req.setFlag(NE_REQFLAG_IDEMPOTENT, 0); // For this to work we must allow the server to overwrite // an item that we might have created before. Don't allow // that in the first attempt. if (counter == 1) { req.addHeader("If-None-Match", "*"); } req.addHeader("Content-Type", contentType().c_str()); static const std::set expected = boost::assign::list_of(412); if (!req.run(&expected)) { goto retry; } SE_LOG_DEBUG(NULL, "add item status: %s", Neon::Status2String(req.getStatus()).c_str()); switch (req.getStatusCode()) { case 204: // stored, potentially in a different resource than requested // when the UID was recognized break; case 201: // created break; case 412: { // "Precondition Failed": our only precondition is the one about // If-None-Match, which means that there must be an existing item // with the same UID. Go find it, so that we can report back the // right luid. std::string uid = extractUID(item); std::string luid = findByUID(uid, deadline); return InsertItemResult(luid, "", ITEM_NEEDS_MERGE); break; } default: SE_THROW_EXCEPTION_STATUS(TransportStatusException, std::string("unexpected status for insert: ") + Neon::Status2String(req.getStatus()), SyncMLStatus(req.getStatus()->code)); break; } rev = getETag(req); std::string real_luid = getLUID(req); if (!real_luid.empty()) { // Google renames the resource automatically to something of the form // .ics. Interestingly enough, our 1234567890!@#$%^&*()<>@dummy UID // test case leads to a resource path which Google then cannot find // via CalDAV. client-test must run with CLIENT_TEST_SIMPLE_UID=1... SE_LOG_DEBUG(NULL, "new item mapped to %s", real_luid.c_str()); new_uid = real_luid; // TODO: find a better way of detecting unexpected updates. // state = ... } else if (!rev.empty()) { // Yahoo Contacts returns an etag, but no href. For items // that were really created as requested, that's okay. But // Yahoo Contacts silently merges the new contact with an // existing one, presumably if it is "similar" enough. The // web interface allows creating identical contacts // multiple times; not so CardDAV. We are not even told // the path of that other contact... Detect this by // checking whether the item really exists. // // Google also returns an etag without a href. However, // Google really creates a new item. We cannot tell here // whether merging took place. As we are supporting Google // but not Yahoo at the moment, let's assume that a new item // was created. RevisionMap_t revisions; bool failed = false; m_session->propfindURI(luid2path(new_uid), 0, getetag, boost::bind(&WebDAVSource::listAllItemsCallback, this, _1, _2, boost::ref(revisions), boost::ref(failed)), deadline); // Turns out we get a result for our original path even in // the case of a merge, although the original path is not // listed when looking at the collection. Let's use that // to return the "real" uid to our caller. if (revisions.size() == 1 && revisions.begin()->first != new_uid) { SE_LOG_DEBUG(NULL, "%s mapped to %s by peer", new_uid.c_str(), revisions.begin()->first.c_str()); new_uid = revisions.begin()->first; // This would have to be uncommented for Yahoo. // state = ITEM_REPLACED; } } } else { new_uid = uid; std::string buffer; const std::string *data = setResourceName(item, buffer, new_uid); Neon::Request req(*m_session, "PUT", luid2path(new_uid), *data, result); // See above for discussion of idempotent and PUT. // req.setFlag(NE_REQFLAG_IDEMPOTENT, 0); req.addHeader("Content-Type", contentType()); // TODO: match exactly the expected revision, aka ETag, // or implement locking. Note that the ETag might not be // known, for example in this case: // - PUT succeeds // - PROPGET does not // - insertItem() fails // - Is retried? Might need slow sync in this case! // // req.addHeader("If-Match", etag); if (!req.run()) { goto retry; } SE_LOG_DEBUG(NULL, "update item status: %s", Neon::Status2String(req.getStatus()).c_str()); switch (req.getStatusCode()) { case 204: // the expected outcome, as we were asking for an overwrite break; case 201: // Huh? Shouldn't happen, but Google sometimes reports it // even when updating an item. Accept it. // SE_THROW("unexpected creation instead of update"); break; default: SE_THROW_EXCEPTION_STATUS(TransportStatusException, std::string("unexpected status for update: ") + Neon::Status2String(req.getStatus()), SyncMLStatus(req.getStatus()->code)); break; } rev = getETag(req); std::string real_luid = getLUID(req); if (!real_luid.empty() && real_luid != new_uid) { SE_THROW(StringPrintf("updating item: real luid %s does not match old luid %s", real_luid.c_str(), new_uid.c_str())); } } if (rev.empty()) { // Server did not include etag header. Must request it // explicitly (leads to race condition!). Google Calendar // assigns a new ETag even if the body has not changed, // so any kind of caching of ETag would not work either. bool failed = false; RevisionMap_t revisions; m_session->propfindURI(luid2path(new_uid), 0, getetag, boost::bind(&WebDAVSource::listAllItemsCallback, this, _1, _2, boost::ref(revisions), boost::ref(failed)), deadline); rev = revisions[new_uid]; if (failed || rev.empty()) { SE_THROW("could not retrieve ETag"); } } return InsertItemResult(new_uid, rev, state); } std::string WebDAVSource::ETag2Rev(const std::string &etag) { std::string res = etag; if (boost::starts_with(res, "W/")) { res.erase(0, 2); } if (res.size() >= 2 && res[0] == '"' && res[res.size() - 1] == '"') { res = res.substr(1, res.size() - 2); } return res; } std::string WebDAVSource::getLUID(Neon::Request &req) { std::string location = req.getResponseHeader("Location"); if (location.empty()) { return location; } else { return path2luid(Neon::URI::parse(location).m_path); } } bool WebDAVSource::ignoreCollection(const StringMap &props) const { // CardDAV and CalDAV both promise to not contain anything // unrelated to them StringMap::const_iterator it = props.find("DAV::resourcetype"); if (it != props.end()) { const std::string &type = it->second; // allow parameters (no closing bracket) // and allow also "carddavaddressbook" (caused by invalid Neon // string concatenation?!) if (type.find("timeoutSeconds(); int retrySeconds = m_settings->retrySeconds(); if (timeoutSeconds > 0 && retrySeconds > 0) { return Timespec::monotonic() + timeoutSeconds; } else { return Timespec(); } } void WebDAVSource::removeItem(const string &uid) { Timespec deadline = createDeadline(); m_session->startOperation("DELETE", deadline); std::string item, result; boost::scoped_ptr req; while (true) { req.reset(new Neon::Request(*m_session, "DELETE", luid2path(uid), item, result)); // TODO: match exactly the expected revision, aka ETag, // or implement locking. // req.addHeader("If-Match", etag); static const std::set expected = boost::assign::list_of(412); if (req->run(&expected)) { break; } } SE_LOG_DEBUG(NULL, "remove item status: %s", Neon::Status2String(req->getStatus()).c_str()); switch (req->getStatusCode()) { case 204: // the expected outcome break; case 200: // reported by Radicale, also okay break; case 412: // Radicale reports 412 'Precondition Failed'. Hmm, okay. // Let's map it to the expected 404. SE_THROW_EXCEPTION_STATUS(TransportStatusException, "object not found (was 412 'Precondition Failed')", SyncMLStatus(404)); break; default: SE_THROW_EXCEPTION_STATUS(TransportStatusException, std::string("unexpected status for removal: ") + Neon::Status2String(req->getStatus()), SyncMLStatus(req->getStatus()->code)); break; } } #endif /* ENABLE_DAV */ SE_END_CXX #ifdef ENABLE_MODULES # include "WebDAVSourceRegister.cpp" #endif syncevolution_1.4/src/backends/webdav/WebDAVSource.h000066400000000000000000000236101230021373600225730ustar00rootroot00000000000000/* * Copyright (C) 2010 Intel Corporation */ #ifndef INCL_WEBDAVSOURCE #define INCL_WEBDAVSOURCE #include #include #include SE_BEGIN_CXX extern BoolConfigProperty &WebDAVCredentialsOkay(); SE_END_CXX #ifdef ENABLE_DAV #include #include #include "NeonCXX.h" SE_BEGIN_CXX class ContextSettings; /** * Implements generic access to a WebDAV collection. * * Change tracking is based on TrackingSyncSource, with the following mapping: * - locally unique id = relative URI of resource in collection * - revision string = ETag of resource in collection */ class WebDAVSource : public TrackingSyncSource, private boost::noncopyable { public: /** * @param settings instance which provides necessary settings callbacks for Neon */ WebDAVSource(const SyncSourceParams ¶ms, const boost::shared_ptr &settings); /** * Utility function: replace HTML entities until none are left * in the decoded string - for Yahoo! Contacts bug. */ static void replaceHTMLEntities(std::string &item); protected: /** * Initialize HTTP session and locate the right collection. * To be called after open() to do the heavy initializtion * work. */ void contactServer(); /** * Scan server based on username/password/syncURL. Callback is * passed name and URL of each collection (in this order), may * return false to stop scanning gracefully or throw errors to * abort. * * @return true if scanning completed, false if callback requested stop */ bool findCollections(const boost::function &callback); /** store resource URL permanently after successful sync */ void storeServerInfos(); /* implementation of SyncSource interface */ virtual void open(); virtual bool isEmpty(); virtual void close(); virtual Databases getDatabases(); void getSynthesisInfo(SynthesisInfo &info, XMLConfigFragments &fragments); /** intercept TrackingSyncSource::beginSync() to do the expensive initialization */ virtual void beginSync(const std::string &lastToken, const std::string &resumeToken) { contactServer(); TrackingSyncSource::beginSync(lastToken, resumeToken); } /** hook into session to store infos */ virtual std::string endSync(bool success) { if (success) { storeServerInfos(); } return TrackingSyncSource::endSync(success); } /* implementation of TrackingSyncSource interface */ virtual std::string databaseRevision(); virtual void listAllItems(RevisionMap_t &revisions); virtual InsertItemResult insertItem(const string &luid, const std::string &item, bool raw); void readItem(const std::string &luid, std::string &item, bool raw); virtual void removeItem(const string &uid); /** * A resource path is turned into a locally unique ID by * stripping the calendar path prefix, or keeping the full * path for resources outside of the calendar. */ std::string path2luid(const std::string &path); /** * Full path can be reconstructed from relative LUID by * appending it to the calendar path, or using the path * as it is. */ std::string luid2path(const std::string &luid); /** * ETags are turned into revision strings by ignoring the W/ weak * marker (because we don't care for literal storage of items) and * by stripping the quotation marks. */ std::string ETag2Rev(const std::string &etag); /** * Calculates the time after which the next operation is * expected to complete before giving up, based on * current time and retry settings. * @return absolute time, empty if no retrying allowed */ Timespec createDeadline() const; // access to neon session and calendar, valid between open() and close() boost::shared_ptr getSession() { return m_session; } Neon::URI &getCalendar() { return m_calendar; } // access to settings owned by this instance Neon::Settings &settings() { return *m_settings; } /** * SRV type to be used for finding URL (caldav, carddav, ...) */ virtual string serviceType() const = 0; /** * return true if resource with the given properties is something we can work with; * properties which are queried are currently hard-coded in WebDAVSource::open() * * @param props mapping from fully qualified properties to their values, * normalized by neon library */ virtual bool typeMatches(const StringMap &props) const = 0; /** * property pointing to URL path with suitable collections ("calendar-home-set", "address-home-set", ...); * must be known to WebDAVSource::open() already */ virtual std::string homeSetProp() const = 0; /** * well-known URL, including full path (/.well-known/caldav), * empty if none */ virtual std::string wellKnownURL() const = 0; /** * HTTP content type for PUT */ virtual std::string contentType() const = 0; /** * VEVENT, VTODO, VJOURNAL, VCARD */ virtual std::string getContent() const = 0; /** * true if a collection might contain items with different content * types */ virtual bool getContentMixed() const = 0; /** * create new resource name (only last component, not full path) * * Some servers require that this matches the item content, * for example Yahoo CardDAV wants .vcf. * * @param item original item data * @param buffer empty, may be filled with modified item data * @retval luid new resource name, not URL encoded * @return item data to be sent */ virtual const std::string *createResourceName(const std::string &item, std::string &buffer, std::string &luid); /** * optionally modify item content to match the luid of the item we are going to update */ virtual const std::string *setResourceName(const std::string &item, std::string &buffer, const std::string &luid); /** * Find one item by its UID property value and return the corresponding * resource name relative to the current collection (aka luid). */ std::string findByUID(const std::string &uid, const Timespec &deadline); /** * Get UID property value from vCard 3.0 or iCalendar 2.0 text * items. * @retval startp offset of first character of UID value (i.e., directly after colon), * npos if no UID was found * @retval endp offset of first line break character (\r or \n) after UID value, * npos if no UID was found * @return UID value without line breaks and folding characters removed */ static std::string extractUID(const std::string &item, size_t *startp = NULL, size_t *endp = NULL); /** * .vcf for VCARD and .ics for everything else. */ virtual std::string getSuffix() const; private: /** settings to be used, never NULL, may be the same as m_contextSettings */ boost::shared_ptr m_settings; /** settings constructed by us instead of caller, may be NULL */ boost::shared_ptr m_contextSettings; boost::shared_ptr m_session; /** normalized path: including backslash, URI encoded */ Neon::URI m_calendar; /** information about certain paths (path->property->value)*/ typedef std::map > Props_t; Props_t m_davProps; /** extract value from first value, empty string if not inside propval */ std::string extractHREF(const std::string &propval); /** extract all value values from a set, empty if none */ std::list extractHREFs(const std::string &propval); void openPropCallback(const Neon::URI &uri, const ne_propname *prop, const char *value, const ne_status *status); void listAllItemsCallback(const Neon::URI &uri, const ne_prop_result_set *results, RevisionMap_t &revisions, bool &failed); int checkItem(RevisionMap_t &revisions, const std::string &href, const std::string &etag, std::string *data); void backupData(const boost::function &op, const Operations::ConstBackupInfo &oldBackup, const Operations::BackupInfo &newBackup, BackupReport &report) { contactServer(); op(oldBackup, newBackup, report); } void restoreData(const boost::function &op, const Operations::ConstBackupInfo &oldBackup, bool dryrun, SyncSourceReport &report) { contactServer(); op(oldBackup, dryrun, report); } /** * return true if the resource with the given properties is one * of those collections which is guaranteed to not contain * other, unrelated collections (a CalDAV collection must not * contain a CardDAV collection, for example) */ bool ignoreCollection(const StringMap &props) const; protected: /** * Extracts ETag from response header, empty if not found. */ std::string getETag(Neon::Request &req) { return ETag2Rev(req.getResponseHeader("ETag")); } /** * Extracts new LUID from response header, empty if not found. */ std::string getLUID(Neon::Request &req); }; SE_END_CXX #endif // ENABLE_DAV #endif // INCL_WEBDAVSOURCE syncevolution_1.4/src/backends/webdav/WebDAVSourceRegister.cpp000066400000000000000000000436601230021373600246420ustar00rootroot00000000000000/* * Copyright (C) 2010 Intel Corporation */ #include "WebDAVSource.h" #include "CalDAVSource.h" #include "CalDAVVxxSource.h" #include "CardDAVSource.h" #include #include #include #ifdef ENABLE_UNIT_TESTS #include "test.h" #endif #ifdef NEON_COMPATIBILITY #include #endif #include #include #include #include SE_BEGIN_CXX static SyncSource *createSource(const SyncSourceParams ¶ms) { SourceType sourceType = SyncSource::getSourceType(params.m_nodes); bool isMe; // Backend is enabled if a suitable libneon can be found. In // binary compatibility mode, libneon was not linked against. // Instead we dlopen() it and don't care whether that is // libneon.so.27 or libneon-gnutls.so.27. Debian Testing only has // the later. #ifdef NEON_COMPATIBILITY static bool enabled; if (!enabled) { // try libneon.so.27 first because it seems to be a bit more // common and upstream seems to use OpenSSL void *dl = dlopen("libneon.so.27", RTLD_LAZY|RTLD_GLOBAL); if (!dl) { dl = dlopen("libneon-gnutls.so.27", RTLD_LAZY|RTLD_GLOBAL); } if (dl) { enabled = true; } } #elif defined(ENABLE_DAV) static bool enabled = true; #endif isMe = sourceType.m_backend == "CalDAV" || sourceType.m_backend == "CalDAVTodo" || sourceType.m_backend == "CalDAVJournal"; if (isMe) { if (sourceType.m_format == "" || sourceType.m_format == "text/calendar" || sourceType.m_format == "text/x-calendar" || sourceType.m_format == "text/x-vcalendar") { #ifdef ENABLE_DAV if (enabled) { boost::shared_ptr settings; if (sourceType.m_backend == "CalDAV") { if (EDSAbiHaveIcal) { boost::shared_ptr sub(new CalDAVSource(params, settings)); return new MapSyncSource(params, sub); } } else { return new CalDAVVxxSource(sourceType.m_backend == "CalDAVTodo" ? "VTODO" : "VJOURNAL", params, settings); } } #endif return RegisterSyncSource::InactiveSource(params); } } isMe = sourceType.m_backend == "CardDAV"; if (isMe) { if (sourceType.m_format == "" || sourceType.m_format == "text/x-vcard" || sourceType.m_format == "text/vcard") { #ifdef ENABLE_DAV if (enabled) { boost::shared_ptr settings; return new CardDAVSource(params, settings); } #endif return RegisterSyncSource::InactiveSource(params); } } return NULL; } static class RegisterWebDAVSyncSource : public RegisterSyncSource { public: RegisterWebDAVSyncSource() : RegisterSyncSource("DAV", #ifdef ENABLE_DAV true, #else false, #endif createSource, "CalDAV\n" " calendar events\n" "CalDAVTodo\n" " tasks\n" "CalDAVJournal\n" " memos\n" "CardDAV\n" " contacts\n" , Values() + Aliases("CalDAV") + Aliases("CalDAVTodo") + Aliases("CalDAVJournal") + Aliases("CardDAV") ) { // configure and register our own property; // do this regardless whether the backend is enabled, // so that config migration always includes this property WebDAVCredentialsOkay().setHidden(true); SyncConfig::getRegistry().push_back(&WebDAVCredentialsOkay()); } } registerMe; #ifdef ENABLE_DAV #ifdef ENABLE_UNIT_TESTS class WebDAVTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(WebDAVTest); CPPUNIT_TEST(testInstantiate); CPPUNIT_TEST(testHTMLEntities); CPPUNIT_TEST_SUITE_END(); protected: void testInstantiate() { boost::shared_ptr source; source.reset((TestingSyncSource *)SyncSource::createTestingSource("CalDAV", "CalDAV", true)); source.reset((TestingSyncSource *)SyncSource::createTestingSource("CalDAV", "CalDAV:text/calendar", true)); source.reset((TestingSyncSource *)SyncSource::createTestingSource("CalDAV", "CalDAV:text/x-vcalendar", true)); source.reset((TestingSyncSource *)SyncSource::createTestingSource("CardDAV", "CardDAV", true)); source.reset((TestingSyncSource *)SyncSource::createTestingSource("CardDAV", "CardDAV:text/vcard", true)); source.reset((TestingSyncSource *)SyncSource::createTestingSource("CardDAV", "CardDAV:text/x-vcard", true)); } std::string decode(const char *item) { std::string buffer = item; CardDAVSource::replaceHTMLEntities(buffer); return buffer; } void testHTMLEntities() { // named entries CPPUNIT_ASSERT_EQUAL(std::string("\" & ' < >"), decode("" & ' < >")); // decimal and hex, encoded in different ways CPPUNIT_ASSERT_EQUAL(std::string("\" & ' < >"), decode("" & ' < >")); // no translation needed CPPUNIT_ASSERT_EQUAL(std::string("hello world"), decode("hello world")); // entity at start CPPUNIT_ASSERT_EQUAL(std::string("< "), decode("< ")); // entity at end CPPUNIT_ASSERT_EQUAL(std::string(" <"), decode(" <")); // double quotation CPPUNIT_ASSERT_EQUAL(std::string("\\"), decode("&#92;")); CPPUNIT_ASSERT_EQUAL(std::string("ampersand entity & less-than entity <"), decode("ampersand entity & less-than entity &lt;")); // invalid entities CPPUNIT_ASSERT_EQUAL(std::string(" &"), decode(" &")); CPPUNIT_ASSERT_EQUAL(std::string("&"), decode("&")); CPPUNIT_ASSERT_EQUAL(std::string("& "), decode("& ")); CPPUNIT_ASSERT_EQUAL(std::string("&;"), decode("&;")); CPPUNIT_ASSERT_EQUAL(std::string("&; "), decode("&; ")); CPPUNIT_ASSERT_EQUAL(std::string(" &; "), decode(" &; ")); CPPUNIT_ASSERT_EQUAL(std::string(" &;"), decode(" &;")); CPPUNIT_ASSERT_EQUAL(std::string("&xyz;"), decode("&xyz;")); CPPUNIT_ASSERT_EQUAL(std::string("f;"), decode("f;")); CPPUNIT_ASSERT_EQUAL(std::string("f;"), decode("f;")); CPPUNIT_ASSERT_EQUAL(std::string(" ;"), decode(" ;")); CPPUNIT_ASSERT_EQUAL(std::string("&#quot ;"), decode("&#quot ;")); } }; SYNCEVOLUTION_TEST_SUITE_REGISTRATION(WebDAVTest); #endif // ENABLE_UNIT_TESTS namespace { #if 0 } #endif /** * implements one specific source for local testing; * creates "target-config@client-test-" peer config * and source inside it before instantiating the * source */ class WebDAVTest : public RegisterSyncSourceTest { std::string m_server; std::string m_type; std::string m_database; ConfigProps m_props; public: /** * @param server for example, "yahoo", "google" * @param type "caldav", "caldavtodo", "caldavjournal" or "carddav" * @param props sync properties (username, password, syncURL, ...) * or key/value parameters for the testing (testcases) */ WebDAVTest(const std::string &server, const std::string &type, const ConfigProps &props) : RegisterSyncSourceTest(server + "_" + type, // for example, google_caldav props.get(type + "/testconfig", props.get("testconfig", type == "caldav" ? "eds_event" : type == "caldavtodo" ? "eds_task" : type == "caldavjournal" ? "eds_memo" : type == "carddav" ? "eds_contact" : type))), m_server(server), m_type(type), m_props(props) {} std::string getDatabase() const { return m_database; } void setDatabase(const std::string &database) { m_database = database; } virtual void updateConfig(ClientTestConfig &config) const { config.m_type = m_type.c_str(); if (m_type == "caldav") { config.m_supportsReccurenceEXDates = true; } config.m_sourceKnowsItemSemantic = m_type == "caldav" || m_type == "caldavjournal" || m_type == "caldavtodo"; config.m_createSourceA = boost::bind(&WebDAVTest::createSource, this, _2, _4); config.m_createSourceB = boost::bind(&WebDAVTest::createSource, this, _2, _4); ConfigProps::const_iterator it = m_props.find(m_type + "/testcases"); if (it != m_props.end() || (it = m_props.find("testcases")) != m_props.end()) { config.m_testcases = it->second.c_str(); } } // This is very similar to client-test-app.cpp. TODO: refactor?! TestingSyncSource *createSource(const std::string &clientID, bool isSourceA) const { std::string name = m_server + "_" + m_type; const char *server = getenv("CLIENT_TEST_SERVER"); std::string config = "target-config@client-test"; if (server) { config += "-"; config += server; } std::string tracking = string("_") + clientID + string("_") + (isSourceA ? "A" : "B"); SE_LOG_DEBUG(NULL, "instantiating testing source %s in config %s, with tracking name %s", name.c_str(), config.c_str(), tracking.c_str()); boost::shared_ptr context(new SyncConfig(config)); SyncSourceNodes nodes = context->getSyncSourceNodes(name, tracking); // Copy properties from the Client::Sync // @_/ config, to ensure // that a testing source used as part of Client::Sync uses the // same settings. std::string peerName = std::string(server ? server : "no-such-server") + "_" + clientID; boost::shared_ptr peer(new SyncConfig(peerName)); // Resolve credentials. SimpleUserInterface ui(peer->getKeyring()); PasswordConfigProperty::checkPasswords(ui, *peer, PasswordConfigProperty::CHECK_PASSWORD_ALL, boost::assign::list_of(name)); SyncSourceNodes peerNodes = peer->getSyncSourceNodes(name); SE_LOG_DEBUG(NULL, "overriding testing source %s properties with the ones from config %s = %s", name.c_str(), peerName.c_str(), peer->getRootPath().c_str()); BOOST_FOREACH(const ConfigProperty *prop, SyncSourceConfig::getRegistry()) { if (prop->isHidden()) { continue; } boost::shared_ptr node = peerNodes.getNode(*prop); InitStateString value = prop->getProperty(*node); SE_LOG_DEBUG(NULL, " %s = %s (%s)", prop->getMainName().c_str(), value.c_str(), value.wasSet() ? "set" : "default"); node = nodes.getNode(*prop); node->setProperty(prop->getMainName(), value); } // Also copy loglevel. context->setLogLevel(peer->getLogLevel()); context->flush(); // Always set properties taken from the environment. nodes.getProperties()->setProperty("backend", InitStateString(m_type, true)); SE_LOG_DEBUG(NULL, " additional property backend = %s (from CLIENT_TEST_WEBDAV)", m_type.c_str()); BOOST_FOREACH(const StringPair &propval, m_props) { boost::shared_ptr node = context->getNode(propval.first); if (node) { SE_LOG_DEBUG(NULL, " additional property %s = %s (from CLIENT_TEST_WEBDAV)", propval.first.c_str(), propval.second.c_str()); node->setProperty(propval.first, InitStateString(propval.second, true)); } else if (!boost::ends_with(propval.first, "testconfig") && !boost::ends_with(propval.first, "testcases")) { SE_THROW(StringPrintf("invalid property %s=%s set in CLIENT_TEST_WEBDAV for %s %s", propval.first.c_str(), propval.second.c_str(), m_server.c_str(), m_type.c_str())); } } context->flush(); SyncSourceParams params(m_type, nodes, context); SyncSource *ss = SyncSource::createSource(params); ss->setDisplayName(ss->getDisplayName() + (isSourceA ? " #A" : " #B")); return static_cast(ss); } }; /** * creates WebDAV sources by parsing * CLIENT_TEST_WEBDAV= [caldav] [carddav] = ...; ... */ static class WebDAVTestSingleton : RegisterSyncSourceTest { /** * It could be that different sources are configured to use * the same resource (= database property). Get the database * property of each source by instantiating it. Check against * already added entries and if a match is found, record the * link. This enables the Client::Source::xxx::testLinkedSources * test of that previous entry. */ class WebDAVList { list< boost::shared_ptr >m_sources; public: void push_back(const boost::shared_ptr &source) { boost::scoped_ptr instance(source->createSource("1", true)); std::string database = instance->getDatabaseID(); source->setDatabase(database); BOOST_FOREACH (const boost::shared_ptr &other, m_sources) { if (other->getDatabase() == database) { other->m_linkedSources.push_back(source->m_configName); break; } } m_sources.push_back(source); } }; mutable WebDAVList m_sources; public: WebDAVTestSingleton() : RegisterSyncSourceTest("", "") // empty, only purpose is to get init() called {} virtual void updateConfig(ClientTestConfig &config) const {} virtual void init() const { static bool initialized; if (initialized) { return; } initialized = true; const char *env = getenv("CLIENT_TEST_WEBDAV"); if (!env) { return; } std::string settings(env); boost::char_separator sep1(";"); boost::char_separator sep2("\t "); BOOST_FOREACH(const std::string &entry, boost::tokenizer< boost::char_separator >(settings, boost::char_separator(";"))) { std::string server; bool caldav = false, caldavtodo = false, caldavjournal = false, carddav = false; ConfigProps props; BOOST_FOREACH(const std::string &token, boost::tokenizer< boost::char_separator >(entry, boost::char_separator("\t "))) { if (server.empty()) { server = token; } else if (token == "caldav") { caldav = true; } else if (token == "caldavtodo") { caldavtodo = true; } else if (token == "caldavjournal") { caldavjournal = true; } else if (token == "carddav") { carddav = true; } else { size_t pos = token.find('='); if (pos == token.npos) { SE_THROW(StringPrintf("CLIENT_TEST_WEBDAV: unknown keyword %s", token.c_str())); } props[token.substr(0,pos)] = token.substr(pos + 1); } } if (caldav) { boost::shared_ptr ptr(new WebDAVTest(server, "caldav", props)); m_sources.push_back(ptr); } if (caldavtodo) { boost::shared_ptr ptr(new WebDAVTest(server, "caldavtodo", props)); m_sources.push_back(ptr); } if (caldavjournal) { boost::shared_ptr ptr(new WebDAVTest(server, "caldavjournal", props)); m_sources.push_back(ptr); } if (carddav) { boost::shared_ptr ptr(new WebDAVTest(server, "carddav", props)); m_sources.push_back(ptr); } } } } WebDAVTestSingleton; } #endif // ENABLE_DAV SE_END_CXX syncevolution_1.4/src/backends/webdav/configure-sub.in000066400000000000000000000031251230021373600232700ustar00rootroot00000000000000dnl -*- mode: Autoconf; -*- dnl Invoke autogen.sh to produce a configure script. SE_ARG_ENABLE_BACKEND(dav, webdav, [AS_HELP_STRING([--enable-dav], [enable WebDAV based backends (CalDAV) (default off)])], [enable_dav="$enableval"], [enable_dav="no"] ) if test "$enable_dav" = "yes"; then need_ical="yes" PKG_CHECK_MODULES(NEON, neon >= 0.29, [AC_DEFINE(HAVE_LIBNEON_SYSTEM_PROXY, 1, [ne_session_system_proxy() available]) AC_DEFINE(HAVE_LIBNEON_OPTIONS, 1, [ne_options2() and NE_CAP_* defines available])], [PKG_CHECK_MODULES(NEON, neon >= 0.27)]) AC_DEFINE(ENABLE_DAV, 1, [DAV available]) AC_DEFINE(ENABLE_ICAL, 1, [libical in use]) BACKEND_CPPFLAGS="$BACKEND_CPPFLAGS $NEON_CFLAGS" fi AC_ARG_ENABLE(neon-compatibility, AS_HELP_STRING([--enable-neon-compatibility], [increase compatibility with binary libneon installations by loading libneon[-gnutls].27.so dynamically instead of linking against it]), [enable_neon_compat="$enableval"], [enable_neon_compat="no"] ) if test "$enable_neon_compat" = "yes"; then AC_DEFINE(NEON_COMPATIBILITY, 1, [dynamically open libneon]) NEON_LIBS="`echo $NEON_LIBS | sed -e 's/\(-lneon\(-gnutls\)*\|[^ ]*libneon\(-gnutls\)*.la\)/-ldl/'`" fi AM_CONDITIONAL([NEON_COMPATIBILITY], [test "$enable_neon_compat" = "yes"]) syncevolution_1.4/src/backends/webdav/syncevo-webdav-lookup.sh000077500000000000000000000077521230021373600250040ustar00rootroot00000000000000#! /bin/sh # # Copyright (C) 2011 Intel Corporation # # Does DNS SRV and TXT queries to find the full URL, including method # (HTTP or HTTPS), host name, port and path (currently hard-coded to # .well-known/, should use TXT, except that none of the current # services seem to use that either, so couldn't test). # # See http://tools.ietf.org/html/draft-daboo-srv-caldav-10 # # Works with a variety of underlying utilities: # - adnshost # - host # - nslookup # # Usage: syncevo-webdav-lookup.sh carddav|caldav # Stdout: http[s]://:/ # Stderr: error messages indicating failure, empty for success # Return codes: # 1 general error # 2 no DNS utility found # 3 no result for domain # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) version 3. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA # set -o pipefail TYPE=$1 DOMAIN=$2 # Dump diagnostics on exit, if it still exists. LOG=`mktemp` trap "[ -s $LOG ] && ( echo $0 failed to find '$TYPE $DOMAIN':; cat $LOG >&2 ); rm -f $LOG" EXIT # find one of the supported tools for DNS queries TOOL= ALTERNATIVES="adnshost host /usr/lib/syncevolution/host nslookup" for i in $ALTERNATIVES; do if which $i >/dev/null; then TOOL=$i break fi done if [ ! "$TOOL" ]; then echo "need one of: $ALTERNATIVES" >&2 exit 2 fi # redirect tool errors and commands executed into log; # try HTTPS first for type in ${TYPE}s ${TYPE}; do ( case $type in *s) METHOD=https;; *) METHOD=http;; esac HOSTNAME= PORT= # should be looked up via TXT, currently hard-coded URLPATH=.well-known/$TYPE set -xeo pipefail case $TOOL in adnshost) res=`$TOOL -Fi -tsrv _$type._tcp.$DOMAIN | tee -a $LOG | grep ^_$type._tcp.$DOMAIN` # _carddavs._tcp.yahoo.com.cn SRV 1 1 443 carddav.address.yahoo.com misconfig 101 prohibitedcname "DNS alias found where canonical name wanted" ( ) PORT=`echo $res | sed -e 's;.* SRV [^ ]* [^ ]* \([^ ]*\) \([^ ]*\).*;\1;'` HOSTNAME=`echo $res | sed -e 's;.* SRV [^ ]* [^ ]* \([^ ]*\) \([^ ]*\).*;\2;'` ;; nslookup) res=`$TOOL -type=srv _$type._tcp.$DOMAIN | tee -a $LOG | grep "service =" | head -1` # _caldavs._tcp.yahoo.com service = 1 1 443 caldav.calendar.yahoo.com. PORT=`echo $res | sed -e 's;.*service = [^ ]* [^ ]* \([^ ]*\) \([^ ]*\)\.;\1;'` HOSTNAME=`echo $res | sed -e 's;.*service = [^ ]* [^ ]* \([^ ]*\) \([^ ]*\)\.;\2;'` ;; host|*/host) res=`$TOOL -t srv _$type._tcp.$DOMAIN | tee -a $LOG | grep "\" | head -1` # _caldavs._tcp.yahoo.com has SRV record 1 1 443 caldav.calendar.yahoo.com. # _caldavs._tcp.yahoo.com SRV 1 1 443 caldav.calendar.yahoo.com PORT=`echo $res | sed -e 's;.* \([^ ]*\) \([^. ]\+\(\.[^. ]\+\)*\)\.\?;\1;'` HOSTNAME=`echo $res | sed -e 's;.* \([^ ]*\) \([^. ]\+\(\.[^. ]\+\)*\)\.\?;\2;'` ;; *) echo "unsupported tool $TOOL" exit 1 ;; esac # print result echo $METHOD://$HOSTNAME:$PORT/$URLPATH ) 2>>$LOG if [ $? -eq 0 ]; then # success, discard errrors rm -f $LOG exit 0 fi done # nothing worked exit 2 syncevolution_1.4/src/backends/webdav/webdav.am000066400000000000000000000042331230021373600217600ustar00rootroot00000000000000dist_noinst_DATA += \ src/backends/webdav/configure-sub.in \ src/backends/webdav/WebDAVSourceRegister.cpp \ src/backends/webdav/syncevo-webdav-lookup.sh \ src/backends/webdav/README src_backends_webdav_lib = src/backends/webdav/syncdav.la MOSTLYCLEANFILES += $(src_backends_webdav_lib) if ENABLE_MODULES src_backends_webdav_backenddir = $(BACKENDS_DIRECTORY) src_backends_webdav_backend_LTLIBRARIES = $(src_backends_webdav_lib) else noinst_LTLIBRARIES += $(src_backends_webdav_lib) endif bin_SCRIPTS += src/backends/webdav/syncevo-webdav-lookup CLEANFILES += src/backends/webdav/syncevo-webdav-lookup src/backends/webdav/syncevo-webdav-lookup: $(srcdir)/src/backends/webdav/syncevo-webdav-lookup.sh $(AM_V_GEN)rm -f $@ ; \ cd src/backends/webdav && ln -s $(notdir $<) $(notdir $@) src_backends_webdav_src = \ src/backends/webdav/CalDAVSource.h \ src/backends/webdav/CalDAVSource.cpp \ src/backends/webdav/CalDAVVxxSource.h \ src/backends/webdav/CalDAVVxxSource.cpp \ src/backends/webdav/CardDAVSource.h \ src/backends/webdav/CardDAVSource.cpp \ src/backends/webdav/WebDAVSource.h \ src/backends/webdav/WebDAVSource.cpp \ src/backends/webdav/NeonCXX.h \ src/backends/webdav/NeonCXX.cpp src_backends_webdav_syncdav_la_SOURCES = $(src_backends_webdav_src) src_backends_webdav_syncdav_la_LIBADD = $(NEON_LIBS) $(SYNCEVOLUTION_LIBS) $(LIBICAL_LIBS) src_backends_webdav_syncdav_la_LDFLAGS = -module -avoid-version src_backends_webdav_syncdav_la_CXXFLAGS = $(NEON_CFLAGS) $(SYNCEVO_WFLAGS) $(LIBICAL_CFLAGS) src_backends_webdav_syncdav_la_CPPFLAGS = $(SYNCEVOLUTION_CFLAGS) -I$(top_srcdir)/test $(BACKEND_CPPFLAGS) src_backends_webdav_syncdav_la_DEPENDENCIES = src/syncevo/libsyncevolution.la if NEON_COMPATIBILITY all_local_installchecks += webdav_so_check endif WEBDAV_SO_CHECK_FILES = $(DESTDIR)/$(bindir)/syncevolution if ENABLE_MODULES WEBDAV_SO_CHECK_FILES += $(DESTDIR)/$(src_backends_webdav_backenddir)/syncdav.so endif webdav_so_check: for i in $(WEBDAV_SO_CHECK_FILES); do \ if [ -e $$i ]; then \ if ldd $$i | grep libneon; then \ echo $$i should not be linked against libneon; exit 1; \ fi; \ else \ echo $$i not found; exit 1; \ fi \ done syncevolution_1.4/src/backends/xmlrpc/000077500000000000000000000000001230021373600202245ustar00rootroot00000000000000syncevolution_1.4/src/backends/xmlrpc/README000066400000000000000000000124541230021373600211120ustar00rootroot00000000000000== Summary == This is a backend to use a web application, probably a webmail or PIM application, as data store. The communication is done using XMLRPC. == Installation == This backend requires the Xmlrpc-c libraries from http://xmlrpc-c.sourceforge.net Precompiled packages exist for many Linux distributions, on Ubuntu or Debian, they development package is installed by: apt-get install libxmlrpc-c3-dev To compile this backend as part of SyncEvolution, configure with --enable-xmlrpc. == Configuration == The type parameter is similar to that on the file backend, e.g. type = xmlrpc:text/calendar:2.0 MIME type and version must be set according to the implementation on the XMLRPC interface side. The evolutionsource parameter contains the information of the XMLRPC service, e.g. evolutionsource = http://hostname/xmlrpc|database|username|password The parameter contains several fields, delimited by a pipe character. The parameters after the URL are used as the first parameters in every XMLRPC request. Their number is not fixed, it depends on the implementation of the XMLRPC interface. == XMLRPC interface == The parameters after the URL in the evolutionsource parameter are passed as the first parameters of the XMLRPC request. Their number is not limited by the backend, the number may even be 0, if the respective information is given as part of the URL (or not necessary). This set of parameters is represented in the specification below with "...". All parameters are sent and expected as string values. Four methods have to implemented in the XMLRPC interface: * listAllItems(...) Returns a list of id with some kind of version information, e.g. timestamp of the last modification, as XMLRPC struct. The id is a string value. * readItem(..., id) Reads a single item from the data store. The return value contains a string containing the element as vcard or icalender object. * insertItem(..., id, object) Creates a new or updates an existing object in the data store. With an empty id parameter, a new object will be created. The return value contains a struct, equal to that of listAllItems, but with only one entry, namely the updated or created one. * removeItem(..., id) Removes an object from the data store. The return value is ignored by the backend. == Sample XMLRPC snippets == The following snippets of the method requests and response shall explain the used data types. If incorrect datatypes (e.g. int value instead of string) are used, the communication will fail. * listAllItems XML-RPC CALL: listAllItems addr testmb04 *secret* XML-RPC RESPONSE: 48541259607642 48551259605716 48561259842773 48581259610815 48571259605717 * readItem XML-RPC CALL: readItem addr testmb04 *secret* 4855 XML-RPC RESPONSE: BEGIN:VCARD VERSION:3.0 N:Knipp;Franz;;; FN:Franz Knipp EMAIL:knipp@m-otion.com END:VCARD * insertItem XML-RPC CALL: insertItem addr testmb04 *secret* BEGIN:VCARD VERSION:3.0 FN:Patrick Ohly N:Ohly;Patrick;;; EMAIL:patrick.ohly@intel.com END:VCARD XML-RPC RESPONSE: 48631260216497 * removeItem XML-RPC CALL: removeItem addr testmb04 *secret* 4863 XML-RPC RESPONSE: syncevolution_1.4/src/backends/xmlrpc/XMLRPCSyncSource.cpp000066400000000000000000000113561230021373600237610ustar00rootroot00000000000000/* * Copyright (C) 2009 m-otion communications GmbH , waived * Copyright (C) 2007-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "config.h" #ifdef ENABLE_XMLRPC #include "XMLRPCSyncSource.h" #include #include #include SE_BEGIN_CXX XMLRPCSyncSource::XMLRPCSyncSource(const SyncSourceParams ¶ms, const string &dataformat) : TrackingSyncSource(params) { if (dataformat.empty()) { throwError("a data format must be specified"); } size_t sep = dataformat.find(':'); if (sep == dataformat.npos) { throwError(string("data format not specified as :: " + dataformat)); } m_mimeType.assign(dataformat, 0, sep); m_mimeVersion = dataformat.substr(sep + 1); m_supportedTypes = dataformat; string const dbid = getDatabaseID(); boost::split(m_splitDatabase, dbid, boost::is_from_range('|', '|')); m_serverUrl = m_splitDatabase[0]; } std::string XMLRPCSyncSource::getMimeType() const { return m_mimeType.c_str(); } std::string XMLRPCSyncSource::getMimeVersion() const { return m_mimeVersion.c_str(); } void XMLRPCSyncSource::open() { } bool XMLRPCSyncSource::isEmpty() { // TODO: provide a real implementation. Always returning false // here disables the "allow slow sync when no local data" heuristic // for preventSlowSync=1. return false; } void XMLRPCSyncSource::close() { } XMLRPCSyncSource::Databases XMLRPCSyncSource::getDatabases() { Databases result; result.push_back(Database("select database via URL", "")); return result; } void XMLRPCSyncSource::listAllItems(RevisionMap_t &revisions) { xmlrpc_c::value result; client.call(m_serverUrl, "listAllItems", prepareParamList(), &result); if(result.type() == xmlrpc_c::value::TYPE_STRUCT) { xmlrpc_c::value_struct const tmp(result); map const resultMap( static_cast >(tmp)); map::const_iterator it; for(it = resultMap.begin(); it != resultMap.end(); ++it) revisions[(*it).first] = xmlrpc_c::value_string((*it).second); } } void XMLRPCSyncSource::readItem(const string &uid, std::string &item, bool raw) { xmlrpc_c::paramList p = prepareParamList(); p.add(xmlrpc_c::value_string(uid)); xmlrpc_c::value result; client.call(m_serverUrl, "readItem", p, &result); item = xmlrpc_c::value_string(result); } TrackingSyncSource::InsertItemResult XMLRPCSyncSource::insertItem(const string &uid, const std::string &item, bool raw) { xmlrpc_c::paramList p = prepareParamList(); p.add(xmlrpc_c::value_string(uid)); p.add(xmlrpc_c::value_string(item)); xmlrpc_c::value result; client.call(m_serverUrl, "insertItem", p, &result); xmlrpc_c::value_struct const tmp(result); map const resultMap( static_cast >(tmp)); if(resultMap.size() != 1) { throw("Return value of insertItem has wrong length."); } map::const_iterator it; it = resultMap.begin(); return InsertItemResult((*it).first, xmlrpc_c::value_string((*it).second), ITEM_OKAY); } void XMLRPCSyncSource::removeItem(const string &uid) { xmlrpc_c::paramList p = prepareParamList(); p.add(xmlrpc_c::value_string(uid)); xmlrpc_c::value result; client.call(m_serverUrl, "removeItem", p, &result); } xmlrpc_c::paramList XMLRPCSyncSource::prepareParamList() { xmlrpc_c::paramList p; for(size_t i = 1; i < m_splitDatabase.size(); i++) { p.add(xmlrpc_c::value_string(m_splitDatabase[i])); } return p; } SE_END_CXX #endif /* ENABLE_XMLRPC */ #ifdef ENABLE_MODULES # include "XMLRPCSyncSourceRegister.cpp" #endif syncevolution_1.4/src/backends/xmlrpc/XMLRPCSyncSource.h000066400000000000000000000046341230021373600234270ustar00rootroot00000000000000/* * Copyright (C) 2009 m-otion communications GmbH , waived * Copyright (C) 2007-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_XMLRPCSYNCSOURCE #define INCL_XMLRPCSYNCSOURCE #include #ifdef ENABLE_XMLRPC #include #include #include SE_BEGIN_CXX class XMLRPCSyncSource : public TrackingSyncSource { public: XMLRPCSyncSource(const SyncSourceParams ¶ms, const string &dataformat); protected: /* implementation of SyncSource interface */ virtual void open(); virtual bool isEmpty(); virtual void close(); virtual Databases getDatabases(); virtual std::string getMimeType() const; virtual std::string getMimeVersion() const; /* implementation of TrackingSyncSource interface */ virtual void listAllItems(RevisionMap_t &revisions); virtual InsertItemResult insertItem(const string &luid, const std::string &item, bool raw); void readItem(const std::string &luid, std::string &item, bool raw); virtual void removeItem(const string &uid); private: /** * @name values obtained from the source's type property * * Other sync sources only support one hard-coded type and * don't need such variables. */ /**@{*/ string m_mimeType; string m_mimeVersion; string m_supportedTypes; /**@}*/ /** @name values obtained from the database name */ string m_serverUrl; vector m_splitDatabase; xmlrpc_c::paramList prepareParamList(); xmlrpc_c::clientSimple client; }; SE_END_CXX #endif // ENABLE_XMLRPC #endif // INCL_XMLRPCSYNCSOURCE syncevolution_1.4/src/backends/xmlrpc/XMLRPCSyncSourceRegister.cpp000066400000000000000000000132251230021373600254630ustar00rootroot00000000000000/* * Copyright (C) 2009 m-otion communications GmbH , waived * Copyright (C) 2008-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "XMLRPCSyncSource.h" #include "test.h" #include SE_BEGIN_CXX static SyncSource *createSource(const SyncSourceParams ¶ms) { SourceType sourceType = SyncSource::getSourceType(params.m_nodes); // The string returned by getSourceType() is always the one // registered as main Aliases() below. bool isMe = sourceType.m_backend == "XMLRPC interface"; #ifndef ENABLE_XMLRPC // tell SyncEvolution if the user wanted to use a disabled sync source, // otherwise let it continue searching return isMe ? RegisterSyncSource::InactiveSource(params) : NULL; #else // Also recognize one of the standard types? // Not in the FileSyncSource! bool maybeMe = false /* sourceType.m_backend == "addressbook" */; if (isMe || maybeMe) { // The FileSyncSource always needs the data format // parameter in sourceType.m_format. if (/* sourceType.m_format == "" || sourceType.m_format == "text/x-vcard" */ sourceType.m_format.size()) { return new XMLRPCSyncSource(params, sourceType.m_format); } else { return NULL; } } return NULL; #endif } static RegisterSyncSource registerMe("XMLRPC interface for data exchange", #ifdef ENABLE_XMLRPC true, #else false, #endif createSource, "XMLRPC interface = xmlrpc\n" " Data exchange is done via an XMLRPC interface on the datastore.\n", Values() + (Aliases("XMLRPC interface") + "xmlrpc")); #ifdef ENABLE_XMLRPC #ifdef ENABLE_UNIT_TESTS class XMLRPCSyncSourceUnitTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(XMLRPCSyncSourceUnitTest); CPPUNIT_TEST(testInstantiate); CPPUNIT_TEST_SUITE_END(); protected: void testInstantiate() { boost::shared_ptr source; source.reset(SyncSource::createTestingSource("xmlrpc", "xmlrpc:text/vcard:3.0", true)); source.reset(SyncSource::createTestingSource("xmlrpc", "xmlrpc:text/plain:1.0", true)); source.reset(SyncSource::createTestingSource("xmlrpc", "XMLRPC interface:text/x-vcard:2.1", true)); } }; SYNCEVOLUTION_TEST_SUITE_REGISTRATION(XMLRPCSyncSourceUnitTest); #endif // ENABLE_UNIT_TESTS #ifdef ENABLE_INTEGRATION_TESTS // The anonymous namespace ensures that we don't get // name clashes: although the classes and objects are // only defined in this file, the methods generated // for local classes will clash with other methods // of other classes with the same name if no namespace // is used. // // The code above is not yet inside the anonymous // name space because it would show up inside // the CPPUnit unit test names. Use a unique class // name there. namespace { #if 0 } #endif static class VCard21Test : public RegisterSyncSourceTest { public: VCard21Test() : RegisterSyncSourceTest("xmlrpc_contact", "eds_contact") {} virtual void updateConfig(ClientTestConfig &config) const { // set type as required by XMLRPCSyncSource // and leave everything else at its default config.type = "xmlrpc:text/x-vcard:2.1"; } } VCard21Test; static class VCard30Test : public RegisterSyncSourceTest { public: VCard30Test() : RegisterSyncSourceTest("xmlrpc_contact", "eds_contact") {} virtual void updateConfig(ClientTestConfig &config) const { config.type = "xmlrpc:text/vcard:3.0"; } } VCard30Test; static class ICal20Test : public RegisterSyncSourceTest { public: ICal20Test() : RegisterSyncSourceTest("xmlrpc_event", "eds_event") {} virtual void updateConfig(ClientTestConfig &config) const { config.type = "xmlrpc:text/calendar:2.0"; // A sync source which supports linked items (= recurring // event with detached exception) is expected to handle // inserting the parent or child twice by turning the // second operation into an update. The file backend is // to dumb for that and therefore fails these tests: // // Client::Source::xmlrpc_event::testLinkedItemsInsertParentTwice // Client::Source::xmlrpc_event::testLinkedItemsInsertChildTwice // // Disable linked item testing to avoid this. config.sourceKnowsItemSemantic = false; } } ICal20Test; static class ITodo20Test : public RegisterSyncSourceTest { public: ITodo20Test() : RegisterSyncSourceTest("xmlrpc_task", "eds_task") {} virtual void updateConfig(ClientTestConfig &config) const { config.type = "xmlrpc:text/calendar:2.0"; } } ITodo20Test; } #endif // ENABLE_INTEGRATION_TESTS #endif // ENABLE_XMLRPC SE_END_CXX syncevolution_1.4/src/backends/xmlrpc/configure-sub.in000066400000000000000000000035561230021373600233350ustar00rootroot00000000000000dnl -*- mode: Autoconf; -*- dnl Invoke autogen.sh to produce a configure script. dnl Checks for required libraries. dnl dnl This is from the sqlite backend: dnl PKG_CHECK_MODULES(SQLITE, sqlite3, SQLITEFOUND=yes, [SQLITEFOUND=no]) dnl AC_SUBST(SQLITE_CFLAGS) dnl AC_SUBST(SQLITE_LIBS) dnl No pkg-config available for xmlrpc-c dnl PKG_CHECK_MODULES(XMLRPC,xmlrpc-c,XMLRPCFOUND=yes, [XMLRPCFOUND=no]) dnl name of backend library (there could be more than one per directory), dnl name of the directory, dnl help string, dnl --enable/disable chosen explicitly dnl default, may depend on availability of prerequisites in more complex backends SE_ARG_ENABLE_BACKEND(xmlrpc, xmlrpc, [AS_HELP_STRING([--enable-xmlrpc], [enable XMLRPC-based backend which stores items on a central web service (default off)])], [enable_xmlrpc="$enableval"], [enable_xmlrpc="no"] ) if test "$enable_xmlrpc" = "yes"; then dnl It's good to check the prerequisites here, in case --enable-xmlrpc was used. dnl test "x${SQLITEFOUND}" = "xyes" || AC_MSG_ERROR([--enable-sqlite requires pkg-config information for sqlite3, which was not found]) AC_PATH_PROG([XMLRPC_C_CONFIG], [xmlrpc-c-config], [no]) test "x$XMLRPC_C_CONFIG" != 'xno' || AC_MSG_ERROR([--enable-xmlrpc requires xmlrpc-c-config, which was not found]) XMLRPC_CFLAGS=`$XMLRPC_C_CONFIG c++2 client --cflags` XMLRPC_LIBS=`$XMLRPC_C_CONFIG c++2 client --libs` AC_SUBST(XMLRPC_CFLAGS) AC_SUBST(XMLRPC_LIBS) dnl If additional compile flags are necessary to include the header dnl files of the backend, then add them here. BACKEND_CPPFLAGS="$BACKEND_CPPFLAGS $XMLRPC_CFLAGS" AC_DEFINE(ENABLE_XMLRPC, 1, [XMLRPC available]) fi syncevolution_1.4/src/backends/xmlrpc/xmlrpc.am000066400000000000000000000025741230021373600220600ustar00rootroot00000000000000dist_noinst_DATA += \ src/backends/xmlrpc/configure-sub.in \ src/backends/xmlrpc/README src_backends_xmlrpc_lib = src/backends/xmlrpc/syncxmlrpc.la MOSTLYCLEANFILES += $(src_backends_xmlrpc_lib) if ENABLE_MODULES src_backends_xmlrpc_backenddir = $(BACKENDS_DIRECTORY) src_backends_xmlrpc_backend_LTLIBRARIES = $(src_backends_xmlrpc_lib) else noinst_LTLIBRARIES += $(src_backends_xmlrpc_lib) endif src_backends_xmlrpc_src = \ src/backends/xmlrpc/XMLRPCSyncSource.h \ src/backends/xmlrpc/XMLRPCSyncSource.cpp src_backends_xmlrpc_syncxmlrpc_la_SOURCES = $(src_backends_xmlrpc_src) src_backends_xmlrpc_syncxmlrpc_la_LIBADD = $(XMLRPC_LIBS) $(SYNCEVOLUTION_LIBS) src_backends_xmlrpc_syncxmlrpc_la_LDFLAGS = -module -avoid-version src_backends_xmlrpc_syncxmlrpc_la_CXXFLAGS = $(SYNCEVOLUTION_CXXFLAGS) $(SYNCEVO_WFLAGS) src_backends_xmlrpc_syncxmlrpc_la_CPPFLAGS = $(SYNCEVOLUTION_CFLAGS) -I$(top_srcdir)/test $(BACKEND_CPPFLAGS) src_backends_xmlrpc_syncxmlrpc_la_DEPENDENCIES = src/syncevo/libsyncevolution.la # If you need special test cases for your sync source, then # install them here. Here's how the sqlite backend does that: # #../../testcases/sqlite_vcard21.vcf: $(FUNAMBOL_SUBDIR)/test/test/testcases/vcard21.vcf # mkdir -p ${@D} # perl -e '$$_ = join("", <>); s/^(ADR|TEL|EMAIL|PHOTO).*?(?=^\S)//msg; s/;X-EVOLUTION-UI-SLOT=\d+//g; print;' $< >$@ #all: ../../testcases/sqlite_vcard21.vcf syncevolution_1.4/src/client-test-app.cpp000066400000000000000000000550101230021373600206630ustar00rootroot00000000000000/* * Copyright (C) 2005-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "config.h" #include #include #include #include #include #include #include "CmdlineSyncClient.h" #include #include #include #include #include SE_BEGIN_CXX /* * always provide this test class, even if not used: * that way the test scripts can unconditionally * invoke "client-test SyncEvolution" */ CPPUNIT_REGISTRY_ADD_TO_DEFAULT("SyncEvolution"); class EvolutionLocalTests : public LocalTests { public: EvolutionLocalTests(const std::string &name, ClientTest &cl, int sourceParam, ClientTest::Config &co) : LocalTests(name, cl, sourceParam, co) {} virtual void addTests() { LocalTests::addTests(); } }; /** * This is a workaround for libecal/libebook in Evolution >= 2.30. * The storage daemons shut down after only 10 seconds of no client * being attached. Due to limitations in libecal/libebook this is not * detected when only using the synchronous API ("destroyed" signal * not delivered, see e-cal.c), which then leads to D-Bus errors. * * The workaround consists of keeping one open SyncEvolution backend * around for each of eds_event and eds_contact/30, if they ever were used * during testing. */ static map > lockEvolution; static void CleanupSources() { lockEvolution.clear(); } /** * This code uses the ClientTest and and information provided by * the backends in their RegisterSyncSourceTest instances to test * real synchronization with a server. * * Configuration is done by environment variables which indicate which * part below the root node "client-test" of the the configuration tree to use; * beyond that everything needed for synchronization is read from the * configuration tree. * * - CLIENT_TEST_SERVER = maps to name of root node in configuration tree * - CLIENT_TEST_EVOLUTION_PREFIX = a common "evolutionsource" prefix for *all* * sources; the source name followed by "_[12]" * is appended to get unique names * - CLIENT_TEST_EVOLUTION_USER = sets the "evolutionuser" property of all sources * - CLIENT_TEST_EVOLUTION_PASSWORD = sets the "evolutionpassword" property of all sources * - CLIENT_TEST_SOURCES = comma separated list of active sources, * names as selected in their RegisterSyncSourceTest * instances * - CLIENT_TEST_DELAY = number of seconds to sleep between syncs, required * by some servers * - CLIENT_TEST_LOG = logfile name of a server, can be empty: * if given, then the content of that file will be * copied and stored together with the client log * (only works on Unix) * - CLIENT_TEST_NUM_ITEMS = numbers of contacts/events/... to use during * local and sync tests which create artificial * items * * The CLIENT_TEST_SERVER also has another meaning: it is used as hint * by the synccompare.pl script and causes it to automatically ignore * known, acceptable data modifications caused by sending an item to * a server and back again. Currently the script recognizes "funambol", * "scheduleworld", "synthesis" and "egroupware" as special server * names. */ class TestEvolution : public ClientTest { public: /** * can be instantiated as client A with id == "1" and client B with id == "2" */ TestEvolution(const string &id) : ClientTest(atoi(getEnv("CLIENT_TEST_DELAY", "0")), getEnv("CLIENT_TEST_LOG", "")), m_initialized(false), m_clientID(id), m_configs(SyncSource::getTestRegistry()) { } virtual std::string getClientID() const { return m_clientID; } /** * code depends on other global constructors to run first, execute it after constructor but before * any other methods */ void init() { if (m_initialized) { return; } else { m_initialized = true; } // allow each backend test to create more backend tests size_t count = 0; while (count != m_configs.size()) { count = m_configs.size(); BOOST_FOREACH (const RegisterSyncSourceTest *test, TestRegistry(m_configs)) { test->init(); } } const char *server = getenv("CLIENT_TEST_SERVER"); if (m_clientID == "1") { m_clientB.reset(new TestEvolution("2")); } /* check server */ if (!server) { server = "funambol"; setenv("CLIENT_TEST_SERVER", "funambol", 1); } /* override Evolution database names? */ const char *evoprefix = getenv("CLIENT_TEST_EVOLUTION_PREFIX"); m_evoPrefix = evoprefix ? evoprefix : "SyncEvolution_Test_"; const char *evouser = getenv("CLIENT_TEST_EVOLUTION_USER"); if (evouser) { m_evoUser = evouser; } const char *evopasswd = getenv("CLIENT_TEST_EVOLUTION_PASSWORD"); if (evopasswd) { m_evoPassword = evopasswd; } /* check sources */ const char *sourcelist = getenv("CLIENT_TEST_SOURCES"); set sources; if (sourcelist) { boost::split(sources, sourcelist, boost::is_any_of(",")); } else { BOOST_FOREACH(const RegisterSyncSourceTest *test, m_configs) { if (!test->m_configName.empty()) { sources.insert(test->m_configName); } } } BOOST_FOREACH(const RegisterSyncSourceTest *test, m_configs) { if (sources.find(test->m_configName) != sources.end() && !test->m_configName.empty()) { m_syncSource2Config.push_back(test->m_configName); } } /* Local Test SyncSource : remove all virtual datastores, inserting the * sub datastores*/ ClientTest::Config conf; BOOST_FOREACH (string source, sources) { getSourceConfig (source, conf); if (!conf.m_subConfigs.empty()) { vector subs; boost::split (subs, conf.m_subConfigs, boost::is_any_of(",")); BOOST_FOREACH (string sub, subs) { pushLocalSource2Config(sub); } } else { pushLocalSource2Config(source); } } // get configuration and set obligatory fields Logger::instance().setLevel(Logger::DEBUG); std::string root = std::string("evolution/") + server + "_" + m_clientID; boost::shared_ptr config(new SyncConfig(string(server) + "_" + m_clientID)); boost::shared_ptr from = boost::shared_ptr (); if (!config->exists()) { // no configuration yet, create in different contexts because // device ID is different config.reset(new SyncConfig(string(server) + "_" + m_clientID + "@client-test-" + m_clientID)); config->setDefaults(); from = SyncConfig::createPeerTemplate(server); if(from) { set filter; config->copy(*from, &filter); } config->setDevID(m_clientID == "1" ? "sc-api-nat" : "sc-pim-ppc"); } BOOST_FOREACH(const RegisterSyncSourceTest *test, m_configs) { if (test->m_configName.empty()) { continue; } ClientTest::Config testconfig; getSourceConfig(test, testconfig); CPPUNIT_ASSERT(!testconfig.m_type.empty()); boost::shared_ptr sc = config->getSyncSourceConfig(testconfig.m_sourceName); if (!sc || !sc->exists()) { // no configuration yet config->setSourceDefaults(testconfig.m_sourceName); sc = config->getSyncSourceConfig(testconfig.m_sourceName); CPPUNIT_ASSERT(sc); sc->setURI(testconfig.m_uri); if(from && !testconfig.m_sourceNameServerTemplate.empty()) { boost::shared_ptr scServerTemplate = from->getSyncSourceConfig(testconfig.m_sourceNameServerTemplate); sc->setURI(scServerTemplate->getURI()); } } // Set these properties if not set yet: that means the env // variables are used when creating the config initially, // but then no longer can be used to change the config. // This prevents accidentally running a test with default // values, for example for the database. if (!sc->getDatabaseID().wasSet()) { string database = getDatabaseName(test->m_configName); sc->setDatabaseID(database); } if (!sc->getUser().wasSet() && !m_evoUser.empty()) { sc->setUsername(m_evoUser); } if (!sc->getPassword().wasSet() && !m_evoPassword.empty()) { sc->setPassword(m_evoPassword); } // Always set this one, to ensure the config matches the test. sc->setBackend(SourceType(testconfig.m_type).m_backend); } config->flush(); } virtual LocalTests *createLocalTests(const std::string &name, int sourceParam, ClientTest::Config &co) { init(); return new EvolutionLocalTests(name, *this, sourceParam, co); } virtual int getNumLocalSources() { init(); return m_localSource2Config.size(); } virtual int getNumSyncSources() { init(); return m_syncSource2Config.size(); } virtual void getLocalSourceConfig(int source, Config &config) { init(); getSourceConfig(m_configs[m_localSource2Config[source]], config); } virtual void getSyncSourceConfig(int source, Config &config) { init(); getSourceConfig(m_configs[m_syncSource2Config[source]], config); } virtual int getLocalSourcePosition(const string &configName) { for (size_t i=0; i< m_localSource2Config.size(); i++) { if(m_localSource2Config[i] == configName) { return i; break; } } return -1; } virtual void getSourceConfig (const string &configName, Config &config) { init(); return getSourceConfig (m_configs[configName], config); } static void getSourceConfig(const RegisterSyncSourceTest *test, Config &config) { ClientTest::getTestData(test->m_testCaseName.c_str(), config); config.m_createSourceA = createSource; config.m_createSourceB = createSource; config.m_sourceName = test->m_configName.c_str(); config.m_linkedSources = test->m_linkedSources; test->updateConfig(config); } virtual ClientTest *getClientB() { init(); return m_clientB.get(); } virtual bool isB64Enabled() { return false; } virtual SyncMLStatus doSync(const int *sources, const std::string &logbase, const SyncOptions &options) { init(); // Let "Client_Sync_Current" symlink point to a new, empty // directory logbase + ".server". Can be used by SyncEvolution // server as per-test logdir. std::string current = logbase + ".server"; rm_r(current); mkdir_p(current); rm_r("Client_Sync_Current"); symlink(current.c_str(), "Client_Sync_Current"); string server = getenv("CLIENT_TEST_SERVER") ? getenv("CLIENT_TEST_SERVER") : "funambol"; server += "_"; server += m_clientID; class ClientTest : public CmdlineSyncClient { public: ClientTest(const string &server, const string &logbase, const SyncOptions &options) : CmdlineSyncClient(server, false), m_logbase(logbase), m_options(options), m_started(false) {} protected: virtual void prepare() { setLogDir(m_logbase, true); setMaxLogDirs(0, true); setMaxObjSize(m_options.m_maxObjSize, true); setMaxMsgSize(m_options.m_maxMsgSize, true); setWBXML(m_options.m_isWBXML, true); setRetryDuration(m_options.m_retryDuration, true); setRetryInterval(m_options.m_retryInterval, true); if (m_options.m_syncMode == SYNC_TWO_WAY && m_options.m_checkReport.syncMode == SYNC_NONE) { // For this test, any kind of final sync mode is // acceptable. Disable slow sync prevention // temporarily. The check for the requested sync // mode is perhaps too conservative, but in // practice the only test where slow sync // prevention caused a test failure was // Client::Sync::eds_contact::testTwoWaySync after // some other failed test, so let's be conservative... setPreventSlowSync(false); } SyncContext::prepare(); if (m_options.m_prepareCallback && m_options.m_prepareCallback(*this, m_options)) { m_options.m_isAborted = SuspendFlags::getSuspendFlags().abort(); } } virtual void displaySyncProgress(sysync::TProgressEventEnum type, int32_t extra1, int32_t extra2, int32_t extra3) { if (!m_started) { m_started = true; if (m_options.m_startCallback(*this, m_options)) { m_options.m_isAborted = SuspendFlags::getSuspendFlags().abort(); } } } virtual boost::shared_ptr createTransportAgent() { boost::shared_ptrwrapper = m_options.m_transport; boost::shared_ptragent =SyncContext::createTransportAgent(); if (!wrapper.get()) return agent; dynamic_cast(wrapper.get())->setAgent(agent); dynamic_cast(wrapper.get())->setSyncOptions(&m_options); return wrapper; } private: const string m_logbase; SyncOptions m_options; bool m_started; } client(server, logbase, options); // configure active sources with the desired sync mode, // disable the rest FilterConfigNode::ConfigFilter filter; filter["sync"] = InitStateString("none", true); client.setConfigFilter(false, "", filter); filter["sync"] = InitStateString(PrettyPrintSyncMode(options.m_syncMode), true); for(int i = 0; sources[i] >= 0; i++) { std::string &name = m_syncSource2Config[sources[i]]; client.setConfigFilter(false, name, filter); checkEvolutionSource(name); } SyncReport report; SyncMLStatus status = client.sync(&report); options.m_checkReport.check(status, report); return status; } private: bool m_initialized; string m_clientID; std::auto_ptr m_clientB; const TestRegistry &m_configs; /** prefix, username, password to be used for local databases */ string m_evoPrefix, m_evoUser, m_evoPassword; /** * The ClientTest framework identifies active configs with an integer. * This is the mapping to the corresponding config name, created when * constructing this instance. */ vector m_localSource2Config; vector m_syncSource2Config; /** returns the name of the Evolution database */ string getDatabaseName(const string &configName) { if (configName == "calendar+todo") { return "eds_event,eds_task"; } else if (configName == "file_calendar+todo") { return "file_event,file_task"; } return m_evoPrefix + configName + "_" + m_clientID; } /** called by test frame work */ static TestingSyncSource *createSource(ClientTest &client, const std::string &clientID, int source, bool isSourceA) { TestEvolution &evClient((TestEvolution &)client); string name = evClient.m_localSource2Config[source]; // implement Evolution shutdown workaround (see lockEvolution above) evClient.checkEvolutionSource(name); return evClient.createNamedSource(name, isSourceA); } /** called internally in this class */ TestingSyncSource *createNamedSource(const string &name, bool isSourceA) { string database = getDatabaseName(name); std::string config = "target-config@client-test"; const char *server = getenv("CLIENT_TEST_SERVER"); if (server) { config += "-"; config += server; } std::string tracking = string("_") + m_clientID + "_" + (isSourceA ? "A" : "B"); SE_LOG_DEBUG(NULL, "instantiating testing source %s in config %s, with tracking name %s", name.c_str(), config.c_str(), tracking.c_str()); boost::shared_ptr context(new SyncConfig(config)); SyncSourceNodes nodes = context->getSyncSourceNodes(name, tracking); // The user of client-test must have configured the source // @_/ when doing // Client::Sync testing. Our testing source must use the same // properties, but different change tracking. std::string peerName = server ? (std::string(server) + "_" + m_clientID) : "@default"; boost::shared_ptr peer(new SyncConfig(peerName)); SyncSourceNodes peerNodes = peer->getSyncSourceNodes(name); SE_LOG_DEBUG(NULL, "overriding testing source %s properties with the ones from config %s = %s", name.c_str(), peerName.c_str(), peer->getRootPath().c_str()); BOOST_FOREACH(const ConfigProperty *prop, SyncSourceConfig::getRegistry()) { if (prop->isHidden()) { continue; } boost::shared_ptr node = peerNodes.getNode(*prop); InitStateString value = prop->getProperty(*node); SE_LOG_DEBUG(NULL, " %s = %s (%s)", prop->getMainName().c_str(), value.c_str(), value.wasSet() ? "set" : "default"); node = nodes.getNode(*prop); node->setProperty(prop->getMainName(), value); } context->flush(); // Same as in init() above: set values if still empty, but don't // overwrite anything. boost::shared_ptr props = nodes.getProperties(); std::string value; if (!props->getProperty("database", value)) { props->setProperty("database", database); } if (!props->getProperty("databaseUser", value) && !m_evoUser.empty()) { props->setProperty("databaseUser", m_evoUser); } if (!props->getProperty("databasePassword", value) && !m_evoPassword.empty()) { props->setProperty("databasePassword", m_evoPassword); } SyncSourceParams params(name, nodes, context); const RegisterSyncSourceTest *test = m_configs[name]; ClientTestConfig testConfig; getSourceConfig(test, testConfig); PersistentSyncSourceConfig sourceConfig(params.m_name, params.m_nodes); sourceConfig.setSourceType(SourceType(testConfig.m_type)); // downcasting here: anyone who registers his sources for testing // must ensure that they are indeed TestingSyncSource instances SyncSource *ss = SyncSource::createSource(params); return static_cast(ss); } // push source into localsource2config if it doesn't exist in the vector void pushLocalSource2Config(const string &source) { bool finded = false; BOOST_FOREACH(string element, m_localSource2Config) { if (boost::iequals(element, source)) { finded = true; break; } } if (!finded) { m_localSource2Config.push_back (source); } } void checkEvolutionSource(std::string &name) { string basename; // hard-coded names as used by src/backends/evolution; // if some other backend reuses them, it gets the // same treatment, which shouldn't cause any harm if (name == "eds_contact") { basename = "ebook"; } else if (name == "eds_event" || name == "text") { basename = "ecal"; } if (!basename.empty() && lockEvolution.find(basename) == lockEvolution.end()) { lockEvolution[basename].reset(createNamedSource(name, true)); lockEvolution[basename]->open(); ClientTest::registerCleanup(CleanupSources); } } }; static class RegisterTestEvolution { public: RegisterTestEvolution() : testClient("1") { testClient.registerTests(); } private: TestEvolution testClient; } testEvolution; SE_END_CXX syncevolution_1.4/src/dbus/000077500000000000000000000000001230021373600161025ustar00rootroot00000000000000syncevolution_1.4/src/dbus/dbus.am000066400000000000000000000002741230021373600173610ustar00rootroot00000000000000include $(top_srcdir)/src/dbus/interfaces/interfaces.am include $(top_srcdir)/src/dbus/glib/glib.am include $(top_srcdir)/src/dbus/qt/qt.am include $(top_srcdir)/src/dbus/server/server.am syncevolution_1.4/src/dbus/glib/000077500000000000000000000000001230021373600170175ustar00rootroot00000000000000syncevolution_1.4/src/dbus/glib/README000066400000000000000000000001211230021373600176710ustar00rootroot00000000000000This is the C wrapper for the SyncEvolution DBus API. It is used by the GTK ui. syncevolution_1.4/src/dbus/glib/glib.am000066400000000000000000000074751230021373600202700ustar00rootroot00000000000000if COND_GUI src_dbus_glib_lib = src/dbus/glib/libsyncevo-dbus.la lib_LTLIBRARIES += $(src_dbus_glib_lib) src_dbus_glib_cppflags = \ -I$(top_srcdir) \ $(SYNTHESIS_CFLAGS) \ -I$(top_srcdir)/src/dbus/glib \ -I$(top_builddir)/src/dbus/glib DISTCLEANFILES += src/dbus/glib/syncevo-dbus.pc dist_noinst_DATA += \ src/dbus/glib/syncevo-dbus.pc.in \ src/dbus/glib/syncevo-marshal.list \ src/dbus/glib/README #pkgconfigdir is defined in $(top_srcdir)/setup-variables.am pkgconfig_DATA += src/dbus/glib/syncevo-dbus.pc noinst_PROGRAMS += src/dbus/glib/test-syncevo-dbus src_dbus_glib_test_syncevo_dbus_SOURCES = src/dbus/glib/test.c src_dbus_glib_test_syncevo_dbus_LDADD = $(DBUS_GLIB_LIBS) $(src_dbus_glib_lib) src_dbus_glib_test_syncevo_dbus_CFLAGS = $(DBUS_GLIB_CFLAGS) $(SYNCEVO_WFLAGS) src_dbus_glib_test_syncevo_dbus_CPPFLAGS = $(src_dbus_glib_cppflags) src_dbus_glib_built_sources = \ src/dbus/glib/syncevo-marshal.c \ src/dbus/glib/syncevo-marshal.h \ src/dbus/glib/syncevo-server-bindings.h \ src/dbus/glib/syncevo-connection-bindings.h \ src/dbus/glib/syncevo-session-bindings.h \ src/dbus/glib/syncevo-server-glue.h \ src/dbus/glib/syncevo-connection-glue.h \ src/dbus/glib/syncevo-session-glue.h \ src/dbus/glib/syncevo-server.xml \ src/dbus/glib/syncevo-connection.xml \ src/dbus/glib/syncevo-session.xml BUILT_SOURCES += $(src_dbus_glib_built_sources) CLEANFILES += $(src_dbus_glib_built_sources) # D-Bus binding tool gets confused by doc comments, strip them first src/dbus/glib/%.xml: $(top_srcdir)/src/dbus/interfaces/spec-strip-docs.xsl $(top_srcdir)/src/dbus/interfaces/%-full.xml $(AM_V_GEN)$(XSLT) -o $@ $+ src/dbus/glib/%-bindings.h: src/dbus/glib/stamp-%-bindings.h $(AM_V_GEN) @true src/dbus/glib/stamp-%-bindings.h: src/dbus/glib/%.xml $(AM_V_at)XGENNAME='$(dir $@)xgen-$(notdir $@)' \ && BINDINGSNAME='$(dir $@)$(subst stamp-,,$(notdir $@))' \ && $(DBUS_BINDING_TOOL) --mode=glib-client --prefix=syncevo $< >"$$XGENNAME" \ && (cmp -s "$$XGENNAME" "$$BINDINGSNAME" || cp "$$XGENNAME" "$$BINDINGSNAME" )\ && rm -f "$$XGENNAME" \ && echo 'timestamp' >$@ src/dbus/glib/syncevo-marshal.h: $(top_srcdir)/src/dbus/glib/syncevo-marshal.list $(GLIB_GENMARSHAL) $(AM_V_GEN)$(GLIB_GENMARSHAL) $< --header --prefix=syncevo_marshal > $@ src/dbus/glib/syncevo-marshal.c: $(top_srcdir)/src/dbus/glib/syncevo-marshal.list src/dbus/glib/syncevo-marshal.h $(GLIB_GENMARSHAL) $(AM_V_GEN)echo "#include \"syncevo-marshal.h\"" > $@ \ && $(GLIB_GENMARSHAL) --prefix=syncevo_marshal $(top_srcdir)/src/dbus/glib/syncevo-marshal.list --body >> $@ src/dbus/glib/%-glue.h: src/dbus/glib/stamp-%-glue.h $(AM_V_GEN) @true src/dbus/glib/stamp-%-glue.h: src/dbus/glib/%.xml $(AM_V_at)XGENNAME='$(dir $@)xgen-$(notdir $@)' \ && GLUENAME='$(dir $@)$(subst stamp-,,$(notdir $@))' \ && $(DBUS_BINDING_TOOL) --prefix=syncevo --mode=glib-server $< >"$$XGENNAME" \ && (cmp -s "$$XGENNAME" "$$GLUENAME" || cp "$$XGENNAME" "$$GLUENAME") \ && rm -f "$$XGENNAME" \ && echo 'timestamp' >$@ nodist_src_dbus_glib_libsyncevo_dbus_la_SOURCES = \ $(src_dbus_glib_built_sources) src_dbus_glib_libsyncevo_dbus_la_SOURCES = \ $(src_dbus_glib_syncevo_dbus_headers) \ src/dbus/glib/syncevo-dbus-types.c \ src/dbus/glib/syncevo-server.c \ src/dbus/glib/syncevo-session.c src_dbus_glib_libsyncevo_dbus_la_CFLAGS = \ -I$(top_srcdir) \ -I$(top_builddir) \ $(DBUS_GLIB_CFLAGS) \ $(SYNCEVO_WFLAGS) src_dbus_glib_libsyncevo_dbus_la_LIBADD = \ $(DBUS_GLIB_LIBS) src_dbus_glib_libsyncevo_dbus_la_CPPFLAGS = \ $(src_dbus_glib_cppflags) src_dbus_glib_syncevo_dbus_headers = \ src/dbus/glib/syncevo-dbus-types.h \ src/dbus/glib/syncevo-server.h \ src/dbus/glib/syncevo-session.h src_dbus_glib_libsyncevo_dbus_includedir = $(includedir)/syncevo-dbus src_dbus_glib_libsyncevo_dbus_include_HEADERS = \ $(src_dbus_glib_syncevo_dbus_headers) endif syncevolution_1.4/src/dbus/glib/syncevo-dbus-types.c000066400000000000000000000306131230021373600227510ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include "syncevo-dbus-types.h" gboolean syncevo_config_get_value (SyncevoConfig *config, const char *source, const char *key, char **value) { char *name; GHashTable *source_config; g_return_val_if_fail (config, FALSE); g_return_val_if_fail (value, FALSE); *value = NULL; if (!source || strlen (source) == 0) { name = g_strdup (""); } else { name = g_strdup_printf ("source/%s", source); } source_config = (GHashTable*)g_hash_table_lookup (config, name); g_free (name); if (source_config) { return g_hash_table_lookup_extended (source_config, key, NULL, (gpointer*)value); } return FALSE; } gboolean syncevo_config_set_value (SyncevoConfig *config, const char *source, const char *key, const char *value) { gboolean changed; char *name; char *old_value; GHashTable *source_config; g_return_val_if_fail (config, FALSE); g_return_val_if_fail (key, FALSE); if (!source || strlen (source) == 0) { name = g_strdup (""); } else { name = g_strdup_printf ("source/%s", source); } source_config = (GHashTable*)g_hash_table_lookup (config, name); if (!source_config) { source_config = g_hash_table_new (g_str_hash, g_str_equal); g_hash_table_insert (config, name, source_config); } else { g_free (name); } old_value = g_hash_table_lookup (source_config, key); if ((!old_value && !value) || (old_value && value && strcmp (old_value, value) == 0)) { changed = FALSE; } else { changed = TRUE; g_hash_table_insert (source_config, g_strdup (key), g_strdup (value)); } return changed; } void syncevo_config_foreach_source (SyncevoConfig *config, ConfigFunc func, gpointer userdata) { GHashTableIter iter; char *key; GHashTable *value; g_hash_table_iter_init (&iter, config); while (g_hash_table_iter_next (&iter, (gpointer*)&key, (gpointer*)&value)) { if (key && g_str_has_prefix (key, "source/")) { char *name; name = key+7; func (name, value, userdata); } } } void syncevo_config_free (SyncevoConfig *config) { /* NOTE: Hashtables gcreated by dbus-glib should free their contents */ if (config) { g_hash_table_destroy (config); } } const char* syncevo_sync_mode_to_string (SyncevoSyncMode mode) { const char *mode_str; switch (mode) { case SYNCEVO_SYNC_NONE: mode_str = "none"; break; case SYNCEVO_SYNC_TWO_WAY: mode_str = "two-way"; break; case SYNCEVO_SYNC_SLOW: mode_str = "slow"; break; case SYNCEVO_SYNC_REFRESH_FROM_CLIENT: mode_str = "refresh-from-client"; break; case SYNCEVO_SYNC_REFRESH_FROM_SERVER: mode_str = "refresh-from-server"; break; case SYNCEVO_SYNC_ONE_WAY_FROM_CLIENT: mode_str = "one-way-from-client"; break; case SYNCEVO_SYNC_ONE_WAY_FROM_SERVER: mode_str = "one-way-from-server"; break; case SYNCEVO_SYNC_DEFAULT: mode_str = ""; break; default: mode_str = ""; break; } return mode_str; } SyncevoSourceModes* syncevo_source_modes_new () { return g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL); } void syncevo_source_modes_add (SyncevoSourceModes *source_modes, char *source, SyncevoSyncMode mode) { const char *mode_str; g_return_if_fail (source_modes); g_return_if_fail (source); mode_str = syncevo_sync_mode_to_string (mode); g_hash_table_insert (source_modes, source, (char*)mode_str); } void syncevo_source_modes_free (SyncevoSourceModes *source_modes) { /* no need to free keys/values */ g_hash_table_destroy (source_modes); } SyncevoSessionStatus syncevo_session_status_from_string (const char *status_str) { SyncevoSessionStatus status; if (!status_str) { status = SYNCEVO_STATUS_UNKNOWN; } else if (g_str_has_prefix (status_str, "queueing")) { status = SYNCEVO_STATUS_QUEUEING; } else if (g_str_has_prefix (status_str, "idle")) { status = SYNCEVO_STATUS_IDLE; } else if (g_str_has_prefix (status_str, "done")) { status = SYNCEVO_STATUS_DONE; } else if (g_str_has_prefix (status_str, "running")) { status = SYNCEVO_STATUS_RUNNING; } else if (g_str_has_prefix (status_str, "aborting")) { status = SYNCEVO_STATUS_ABORTING; } else if (g_str_has_prefix (status_str, "suspending")) { status = SYNCEVO_STATUS_SUSPENDING; } else { status = SYNCEVO_STATUS_UNKNOWN; } if (status_str && strstr (status_str, ";waiting")) { status |= SYNCEVO_STATUS_WAITING; } return status; } SyncevoSyncMode syncevo_sync_mode_from_string (const char *mode_str) { if (!mode_str || g_str_has_prefix (mode_str, "none") || g_str_has_prefix (mode_str, "disabled")) { return SYNCEVO_SYNC_NONE; } else if (g_str_has_prefix (mode_str, "two-way")) { return SYNCEVO_SYNC_TWO_WAY; } else if (g_str_has_prefix (mode_str, "slow")) { return SYNCEVO_SYNC_SLOW; } else if (g_str_has_prefix (mode_str, "refresh-from-client")) { return SYNCEVO_SYNC_REFRESH_FROM_CLIENT; } else if (g_str_has_prefix (mode_str, "refresh-from-server")) { return SYNCEVO_SYNC_REFRESH_FROM_SERVER; } else if (g_str_has_prefix (mode_str, "one-way-from-client")) { return SYNCEVO_SYNC_ONE_WAY_FROM_CLIENT; } else if (g_str_has_prefix (mode_str, "one-way-from-server")) { return SYNCEVO_SYNC_ONE_WAY_FROM_SERVER; } else { return SYNCEVO_SYNC_UNKNOWN; } } static SyncevoSessionStatus syncevo_source_status_from_string (const char *status_str) { SyncevoSessionStatus status; if (!status_str) { status = SYNCEVO_STATUS_UNKNOWN; } else if (g_str_has_prefix (status_str, "idle")) { status = SYNCEVO_STATUS_IDLE; } else if (g_str_has_prefix (status_str, "running")) { status = SYNCEVO_STATUS_RUNNING; } else if (g_str_has_prefix (status_str, "done")) { status = SYNCEVO_STATUS_DONE; } else { status = SYNCEVO_STATUS_UNKNOWN; } /* check modifiers */ if (status_str && strstr (status_str, ";waiting")) { status |= SYNCEVO_STATUS_WAITING; } return status; } void syncevo_source_statuses_foreach (SyncevoSourceStatuses *source_statuses, SourceStatusFunc func, gpointer data) { GHashTableIter iter; GValueArray *source_status; char *name; g_return_if_fail (source_statuses); g_hash_table_iter_init (&iter, source_statuses); while (g_hash_table_iter_next (&iter, (gpointer)&name, (gpointer)&source_status)) { const char *mode_str; const char *status_str; SyncevoSyncMode mode; SyncevoSessionStatus status; guint error_code; mode_str = g_value_get_string (g_value_array_get_nth (source_status, 0)); mode = syncevo_sync_mode_from_string (mode_str); status_str = g_value_get_string (g_value_array_get_nth (source_status, 1)); status = syncevo_source_status_from_string (status_str); error_code = g_value_get_uint (g_value_array_get_nth (source_status, 2)); func (name, mode, status, error_code, data); } } static void free_source_status_item (char *source, GValueArray *status_array) { g_free (source); g_boxed_free (SYNCEVO_TYPE_SOURCE_STATUS, status_array); } void syncevo_source_statuses_free (SyncevoSourceStatuses *source_statuses) { g_hash_table_foreach (source_statuses, (GHFunc)free_source_status_item, NULL); g_hash_table_destroy (source_statuses); } void syncevo_source_progresses_foreach (SyncevoSourceProgresses *source_progresses, SourceProgressFunc func, gpointer userdata) { const char *phase_str, *name; GHashTableIter iter; GValueArray *progress_array; g_return_if_fail (source_progresses); g_hash_table_iter_init (&iter, source_progresses); while (g_hash_table_iter_next (&iter, (gpointer)&name, (gpointer)&progress_array)) { SyncevoSourcePhase phase; phase_str = g_value_get_string (g_value_array_get_nth (progress_array, 0)); if (!phase_str) { phase = SYNCEVO_PHASE_NONE; } else if (g_str_has_prefix (phase_str, "preparing")) { phase = SYNCEVO_PHASE_PREPARING; } else if (g_str_has_prefix (phase_str, "sending")) { phase = SYNCEVO_PHASE_SENDING; } else if (g_str_has_prefix (phase_str, "receiving")) { phase = SYNCEVO_PHASE_RECEIVING; } else { phase = SYNCEVO_PHASE_NONE; } /* val = g_value_array_get_nth (progress_array, 1); progress->prepare_current = g_value_get_int (val); val = g_value_array_get_nth (progress_array, 2); progress->prepare_total = g_value_get_int (val); val = g_value_array_get_nth (progress_array, 3); progress->send_current = g_value_get_int (val); val = g_value_array_get_nth (progress_array, 4); progress->send_total = g_value_get_int (val); val = g_value_array_get_nth (progress_array, 5); progress->receive_current = g_value_get_int (val); val = g_value_array_get_nth (progress_array, 6); progress->receive_total = g_value_get_int (val); */ func (name, phase, userdata); } } static void free_source_progress_item (char *source, GValueArray *progress_array) { g_free (source); g_boxed_free (SYNCEVO_TYPE_SOURCE_PROGRESS, progress_array); } void syncevo_source_progresses_free (SyncevoSourceProgresses *source_progresses) { g_hash_table_foreach (source_progresses, (GHFunc)free_source_progress_item, NULL); g_hash_table_destroy (source_progresses); } static void free_report_item (char *key, char *value) { g_free (key); g_free (value); } static void syncevo_report_free (GHashTable *report) { g_hash_table_foreach (report, (GHFunc)free_report_item, NULL); g_hash_table_destroy (report); } GHashTable* syncevo_reports_index (SyncevoReports *reports, guint index) { g_return_val_if_fail (reports, NULL); return (GHashTable*)g_ptr_array_index (reports, index); } guint syncevo_reports_get_length (SyncevoReports *reports) { return reports->len; } void syncevo_reports_free (SyncevoReports *reports) { g_ptr_array_foreach (reports, (GFunc)syncevo_report_free, NULL); g_ptr_array_free (reports, TRUE); } const char* syncevo_sessions_index (SyncevoSessions *sessions, guint index) { g_return_val_if_fail (sessions, NULL); if (index >= sessions->len) { return NULL; } return (const char*)g_ptr_array_index (sessions, index); } void syncevo_sessions_free (SyncevoSessions *sessions) { g_ptr_array_foreach (sessions, (GFunc)g_free, NULL); g_ptr_array_free (sessions, TRUE); } syncevolution_1.4/src/dbus/glib/syncevo-dbus-types.h000066400000000000000000000132011230021373600227500ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef __SYNCEVO_TYPES_H__ #define __SYNCEVO_TYPES_H__ #include #define SYNCEVO_DBUS_ERROR_EXCEPTION "org.syncevolution.Exception" #define SYNCEVO_DBUS_ERROR_NO_SUCH_CONFIG "org.syncevolution.NoSuchConfig" #define SYNCEVO_DBUS_ERROR_NO_SUCH_SOURCE "org.syncevolution.NoSuchsource" #define SYNCEVO_DBUS_ERROR_INVALID_CALL "org.syncevolution.InvalidCall" #define SYNCEVO_DBUS_ERROR_SOURCE_UNUSABLE "org.syncevolution.SourceUnusable" typedef enum { SYNCEVO_SYNC_UNKNOWN, /* Cannot be used in Sync */ SYNCEVO_SYNC_DEFAULT, /* cannot be received in GetStatus*/ SYNCEVO_SYNC_NONE, SYNCEVO_SYNC_TWO_WAY, SYNCEVO_SYNC_SLOW, SYNCEVO_SYNC_REFRESH_FROM_CLIENT, SYNCEVO_SYNC_REFRESH_FROM_SERVER, SYNCEVO_SYNC_ONE_WAY_FROM_CLIENT, SYNCEVO_SYNC_ONE_WAY_FROM_SERVER, } SyncevoSyncMode; /* SyncevoSessionStatus is a bitfield, although most value are exclusive */ typedef enum { SYNCEVO_STATUS_UNKNOWN = 0, SYNCEVO_STATUS_QUEUEING = 1 << 0, SYNCEVO_STATUS_IDLE = 1 << 1, SYNCEVO_STATUS_RUNNING = 1 << 2, SYNCEVO_STATUS_ABORTING = 1 << 3, SYNCEVO_STATUS_SUSPENDING = 1 << 4, SYNCEVO_STATUS_DONE = 1 << 5, /* the ones below are modifiers */ SYNCEVO_STATUS_WAITING = 1 << 6 } SyncevoSessionStatus; typedef enum { SYNCEVO_PHASE_NONE, SYNCEVO_PHASE_PREPARING, SYNCEVO_PHASE_SENDING, SYNCEVO_PHASE_RECEIVING, } SyncevoSourcePhase; #define SYNCEVO_TYPE_STRING_STRING_HASHTABLE (dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_STRING)) #define SYNCEVO_TYPE_SOURCE_STATUS (dbus_g_type_get_struct ("GValueArray", G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_INVALID)) #define SYNCEVO_TYPE_SOURCE_STATUSES (dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, SYNCEVO_TYPE_SOURCE_STATUS)) #define SYNCEVO_TYPE_SOURCE_PROGRESS (dbus_g_type_get_struct ("GValueArray", G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INVALID)) #define SYNCEVO_TYPE_SOURCE_PROGRESSES (dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, SYNCEVO_TYPE_SOURCE_PROGRESS)) typedef GHashTable SyncevoConfig; typedef GHashTable SyncevoSourceModes; typedef GHashTable SyncevoSourceStatuses; typedef GHashTable SyncevoSourceProgresses; typedef GPtrArray SyncevoReports; typedef GPtrArray SyncevoSessions; gboolean syncevo_config_get_value (SyncevoConfig *config, const char *source, const char *key, char **value); gboolean syncevo_config_set_value (SyncevoConfig *config, const char *source, const char *key, const char *value); typedef void (*ConfigFunc) (char *name, GHashTable *source_configuration, gpointer user_data); void syncevo_config_foreach_source (SyncevoConfig *config, ConfigFunc func, gpointer userdata); void syncevo_config_free (SyncevoConfig *config); const char* syncevo_sync_mode_to_string (SyncevoSyncMode mode); SyncevoSyncMode syncevo_sync_mode_from_string (const char *mode_str); SyncevoSourceModes* syncevo_source_modes_new (); void syncevo_source_modes_add (SyncevoSourceModes *source_modes, char *source, SyncevoSyncMode mode); void syncevo_source_modes_free (SyncevoSourceModes *source_modes); SyncevoSessionStatus syncevo_session_status_from_string (const char *status_str); typedef void (*SourceStatusFunc) (char *name, SyncevoSyncMode mode, SyncevoSessionStatus status, guint error_code, gpointer user_data); void syncevo_source_statuses_foreach (SyncevoSourceStatuses *source_statuses, SourceStatusFunc func, gpointer data); void syncevo_source_statuses_free (SyncevoSourceStatuses *source_statuses); typedef void (*SourceProgressFunc) (const char *name, SyncevoSourcePhase phase, gpointer user_data); void syncevo_source_progresses_foreach (SyncevoSourceProgresses *source_progresses, SourceProgressFunc func, gpointer userdata); void syncevo_source_progresses_free (SyncevoSourceProgresses *source_progresses); GHashTable* syncevo_reports_index (SyncevoReports *reports, guint index); guint syncevo_reports_get_length (SyncevoReports *reports); void syncevo_reports_free (SyncevoReports *reports); const char* syncevo_sessions_index (SyncevoSessions *sessions, guint index); void syncevo_sessions_free (SyncevoSessions *sessions); #endif syncevolution_1.4/src/dbus/glib/syncevo-dbus.pc.in000066400000000000000000000003651230021373600223750ustar00rootroot00000000000000prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ Name: syncevo-dbus Description: SyncEvolution D-Bus API wrapper Version: @VERSION@ Cflags: -I${includedir} Requires: dbus-glib-1 Libs: -L${libdir} -lsyncevo-dbus syncevolution_1.4/src/dbus/glib/syncevo-marshal.list000066400000000000000000000003151230021373600230260ustar00rootroot00000000000000VOID:STRING,STRING,INT,INT,INT,INT VOID:STRING,STRING VOID:STRING,BOOLEAN VOID:STRING,STRING,STRING VOID:STRING,STRING,STRING,STRING,STRING,BOXED VOID:INT,BOXED VOID:UINT,UINT,BOXED VOID:STRING,UINT,BOXED syncevolution_1.4/src/dbus/glib/syncevo-server.c000066400000000000000000000672631230021373600221730ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include "syncevo-server.h" #include "syncevo-marshal.h" #include "syncevo-server-bindings.h" typedef struct _ServerAsyncData { SyncevoServer *server; GCallback callback; gpointer userdata; } ServerAsyncData; enum { SESSION_CHANGED, PRESENCE_CHANGED, INFO_REQUEST, TEMPLATES_CHANGED, SHUTDOWN, LAST_SIGNAL }; static guint32 signals[LAST_SIGNAL] = {0, }; typedef struct _SyncevoServerPrivate { DBusGProxy *proxy; } SyncevoServerPrivate; #define GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SYNCEVO_TYPE_SERVER, SyncevoServerPrivate)) G_DEFINE_TYPE (SyncevoServer, syncevo_server, G_TYPE_OBJECT); static ServerAsyncData* server_async_data_new (SyncevoServer *server, GCallback callback, gpointer userdata) { ServerAsyncData *data; data = g_slice_new0 (ServerAsyncData); data->server = server; data->callback = G_CALLBACK (callback); data->userdata = userdata; return data; } static void server_async_data_free (ServerAsyncData *data) { g_slice_free (ServerAsyncData, data); } static void generic_callback (DBusGProxy *proxy, GError *error, ServerAsyncData *data) { if (data->callback) { (*(SyncevoServerGenericCb)data->callback) (data->server, error, data->userdata); } server_async_data_free (data); } static gboolean generic_error (ServerAsyncData *data) { GError *error; error = g_error_new_literal (SYNCEVO_SERVER_ERROR_QUARK, SYNCEVO_SERVER_ERROR_NO_DBUS_OBJECT, "The D-Bus object does not exist"); (*(SyncevoServerGenericCb)data->callback) (data->server, error, data->userdata); server_async_data_free (data); return FALSE; } static void templates_changed_cb (DBusGProxy *proxy, SyncevoServer *server) { g_signal_emit (server, signals[TEMPLATES_CHANGED], 0); } static void session_changed_cb (DBusGProxy *proxy, char *session_path, gboolean started, SyncevoServer *server) { g_signal_emit (server, signals[SESSION_CHANGED], 0, session_path, started); } static void presence_cb (DBusGProxy *proxy, char *configuration, char *status, char *transport, SyncevoServer *server) { g_signal_emit (server, signals[PRESENCE_CHANGED], 0, configuration, status, transport); } static void info_request_cb (DBusGProxy *proxy, char *id, char *session_path, char *state, char *handler_path, char *type, GHashTable *parameters, SyncevoServer *server) { g_signal_emit (server, signals[INFO_REQUEST], 0, id, session_path, state, handler_path, type, parameters); } static void proxy_destroy_cb (DBusGProxy *proxy, SyncevoServer *server) { SyncevoServerPrivate *priv; priv = GET_PRIVATE (server); if (priv->proxy) { g_object_unref (priv->proxy); } priv->proxy = NULL; g_signal_emit (server, signals[SHUTDOWN], 0); } #if 0 static void detach_cb (DBusGProxy *proxy, GError *error, gpointer userdata) { if (error) { g_warning ("Server.Detach failed: %s", error->message); g_error_free (error); } } #endif static void dispose (GObject *object) { SyncevoServerPrivate *priv; priv = GET_PRIVATE (object); if (priv->proxy) { dbus_g_proxy_disconnect_signal (priv->proxy, "SessionChanged", G_CALLBACK (session_changed_cb), object); dbus_g_proxy_disconnect_signal (priv->proxy, "Presence", G_CALLBACK (presence_cb), object); dbus_g_proxy_disconnect_signal (priv->proxy, "InfoRequest", G_CALLBACK (info_request_cb), object); dbus_g_proxy_disconnect_signal (priv->proxy, "TemplatesChanged", G_CALLBACK (templates_changed_cb), object); dbus_g_proxy_disconnect_signal (priv->proxy, "destroy", G_CALLBACK (proxy_destroy_cb), object); org_syncevolution_Server_detach (priv->proxy, NULL); g_object_unref (priv->proxy); priv->proxy = NULL; } G_OBJECT_CLASS (syncevo_server_parent_class)->dispose (object); } static void attach_cb (DBusGProxy *proxy, GError *error, gpointer userdata) { if (error) { g_warning ("Server.Attach failed: %s", error->message); g_error_free (error); } } static gboolean syncevo_server_get_new_proxy (SyncevoServer *server) { DBusGConnection *connection; GError *error; guint32 result; SyncevoServerPrivate *priv; DBusGProxy *proxy; priv = GET_PRIVATE (server); error = NULL; connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error); if (connection == NULL) { g_printerr ("Failed to open connection to bus: %s\n", error->message); g_error_free (error); priv->proxy = NULL; return FALSE; } /* we want to use dbus_g_proxy_new_for_name_owner() for the destroy signal * so need to start the service by hand by using DBUS proxy: */ proxy = dbus_g_proxy_new_for_name (connection, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS); if (!dbus_g_proxy_call (proxy, "StartServiceByName", NULL, G_TYPE_STRING, DBUS_SERVICE_SYNCEVO_SERVER, G_TYPE_UINT, 0, G_TYPE_INVALID, G_TYPE_UINT, &result, G_TYPE_INVALID)) { g_warning ("StartServiceByName call failed"); } g_object_unref (proxy); /* the real proxy */ priv->proxy = dbus_g_proxy_new_for_name_owner (connection, DBUS_SERVICE_SYNCEVO_SERVER, DBUS_PATH_SYNCEVO_SERVER, DBUS_INTERFACE_SYNCEVO_SERVER, &error); if (priv->proxy == NULL) { g_printerr ("dbus_g_proxy_new_for_name_owner() failed: %s\n", error->message); g_error_free (error); return FALSE; } dbus_g_proxy_add_signal (priv->proxy, "SessionChanged", DBUS_TYPE_G_OBJECT_PATH, G_TYPE_BOOLEAN, G_TYPE_INVALID); dbus_g_proxy_connect_signal (priv->proxy, "SessionChanged", G_CALLBACK (session_changed_cb), server, NULL); dbus_g_proxy_add_signal (priv->proxy, "Presence", G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID); dbus_g_proxy_connect_signal (priv->proxy, "Presence", G_CALLBACK (presence_cb), server, NULL); dbus_g_proxy_add_signal (priv->proxy, "InfoRequest", G_TYPE_STRING, DBUS_TYPE_G_OBJECT_PATH, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, SYNCEVO_TYPE_STRING_STRING_HASHTABLE, G_TYPE_INVALID); dbus_g_proxy_connect_signal (priv->proxy, "InfoRequest", G_CALLBACK (info_request_cb), server, NULL); dbus_g_proxy_add_signal (priv->proxy, "TemplatesChanged", G_TYPE_INVALID); dbus_g_proxy_connect_signal (priv->proxy, "TemplatesChanged", G_CALLBACK (templates_changed_cb), server, NULL); g_signal_connect (priv->proxy, "destroy", G_CALLBACK (proxy_destroy_cb), server); org_syncevolution_Server_attach_async (priv->proxy, (org_syncevolution_Server_attach_reply)attach_cb, NULL); return TRUE; } static void syncevo_server_init (SyncevoServer *server) { /* SessionChanged */ dbus_g_object_register_marshaller (syncevo_marshal_VOID__STRING_BOOLEAN, G_TYPE_NONE, DBUS_TYPE_G_OBJECT_PATH, G_TYPE_BOOLEAN, G_TYPE_INVALID); /* Presence */ dbus_g_object_register_marshaller (syncevo_marshal_VOID__STRING_STRING_STRING, G_TYPE_NONE, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID); /* InfoRequest */ dbus_g_object_register_marshaller (syncevo_marshal_VOID__STRING_STRING_STRING_STRING_STRING_BOXED, G_TYPE_NONE, G_TYPE_STRING, DBUS_TYPE_G_OBJECT_PATH, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOXED, G_TYPE_INVALID); /* TemplatesChanged */ dbus_g_object_register_marshaller (g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, G_TYPE_INVALID); syncevo_server_get_new_proxy (server); } static void syncevo_server_class_init (SyncevoServerClass *klass) { GObjectClass *o_class = (GObjectClass *) klass; o_class->dispose = dispose; g_type_class_add_private (klass, sizeof (SyncevoServerPrivate)); signals[SESSION_CHANGED] = g_signal_new ("session-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE, G_STRUCT_OFFSET (SyncevoServerClass, session_changed), NULL, NULL, syncevo_marshal_VOID__STRING_BOOLEAN, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_BOOLEAN); signals[PRESENCE_CHANGED] = g_signal_new ("presence-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE, G_STRUCT_OFFSET (SyncevoServerClass, presence_changed), NULL, NULL, syncevo_marshal_VOID__STRING_STRING_STRING, G_TYPE_NONE, 3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); signals[INFO_REQUEST] = g_signal_new ("info-request", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE, G_STRUCT_OFFSET (SyncevoServerClass, info_request), NULL, NULL, syncevo_marshal_VOID__STRING_STRING_STRING_STRING_STRING_BOXED, G_TYPE_NONE, 6, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER); signals[TEMPLATES_CHANGED] = g_signal_new ("templates-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE, G_STRUCT_OFFSET (SyncevoServerClass, templates_changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[SHUTDOWN] = g_signal_new ("shutdown", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE, G_STRUCT_OFFSET (SyncevoServerClass, shutdown), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } SyncevoServer * syncevo_server_get_default () { static SyncevoServer *server = NULL; if (server == NULL) { server = g_object_new (SYNCEVO_TYPE_SERVER, NULL); g_object_add_weak_pointer (G_OBJECT (server), (gpointer) &server); return server; } return g_object_ref (server); } static void get_configs_callback (DBusGProxy *proxy, char **config_names, GError *error, ServerAsyncData *data) { if (data->callback) { (*(SyncevoServerGetConfigsCb)data->callback) (data->server, config_names, error, data->userdata); } server_async_data_free (data); } static gboolean get_configs_error (ServerAsyncData *data) { GError *error; error = g_error_new_literal (SYNCEVO_SERVER_ERROR_QUARK, SYNCEVO_SERVER_ERROR_NO_DBUS_OBJECT, "Could not start service"); (*(SyncevoServerGetConfigsCb)data->callback) (data->server, NULL, error, data->userdata); server_async_data_free (data); return FALSE; } void syncevo_server_get_configs (SyncevoServer *syncevo, gboolean template, SyncevoServerGetConfigsCb callback, gpointer userdata) { ServerAsyncData *data; SyncevoServerPrivate *priv; priv = GET_PRIVATE (syncevo); data = server_async_data_new (syncevo, G_CALLBACK (callback), userdata); if (!priv->proxy && !syncevo_server_get_new_proxy (syncevo)) { if (callback) { g_idle_add ((GSourceFunc)get_configs_error, data); } return; } org_syncevolution_Server_get_configs_async (priv->proxy, template, (org_syncevolution_Server_get_configs_reply) get_configs_callback, data); } static void get_config_callback (DBusGProxy *proxy, SyncevoConfig *configuration, GError *error, ServerAsyncData *data) { if (data->callback) { (*(SyncevoServerGetConfigCb)data->callback) (data->server, configuration, error, data->userdata); } server_async_data_free (data); } static gboolean get_config_error (ServerAsyncData *data) { GError *error; error = g_error_new_literal (SYNCEVO_SERVER_ERROR_QUARK, SYNCEVO_SERVER_ERROR_NO_DBUS_OBJECT, "Could not start service"); (*(SyncevoServerGetConfigCb)data->callback) (data->server, NULL, error, data->userdata); server_async_data_free (data); return FALSE; } void syncevo_server_get_config (SyncevoServer *syncevo, const char *config_name, gboolean template, SyncevoServerGetConfigCb callback, gpointer userdata) { ServerAsyncData *data; SyncevoServerPrivate *priv; priv = GET_PRIVATE (syncevo); data = server_async_data_new (syncevo, G_CALLBACK (callback), userdata); if (!priv->proxy && !syncevo_server_get_new_proxy (syncevo)) { if (callback) { g_idle_add ((GSourceFunc)get_config_error, data); } return; } org_syncevolution_Server_get_config_async (priv->proxy, config_name, template, (org_syncevolution_Server_get_config_reply) get_config_callback, data); } static void get_reports_callback (DBusGProxy *proxy, SyncevoReports *reports, GError *error, ServerAsyncData *data) { if (data->callback) { (*(SyncevoServerGetReportsCb)data->callback) (data->server, reports, error, data->userdata); } server_async_data_free (data); } static gboolean get_reports_error (ServerAsyncData *data) { GError *error; error = g_error_new_literal (SYNCEVO_SERVER_ERROR_QUARK, SYNCEVO_SERVER_ERROR_NO_DBUS_OBJECT, "Could not start service"); (*(SyncevoServerGetReportsCb)data->callback) (data->server, NULL, error, data->userdata); server_async_data_free (data); return FALSE; } void syncevo_server_get_reports (SyncevoServer *syncevo, const char *config_name, guint start, guint count, SyncevoServerGetReportsCb callback, gpointer userdata) { ServerAsyncData *data; SyncevoServerPrivate *priv; priv = GET_PRIVATE (syncevo); data = server_async_data_new (syncevo, G_CALLBACK (callback), userdata); if (!priv->proxy && !syncevo_server_get_new_proxy (syncevo)) { if (callback) { g_idle_add ((GSourceFunc)get_reports_error, data); } return; } org_syncevolution_Server_get_reports_async (priv->proxy, config_name, start, count, (org_syncevolution_Server_get_reports_reply) get_reports_callback, data); } static void start_session_callback (SyncevoServer *syncevo, char *session_path, GError *error, ServerAsyncData *data) { if (data->callback) { (*(SyncevoServerStartSessionCb)data->callback) (data->server, session_path, error, data->userdata); } server_async_data_free (data); } static gboolean start_session_error (ServerAsyncData *data) { GError *error; error = g_error_new_literal (SYNCEVO_SERVER_ERROR_QUARK, SYNCEVO_SERVER_ERROR_NO_DBUS_OBJECT, "Could not start service"); (*(SyncevoServerStartSessionCb)data->callback) (data->server, NULL, error, data->userdata); server_async_data_free (data); return FALSE; } void syncevo_server_start_session (SyncevoServer *syncevo, const char *config_name, SyncevoServerStartSessionCb callback, gpointer userdata) { ServerAsyncData *data; SyncevoServerPrivate *priv; priv = GET_PRIVATE (syncevo); data = server_async_data_new (syncevo, G_CALLBACK (callback), userdata); if (!priv->proxy && !syncevo_server_get_new_proxy (syncevo)) { if (callback) { g_idle_add ((GSourceFunc)start_session_error, data); } return; } org_syncevolution_Server_start_session_async (priv->proxy, config_name, (org_syncevolution_Server_start_session_reply) start_session_callback, data); } void syncevo_server_start_no_sync_session (SyncevoServer *syncevo, const char *config_name, SyncevoServerStartSessionCb callback, gpointer userdata) { ServerAsyncData *data; SyncevoServerPrivate *priv; const char *flags[2] = {"no-sync", NULL}; priv = GET_PRIVATE (syncevo); data = server_async_data_new (syncevo, G_CALLBACK (callback), userdata); if (!priv->proxy && !syncevo_server_get_new_proxy (syncevo)) { if (callback) { g_idle_add ((GSourceFunc)start_session_error, data); } return; } org_syncevolution_Server_start_session_with_flags_async (priv->proxy, config_name, flags, (org_syncevolution_Server_start_session_reply) start_session_callback, data); } static void get_sessions_callback (SyncevoServer *syncevo, SyncevoSessions *sessions, GError *error, ServerAsyncData *data) { if (data->callback) { (*(SyncevoServerGetSessionsCb)data->callback) (data->server, sessions, error, data->userdata); } server_async_data_free (data); } static gboolean get_sessions_error (ServerAsyncData *data) { GError *error; error = g_error_new_literal (SYNCEVO_SERVER_ERROR_QUARK, SYNCEVO_SERVER_ERROR_NO_DBUS_OBJECT, "Could not start service"); (*(SyncevoServerGetSessionsCb)data->callback) (data->server, NULL, error, data->userdata); server_async_data_free (data); return FALSE; } void syncevo_server_get_sessions (SyncevoServer *syncevo, SyncevoServerGetSessionsCb callback, gpointer userdata) { ServerAsyncData *data; SyncevoServerPrivate *priv; priv = GET_PRIVATE (syncevo); data = server_async_data_new (syncevo, G_CALLBACK (callback), userdata); if (!priv->proxy && !syncevo_server_get_new_proxy (syncevo)) { if (callback) { g_idle_add ((GSourceFunc)get_sessions_error, data); } return; } org_syncevolution_Server_get_sessions_async (priv->proxy, (org_syncevolution_Server_get_reports_reply) get_sessions_callback, data); } static void check_presence_callback (SyncevoServer *syncevo, char *status, char **transports, GError *error, ServerAsyncData *data) { if (data->callback) { (*(SyncevoServerGetPresenceCb)data->callback) (data->server, status, transports, error, data->userdata); } server_async_data_free (data); } static gboolean check_presence_error (ServerAsyncData *data) { GError *error; error = g_error_new_literal (SYNCEVO_SERVER_ERROR_QUARK, SYNCEVO_SERVER_ERROR_NO_DBUS_OBJECT, "Could not start service"); (*(SyncevoServerGetPresenceCb)data->callback) (data->server, NULL, NULL, error, data->userdata); server_async_data_free (data); return FALSE; } void syncevo_server_get_presence (SyncevoServer *syncevo, const char *config_name, SyncevoServerGetPresenceCb callback, gpointer userdata) { ServerAsyncData *data; SyncevoServerPrivate *priv; priv = GET_PRIVATE (syncevo); data = server_async_data_new (syncevo, G_CALLBACK (callback), userdata); if (!priv->proxy && !syncevo_server_get_new_proxy (syncevo)) { if (callback) { g_idle_add ((GSourceFunc)check_presence_error, data); } return; } org_syncevolution_Server_check_presence_async (priv->proxy, config_name, (org_syncevolution_Server_check_presence_reply) check_presence_callback, data); } void syncevo_server_check_source (SyncevoServer *server, const char *config, const char *source, SyncevoServerGenericCb callback, gpointer userdata) { ServerAsyncData *data; SyncevoServerPrivate *priv; priv = GET_PRIVATE (server); data = server_async_data_new (server, G_CALLBACK (callback), userdata); if (!priv->proxy) { if (callback) { g_idle_add ((GSourceFunc)generic_error, data); } return; } org_syncevolution_Server_check_source_async (priv->proxy, config, source, (org_syncevolution_Server_check_source_reply) generic_callback, data); } void syncevo_server_info_response (SyncevoServer *server, const char *id, const char *state, GHashTable *response, SyncevoServerGenericCb callback, gpointer userdata) { ServerAsyncData *data; SyncevoServerPrivate *priv; priv = GET_PRIVATE (server); data = server_async_data_new (server, G_CALLBACK (callback), userdata); if (!priv->proxy) { if (callback) { g_idle_add ((GSourceFunc)generic_error, data); } return; } org_syncevolution_Server_info_response_async (priv->proxy, id, state, response, (org_syncevolution_Server_info_response_reply) generic_callback, data); } syncevolution_1.4/src/dbus/glib/syncevo-server.h000066400000000000000000000151571230021373600221730ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef __SYNCEVO_SERVER_H__ #define __SYNCEVO_SERVER_H__ #include #include "syncevo-dbus-types.h" #include "syncevo-session.h" G_BEGIN_DECLS enum SyncevoServerError{ SYNCEVO_SERVER_ERROR_NO_DBUS_OBJECT = 1, }; #define SYNCEVO_SERVER_ERROR_QUARK g_quark_from_static_string ("syncevo-server") #define DBUS_SERVICE_SYNCEVO_SERVER "org.syncevolution" #define DBUS_PATH_SYNCEVO_SERVER "/org/syncevolution/Server" #define DBUS_INTERFACE_SYNCEVO_SERVER "org.syncevolution.Server" #define SYNCEVO_TYPE_SERVER (syncevo_server_get_type ()) #define SYNCEVO_SERVER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), SYNCEVO_TYPE_SERVER, SyncevoServer)) #define SYNCEVO_IS_SERVER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), SYNCEVO_TYPE_SERVER)) typedef struct _SyncevoServer { GObject parent_object; } SyncevoServer; typedef struct _SyncevoServerClass { GObjectClass parent_class; void (*session_changed) (SyncevoServer *syncevo, char *session_path, gboolean started); void (*presence_changed) (SyncevoServer *syncevo, char *configuration, char *status, char *transport); void (*info_request) (SyncevoServer *syncevo, char *id, char *session_path, char *state, char *handler_path, char *type, GHashTable *parameters); void (*templates_changed) (SyncevoServer *syncevo); void (*shutdown) (SyncevoServer *syncevo); } SyncevoServerClass; GType syncevo_server_get_type (void); SyncevoServer *syncevo_server_get_default (); typedef void (*SyncevoServerGenericCb) (SyncevoServer *server, GError *error, gpointer userdata); typedef void (*SyncevoServerGetConfigsCb) (SyncevoServer *syncevo, char **config_names, GError *error, gpointer userdata); void syncevo_server_get_configs (SyncevoServer *syncevo, gboolean template, SyncevoServerGetConfigsCb callback, gpointer userdata); typedef void (*SyncevoServerGetConfigCb) (SyncevoServer *syncevo, SyncevoConfig *config, GError *error, gpointer userdata); void syncevo_server_get_config (SyncevoServer *syncevo, const char *config_name, gboolean template, SyncevoServerGetConfigCb callback, gpointer userdata); typedef void (*SyncevoServerGetReportsCb) (SyncevoServer *syncevo, SyncevoReports *reports, GError *error, gpointer userdata); void syncevo_server_get_reports (SyncevoServer *syncevo, const char *config_name, guint start, guint count, SyncevoServerGetReportsCb callback, gpointer userdata); typedef void (*SyncevoServerStartSessionCb) (SyncevoServer *syncevo, char *session_path, GError *error, gpointer userdata); void syncevo_server_start_session (SyncevoServer *syncevo, const char *config_name, SyncevoServerStartSessionCb callback, gpointer userdata); void syncevo_server_start_no_sync_session (SyncevoServer *syncevo, const char *config_name, SyncevoServerStartSessionCb callback, gpointer userdata); typedef void (*SyncevoServerGetSessionsCb) (SyncevoServer *syncevo, SyncevoSessions *sessions, GError *error, gpointer userdata); void syncevo_server_get_sessions (SyncevoServer *syncevo, SyncevoServerGetSessionsCb callback, gpointer userdata); typedef void (*SyncevoServerGetPresenceCb) (SyncevoServer *syncevo, char *status, char **transports, GError *error, gpointer userdata); void syncevo_server_get_presence (SyncevoServer *syncevo, const char *config_name, SyncevoServerGetPresenceCb callback, gpointer userdata); void syncevo_server_check_source (SyncevoServer *server, const char *config, const char *source, SyncevoServerGenericCb callback, gpointer userdata); void syncevo_server_info_response (SyncevoServer *server, const char *id, const char *state, GHashTable *response, SyncevoServerGenericCb callback, gpointer userdata); G_END_DECLS #endif syncevolution_1.4/src/dbus/glib/syncevo-session.c000066400000000000000000000556541230021373600223510ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include "syncevo-session.h" #include "syncevo-marshal.h" #include "syncevo-session-bindings.h" typedef struct _SessionAsyncData { SyncevoSession *session; GCallback callback; gpointer userdata; } SessionAsyncData; enum { PROP_0, PROP_SESSION_PATH, }; enum { STATUS_CHANGED, PROGRESS_CHANGED, LAST_SIGNAL }; static guint32 signals[LAST_SIGNAL] = {0, }; typedef struct _SyncevoSessionPrivate { DBusGProxy *proxy; char *path; } SyncevoSessionPrivate; #define GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SYNCEVO_TYPE_SESSION, SyncevoSessionPrivate)) G_DEFINE_TYPE (SyncevoSession, syncevo_session, G_TYPE_OBJECT); static SessionAsyncData* session_async_data_new (SyncevoSession *session, GCallback callback, gpointer userdata) { SessionAsyncData *data; data = g_slice_new0 (SessionAsyncData); data->session = session; data->callback = G_CALLBACK (callback); data->userdata = userdata; return data; } static void session_async_data_free (SessionAsyncData *data) { g_slice_free (SessionAsyncData, data); } static void status_changed_cb (DBusGProxy *proxy, char *status, guint error_code, SyncevoSourceStatuses *source_statuses, SyncevoSession *session) { g_signal_emit (session, signals[STATUS_CHANGED], 0, syncevo_session_status_from_string (status), error_code, source_statuses); } static void progress_changed_cb (DBusGProxy *proxy, int progress, SyncevoSourceProgresses *source_progresses, SyncevoSession *session) { g_signal_emit (session, signals[PROGRESS_CHANGED], 0, progress, source_progresses); } static void syncevo_session_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { SyncevoSession *session = SYNCEVO_SESSION (object); SyncevoSessionPrivate *priv; priv = GET_PRIVATE (session); switch (property_id) { case PROP_SESSION_PATH: g_value_set_string (value, priv->path); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void syncevo_session_set_path (SyncevoSession *session, const char *path) { SyncevoSessionPrivate *priv; DBusGConnection *connection; GError *error; priv = GET_PRIVATE (session); error = NULL; priv->path = g_strdup (path); if (!priv->path) { return; } connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error); if (connection == NULL) { g_printerr ("Failed to open connection to bus: %s\n", error->message); g_error_free (error); priv->proxy = NULL; return; } priv->proxy = dbus_g_proxy_new_for_name (connection, SYNCEVO_SESSION_DBUS_SERVICE, priv->path, SYNCEVO_SESSION_DBUS_INTERFACE); if (priv->proxy == NULL) { g_printerr ("dbus_g_proxy_new_for_name() failed for path '%s'", priv->path); return; } dbus_g_proxy_add_signal (priv->proxy, "StatusChanged", G_TYPE_STRING, G_TYPE_UINT, SYNCEVO_TYPE_SOURCE_STATUSES, G_TYPE_INVALID); dbus_g_proxy_connect_signal (priv->proxy, "StatusChanged", G_CALLBACK (status_changed_cb), session, NULL); dbus_g_proxy_add_signal (priv->proxy, "ProgressChanged", G_TYPE_INT, SYNCEVO_TYPE_SOURCE_PROGRESSES, G_TYPE_INVALID); dbus_g_proxy_connect_signal (priv->proxy, "ProgressChanged", G_CALLBACK (progress_changed_cb), session, NULL); } static void syncevo_session_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { SyncevoSession *session = SYNCEVO_SESSION (object); switch (property_id) { case PROP_SESSION_PATH: syncevo_session_set_path (session, g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void dispose (GObject *object) { SyncevoSessionPrivate *priv; priv = GET_PRIVATE (object); if (priv->proxy) { dbus_g_proxy_disconnect_signal (priv->proxy, "ProgressChanged", G_CALLBACK (progress_changed_cb), object); dbus_g_proxy_disconnect_signal (priv->proxy, "StatusChanged", G_CALLBACK (status_changed_cb), object); /* TODO: need to do this async... */ org_syncevolution_Session_detach (priv->proxy, NULL); g_object_unref (priv->proxy); priv->proxy = NULL; } G_OBJECT_CLASS (syncevo_session_parent_class)->dispose (object); } static void syncevo_session_class_init (SyncevoSessionClass *klass) { GObjectClass *o_class = (GObjectClass *) klass; GParamSpec *pspec; o_class->dispose = dispose; o_class->set_property = syncevo_session_set_property; o_class->get_property = syncevo_session_get_property; g_type_class_add_private (klass, sizeof (SyncevoSessionPrivate)); signals[STATUS_CHANGED] = g_signal_new ("status-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE, G_STRUCT_OFFSET (SyncevoSessionClass, status_changed), NULL, NULL, syncevo_marshal_VOID__UINT_UINT_BOXED, G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_POINTER); signals[PROGRESS_CHANGED] = g_signal_new ("progress-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE, G_STRUCT_OFFSET (SyncevoSessionClass, progress_changed), NULL, NULL, syncevo_marshal_VOID__INT_BOXED, G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_POINTER); pspec = g_param_spec_string ("session-path", "Session path", "The D-Bus path this Syncevolution session uses", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); g_object_class_install_property (o_class, PROP_SESSION_PATH, pspec); } static void syncevo_session_init (SyncevoSession *session) { /* ProgressChanged */ dbus_g_object_register_marshaller (syncevo_marshal_VOID__INT_BOXED, G_TYPE_NONE, G_TYPE_INT, G_TYPE_BOXED, G_TYPE_INVALID); /* StatusChanged */ dbus_g_object_register_marshaller (syncevo_marshal_VOID__STRING_UINT_BOXED, G_TYPE_NONE, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_BOXED, G_TYPE_INVALID); } static void generic_callback (DBusGProxy *proxy, GError *error, SessionAsyncData *data) { if (data->callback) { (*(SyncevoSessionGenericCb)data->callback) (data->session, error, data->userdata); } session_async_data_free (data); } static gboolean generic_error (SessionAsyncData *data) { GError *error; error = g_error_new_literal (g_quark_from_static_string ("syncevo-session"), SYNCEVO_SESSION_ERROR_NO_DBUS_OBJECT, "The D-Bus object does not exist"); (*(SyncevoSessionGenericCb)data->callback) (data->session, error, data->userdata); session_async_data_free (data); return FALSE; } static void get_config_name_callback (DBusGProxy *proxy, char *name, GError *error, SessionAsyncData *data) { if (data->callback) { (*(SyncevoSessionGetConfigNameCb)data->callback) (data->session, name, error, data->userdata); } session_async_data_free (data); } void syncevo_session_get_config_name (SyncevoSession *session, SyncevoSessionGetConfigNameCb callback, gpointer userdata) { SessionAsyncData *data; SyncevoSessionPrivate *priv; priv = GET_PRIVATE (session); data = session_async_data_new (session, G_CALLBACK (callback), userdata); if (!priv->proxy) { if (callback) { g_idle_add ((GSourceFunc)generic_error, data); } return; } org_syncevolution_Session_get_config_name_async (priv->proxy, (org_syncevolution_Session_get_config_name_reply) get_config_name_callback, data); } static void get_config_callback (DBusGProxy *proxy, SyncevoConfig *configuration, GError *error, SessionAsyncData *data) { if (data->callback) { (*(SyncevoSessionGetConfigCb)data->callback) (data->session, configuration, error, data->userdata); } session_async_data_free (data); } static gboolean get_config_error (SessionAsyncData *data) { GError *error; error = g_error_new_literal (g_quark_from_static_string ("syncevo-session"), SYNCEVO_SESSION_ERROR_NO_DBUS_OBJECT, "The D-Bus object does not exist"); (*(SyncevoSessionGetConfigCb)data->callback) (data->session, NULL, error, data->userdata); session_async_data_free (data); return FALSE; } void syncevo_session_get_config (SyncevoSession *session, gboolean template, SyncevoSessionGetConfigCb callback, gpointer userdata) { SessionAsyncData *data; SyncevoSessionPrivate *priv; priv = GET_PRIVATE (session); data = session_async_data_new (session, G_CALLBACK (callback), userdata); if (!priv->proxy) { if (callback) { g_idle_add ((GSourceFunc)get_config_error, data); } return; } org_syncevolution_Session_get_config_async (priv->proxy, template, (org_syncevolution_Session_get_config_reply) get_config_callback, data); } void syncevo_session_set_config (SyncevoSession *session, gboolean update, gboolean temporary, SyncevoConfig *config, SyncevoSessionGenericCb callback, gpointer userdata) { SessionAsyncData *data; SyncevoSessionPrivate *priv; priv = GET_PRIVATE (session); data = session_async_data_new (session, G_CALLBACK (callback), userdata); if (!priv->proxy) { if (callback) { g_idle_add ((GSourceFunc)generic_error, data); } return; } org_syncevolution_Session_set_config_async (priv->proxy, update, temporary, config, (org_syncevolution_Session_set_config_reply) generic_callback, data); } static void get_reports_callback (DBusGProxy *proxy, SyncevoReports *reports, GError *error, SessionAsyncData *data) { if (data->callback) { (*(SyncevoSessionGetReportsCb)data->callback) (data->session, reports, error, data->userdata); } session_async_data_free (data); } static gboolean get_reports_error (SessionAsyncData *data) { GError *error; error = g_error_new_literal (g_quark_from_static_string ("syncevo-session"), SYNCEVO_SESSION_ERROR_NO_DBUS_OBJECT, "The D-Bus object does not exist"); (*(SyncevoSessionGetReportsCb)data->callback) (data->session, NULL, error, data->userdata); session_async_data_free (data); return FALSE; } void syncevo_session_get_reports (SyncevoSession *session, guint start, guint count, SyncevoSessionGetReportsCb callback, gpointer userdata) { SessionAsyncData *data; SyncevoSessionPrivate *priv; priv = GET_PRIVATE (session); data = session_async_data_new (session, G_CALLBACK (callback), userdata); if (!priv->proxy) { if (callback) { g_idle_add ((GSourceFunc)get_reports_error, data); } return; } org_syncevolution_Session_get_reports_async (priv->proxy, start, count, (org_syncevolution_Session_get_reports_reply)get_reports_callback, data); } void syncevo_session_sync (SyncevoSession *session, SyncevoSyncMode mode, SyncevoSourceModes *source_modes, SyncevoSessionGenericCb callback, gpointer userdata) { SessionAsyncData *data; SyncevoSessionPrivate *priv; priv = GET_PRIVATE (session); data = session_async_data_new (session, G_CALLBACK (callback), userdata); if (!priv->proxy) { if (callback) { g_idle_add ((GSourceFunc)generic_error, data); } return; } org_syncevolution_Session_sync_async (priv->proxy, syncevo_sync_mode_to_string (mode), source_modes, (org_syncevolution_Session_sync_reply) generic_callback, data); } void syncevo_session_abort (SyncevoSession *session, SyncevoSessionGenericCb callback, gpointer userdata) { SessionAsyncData *data; SyncevoSessionPrivate *priv; priv = GET_PRIVATE (session); data = session_async_data_new (session, G_CALLBACK (callback), userdata); if (!priv->proxy) { if (callback) { g_idle_add ((GSourceFunc)generic_error, data); } return; } org_syncevolution_Session_abort_async (priv->proxy, (org_syncevolution_Session_abort_reply) generic_callback, data); } void syncevo_session_suspend (SyncevoSession *session, SyncevoSessionGenericCb callback, gpointer userdata) { SessionAsyncData *data; SyncevoSessionPrivate *priv; priv = GET_PRIVATE (session); data = session_async_data_new (session, G_CALLBACK (callback), userdata); if (!priv->proxy) { if (callback) { g_idle_add ((GSourceFunc)generic_error, data); } return; } org_syncevolution_Session_suspend_async (priv->proxy, (org_syncevolution_Session_suspend_reply) generic_callback, data); } static void get_status_callback (DBusGProxy *proxy, char *status, guint error_code, SyncevoSourceStatuses *sources, GError *error, SessionAsyncData *data) { if (data->callback) { (*(SyncevoSessionGetStatusCb)data->callback) (data->session, syncevo_session_status_from_string (status), error_code, sources, error, data->userdata); } session_async_data_free (data); } static gboolean get_status_error (SessionAsyncData *data) { GError *error; error = g_error_new_literal (g_quark_from_static_string ("syncevo-session"), SYNCEVO_SESSION_ERROR_NO_DBUS_OBJECT, "The D-Bus object does not exist"); (*(SyncevoSessionGetStatusCb)data->callback) (data->session, SYNCEVO_STATUS_UNKNOWN, 0, NULL, error, data->userdata); session_async_data_free (data); return FALSE; } void syncevo_session_get_status (SyncevoSession *session, SyncevoSessionGetStatusCb callback, gpointer userdata) { SessionAsyncData *data; SyncevoSessionPrivate *priv; priv = GET_PRIVATE (session); data = session_async_data_new (session, G_CALLBACK (callback), userdata); if (!priv->proxy) { if (callback) { g_idle_add ((GSourceFunc)get_status_error, data); } return; } org_syncevolution_Session_get_status_async (priv->proxy, (org_syncevolution_Session_get_status_reply) get_status_callback, data); } static void get_progress_callback (DBusGProxy *proxy, int progress, SyncevoSourceProgresses *source_progresses, GError *error, SessionAsyncData *data) { if (data->callback) { (*(SyncevoSessionGetProgressCb)data->callback) (data->session, progress, source_progresses, error, data->userdata); } session_async_data_free (data); } static gboolean get_progress_error (SessionAsyncData *data) { GError *error; error = g_error_new_literal (g_quark_from_static_string ("syncevo-session"), SYNCEVO_SESSION_ERROR_NO_DBUS_OBJECT, "The D-Bus object does not exist"); (*(SyncevoSessionGetProgressCb)data->callback) (data->session, -1, NULL, error, data->userdata); session_async_data_free (data); return FALSE; } void syncevo_session_get_progress (SyncevoSession *session, SyncevoSessionGetProgressCb callback, gpointer userdata) { SessionAsyncData *data; SyncevoSessionPrivate *priv; priv = GET_PRIVATE (session); data = session_async_data_new (session, G_CALLBACK (callback), userdata); if (!priv->proxy) { if (callback) { g_idle_add ((GSourceFunc)get_progress_error, data); } return; } org_syncevolution_Session_get_progress_async (priv->proxy, (org_syncevolution_Session_get_progress_reply) get_progress_callback, data); } void syncevo_session_check_source (SyncevoSession *session, const char *source, SyncevoSessionGenericCb callback, gpointer userdata) { SessionAsyncData *data; SyncevoSessionPrivate *priv; priv = GET_PRIVATE (session); data = session_async_data_new (session, G_CALLBACK (callback), userdata); if (!priv->proxy) { if (callback) { g_idle_add ((GSourceFunc)generic_error, data); } return; } org_syncevolution_Session_check_source_async (priv->proxy, source, (org_syncevolution_Session_check_source_reply) generic_callback, data); } void syncevo_session_restore (SyncevoSession *session, const char *backup_dir, const gboolean before, const char **sources, SyncevoSessionGenericCb callback, gpointer userdata) { SessionAsyncData *data; SyncevoSessionPrivate *priv; priv = GET_PRIVATE (session); data = session_async_data_new (session, G_CALLBACK (callback), userdata); if (!priv->proxy) { if (callback) { g_idle_add ((GSourceFunc)generic_error, data); } return; } org_syncevolution_Session_restore_async (priv->proxy, backup_dir, before, sources, (org_syncevolution_Session_check_source_reply) generic_callback, data); } const char* syncevo_session_get_path (SyncevoSession *session) { SyncevoSessionPrivate *priv; priv = GET_PRIVATE (session); return priv->path; } SyncevoSession* syncevo_session_new (const char *path) { return g_object_new (SYNCEVO_TYPE_SESSION, "session-path", path, NULL); } syncevolution_1.4/src/dbus/glib/syncevo-session.h000066400000000000000000000144351230021373600223460ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef __SYNCEVO_SESSION_H__ #define __SYNCEVO_SESSION_H__ #include #include "syncevo-dbus-types.h" G_BEGIN_DECLS enum SyncevoSessionError{ SYNCEVO_SESSION_ERROR_NO_DBUS_OBJECT = 1, }; #define SYNCEVO_SESSION_DBUS_SERVICE "org.syncevolution" #define SYNCEVO_SESSION_DBUS_INTERFACE "org.syncevolution.Session" #define SYNCEVO_TYPE_SESSION (syncevo_session_get_type ()) #define SYNCEVO_SESSION(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), SYNCEVO_TYPE_SESSION, SyncevoSession)) #define SYNCEVO_IS_SESSION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), SYNCEVO_TYPE_SESSION)) typedef struct _SyncevoSession { GObject parent_object; } SyncevoSession; typedef struct _SyncevoSessionClass { GObjectClass parent_class; void (*status_changed) (SyncevoSession *session, SyncevoSessionStatus status, guint error_code, SyncevoSourceStatuses *source_statuses); void (*progress_changed) (SyncevoSession *session, int progress, SyncevoSourceProgresses *source_progresses); } SyncevoSessionClass; GType syncevo_session_get_type (void); typedef void (*SyncevoSessionGenericCb) (SyncevoSession *session, GError *error, gpointer userdata); typedef void (*SyncevoSessionGetConfigNameCb) (SyncevoSession *session, char *name, GError *error, gpointer userdata); void syncevo_session_get_config_name (SyncevoSession *session, SyncevoSessionGetConfigNameCb callback, gpointer userdata); typedef void (*SyncevoSessionGetConfigCb) (SyncevoSession *session, SyncevoConfig *config, GError *error, gpointer userdata); void syncevo_session_get_config (SyncevoSession *session, gboolean template, SyncevoSessionGetConfigCb callback, gpointer userdata); void syncevo_session_set_config (SyncevoSession *session, gboolean update, gboolean temporary, SyncevoConfig *config, SyncevoSessionGenericCb callback, gpointer userdata); typedef void (*SyncevoSessionGetReportsCb) (SyncevoSession *session, SyncevoReports *reports, GError *error, gpointer userdata); void syncevo_session_get_reports (SyncevoSession *session, guint start, guint count, SyncevoSessionGetReportsCb callback, gpointer userdata); void syncevo_session_sync (SyncevoSession *session, SyncevoSyncMode mode, SyncevoSourceModes *source_modes, SyncevoSessionGenericCb callback, gpointer userdata); void syncevo_session_abort (SyncevoSession *session, SyncevoSessionGenericCb callback, gpointer userdata); void syncevo_session_suspend (SyncevoSession *session, SyncevoSessionGenericCb callback, gpointer userdata); typedef void (*SyncevoSessionGetStatusCb) (SyncevoSession *session, SyncevoSessionStatus status, guint error_code, SyncevoSourceStatuses *source_statuses, GError *error, gpointer userdata); void syncevo_session_get_status (SyncevoSession *session, SyncevoSessionGetStatusCb callback, gpointer userdata); typedef void (*SyncevoSessionGetProgressCb) (SyncevoSession *session, guint progress, SyncevoSourceProgresses *source_progresses, GError *error, gpointer userdata); void syncevo_session_get_progress (SyncevoSession *session, SyncevoSessionGetProgressCb callback, gpointer userdata); void syncevo_session_check_source (SyncevoSession *session, const char *source, SyncevoSessionGenericCb callback, gpointer userdata); void syncevo_session_restore (SyncevoSession *session, const char *backup_dir, const gboolean before, const char **sources, SyncevoSessionGenericCb callback, gpointer userdata); const char *syncevo_session_get_path (SyncevoSession *session); SyncevoSession *syncevo_session_new (const char *path); G_END_DECLS #endif syncevolution_1.4/src/dbus/glib/test.c000066400000000000000000000217031230021373600201450ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ /* test syncevo dbus client wrappers */ #include "syncevo-server.h" #include gboolean stop = FALSE; GMainLoop *loop; /* // This is the old progress callback, here for future reference... static void progress_cb (SyncevoService *service, char *server, char *source, int type, int extra1, int extra2, int extra3, GMainLoop *loop) { char *mode, *speed; int percent; switch(type) { case -1: g_print ("Finished syncing %s with return value %d\n", server, extra1); g_main_loop_quit (loop); break; case PEV_SESSIONSTART: g_debug (" progress: %s: session start", server); break; case PEV_SESSIONEND: g_debug (" progress: %s: session end", server); break; case PEV_SENDSTART: g_debug (" progress: %s: send start", server); break; case PEV_SENDEND: g_debug (" progress: %s: send end", server); break; case PEV_RECVSTART: g_debug (" progress: %s: receive start", server); break; case PEV_RECVEND: g_debug (" progress: %s: receive end", server); break; case PEV_ALERTED: switch (extra1) { case 0: speed = ""; break; case 1: speed = "slow "; break; case 2: speed = "first time slow "; break; default: g_assert_not_reached(); } switch (extra3) { case 0: mode = "two-way"; break; case 1: mode = "from server"; break; case 2: mode = "from client"; break; default: g_assert_not_reached(); } g_debug (" source progress: %s/%s: alert (%s%s)", server, source, speed, mode); break; case PEV_PREPARING: percent = CLAMP (100 * extra1 / extra2, 0, 100); g_debug (" source progress: %s/%s: preparing (%d%%)", server, source, percent); break; case PEV_ITEMSENT: percent = CLAMP (100 * extra1 / extra2, 0, 100); g_debug (" source progress: %s/%s: item sent (%d%%)", server, source, percent); break; case PEV_ITEMRECEIVED: percent = CLAMP (100 * extra1 / extra2, 0, 100); g_debug (" source progress: %s/%s: item received (%d%%)", server, source, percent); break; case PEV_ITEMPROCESSED: g_debug (" source progress: %s/%s: item processed (added %d, updated %d, deleted %d)", server, source, extra1, extra2, extra3); break; case PEV_SYNCSTART: g_debug (" source progress: %s/%s: sync started", server, source); break; case PEV_SYNCEND: switch (extra1) { case 0: g_debug (" source progress: %s/%s: sync finished", server, source); break; case LOCERR_USERABORT: g_debug (" source progress: %s/%s: sync aborted by user", server, source); break; case LOCERR_USERSUSPEND: g_debug (" source progress: %s/%s: sync suspended by user", server, source); break; default: g_debug (" source progress: %s/%s: sync finished with error %d", server, source, extra1); } break; default: if(source) g_debug (" source progress: %s/%s: unknown type (%d)", server, source, type); else g_debug (" progress: %s: unknown type (%d)", server, type); g_debug (" %d, %d, %d", extra1, extra2, extra3); } } */ static void get_template_configs_cb(SyncevoServer *server, char **config_names, GError *error, gpointer userdata) { char **name; if (error) { g_printerr ("GetConfigs error: %s", error->message); g_error_free (error); return; } g_print ("GetConfigs (template=TRUE):\n"); for (name = config_names; name && *name; name++) { g_print ("\t%s\n", *name); } g_print ("\n"); } static void print_config_value (char *key, char *value, gpointer data) { g_print ("\t\t%s = %s\n", key, value); } static void print_config (char *key, GHashTable *sourceconfig, gpointer data) { g_print ("\tsource = %s\n", key); g_hash_table_foreach (sourceconfig, (GHFunc)print_config_value, NULL); } static void get_config_cb (SyncevoSession *session, SyncevoConfig *config, GError *error, gpointer userdata) { if (error) { g_printerr ("GetConfig error: %s\n", error->message); g_error_free (error); return; } g_print ("Session configuration:\n"); g_hash_table_foreach (config, (GHFunc)print_config, NULL); } static void progress_cb (SyncevoSession *session, int progress, SyncevoSourceProgresses *source_progresses) { g_print ("\tprogress = %d\n", progress); } static void status_cb (SyncevoSession *session, SyncevoSessionStatus status, guint error_code, SyncevoSourceStatuses *source_statuses, gpointer *data) { if (status == SYNCEVO_STATUS_DONE) { g_print ("Session done."); g_object_unref (session); g_main_loop_quit (loop); } } static void start_session_cb (SyncevoServer *server, char *path, GError *error, gpointer userdata) { GHashTable *source_modes; SyncevoSession *session; if (error) { g_printerr ("StartSession error: %s\n", error->message); g_error_free (error); return; } g_print ("\nTesting Session...\n\n"); session = syncevo_session_new (path); syncevo_session_get_config (session, FALSE, (SyncevoSessionGetConfigCb)get_config_cb, NULL); g_signal_connect (session, "progress-changed", G_CALLBACK (progress_cb), NULL); g_signal_connect (session, "status-changed", G_CALLBACK (status_cb), NULL); /* TODO should wait for session status == idle */ source_modes = g_hash_table_new (g_str_hash, g_str_equal); syncevo_session_sync (session, SYNCEVO_SYNC_DEFAULT, source_modes, NULL, NULL); g_hash_table_unref (source_modes); } static void get_configs_cb(SyncevoServer *server, char **config_names, GError *error, char *service_name) { char **name; if (error) { g_printerr ("GetConfigs error: %s\n", error->message); g_error_free (error); return; } g_print ("GetConfigs (template=FALSE):\n"); for (name = config_names; name && *name; name++){ g_print ("\t%s\n", *name); } g_print ("\n"); if (stop) { g_print ("No server given, stopping here.\n"); g_main_loop_quit (loop); } } static void session_changed_cb (SyncevoServer *server, char *path, gboolean active, gpointer data) { g_print ("Session %s is now %s\n", path, active ? "active" : "not active"); } int main (int argc, char *argv[]) { SyncevoServer *server; char *service = NULL; g_type_init(); g_print ("Testing Server...\n"); server = syncevo_server_get_default (); syncevo_server_get_configs (server, TRUE, (SyncevoServerGetConfigsCb)get_template_configs_cb, NULL); syncevo_server_get_configs (server, FALSE, (SyncevoServerGetConfigsCb)get_configs_cb, NULL); g_signal_connect (server, "session-changed", G_CALLBACK (session_changed_cb), NULL); if (argc < 2) { stop = TRUE; } service = argv[1]; if (service) syncevo_server_start_session (server, service, (SyncevoServerStartSessionCb)start_session_cb, NULL); loop = g_main_loop_new (NULL, TRUE); g_main_loop_run (loop); return 0; } syncevolution_1.4/src/dbus/interfaces/000077500000000000000000000000001230021373600202255ustar00rootroot00000000000000syncevolution_1.4/src/dbus/interfaces/README000066400000000000000000000005511230021373600211060ustar00rootroot00000000000000The documentation of .xml files follows dbus-introspect-docs.dtd and uses spec-to-docbook.xsl from William Jon McCann to convert the documentation into DocBook and then HTML. See http://cgit.freedesktop.org/ConsoleKit/tree/doc/dbus for latest (?) copy of the script and http://cgit.freedesktop.org/ConsoleKit/tree/src for some examples. syncevolution_1.4/src/dbus/interfaces/dbus-introspect-docs.dtd000066400000000000000000000021371230021373600250000ustar00rootroot00000000000000 syncevolution_1.4/src/dbus/interfaces/interfaces.am000066400000000000000000000040521230021373600226700ustar00rootroot00000000000000src/dbus/interfaces/%.xml: src/dbus/interfaces/%-full.xml src/dbus/interfaces/.stamp $(AM_V_GEN)$(XSLT) -o $@ $(top_srcdir)/src/dbus/interfaces/spec-strip-docs.xsl $< if COND_DOC src/dbus/interfaces/%-doc.xml: src/dbus/interfaces/%-full.xml src/dbus/interfaces/.stamp $(AM_V_GEN)$(XSLT) -o $@ $(top_srcdir)/src/dbus/interfaces/spec-to-docbook.xsl $< src/dbus/interfaces/syncevo-dbus-api-doc.xml: src/dbus/interfaces/syncevo-server-doc.xml src/dbus/interfaces/syncevo-session-doc.xml src/dbus/interfaces/syncevo-connection-doc.xml $(AM_V_GEN)echo '' >$@ \ && echo 'SyncEvolution D-Bus API $(VERSION)' >>$@ \ && for xml in $+; \ do \ tail -n +2 $$xml >>$@; \ done; \ echo '' >>$@ src/dbus/interfaces/syncevo-dbus-api-doc.html: src/dbus/interfaces/syncevo-dbus-api-doc.xml build/xsl/html/docbook.xsl src/dbus/interfaces/.stamp $(AM_V_GEN)$(XSLT) -o $@ $(top_srcdir)/build/xsl/html/docbook.xsl $< # This serializes the creation of src/dbus/interfaces when using # out-of-tree builds. xsltproc does this as part of its -o # implementation, but that failed once when using parallel make (race # condition?). src/dbus/interfaces/.stamp: mkdir -p $(@D) touch $@ doc_DATA += src/dbus/interfaces/syncevo-dbus-api-doc.html endif if COND_DOC src_dbus_interfaces_built_sources = \ src/dbus/interfaces/syncevo-server-doc.xml \ src/dbus/interfaces/syncevo-connection-doc.xml \ src/dbus/interfaces/syncevo-session-doc.xml \ src/dbus/interfaces/syncevo-dbus-api-doc.xml \ src/dbus/interfaces/syncevo-dbus-api-doc.html else src_dbus_interfaces_built_sources = endif BUILT_SOURCES += $(src_dbus_interfaces_built_sources) CLEANFILES += \ $(src_dbus_interfaces_built_sources) \ src/dbus/interfaces/.stamp \ $(NONE) dist_noinst_DATA += \ src/dbus/interfaces/spec-strip-docs.xsl \ src/dbus/interfaces/spec-to-docbook.xsl \ src/dbus/interfaces/syncevo-connection-full.xml \ src/dbus/interfaces/syncevo-server-full.xml \ src/dbus/interfaces/syncevo-session-full.xml \ src/dbus/interfaces/README syncevolution_1.4/src/dbus/interfaces/spec-strip-docs.xsl000066400000000000000000000021501230021373600237720ustar00rootroot00000000000000 syncevolution_1.4/src/dbus/interfaces/spec-to-docbook.xsl000066400000000000000000000472651230021373600237630ustar00rootroot00000000000000 interface Methods Signals Implemented Interfaces Objects implementing also implements org.freedesktop.DBus.Introspectable, org.freedesktop.DBus.Properties Properties Description Details Signal Details Property Details : <anchor role="function"><xsl:attribute name="id"><xsl:value-of select="$basename"/>:<xsl:value-of select="@name"/></xsl:attribute></anchor>The "<xsl:value-of select="@name"/>" property '' : <anchor role="function"><xsl:attribute name="id"><xsl:value-of select="$basename"/>::<xsl:value-of select="@name"/></xsl:attribute></anchor>The <xsl:value-of select="@name"/> signal () : Since /> is deprecated since version and should not be used in newly-written code. Use : :: . instead. See also: : Errors : Permissions <anchor role="function"><xsl:attribute name="id"><xsl:value-of select="$basename"/>.<xsl:value-of select="@name"/></xsl:attribute></anchor><xsl:value-of select="@name"/> () () :'' ::() .() '' , '' , '' syncevolution_1.4/src/dbus/interfaces/syncevo-connection-full.xml000066400000000000000000000114311230021373600255320ustar00rootroot00000000000000 The SyncEvolution connection object can be used to exchange messages. Pass data to SyncEvolution. The response is returned via the Reply-signal, so subscribe to that first. The data to be processed. Empty messages are invalid and will trigger an error. The MIME type of the message. "application/vnd.syncml.ds.notification", "application/vnd.syncml+xml" and "application/vnd.syncml+wbxml" are currently supported. Indicates that the connection object is no longer needed. TRUE if the connection is being closed after successfully delivering the final response. FALSE if an error is encountered that cannot be handled by the peer or its transport. SyncEvolution cannot rely on getting a close(FALSE) call in all cases. If its caller disappears from the bus it must assume that there was a fatal error and close the connection. A description which explains the error, may be empty. Used for debugging. The data to be returned to the peer. An empty reply is possible as response to the last message; it must not be delivered. Instead, final will be true and the connection needs to be closed. It is possible that a non-empty reply is sent without the final flag, immediately followed by an empty reply which has the flag set. As described above, the empty reply must be ignored and close() must be called once the non-empty reply is delivered. The MIME type of the reply. The same types as for the message are supported. Transport specific meta information. Currently defined: "URL" - the URL for an HTTP POST. True if this is the last reply. No further messages are expected and the session should be closed once the reply was delivered successfully. The first message in a new connection triggers the creation of a new, random session ID which is returned together with the response. The caller is responsible for ensuring that only messages belonging to this session are passed to future process() calls. In practice the session ID does not change after the first message, but this is not guaranteed and the caller should always use the most recent value. Sent when the server has to shut down the connection. All further operations on it will fail. Resuming the connection may or may not work in such a case. This signal is sent at most once for each connection. No reply will be sent on an aborted connection. syncevolution_1.4/src/dbus/interfaces/syncevo-server-full.xml000066400000000000000000001113441230021373600247050ustar00rootroot00000000000000 Server is the entry object for SyncEvolution client API. It can be used to query information and to start and monitor sessions. Sessions are required to modify the state of SyncEvolution and to synchronize data. They are implemented by additional objects, which exist as long as they are needed (= have clients) or are executing (for example, running a sync session). A session must be active before it can be used. If there are multiple conflicting session requests, they will be queued and started one after the other. At the moment, SyncEvolution will only run one session at a time, although the API would allow concurrent sessions. To be notified when a session is ready for use, subscribe to the SessionChanged signal before asking for a session. It may fire before the request to create a session returns. Either handle that or use Session.GetStatus() to check whether the session is still "queueing". Method calls may fail with the following errors: org.syncevolution.Exceptioncatch-all error condition. org.syncevolution.NoSuchConfigserver configuration name is invalid org.syncevolution.NoSuchSourcesource name is invalid org.syncevolution.SourceUnusableCheckSource() may return this if source is not usable (for various possible reasons). org.syncevolution.InvalidCalla call is (perhaps no longer) allowed or suitable in the current situation, like Detach() when the client is not attached. Describes which features are implemented by the server. If the method itself is unavailable, then the features correspond to SyncEvolution 1.0. The following capabilities are currently defined: ConfigChanged Server.ConfigChange signal available; if not, reread config after each session GetConfigName Session.GetConfigName() implemented SessionAttach Session.Attach() implemented Notifications Server.DisableNotifications() and Server.EnableNotifications() implemented Version Server.GetVersion() implemented; note that this is not meant to be used to determine supported features SessionFlags Server.StartSessionWithFlags() and Session.GetFlags() are implemented DatabaseProperties uses "database", "databaseUser", "databasePassword" source properties instead of the older names "evolutionsource", "evolutionuser", "evolutionpassword"; semantic is unchanged NamedConfig Session.Get/SetNamedConfig() are implemented set of supported capabilities Returns information about server side implementations. "version" - main SyncEvolution release name (usually a number, sometimes also a beta or alpha suffix), "system" - some plain text information about system libraries, "backends" - available backend libraries With no client attached, the server will shut down after a certain period of inactivity. Attaching to the server prevents that. Attaching is not necessary to invoke methods. The main purpose is to keep the server running while clients are around and listen for signals, in particular the Presence signal. Each attach has to be matched by one detach, so that one client has the capability to attach to the server many times in different modules. The behavior of calling Detach() more often than Attach() is undefined. Detaches an attached client. A client which disconnects from D-Bus is automatically detached from the server. Prevents showing of user visible notifications by the syncevo-dbus-server. Must be called while the client is attached to the server. Notifications will be disabled until the client detaches or calls EnableNotifications(). The calls cannot be nested, so calling DisableNotifications()/DisableNoticiations()/EnableNotifications() will enable notifications. describes the notifications which are to be disabled; currently ignored by server, pass empty string Allows showing of user visible notifications again. describes the notifications which are to be enabled; currently ignored by server, pass empty string Launches sync-ui as a reaction of the user activating a notification. Get an array of all configured servers (or templates) In getting templates mode, the dbus server checks all paired devices from bluez daemon and filters them by SyncML capability. Then it looks through built-in templates and returns their matched templates. Multiple templates might be created for each device, with different matching scores(range: 1-5). Scores represent how well the device name matches a template. The higher, the better. A template might be used to help creation for multiple devices. Thus template names must be reset in a naming rule. They are re-named with 'Bluetooth_< mac address>_<sequence number>'. Here 'mac address' is the mac address of the Bluetooth device and 'sequence number' enumerates all the matched templates created for the device. When retrieving the templates with GetConfig(), several additional properties will be returned which can be used to pick the right template for a device, see GetConfig(). The 'syncURL' is already replaced with the mac address of the device and thus can be used to find all templates refering to the same device. if TRUE, will return template names, otherwise will return configured servers array of configured server (or template) names Get the configuration of a specific server (or template). The dictionary keys are "source/<source name>" for sources and the empty string for the main server configuration. More keys might be added in the future. The values are "configuration dictionaries" which contain keys and values matching those in the SyncEvolution server configuration files. In addition, some special keys for read-only values are added. These entries may be set when reading a config or template and can be sent when writing it, but will not actually be stored. configName Normalized configuration. For example, if a session is opened for "FooBar" and there is an existing configuration "foobar@some-context", then the latter is used for the session instead of the "FooBar" shorthand. All configuration names sent by syncevo-dbus-server are normalized. D-Bus clients should compare that against the "configName" value instead of the config name chosen by them or the user. description device template: the description for the template (non-localized string) score device template: the calculated score based on the device name and template (1-5, 5 is best) deviceName device template: the device name that the template is for (copied verbatim from that device, typically chosen by the user of the device) templateName device template: string identifying the class of devices the templates works for, like "Nokia S40"; meant to be shown to users; optional, fall back to first entry in fingerPrint if not set hardwareName device template: "vendor[ model]" string extracted from a device database, unset if neither vendor nor model are known. The deviceName above is probably a better way to present the device to the user, because if a user has multiple identical devices, he hopefully chose unique names for them. fingerPrint device template: comma separated list of devices which work with this template, typically in "vendor model" format; can be used by D-Bus clients to re-match with user provided device information Properties which are not set are also not present in the configuration dictionaries. The semantic difference between "not set" and "empty" or "set to default" is that unset values will always use the default value, even after that changed during a software update. Properties that are set always use the chosen value. Note that property keys are case insensitive. The D-Bus interface specification would allow to send two properties whose keys only differ in case to the server. The result is undefined. server name if TRUE, will return a matching template configuration, otherwise will return a matching server configuration server (or template) configuration Checks whether a sync with a particular server can start. server name See Presence signal for details. All currently available transports. See Presence signal for details. Get synchronization reports for a specific server server name index of the first (newest) report that will be returned; reports are number starting with zero for the newest maximum number of returned reports synchronization reports The array contains report dictionaries. The dictionary keys can be defined by below BNFs: Key ::= 'dir' | 'peer' | 'start' | 'end' | 'status' | 'error' | SourceKey SourceKey ::= SourcePrefix SourcePart SourcePrefix ::= 'source' Sep SourceName SourceName ::= character+ SourcePart ::= Sep ('mode' | 'first' | 'resume' | 'status' | 'backup-before' | 'backup-after' | StatPart) StatPart ::= 'stat' Sep LocName Sep StateName Sep ResultName LocName ::= 'local' | 'remote' StateName ::= 'added' | 'updated' | 'removed' | 'any' ResultName ::= 'total' | 'reject' | 'match' | 'conflict_server_won' | 'conflict_client_won' | 'conflict_duplicated' | 'sent' | 'received' Sep ::= '-' If SourceName has characters '_' and '-', they will be escaped with '__' and '_+' respectively. This means that a key can be split at '-' before replacing these escape sequences in the source name. For a key which contains StatPart, if its value is 0, its pair-value won't be included in the dictionary. Get list of available databases that can be synchronized by a source backend. server name name of the source configuration which defines the backend ("type" property) information about all available databases each entry contains in this order: an optional name that can be shown to the user (already localized or chosen by the user, empty if unavailable), a unique value for the "database" (previously, "evolutionSource") property, a boolean which is true at most once for the default source Tests whether the source configuration is correct. Raises the SourceUnusable exception if not. server name name of the source configuration which is to be tested Start a session. The object is created instantly but will not be ready for method calls until status changes from "queueing" to "idle". The Detach() method can be called before that. Same as StartSessionWithFlags() without any flags set. name of configuration to be created or used in session session D-Bus object path Start a session. The object is created instantly but will not be ready for method calls until status changes from "queueing" to "idle". The Detach() method can be called before that. Additional flags, identified by strings, can be passed to control the session creation. They can be retrieved with Session.GetFlags(). The following flags are currently defined: no-sync session will not be used for running a synchronization all-configs session will provide read/write access to all configurations, via Get/SetNamedConfig() name of configuration to be created or used in session; typically this will be empty when used in combination with 'all-configs' and Get/SetNamedConfig() optional flags session D-Bus object path Establishes a connection between SyncEvolution and a peer (SyncML client or server). That peer might contact SyncEvolution via D-Bus directly (local sync) or via a SyncEvolution server stub that acts as gateway between a peer that is connected to the stub via some other transport mechanism (remote sync). For SyncEvolution this difference is almost completely transparent. In contrast to connections established by SyncEvolution itself, the peer has to send the first message and SyncEvolution replies. If the first message is a normal SyncML message, then SyncEvolution acts as SyncML server. Alternatively, a Notification message can be sent to request that SyncEvolution initiates a SyncML session as client. In the later case, SyncEvolution may or may not use the connection established by Connect(), depending on the content of that first message. The result of Connect() is an object that implements the org.syncevolution.Connection interface. It has to be used for sending at least one message to start the sync. If SyncEvolution needs to abort the connection, it will issue the Close-signal and remove the object. A peer needs to subscribe to that signal before starting to wait for a reply. In addition, the client should also watch out for SyncEvolution quitting unexpectedly. SyncEvolution supports re-establishing a connection that was interrupted. This only works when the peer is a SyncML client, supports resending messages, and the non-D-Bus message transport supports sending the session number as part of the meta information. Various information about the peer who initiated the connection. All of it is optional unless explicitly specified otherwise. Short, single line strings are preferred. "description" - a description of the peer in a format and language that is understood by the user. "id" - a unique ID for this particular peer, in a format that is specific to the transport. The ID only has to be unique among peers using that transport at the current point in time. "transport" - a string identifying the entity which talks directly to SyncEvolution (peer or transport stub). If available, this should be a D-Bus interface name, like "org.openobex.obexd". The main purpose right now is for informing the user and debugging. Later it might also be used to call methods in that interface or for choosing a local configuration for the peer based on its ID. "transport_description" - could be used to describe the version of the transport entity. If false, then the peer is trusted and shall be given access to SyncEvolution without further checks by SyncEvolution itself. This is useful for peers which already run as local user processes with same access rights to the data as SyncEvolution or for transports that authenticate and authorize access via their own mechanisms. If true, then a SyncML client peer must provide valid credentials as part of the SyncML session. For a server, a valid configuration must exist. SyncEvolution searches for such a configuration by matching the sync URL in the Notification with sync URLs in the configurations. If this is a reconnect for an older session, then pass the session ID here. Otherwise pass an empty string. New session IDs are created in response to the initial message, see Reply signal. The connection object created by SyncEvolution in response to this connection request. Implements the org.syncevolution.Connection interface. Get currently existing sessions. This includes active and queueing sessions. array of session D-Bus object paths, in the order in which they will run, running ones first Session start or end session D-Bus object path TRUE if session was started and is active now (= ready for use), FALSE if it ended Template added or removed, for example because a Bluetooth peer was paired resp. removed. To find out more, request list of templates anew. Configuration added, updated or removed. To find out more, request list of configurations anew. Indicates whether a server can be reached right now. This signal can be used by GUIs to prevent starting a sync when it is known to fail, for example because the network is currently down. At the moment, the SyncEvolution server can only monitor network connectivity, which is a cheap local operation and thus done unconditionally while the server runs (see Attach()). Detecting the presence of non-HTTP-based peers might be more costly. Additional APIs might be added to turn that on only when needed. The CheckPresence() method will always force a check. name of the server configuration "no transport" - the transport necessary to reach the server is not working. "not present" - the server is known to be down or unreachable. "" - the server might be usable. Syncs can still fail. Other non-empty strings might be added in the future. They always indicate a condition which prevents syncing. If the server can be reached via multiple transports, this is the one which triggered the signal. Content of the string to be decided... Emitted whenever the server needs information that only a client can provide. Because the server does not know whether clients are monitoring it (attaching to the server is optional) and/or which of the attached clients are able to handle the request, it broadcasts the request. Clients react by calling InfoResponse. The flow of events is this: information needed InfoRequest("request") InfoResponse("working") + dialog is opened (if necessary) InfoRequest("waiting") information becomes available InfoResponse("response") InfoRequest("done") Clients should work on those requests that they support, unless another client was faster (InfoRequest("waiting")). Because there is a race condition, multiple dialogs might be opened. The user only has to enter data in one of them. A client can close his dialog upon InfoRequest("done") and/or InfoRequest("waiting") with a 'handler' parameter which is some other client. If the server does not get a InfoResponse("working") soon enough (in the range of minutes, not seconds), it has to assume that no client can provide the information and fall back to some default or abort. For "type = password" the following keys are used as part of the "parameters" string hash: "name" name of the password property in the config "description" unique English description of the required password. Content is determined by the individual password property, so this may change. Currently used are "SyncML Server", "proxy", "'source name' backend" (with 'source name' replaced by the same names also used for the corresponding config entry). "user", "server", "domain", "object", "protocol", "authtype", "port" optional keys as they would be used in the GNOME keyring. unique ID for the request the Session which is affected, may be empty "request" for a new request, "waiting" for one which is being serviced by some client, "done" for a request which was resolved or timed out for state="waiting": the client which first replied with InfoResponse("working") Determines which information is needed. Currently only "password" for interactive password requests is defined. Auxiliary parameters which depend on the type. reply for a specific InfoRequest unique ID sent by InfoRequest "working" to indicate that a response will be sent later, "response" for the actual reply Response values, valid in state="response", depend on type. For "password" the following keys are used: "password" - the password text, optional, do not set the key if the user cancelled the request Broadcast the console part of the output D-Bus object path. If the output belongs to a session, then path is set as session's object path. Else, it's set as dbus server's object path. the output level (DEBUG/INFO/SHOW/ERROR/WARNING/DEVELOPER) the output string to be broadcast A short tag identifying which process produced the output. Should be shown to the user. Empty for main process in a sync. syncevolution_1.4/src/dbus/interfaces/syncevo-session-full.xml000066400000000000000000000461011230021373600250600ustar00rootroot00000000000000 A Session object is used to do do syncs and to modify the server configurations. Clients can create a Session with Server.StartSession(), attach to a session started by some other client with Session.Attach(), and detach from it with Session.Detach(). Commands (other than Attach() and Detach()) cannot be used before the status changes to "idle" (see GetStatus() and StatusChanged). Get the session flags set with Server.StartSessionWithFlags(). session flags Get the configuration name of the session. Every session is associated with a specific configuration, as requested by the creator of the session. This call returns the normalized configuration name. Note that this may be different from the name used when creating the configuration, because that name is not required to be normalized. Therefore this method may be useful both for the creator of the session and other clients which cannot know the configuration name without calling this method. peer configuration name Get the configuration identified by the name given to StartSession() if TRUE, will return a matching template configuration, otherwise will return the stored configuration server configuration See Server.GetConfig() for dictionary description. Set the configuration of the server TRUE if existing configuration should be updated. FALSE if existing configuration should be cleared. "Cleared" in this context means that all existing properties are removed before setting those passed as argument. Configuration entries (the user-visible part as well as the related meta information, plus the containing directory if it is empty) which are not referenced by a key in the configuration are removed. Setting a completely empty configuration with "update=FALSE" can thus be used to remove the entire configuration. "Completely empty" means "no entries at all" ({} in Python notation). This is different from a config with no properties set ({"": {}} = sync properties reset to defaults, no sources; {"": {}, "source/addressbook": {}} = same, with address book reset to defaults). When a specific peer was selected via the configuration name, clearing and removing properties is done only for the peer-specific properties. When no specific peer was selected, setting an empty configuration with "update=FALSE" removes all source settings and all peers. This allows starting from scratch; setting a non-empty configuration with "update=FALSE" will replace the peer-independent source properties with those that are sent in the new configuration and remove the sources which are not listed, also in all peers. TRUE if configuration changes should only be used for the duration of the session. This is useful to run a single sync session with different settings, for example with an increased logging level. "update=FALSE" removes all previous temporary settings before adding the current ones. Temporary config changes modify the result of GetConfig(), but are ignored when making permanent changes in SetConfig(). server configuration See Server.GetConfig() for dictionary description. Get the configuration identified by the name given in the first argument; same as Server.GetConfig(), provided again in Session for the sake of completeness configuration name Same as SetConfig() except that the modified configuration is named explicitly configuration name; if exactly the same as in StartSession() or StartSessionWithFlags(), then SetNamedConfig() behaves exactly like SetConfig() and none of the constraints for SetNamedConfig() apply temporary changes of the configuration are currently only supported for the configuration chosen when creating the session Get synchronization reports for the server index of the first (newest) report that will be returned maximum number of returned reports synchronization reports See Server.GetReports() for array description. Get list of available databases that can be synchronized by a source backend. name of the source configuration which defines the backend ("type" property); a temporary config is allowed here information about all available databases each entry contains in this order: an optional name that can be shown to the user (already localized or chosen by the user, empty if unavailable), a unique value for the "database" (previously "evolutionSource") property, a boolean which is true at most once for the default source Tests whether the source configuration is correct. Raises the SourceUnusable exception if not. name of the source configuration which is to be tested; a temporary config is allowed here Start synchronization. The synchronization mode selection for sources works like this: Primarily, use mode from "sources" array. If the source was not found or its mode was empty, use the mode parameter. If mode parameter is empty, use the mode in configuration. Examples: sync all with mode from config: Sync (NULL, ()) refresh all from server: Sync ("refresh-from-server", ()) force slow sync for calendar, use mode from config for others: Sync (NULL, (("calendar", "slow"))) sync only calendar and addressbook, with mode from config: Sync ("none", (("calendar", NULL), ("addressbook", NULL))) Syncevolution will by default output a sync "diff" in the end of Sync(). Producing the diff can be expensive CPU-wise, so setting the configuration value "printChanges" to 0 before a Sync() is advised for clients who are not interested in the diff. synchronization mode Valid values are all synchronization modes used in syncevolution server configuration files and the empty string. synchronization source modes Source modes to override the 'mode' variable for specific sources. The dictionary key is source name, value is synchronization mode. Valid synchronization modes are all synchronization modes used in syncevolution server configuration files and the empty string. Abort synchronization. See Status-signal for results. Suspend synchronization. See Status-signal for results. Prevents destruction of the session until the client detaches or quits. Implemented with a counter, so each Attach() must be matched by a Detach() to free the session. Meant to be used by clients which want to follow the progress of a session started by the daemon or some other client. StartSession() automatically includes one Attach() call, so typically the client which created a session never calls Attach() and Detach() only once. Each Attach() or StartSession() must be matched by one Detach() call, otherwise the session will not be destructed. A client that quits without these calls will be detached automatically. Restores the data of the selected sources to the state from before or after the selected synchronization. The synchronization is selected via its session directory. Other directories can also be given as long as they contain database dumps in the format created by SyncEvolution. When "sources" are empty, it assumes to restore all available backup sources in the session. If a source is not found in the backup database, then an exception is thrown. Once this method is called, the session is not active anymore. Thus, to call other APIs, a new session is needed to create. the session directory restore to the data before or after the session sources list that be needed to restore Valid values are all available sources. Checks whether a sync with the current server can start. See org.syncevolution.Server Presence signal for details. Get session status. Individual source statuses are relevant and provided only when status is neither "queuing" nor "idle". Session status Valid values include strings starting with "queueing", "idle" (ready to execute commands), "running", "aborting", "suspending", "done" (a sync was executed, individual source statuses for that sync are available, session is now inactive and cannot execute new commands). The strings may contain additional specifiers separated by a semicolons: "running;waiting", "suspending;waiting". There may be several specifiers: "running;waiting;foo". Specifier "waiting" means that the dbus server is waiting for some external events, typically IO events(like network transports, etc). If "waiting" is absent, then we've done the waiting. Error code for current or last action (zero for no error). Synchronization source status dictionary Dictionary key is source name. The value structs contain synchronization mode, source status and error code. Valid values for status are the same as for status parameter above. "done" represents a synced source when the whole sync is not done yet. Get synchronization progress Rough estimate of current progress 0-100. Synchronization source progress dictionary Dictionary key is source name. The value structs contain phase (can be one of "", "preparing", "sending", "receiving"), prepare count, prepare total, send count, send total, receive count and receive total. -1 is used for unknown. Normally only the 'counts' increase but there are cases where the total will increase as well. Starts execution of the operation defined via the command line arguments. Like Sync(), it returns immediately even if the operation still runs. Session completion indicates that the operation is done. To determine whether that operation was successful, check the session status. Command line arguments Environment variables in clients Session status change. See GetStatus() for argument descriptions. Synchronization progress change. See GetProgress() for argument descriptions. syncevolution_1.4/src/dbus/qt/000077500000000000000000000000001230021373600165265ustar00rootroot00000000000000syncevolution_1.4/src/dbus/qt/dbustypes.cpp000066400000000000000000000050121230021373600212520ustar00rootroot00000000000000/* * Copyright (C) 2010 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "dbustypes.h" // Marshall the SyncDatabase data into a D-BUS argument QDBusArgument &operator<<(QDBusArgument &argument, const SyncDatabase &d) { argument.beginStructure(); argument << d.name << d.source << d.flag; argument.endStructure(); return argument; } // Retrieve the SyncDatabase data from the D-BUS argument const QDBusArgument &operator>>(const QDBusArgument &argument, SyncDatabase &d) { argument.beginStructure(); argument >> d.name >> d.source >> d.flag; argument.endStructure(); return argument; } // Marshall the SyncProgress data into a D-BUS argument QDBusArgument &operator<<(QDBusArgument &argument, const SyncProgress &p) { argument.beginStructure(); argument << p.prepareCount << p.prepareTotal << p.sendCount << p.sendTotal << p.recieveCount \ << p.recieveTotal; argument.endStructure(); return argument; } // Retrieve the SyncProgress data from the D-BUS argument const QDBusArgument &operator>>(const QDBusArgument &argument, SyncProgress &p) { argument.beginStructure(); argument >> p.prepareCount >> p.prepareTotal >> p.sendCount >> p.sendTotal >> p.recieveCount \ >> p.recieveTotal; argument.endStructure(); return argument; } // Marshall the SyncStatus data into a D-BUS argument QDBusArgument &operator<<(QDBusArgument &argument, const SyncStatus &s) { argument.beginStructure(); argument << s.mode << s.status << s.error; argument.endStructure(); return argument; } // Retrieve the SyncStatus data from the D-BUS argument const QDBusArgument &operator>>(const QDBusArgument &argument, SyncStatus &s) { argument.beginStructure(); argument >> s.mode >> s.status >> s.error; argument.endStructure(); return argument; } syncevolution_1.4/src/dbus/qt/dbustypes.h000066400000000000000000000063121230021373600207230ustar00rootroot00000000000000/* * Copyright (C) 2010 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef DBUSTYPES_H #define DBUSTYPES_H #include #include #include #include #include struct SyncDatabase { QString name; QString source; bool flag; }; Q_DECLARE_METATYPE ( SyncDatabase ) // Marshall the SyncDatabase data into a D-BUS argument QDBusArgument &operator<<(QDBusArgument &argument, const SyncDatabase &mystruct); // Retrieve the SyncDatabase data from the D-BUS argument const QDBusArgument &operator>>(const QDBusArgument &argument, SyncDatabase &mystruct); struct SyncProgress { QString phase; int prepareCount; int prepareTotal; int sendCount; int sendTotal; int recieveCount; int recieveTotal; }; Q_DECLARE_METATYPE ( SyncProgress ) // Marshall the SyncProgress data into a D-BUS argument QDBusArgument &operator<<(QDBusArgument &argument, const SyncProgress &mystruct); // Retrieve the SyncProgress data from the D-BUS argument const QDBusArgument &operator>>(const QDBusArgument &argument, SyncProgress &mystruct); struct SyncStatus { QString mode; QString status; unsigned int error; }; Q_DECLARE_METATYPE ( SyncStatus ) // Marshall the SyncStatus data into a D-BUS argument QDBusArgument &operator<<(QDBusArgument &argument, const SyncStatus &mystruct); // Retrieve the SyncStatus data from the D-BUS argument const QDBusArgument &operator>>(const QDBusArgument &argument, SyncStatus &mystruct); typedef QMap QStringMap; typedef QMap QStringMultiMap; typedef QList< QStringMap > QArrayOfStringMap; typedef QList< SyncDatabase > QArrayOfDatabases; typedef QMap QSyncProgressMap; typedef QMap QSyncStatusMap; Q_DECLARE_METATYPE ( QStringMap ) Q_DECLARE_METATYPE ( QStringMultiMap ) Q_DECLARE_METATYPE ( QArrayOfStringMap ) Q_DECLARE_METATYPE ( QArrayOfDatabases ) Q_DECLARE_METATYPE ( QSyncProgressMap ) Q_DECLARE_METATYPE ( QSyncStatusMap ) inline void syncevolution_qt_dbus_register_types() { qDBusRegisterMetaType< SyncDatabase >(); qDBusRegisterMetaType< QStringMap >(); qDBusRegisterMetaType< QStringMultiMap >(); qDBusRegisterMetaType< QArrayOfStringMap >(); qDBusRegisterMetaType< QArrayOfDatabases >(); qDBusRegisterMetaType< SyncProgress >(); qDBusRegisterMetaType< QSyncProgressMap >(); qDBusRegisterMetaType< SyncStatus >(); qDBusRegisterMetaType< QSyncStatusMap >(); } #endif //MYTYPES_H syncevolution_1.4/src/dbus/qt/qt.am000066400000000000000000000053401230021373600174730ustar00rootroot00000000000000if ENABLE_QT_DBUS src_dbus_qt_libsyncevolution_qt_dbus_libraries = \ src/dbus/qt/libsyncevolution-qt-dbus.la lib_LTLIBRARIES += $(src_dbus_qt_libsyncevolution_qt_dbus_libraries) src_dbus_qt_libsyncevolution_qt_dbus_headers = src/dbus/qt/dbustypes.h src_dbus_qt_libsyncevolution_qt_dbus_includedir = $(includedir)/syncevolution-qt-dbus nodist_src_dbus_qt_libsyncevolution_qt_dbus_include_HEADERS = \ $(src_dbus_qt_libsyncevolution_qt_dbus_headers) \ $(built_headers) dist_src_dbus_qt_libsyncevolution_qt_dbus_la_SOURCES = \ $(src_dbus_qt_libsyncevolution_qt_dbus_headers) \ src/dbus/qt/dbustypes.cpp nodist_src_dbus_qt_libsyncevolution_qt_dbus_la_SOURCES = \ $(src_dbus_qt_built_sources) src_dbus_qt_cppflags = \ $(SYNCEVOLUTION_CFLAGS) \ -I$(top_srcdir)/test \ $(BACKEND_CPPFLAGS) built_headers = \ src/dbus/qt/syncevo-server-full.h \ src/dbus/qt/syncevo-session-full.h \ src/dbus/qt/syncevo-connection-full.h src_dbus_qt_built_sources = \ src/dbus/qt/syncevo-server-full.cpp \ src/dbus/qt/syncevo-session-full.cpp \ src/dbus/qt/syncevo-connection-full.cpp \ src/dbus/qt/syncevo-server-full.moc.cpp \ src/dbus/qt/syncevo-session-full.moc.cpp \ src/dbus/qt/syncevo-connection-full.moc.cpp \ $(built_headers) BUILT_SOURCES += $(src_dbus_qt_built_sources) MOSTLYCLEANFILES += $(src_dbus_qt_libsyncevolution_qt_dbus_libraries) MAINTAINERCLEANFILES += src/dbus/qt/Makefile.in CLEANFILES += $(src_dbus_qt_built_sources) \ src/dbus/qt/stamp-server \ src/dbus/qt/stamp-session \ src/dbus/qt/stamp-connection DISTCLEANFILES += src/dbus/qt/syncevolution-qt-dbus.pc dist_noinst_DATA += \ src/dbus/qt/syncevolution-qt-dbus.pc.in #pkgconfigdir is defined in $(top_srcdir)/setup-variables.am pkgconfig_DATA += src/dbus/qt/syncevolution-qt-dbus.pc src_dbus_qt_libsyncevolution_qt_dbus_la_LIBADD = $(QT_DBUS_LIBS) $(QT_LIBS) src_dbus_qt_libsyncevolution_qt_dbus_la_CPPFLAGS = $(src_dbus_qt_cppflags) $(QT_CPPFLAGS) # Allow Qt to set some compile flags, but not the ones normally set via configure. # In particular -W is not compatible with the SyncEvolution header files (we have # unused parameters in some inline functions). src_dbus_qt_libsyncevolution_qt_dbus_la_CXXFLAGS = $(SYNCEVOLUTION_CXXFLAGS) $(filter-out -O2 -g -W -Wall, $(QT_CXXFLAGS)) $(SYNCEVO_WFLAGS) src/dbus/qt/syncevo-%-full.cpp src/dbus/qt/syncevo-%-full.h: src/dbus/qt/stamp-% $(AM_V_GEN) @true # work around #ifndef SYNCEVO-SERVER-FULL_H_1305547804 bug src/dbus/qt/stamp-%: $(top_srcdir)/src/dbus/interfaces/syncevo-%-full.xml $(AM_V_at)@QDBUSXML_TO_CPP@ -p src/dbus/qt/syncevo-$*-full -i src/dbus/qt/dbustypes.h $< \ && perl -pi -e 's/SYNCEVO-(\w*)-FULL_H/SYNCEVO_$$1_FULL_H/' src/dbus/qt/syncevo-$*-full.* \ && echo 'timestamp' >$@ endif # ENABLE_QT_DBUS syncevolution_1.4/src/dbus/qt/syncevolution-qt-dbus.pc.in000066400000000000000000000004021230021373600237510ustar00rootroot00000000000000prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ Name: syncevolution-qt-dbus Description: SyncEvolution Qt D-Bus bindings Version: @VERSION@ Cflags: -I${includedir} Requires: QtDBus Libs: -L${libdir} -lsyncevolution-qt-dbus syncevolution_1.4/src/dbus/server/000077500000000000000000000000001230021373600174105ustar00rootroot00000000000000syncevolution_1.4/src/dbus/server/auto-sync-manager.cpp000066400000000000000000000525561230021373600234630ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "auto-sync-manager.h" #include "session.h" #include "server.h" #include "dbus-callbacks.h" #include "presence-status.h" #include #include #include SE_BEGIN_CXX AutoSyncManager::AutoSyncManager(Server &server) : m_server(server), m_autoTermLocked(false) { } static void updatePresence(Timespec *t, bool present) { *t = present ? Timespec::monotonic() : Timespec(); } boost::shared_ptr AutoSyncManager::createAutoSyncManager(Server &server) { boost::shared_ptr result(new AutoSyncManager(server)); result->m_me = result; result->init(); // update cached information about a config each time it changes server.m_configChangedSignal.connect(Server::ConfigChangedSignal_t::slot_type(&AutoSyncManager::initConfig, result.get(), _1).track(result)); // monitor running sessions server.m_newSyncSessionSignal.connect(Server::NewSyncSessionSignal_t::slot_type(&AutoSyncManager::sessionStarted, result.get(), _1).track(result)); // Keep track of the time when a transport became online. As with // time of last sync, we are pessimistic here and assume that the // transport just now became available. PresenceStatus &p = server.getPresenceStatus(); Timespec now = Timespec::monotonic(); if (p.getBtPresence()) { result->m_btStartTime = now; } p.m_btPresenceSignal.connect(PresenceStatus::PresenceSignal_t::slot_type(updatePresence, &result->m_btStartTime, _1).track(result)); if (p.getHttpPresence()) { result->m_httpStartTime = now; } p.m_httpPresenceSignal.connect(PresenceStatus::PresenceSignal_t::slot_type(updatePresence, &result->m_httpStartTime, _1).track(result)); return result; } void AutoSyncManager::init() { m_notificationManager = NotificationManagerFactory::createManager(); m_notificationManager->init(); m_peerMap.clear(); SyncConfig::ConfigList list = SyncConfig::getConfigs(); BOOST_FOREACH(const SyncConfig::ConfigList::value_type &server, list) { initConfig(server.first); } } void AutoSyncManager::initConfig(const std::string &configName) { SE_LOG_DEBUG(NULL, "auto sync: updating info about config %s", configName.c_str()); if (configName.empty()) { // Anything might have changed. Check all configs we know // about (might have been removed) and all existing configs // (might have been modified). std::set configs; BOOST_FOREACH (const PeerMap::value_type &entry, m_peerMap) { const std::string &configName = entry.first; configs.insert(configName); } BOOST_FOREACH (const StringPair &entry, SyncConfig::getConfigs()) { const std::string &configName = entry.first; configs.insert(configName); } BOOST_FOREACH (const std::string &configName, configs) { if (!configName.empty()) { initConfig(configName); } } // TODO: only call schedule() once in this case, instead // once per recursive initConfig(). } // TODO: once we depend on shared settings, remember to check // all other configs which share the same set of settings. // Not currently the case. // Create anew or update, directly in map. Never remove // old entries, because we want to keep the m_lastSyncTime // in cases where configs get removed and recreated. boost::shared_ptr &task = m_peerMap[configName]; if (!task) { task.reset(new AutoSyncTask(configName)); // We should check past sessions here. Instead we assume // the "worst" case, which is that the session ran zero // seconds ago. This has the additional benefit that we // don't run automatic sync sessions directly after // starting up (the system or syncevo-dbus-server). task->m_lastSyncTime = Timespec::monotonic(); } SyncConfig config (configName); if (config.exists()) { std::vector urls = config.getSyncURL(); std::string autoSync = config.getAutoSync(); //enable http and bt? bool http = false, bt = false; bool any = false; if (autoSync.empty() || boost::iequals(autoSync, "0") || boost::iequals(autoSync, "f")) { http = false; bt = false; any = false; } else if (boost::iequals(autoSync, "1") || boost::iequals(autoSync, "t")) { http = true; bt = true; any = true; } else { BOOST_FOREACH(std::string op, boost::tokenizer< boost::char_separator >(autoSync, boost::char_separator(","))) { if(boost::iequals(op, "http")) { http = true; } else if(boost::iequals(op, "obex-bt")) { bt = true; } } } task->m_peerName = config.getPeerName(); if (task->m_peerName.empty()) { task->m_peerName = configName; } task->m_interval = config.getAutoSyncInterval(); task->m_delay = config.getAutoSyncDelay(); task->m_remoteDeviceId = config.getRemoteDevID(); task->m_notifyLevel = config.getNotifyLevel(); // Assume that whatever change was made might have resolved // the past problem -> allow auto syncing again. task->m_permanentFailure = false; SE_LOG_DEBUG(NULL, "auto sync: %s: auto sync '%s', %s, %s, %d seconds repeat interval, %d seconds online delay", configName.c_str(), autoSync.c_str(), bt ? "Bluetooth" : "no Bluetooth", http ? "HTTP" : "no HTTP", task->m_interval, task->m_delay); task->m_urls.clear(); BOOST_FOREACH(std::string url, urls) { AutoSyncTask::Transport transport = AutoSyncTask::NEEDS_OTHER; // fallback for unknown sync URL if (boost::istarts_with(url, "http")) { transport = AutoSyncTask::NEEDS_HTTP; } else if (boost::istarts_with(url, "local")) { // TODO: instead of assuming that local sync needs HTTP, really look into the target config // and determine what the peerType is transport = AutoSyncTask::NEEDS_HTTP; } else if (boost::istarts_with(url, "obex-bt")) { transport = AutoSyncTask::NEEDS_BT; } if((transport == AutoSyncTask::NEEDS_HTTP && http) || (transport == AutoSyncTask::NEEDS_BT && bt) || (transport == AutoSyncTask::NEEDS_OTHER && any)) { task->m_urls.push_back(std::make_pair(transport, url)); SE_LOG_DEBUG(NULL, "auto sync: adding config %s url %s", configName.c_str(), url.c_str()); } } } else { // Just clear urls, which disables auto syncing. task->m_urls.clear(); } bool lock = preventTerm(); if (m_autoTermLocked && !lock) { SE_LOG_DEBUG(NULL, "auto sync: allow auto shutdown"); m_server.autoTermUnref(); m_autoTermLocked = false; } else if (!m_autoTermLocked && lock) { SE_LOG_DEBUG(NULL, "auto sync: prevent auto shutdown"); m_server.autoTermRef(); m_autoTermLocked = true; } // reschedule schedule("initConfig() for " + configName); } void AutoSyncManager::schedule(const std::string &reason) { SE_LOG_DEBUG(NULL, "auto sync: reschedule, %s", reason.c_str()); // idle callback will be (re)set if needed m_idleConnection.disconnect(); if (!preventTerm()) { SE_LOG_DEBUG(NULL, "auto sync: nothing to do"); return; } if (!m_server.isIdle()) { // Only schedule automatic syncs when nothing else is // going on or pending. SE_LOG_DEBUG(NULL, "auto sync: server not idle"); connectIdle(); return; } // Now look for a suitable task that is ready to run. Timespec now = Timespec::monotonic(); BOOST_FOREACH (const PeerMap::value_type &entry, m_peerMap) { const std::string &configName = entry.first; const boost::shared_ptr &task = entry.second; if (task->m_interval <= 0 || // not enabled task->m_permanentFailure) { // don't try again continue; } if (task->m_lastSyncTime + task->m_interval > now) { // Ran too recently, check again in the future. Always // reset timer, because both m_lastSyncTime and m_interval // may have changed. // // Adds two seconds just to be on the safe side and not // wake up again too soon. int seconds = (task->m_lastSyncTime + task->m_interval - now).seconds() + 2; SE_LOG_DEBUG(NULL, "auto sync: %s: interval expires in %ds", configName.c_str(), seconds); task->m_intervalTimeout.runOnce(seconds, boost::bind(&AutoSyncManager::schedule, this, configName + " interval timer")); continue; } std::string readyURL; BOOST_FOREACH (const AutoSyncTask::URLInfo_t::value_type &urlinfo, task->m_urls) { // check m_delay against presence of transport Timespec *starttime = NULL; PresenceStatus::PresenceSignal_t *signal = NULL; Timeout *timeout = NULL; switch (urlinfo.first) { case AutoSyncTask::NEEDS_HTTP: SE_LOG_DEBUG(NULL, "auto sync: %s: %s uses HTTP", configName.c_str(), urlinfo.second.c_str()); starttime = &m_httpStartTime; signal = &m_server.getPresenceStatus().m_httpPresenceSignal; timeout = &task->m_httpTimeout; break; case AutoSyncTask::NEEDS_BT: SE_LOG_DEBUG(NULL, "auto sync: %s: %s uses Bluetooth", configName.c_str(), urlinfo.second.c_str()); starttime = &m_btStartTime; signal = &m_server.getPresenceStatus().m_btPresenceSignal; timeout = &task->m_btTimeout; break; case AutoSyncTask::NEEDS_OTHER: SE_LOG_DEBUG(NULL, "auto sync: %s: %s uses some unknown transport", configName.c_str(), urlinfo.second.c_str()); break; } if (!starttime || // some other transport, assumed to be online, use it (*starttime && // present (task->m_delay <= 0 || *starttime + task->m_delay <= now))) { // present long enough SE_LOG_DEBUG(NULL, "auto sync: %s: ready to run via %s (transport present for %lds > %ds auto sync delay)", configName.c_str(), urlinfo.second.c_str(), starttime ? (long)(*starttime - now).seconds() : -1, task->m_delay); readyURL = urlinfo.second; break; } if (!*starttime) { // check again when it becomes present signal->connect(PresenceStatus::PresenceSignal_t::slot_type(&AutoSyncManager::schedule, this, "presence change").track(m_me)); SE_LOG_DEBUG(NULL, "auto sync: %s: transport for %s not present", configName.c_str(), urlinfo.second.c_str()); } else { // check again after waiting the requested amount of time int seconds = (*starttime + task->m_delay - now).seconds() + 2; SE_LOG_DEBUG(NULL, "auto sync: %s: presence delay of transport for %s expires in %ds", configName.c_str(), urlinfo.second.c_str(), seconds); timeout->runOnce(seconds, boost::bind(&AutoSyncManager::schedule, this, configName + " transport timer")); } } if (!readyURL.empty()) { // Found a task, run it. The session is not attached to any client, // but we keep a pointer to it, so it won't go away. // m_task = task; // Just in case... also done in syncDone() when we detect // that session is completed. m_server.delaySessionDestruction(m_session); task->m_syncSuccessStart = false; m_session = Session::createSession(m_server, task->m_remoteDeviceId, configName, m_server.getNextSession()); // Temporarily set sync URL to the one which we picked above // once the session is active (setConfig() not allowed earlier). ReadOperations::Config_t config; config[""]["syncURL"] = readyURL; m_session->m_sessionActiveSignal.connect(boost::bind(&Session::setConfig, m_session.get(), true, true, config)); // Run sync as soon as it is active. m_session->m_sessionActiveSignal.connect(boost::bind(&Session::sync, m_session.get(), "", SessionCommon::SourceModes_t())); // Now run it. m_session->activate(); m_server.enqueue(m_session); // Reschedule when server is idle again. connectIdle(); return; } } SE_LOG_DEBUG(NULL, "auto sync: nothing to do now"); } void AutoSyncManager::connectIdle() { m_idleConnection = m_server.m_idleSignal.connect(Server::IdleSignal_t::slot_type(&AutoSyncManager::schedule, this, "server is idle").track(m_me)); } void AutoSyncManager::sessionStarted(const boost::shared_ptr &session) { // Do we have a task for this config? std::string configName = session->getConfigName(); PeerMap::iterator it = m_peerMap.find(configName); if (it == m_peerMap.end()) { SE_LOG_DEBUG(NULL, "auto sync: ignore running sync %s without config", configName.c_str()); return; } boost::shared_ptr me = m_me.lock(); if (!me) { SE_LOG_DEBUG(NULL, "auto sync: already destructing, ignore new sync %s", configName.c_str()); return; } const boost::shared_ptr &task = it->second; task->m_lastSyncTime = Timespec::monotonic(); // track permanent failure session->m_doneSignal.connect(Session::DoneSignal_t::slot_type(&AutoSyncManager::anySyncDone, this, task.get(), _1).track(task).track(me)); if (m_session == session) { // Only for our own auto sync session: notify user once session starts successful. // // In the (unlikely) case that the AutoSyncTask gets deleted, the // slot won't get involved, thus skipping user notifications. // Also protects against manager destructing before session. session->m_syncSuccessStartSignal.connect(Session::SyncSuccessStartSignal_t::slot_type(&AutoSyncManager::autoSyncSuccessStart, this, task.get()).track(task).track(me)); // Notify user once session ends, with or without failure. // Same instance tracking as for sync success start. session->m_doneSignal.connect(Session::DoneSignal_t::slot_type(&AutoSyncManager::autoSyncDone, this, task.get(), _1).track(task).track(me)); } } bool AutoSyncManager::preventTerm() { BOOST_FOREACH (const PeerMap::value_type &entry, m_peerMap) { const boost::shared_ptr &task = entry.second; if (task->m_interval > 0 && !task->m_permanentFailure && !task->m_urls.empty()) { // that task might run return true; } } return false; } void AutoSyncManager::autoSyncSuccessStart(AutoSyncTask *task) { task->m_syncSuccessStart = true; SE_LOG_INFO(NULL,"Automatic sync for '%s' has been successfully started.\n", task->m_peerName.c_str()); if (m_server.notificationsEnabled() && task->m_notifyLevel >= SyncConfig::NOTIFY_ALL) { std::string summary = StringPrintf(_("%s is syncing"), task->m_peerName.c_str()); std::string body = StringPrintf(_("We have just started to sync your computer with the %s sync service."), task->m_peerName.c_str()); // TODO: set config information for 'sync-ui' m_notificationManager->publish(summary, body); } } /** * True if the error is likely to go away by itself when continuing * with auto-syncing. This errs on the side of showing notifications * too often rather than not often enough. */ static bool ErrorIsTemporary(SyncMLStatus status) { switch (status) { case STATUS_TRANSPORT_FAILURE: return true; default: // pretty much everying this not temporary return false; } } void AutoSyncManager::autoSyncDone(AutoSyncTask *task, SyncMLStatus status) { SE_LOG_INFO(NULL,"Automatic sync for '%s' has been done.\n", task->m_peerName.c_str()); if (m_server.notificationsEnabled()) { // send a notification to notification server std::string summary, body; if (task->m_syncSuccessStart && status == STATUS_OK) { if (task->m_notifyLevel >= SyncConfig::NOTIFY_ALL) { // if sync is successfully started and done summary = StringPrintf(_("%s sync complete"), task->m_peerName.c_str()); body = StringPrintf(_("We have just finished syncing your computer with the %s sync service."), task->m_peerName.c_str()); //TODO: set config information for 'sync-ui' m_notificationManager->publish(summary, body); } } else if (task->m_syncSuccessStart || !ErrorIsTemporary(status)) { if (task->m_notifyLevel >= SyncConfig::NOTIFY_ERROR) { // if sync is successfully started and has errors, or not started successful with a permanent error // that needs attention summary = StringPrintf(_("Sync problem.")); body = StringPrintf(_("Sorry, there's a problem with your sync that you need to attend to.")); //TODO: set config information for 'sync-ui' m_notificationManager->publish(summary, body); } } } // keep session around to give clients a chance to query it m_server.delaySessionDestruction(m_session); m_session.reset(); } void AutoSyncManager::anySyncDone(AutoSyncTask *task, SyncMLStatus status) { // set "permanently failed" flag according to most recent result task->m_permanentFailure = status != STATUS_OK && !ErrorIsTemporary(status); SE_LOG_DEBUG(NULL, "auto sync: sync session %s done, result %d %s", task->m_configName.c_str(), status, task->m_permanentFailure ? "is a permanent failure" : status == STATUS_OK ? "is success" : "is temporary failure"); } SE_END_CXX syncevolution_1.4/src/dbus/server/auto-sync-manager.h000066400000000000000000000165721230021373600231260ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef AUTO_SYNC_MANAGER_H #define AUTO_SYNC_MANAGER_H #include #include #include #include #include #include #include #include #include "notification-manager-factory.h" #include "timeout.h" SE_BEGIN_CXX class Server; class Session; /** * Manager to manage automatic sync. * * Once a configuration is enabled with automatic sync, possibly http * or obex-bt or both, the manager tracks whether they are ready to * run. For that it watches which transports are available (and how * long), which syncs run, etc. * * Automatic syncs only run when the server is idle. Then a new * Session is created and thus runs immediately. Because multiple * parallel sessions are not currently supported by SyncEvolution, * scheduling the next session waits until the server is idle again. * * Currently only time-based automatic syncs are supported. * Syncs triggered by local or remote changes will be added * later. */ class AutoSyncManager { Server &m_server; boost::weak_ptr m_me; /** true if we currently hold a ref for AutoTerm */ bool m_autoTermLocked; /** currently running auto sync session */ boost::shared_ptr m_session; /** connects m_server.m_idleSignal with schedule() */ boost::signals2::connection m_idleConnection; /** time when Bluetooth and HTTP transports became available, zero if not available */ Timespec m_btStartTime, m_httpStartTime; /** initialize m_idleConnection */ void connectIdle(); public: /** * A single task for automatic sync. * * Caches information about the corresponding configuration. * Some of that information is directly from the config, other * is collected from sessions (time of last sync). * * Each task maintains information for all sync URLs. */ class AutoSyncTask { public: /** * unique, normalized config name, set when task is created the first time; * by definition it cannot be changed later */ const std::string m_configName; /** * user-configurable peer name, with config name as fallback */ std::string m_peerName; /** copy of config's remoteDeviceId sync property */ std::string m_remoteDeviceId; /** copy of config's notifyLevel property */ SyncConfig::NotifyLevel m_notifyLevel; /** last auto sync attempt succeeded (needed for notification logic) */ bool m_syncSuccessStart; /** last auto sync attempt showed permanent failure (don't retry) */ bool m_permanentFailure; /** autoSyncDelay = the time that the peer must at least have been around (seconds) */ unsigned int m_delay; /** * autoSyncInterval = the minimum time in seconds between syncs. * * Documentation is vague about whether this is measured as the time from * start to start or between end to start. Traditionally, the implementation * was between starts (= fixed rate). This assumed that syncs are short compared * to the interval. In the extreme case (seen in testing), a sync takes longer * than the interval and thus the next sync is started immediately - probably * not what is expected. Keeping the behavior for now. */ unsigned int m_interval; /** * currently the start time of the last sync, measured with * the monotonicly increasing OS time */ Timespec m_lastSyncTime; /** maps syncURL to a specific transport mechanism */ enum Transport { NEEDS_HTTP, NEEDS_BT, NEEDS_OTHER }; /** list of sync URLs for which autosyncing over their transport mechanism , in same order as in syncURL */ typedef std::list< std::pair > URLInfo_t; URLInfo_t m_urls; AutoSyncTask(const std::string &configName) : m_configName(configName), m_syncSuccessStart(false), m_permanentFailure(false), m_delay(0), m_interval(0) { } /* /\** compare whether two tasks are the same, based on unique config name *\/ */ /* bool operator==(const AutoSyncTask &right) const */ /* { */ /* return m_peer == right.m_peer; */ /* } */ Timeout m_intervalTimeout; Timeout m_btTimeout; Timeout m_httpTimeout; }; /* /\** remove tasks from m_peerMap and m_workQueue created from the config *\/ */ /* void remove(const std::string &configName); */ /** * A map with information about *all* configs ever seen while auto * sync manager was active, including configs without auto sync * enabled (to track when and if they ran) and deleted configs * (because they might get recreated). */ typedef std::map > PeerMap; PeerMap m_peerMap; /** used to send notifications */ boost::shared_ptr m_notificationManager; /** * It reads all peers which are enabled to do auto sync and store them in * the m_peerMap and then add timeout sources in the main loop to schedule * auto sync tasks. */ void init(); /** * check m_peerMap: runs syncs that are ready, sets/updates timers for the rest * * @param reason a short explanation why the method gets called (for debugging) */ void schedule(const std::string &reason); /** * Watch further progress (if auto sync session), * record start time (in all cases). */ void sessionStarted(const boost::shared_ptr &session); /** Show "sync started" notification. */ void autoSyncSuccessStart(AutoSyncTask *task); /** Show completion notification. */ void autoSyncDone(AutoSyncTask *task, SyncMLStatus status); /** Record result. */ void anySyncDone(AutoSyncTask *task, SyncMLStatus status); AutoSyncManager(Server &server); public: static boost::shared_ptr createAutoSyncManager(Server &server); /** * prevent dbus server automatic termination when it has * any auto sync task enabled in the configs. * If returning true, prevent automatic termination. */ bool preventTerm(); /** init a config and set up auto sync task for it */ void initConfig(const std::string &configName); }; SE_END_CXX #endif // AUTO_SYNC_MANAGER_H syncevolution_1.4/src/dbus/server/auto-term.h000066400000000000000000000122371230021373600215030ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef AUTOTERM_H #define AUTOTERM_H #include #include SE_BEGIN_CXX /** * Automatic termination and track clients * The dbus server will automatic terminate once it is idle in a given time. * If any attached clients or connections, it never terminate. * Once no actives, timer is started to detect the time of idle. * Note that there will be less-than TERM_INTERVAL inaccuracy in seconds, * that's because we do check every TERM_INTERVAL seconds. */ class AutoTerm { GMainLoop *m_loop; bool &m_shutdownRequested; int m_refs; time_t m_interval; guint m_checkSource; time_t m_lastUsed; /** * This callback is called as soon as we might have to terminate. * If it finds that the server has been used in the meantime, it * will simply set another timeout and check again later. */ static gboolean checkCallback(gpointer data) { AutoTerm *at = static_cast(data); if (!at->m_refs) { // currently idle, but also long enough? time_t now = time(NULL); if (at->m_lastUsed + at->m_interval <= now) { // yes, shut down event loop and daemon SE_LOG_DEBUG(NULL, "terminating because not in use and idle for more than %ld seconds", (long)at->m_interval); at->m_shutdownRequested = true; g_main_loop_quit(at->getLoop()); } else { // check again later SE_LOG_DEBUG(NULL, "not terminating because last used %ld seconds ago, check again in %ld seconds", (long)(now - at->m_lastUsed), (long)(at->m_lastUsed + at->m_interval - now)); at->m_checkSource = g_timeout_add_seconds(at->m_lastUsed + at->m_interval - now, checkCallback, data); } } else { SE_LOG_DEBUG(NULL, "not terminating, not renewing timeout because busy"); } // always remove the current timeout, its job is done return FALSE; } public: /** * constructor * If interval is less than 0, it means 'unlimited' and never terminate */ AutoTerm(GMainLoop *loop, bool &shutdownRequested, int interval) : m_loop(loop), m_shutdownRequested(shutdownRequested), m_refs(0), m_checkSource(0), m_lastUsed(0) { if (interval <= 0) { m_interval = 0; // increasing reference counts prevents shutdown forever ref(); } else { m_interval = interval; } reset(); } ~AutoTerm() { if (m_checkSource) { g_source_remove(m_checkSource); } } /** access to the GMainLoop. */ GMainLoop *getLoop() { return m_loop; } //increase the actives objects void ref(int refs = 1) { m_refs += refs; reset(); } //decrease the actives objects void unref(int refs = 1) { m_refs -= refs; if(m_refs <= 0) { m_refs = 0; } reset(); } /** * To be called each time the server interacts with a client, * which includes adding or removing a client. If necessary, * this installs a timeout to stop the daemon when it has been * idle long enough. */ void reset() { if (m_refs > 0) { // in use, don't need timeout if (m_checkSource) { SE_LOG_DEBUG(NULL, "deactivating idle termination because in use"); g_source_remove(m_checkSource); m_checkSource = 0; } } else { // An already active timeout will trigger at the chosen time, // then notice that the server has been used in the meantime and // reset the timer. Therefore we don't have to remove it. m_lastUsed = time(NULL); if (!m_checkSource) { SE_LOG_DEBUG(NULL, "activating idle termination in %ld seconds because idle", m_interval); m_checkSource = g_timeout_add_seconds(m_interval, checkCallback, static_cast(this)); } } } }; SE_END_CXX #endif // AUTOTERM_H syncevolution_1.4/src/dbus/server/bluetooth_products.ini000066400000000000000000000106441230021373600240460ustar00rootroot00000000000000# Vendor lookup table # # The key is the VendorID and the value the Vendor name. This list # was obtained from # http://www.bluetooth.org/Technical/AssignedNumbers/identifiers.htm # # FIXME: This is available from Bluez. Use that. [Vendors] 0x0000=Sony Ericsson 0x0001=Nokia 0x0002=Intel Corp. 0x0003=IBM Corp. 0x0004=Toshiba Corp. 0x0005=3Com 0x0006=Microsoft 0x0007=Lucent 0x0008=Motorola 0x0009=Infineon Technologies AG 0x000A=Cambridge Silicon Radio 0x000B=Silicon Wave 0x000C=Digianswer A/S 0x000D=Texas Instruments Inc. 0x000E=Parthus Technologies Inc. 0x000F=Broadcom Corporation 0x0010=Mitel Semiconductor 0x0011=Widcomm, Inc. 0x0012=Zeevo, Inc. 0x0013=Atmel Corporation 0x0014=Mitsubishi Electric Corporation 0x0015=RTX Telecom A/S 0x0016=KC Technology Inc. 0x0017=Newlogic 0x0018=Transilica, Inc. 0x0019=Rohde & Schwarz GmbH & Co. KG 0x001A=TTPCom Limited 0x001B=Signia Technologies, Inc. 0x001C=Conexant Systems Inc. 0x001D=Qualcomm 0x001E=Inventel 0x001F=AVM Berlin 0x0020=BandSpeed, Inc. 0x0021=Mansella Ltd 0x0022=NEC Corporation 0x0023=WavePlus Technology Co., Ltd. 0x0024=Alcatel 0x0025=Philips Semiconductors 0x0026=C Technologies 0x0027=Open Interface 0x0028=R F Micro Devices 0x0029=Hitachi Ltd 0x002A=Symbol Technologies, Inc. 0x002B=Tenovis 0x002C=Macronix International Co. Ltd. 0x002D=GCT Semiconductor 0x002E=Norwood Systems 0x002F=MewTel Technology Inc. 0x0030=ST Microelectronics 0x0031=Synopsys 0x0032=Red-M (Communications) Ltd 0x0033=Commil Ltd 0x0034=Computer Access Technology Corporation (CATC) 0x0035=Eclipse (HQ Espana) S.L. 0x0036=Renesas Technology Corp. 0x0037=Mobilian Corporation 0x0038=Terax 0x0039=Integrated System Solution Corp. 0x003A=Matsushita Electric Industrial Co., Ltd. 0x003B=Gennum Corporation 0x003C=Research In Motion 0x003D=IPextreme, Inc. 0x003E=Systems and Chips, Inc 0x003F=Bluetooth SIG, Inc 0x0040=Seiko Epson Corporation 0x0041=Integrated Silicon Solution Taiwan, Inc. 0x0042=CONWISE Technology Corporation Ltd 0x0043=PARROT SA 0x0044=Socket Mobile 0x0045=Atheros Communications, Inc. 0x0046=MediaTek, Inc. 0x0047=Bluegiga 0x0048=Marvell Technology Group Ltd. 0x0049=3DSP Corporation 0x004A=Accel Semiconductor Ltd. 0x004B=Continental Automotive Systems 0x004C=Apple, Inc. 0x004D=Staccato Communications, Inc. 0x004E=Avago Technologies 0x004F=APT Licensing Ltd. 0x0050=SiRF Technology, Inc. 0x0051=Tzero Technologies, Inc. 0x0052=J&M Corporation 0x0053=Free2move AB 0x0054=3DiJoy Corporation 0x0055=Plantronics, Inc. 0x0056=Sony Ericsson 0x0057=Harman International Industries, Inc. 0x0058=Vizio, Inc. 0x0059=Nordic Semiconductor ASA 0x005A=EM Microelectronic-Marin SA 0x005B=Ralink Technology Corporation 0x005C=Belkin International, Inc. 0x005D=Realtek Semiconductor Corporation 0x005E=Stonestreet One, LLC 0x005F=Wicentric, Inc. 0x0060=RivieraWaves S.A.S 0x0061=RDA Microelectronics 0x0062=Gibson Guitars 0x0063=MiCommand Inc. 0x0064=Band XI International, LLC 0x0065=Hewlett-Packard Company 0x0066=9Solutions Oy 0x0067=GN Netcom A/S 0x0068=General Motors 0x0069=A&D Engineering, Inc. 0x006A=MindTree Ltd. 0x006B=Polar Electro OY 0x006C=Beautiful Enterprise Co., Ltd. 0x006D=BriarTek, Inc. 0x006E=Summit Data Communications, Inc. 0x006F=Sound ID 0x0070=Monster, LLC 0x0071=connectBlue AB # Product lookup table # # The keys are of the form "VendorID_ProductID", and the value "Vendor # Model". The VendorID needs to be used as a prefix because ProductIDs # are only unique per vendor. Currently we have no list of product IDs # from vendors so we add them as we find them. To this end we've # written a script (bluetooth-device-id-inspector.py) located in the # top level "test" directory which scans for this info and outputs a # file that can be sent to syncEvolution developers for inclusion. # [Products] 0x0000_0xc089=Sony Ericsson W580i 0x0000_0xc112=Sony Ericsson W995 0x0001_0x003c=Nokia 5310 0x0001_0x0084=Nokia N85 0x0001_0x00c2=Nokia 3720 Classic 0x0001_0x00e7=Nokia 5230 0x0001_0x00ff=Nokia E7 0x0001_0x0191=Nokia 2323 # Questionable devices # # The following all have 0x0000 as product ids. The Android devices # all seem to do this. # # SyncML support: False # Source: 0x0002 # Vendor: 0x000a=LG # product: 0x0000=P990 # # SyncML support: False # Source: 0x0001 # Vendor: 0x000f=HTC # product: 0x0000=Desire HD # # SyncML support: True # Source: 0x0002 # Vendor: 0x000a=Samsung # product: 0x0000=Nexus S # # Devices known to not support the Bluetooth Device ID profile # # Nokia N95 # Nokia N900 # Nokia E71 # Nokia N950 # Siemens S55 syncevolution_1.4/src/dbus/server/bluez-manager.cpp000066400000000000000000000340601230021373600226500ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include "bluez-manager.h" #include "server.h" #include "syncevo/SmartPtr.h" #include #include using namespace GDBusCXX; SE_BEGIN_CXX BluezManager::BluezManager(Server &server) : DBusRemoteObject(!strcmp(getEnv("DBUS_TEST_BLUETOOTH", ""), "none") ? NULL : /* simulate missing Bluez */ GDBusCXX::dbus_get_bus_connection(!strcmp(getEnv("DBUS_TEST_BLUETOOTH", ""), "session") ? "SESSION" : /* use our own Bluez stub */ "SYSTEM" /* use real Bluez */, NULL, true, NULL), "/", "org.bluez.Manager", "org.bluez", true), m_server(server), m_adapterChanged(*this, "DefaultAdapterChanged") { if (getConnection()) { m_done = false; DBusClientCall1 getAdapter(*this, "DefaultAdapter"); getAdapter.start(boost::bind(&BluezManager::defaultAdapterCb, this, _1, _2 )); m_adapterChanged.activate(boost::bind(&BluezManager::defaultAdapterChanged, this, _1)); } else { m_done = true; } } void BluezManager::defaultAdapterChanged(const DBusObject_t &adapter) { m_done = false; //remove devices that belong to this original adapter if(m_adapter) { BOOST_FOREACH(boost::shared_ptr &device, m_adapter->getDevices()) { m_server.removeDevice(device->getMac()); } } string error; defaultAdapterCb(adapter, error); } void BluezManager::defaultAdapterCb(const DBusObject_t &adapter, const string &error) { if(!error.empty()) { SE_LOG_DEBUG(NULL, "Error in calling DefaultAdapter of Interface org.bluez.Manager: %s", error.c_str()); m_done = true; return; } m_adapter.reset(new BluezAdapter(*this, adapter)); } BluezManager::BluezAdapter::BluezAdapter(BluezManager &manager, const string &path) : DBusRemoteObject(manager.getConnection(), path, "org.bluez.Adapter", "org.bluez"), m_manager(manager), m_devNo(0), m_devReplies(0), m_deviceRemoved(*this, "DeviceRemoved"), m_deviceAdded(*this, "DeviceCreated") { DBusClientCall1 > listDevices(*this, "ListDevices"); listDevices.start(boost::bind(&BluezAdapter::listDevicesCb, this, _1, _2)); m_deviceRemoved.activate(boost::bind(&BluezAdapter::deviceRemoved, this, _1)); m_deviceAdded.activate(boost::bind(&BluezAdapter::deviceCreated, this, _1)); } void BluezManager::BluezAdapter::listDevicesCb(const std::vector &devices, const string &error) { if(!error.empty()) { SE_LOG_DEBUG(NULL, "Error in calling ListDevices of Interface org.bluez.Adapter: %s", error.c_str()); checkDone(true); return; } m_devNo = devices.size(); BOOST_FOREACH(const DBusObject_t &device, devices) { boost::shared_ptr bluezDevice(new BluezDevice(*this, device)); m_devices.push_back(bluezDevice); } checkDone(); } void BluezManager::BluezAdapter::deviceRemoved(const DBusObject_t &object) { string address; std::vector >::iterator devIt; for(devIt = m_devices.begin(); devIt != m_devices.end(); ++devIt) { if(boost::equals((*devIt)->getPath(), object)) { address = (*devIt)->m_mac; if((*devIt)->m_reply) { m_devReplies--; } m_devNo--; m_devices.erase(devIt); break; } } m_manager.m_server.removeDevice(address); } void BluezManager::BluezAdapter::deviceCreated(const DBusObject_t &object) { m_devNo++; boost::shared_ptr bluezDevice(new BluezDevice(*this, object)); m_devices.push_back(bluezDevice); } BluezManager::BluezDevice::BluezDevice (BluezAdapter &adapter, const string &path) : GDBusCXX::DBusRemoteObject(adapter.m_manager.getConnection(), path, "org.bluez.Device", "org.bluez"), m_adapter(adapter), m_reply(false), m_propertyChanged(*this, "PropertyChanged") { DBusClientCall1 getProperties(*this, "GetProperties"); getProperties.start(boost::bind(&BluezDevice::getPropertiesCb, this, _1, _2)); m_propertyChanged.activate(boost::bind(&BluezDevice::propertyChanged, this, _1, _2)); } /** * check whether the current device has the PnP Information attribute. */ static bool hasPnpInfoService(const std::vector &uuids) { // The UUID that indicates the PnPInformation attribute is available. static const char * PNPINFOMATION_ATTRIBUTE_UUID = "00001200-0000-1000-8000-00805f9b34fb"; // Note: GetProperties appears to return this list sorted which binary_search requires. if(std::binary_search(uuids.begin(), uuids.end(), PNPINFOMATION_ATTRIBUTE_UUID)) { return true; } return false; } void BluezManager::BluezDevice::checkSyncService(const std::vector &uuids) { static const char * SYNCML_CLIENT_UUID = "00000002-0000-1000-8000-0002ee000002"; bool hasSyncService = false; Server &server = m_adapter.m_manager.m_server; BOOST_FOREACH(const string &uuid, uuids) { //if the device has sync service, add it to the device list if(boost::iequals(uuid, SYNCML_CLIENT_UUID)) { hasSyncService = true; if(!m_mac.empty()) { SyncConfig::DeviceDescription deviceDesc(m_mac, m_name, SyncConfig::MATCH_FOR_SERVER_MODE); server.addDevice(deviceDesc); if(hasPnpInfoService(uuids)) { DBusClientCall1 discoverServices(*this, "DiscoverServices"); static const std::string PNP_INFO_UUID("0x1200"); discoverServices.start(PNP_INFO_UUID, boost::bind(&BluezDevice::discoverServicesCb, this, _1, _2)); } } break; } } // if sync service is not available now, possible to remove device if(!hasSyncService && !m_mac.empty()) { server.removeDevice(m_mac); } } /* * Parse the XML-formatted service record. */ bool extractValuefromServiceRecord(const std::string &serviceRecord, const std::string &attributeId, std::string &attributeValue) { // Find atribute size_t pos = serviceRecord.find(attributeId); // Only proceed if the attribute id was found. if(pos != std::string::npos) { pos = serviceRecord.find("value", pos + attributeId.size()); pos = serviceRecord.find("\"", pos) + 1; int valLen = serviceRecord.find("\"", pos) - pos; attributeValue = serviceRecord.substr(pos, valLen); return true; } return false; } void BluezManager::loadBluetoothDeviceLookupTable() { GError *err = NULL; string filePath(SyncEvolutionDataDir() + "/bluetooth_products.ini"); if(!g_key_file_load_from_file(m_lookupTable.bt_key_file, filePath.c_str(), G_KEY_FILE_NONE, &err)) { SE_LOG_DEBUG(NULL, "%s[%d]: %s - filePath = %s, error = %s", __FILE__, __LINE__, "Bluetooth products file not loaded", filePath.c_str(), err->message); m_lookupTable.isLoaded = false; } else { m_lookupTable.isLoaded = true; } } /* * Get the names of the PnpInformation vendor and product from their * respective ids. At a minimum we need a matching vendor id for this * function to return true. If the product id is not found then we set * it to "", an empty string. */ bool BluezManager::getPnpInfoNamesFromValues(const std::string &vendorValue, std::string &vendorName, const std::string &productValue, std::string &productName) { if(!m_lookupTable.bt_key_file) { // If this is the first invocation we then we need to start watching the loopup table. if (!m_watchedFile) { m_lookupTable.bt_key_file = g_key_file_new(); string filePath(SyncEvolutionDataDir() + "/bluetooth_products.ini"); m_watchedFile = boost::shared_ptr( new GLibNotify(filePath.c_str(), boost::bind(&BluezManager::loadBluetoothDeviceLookupTable, this))); } loadBluetoothDeviceLookupTable(); // Make sure the file was actually loaded if(!m_lookupTable.isLoaded) { return false; } } const char *VENDOR_GROUP = "Vendors"; const char *PRODUCT_GROUP = "Products"; GStringPtr vendor(g_key_file_get_string(m_lookupTable.bt_key_file, VENDOR_GROUP, vendorValue.c_str(), NULL)); if(vendor) { vendorName = vendor.get(); } else { // We at least need a vendor id match. return false; } GStringPtr product(g_key_file_get_string(m_lookupTable.bt_key_file, PRODUCT_GROUP, productValue.c_str(), NULL)); if(product) { productName = product.get(); } else { // If the product is not in the look-up table, the product is // set to an empty string. productName = ""; } return true; } void BluezManager::BluezDevice::discoverServicesCb(const ServiceDict &serviceDict, const string &error) { ServiceDict::const_iterator iter = serviceDict.begin(); if(iter != serviceDict.end()) { std::string serviceRecord = (*iter).second; if(!serviceRecord.empty()) { static const std::string SOURCE_ATTRIBUTE_ID("0x0205"); std::string sourceId; extractValuefromServiceRecord(serviceRecord, SOURCE_ATTRIBUTE_ID, sourceId); // A sourceId of 0x001 indicates that the vendor ID was // assigned by the Bluetooth SIG. // TODO: A sourceId of 0x002, means the vendor id was assigned by // the USB Implementor's forum. We do nothing in this case but // should do that look up as well. if(!boost::iequals(sourceId, "0x0001")) { return; } std::string vendorId, productId; static const std::string VENDOR_ATTRIBUTE_ID ("0x0201"); static const std::string PRODUCT_ATTRIBUTE_ID("0x0202"); extractValuefromServiceRecord(serviceRecord, VENDOR_ATTRIBUTE_ID, vendorId); extractValuefromServiceRecord(serviceRecord, PRODUCT_ATTRIBUTE_ID, productId); std::string vendorName, productName; if (!m_adapter.m_manager.getPnpInfoNamesFromValues(vendorId, vendorName, vendorId + "_" + productId, productName)) { return; } Server &server = m_adapter.m_manager.m_server; SyncConfig::DeviceDescription devDesc; if (server.getDevice(m_mac, devDesc)) { devDesc.m_pnpInformation = boost::shared_ptr( new SyncConfig::PnpInformation(vendorName, productName)); server.updateDevice(m_mac, devDesc); } } } } void BluezManager::BluezDevice::getPropertiesCb(const PropDict &props, const string &error) { m_adapter.m_devReplies++; m_reply = true; if(!error.empty()) { SE_LOG_DEBUG(NULL, "Error in calling GetProperties of Interface org.bluez.Device: %s", error.c_str()); } else { PropDict::const_iterator it = props.find("Name"); if(it != props.end()) { m_name = boost::get(it->second); } it = props.find("Address"); if(it != props.end()) { m_mac = boost::get(it->second); } PropDict::const_iterator uuids = props.find("UUIDs"); if(uuids != props.end()) { const std::vector uuidVec = boost::get >(uuids->second); checkSyncService(uuidVec); } } m_adapter.checkDone(); } void BluezManager::BluezDevice::propertyChanged(const string &name, const boost::variant, string> &prop) { Server &server = m_adapter.m_manager.m_server; if(boost::iequals(name, "Name")) { m_name = boost::get(prop); SyncConfig::DeviceDescription device; if(server.getDevice(m_mac, device)) { device.m_deviceName = m_name; server.updateDevice(m_mac, device); } } else if(boost::iequals(name, "UUIDs")) { const std::vector uuidVec = boost::get >(prop); checkSyncService(uuidVec); } else if(boost::iequals(name, "Address")) { string mac = boost::get(prop); SyncConfig::DeviceDescription device; if(server.getDevice(m_mac, device)) { device.m_deviceId = mac; server.updateDevice(m_mac, device); } m_mac = mac; } } SE_END_CXX syncevolution_1.4/src/dbus/server/bluez-manager.h000066400000000000000000000166341230021373600223240ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef BLUEZ_MANAGER_H #define BLUEZ_MANAGER_H #include #include "gdbus-cxx-bridge.h" #include #include SE_BEGIN_CXX class Server; class GLibNotify; /** * Query bluetooth devices from org.bluez * The basic workflow is: * 1) get default adapter from bluez by calling 'DefaultAdapter' method of org.bluez.Manager * 2) get all devices of the adapter by calling 'ListDevices' method of org.bluez.Adapter * 3) iterate all devices and get properties for each one by calling 'GetProperties' method of org.bluez.Device. * Then check its UUIDs whether it contains sync services and put it in the sync device list if it is. If this * is a sync device we then call DiscoverServices to check for the PnPInformation service record. * * To track changes of devices dynamically, here also listen signals from bluez: * org.bluez.Manager - DefaultAdapterChanged: default adapter is changed and thus have to get its devices * and update sync device list * org.bluez.Adapter - DeviceCreated, DeviceRemoved: device is created or removed and device list is updated * org.bluez.Device - PropertyChanged: property is changed and device information is changed and tracked * * This class is to manage querying bluetooth devices from org.bluez. Also * it acts a proxy to org.bluez.Manager. */ class BluezManager : public GDBusCXX::DBusRemoteObject { public: BluezManager(Server &server); bool isDone() { return m_done; } private: class BluezDevice; /** * This class acts a proxy to org.bluez.Adapter. * Call methods of org.bluez.Adapter and listen signals from it * to get devices list and track its changes */ class BluezAdapter: public GDBusCXX::DBusRemoteObject { public: BluezAdapter (BluezManager &manager, const std::string &path); void checkDone(bool forceDone = false) { if(forceDone || m_devReplies >= m_devNo) { m_devReplies = m_devNo = 0; m_manager.setDone(true); } else { m_manager.setDone(false); } } std::vector >& getDevices() { return m_devices; } private: /** callback of 'ListDevices' signal. Used to get all available devices of the adapter */ void listDevicesCb(const std::vector &devices, const std::string &error); /** callback of 'DeviceRemoved' signal. Used to track a device is removed */ void deviceRemoved(const GDBusCXX::DBusObject_t &object); /** callback of 'DeviceCreated' signal. Used to track a new device is created */ void deviceCreated(const GDBusCXX::DBusObject_t &object); BluezManager &m_manager; /** the number of device for the default adapter */ int m_devNo; /** the number of devices having reply */ int m_devReplies; /** all available devices */ std::vector > m_devices; /** represents 'DeviceRemoved' signal of org.bluez.Adapter*/ GDBusCXX::SignalWatch1 m_deviceRemoved; /** represents 'DeviceAdded' signal of org.bluez.Adapter*/ GDBusCXX::SignalWatch1 m_deviceAdded; friend class BluezDevice; }; /** * This class acts a proxy to org.bluez.Device. * Call methods of org.bluez.Device and listen signals from it * to get properties of device and track its changes */ class BluezDevice: public GDBusCXX::DBusRemoteObject { public: typedef std::map, std::string > > PropDict; typedef std::map ServiceDict; BluezDevice (BluezAdapter &adapter, const std::string &path); std::string getMac() { return m_mac; } /** * check whether the current device has sync service if yes, * put it in the adapter's sync devices list */ void checkSyncService(const std::vector &uuids); private: /** callback of 'GetProperties' method. The properties of the device is gotten */ void getPropertiesCb(const PropDict &props, const std::string &error); /** callback of 'DiscoverServices' method. The service records are retrieved */ void discoverServicesCb(const ServiceDict &serviceDict, const std::string &error); /** callback of 'PropertyChanged' signal. Changed property is tracked */ void propertyChanged(const std::string &name, const boost::variant, std::string> &prop); BluezAdapter &m_adapter; /** name of the device */ std::string m_name; /** mac address of the device */ std::string m_mac; /** whether the calling of 'GetProperties' is returned */ bool m_reply; typedef GDBusCXX::SignalWatch2, std::string> > PropertySignal; /** represents 'PropertyChanged' signal of org.bluez.Device */ PropertySignal m_propertyChanged; friend class BluezAdapter; }; /* * check whether the data is generated. If errors, force initilization done */ void setDone(bool done) { m_done = done; } /** callback of 'DefaultAdapter' method to get the default bluetooth adapter */ void defaultAdapterCb(const GDBusCXX::DBusObject_t &adapter, const std::string &error); /** callback of 'DefaultAdapterChanged' signal to track changes of the default adapter */ void defaultAdapterChanged(const GDBusCXX::DBusObject_t &adapter); Server &m_server; GDBusCXX::DBusConnectionPtr m_bluezConn; boost::shared_ptr m_adapter; // Holds the bluetooth lookup table and whether it was successfully loaded. class lookupTable : private boost::noncopyable { public: lookupTable() : bt_key_file(NULL), isLoaded(false) {} ~lookupTable() { if (bt_key_file) g_key_file_free(bt_key_file); } GKeyFile *bt_key_file; bool isLoaded; } m_lookupTable; boost::shared_ptr m_watchedFile; void loadBluetoothDeviceLookupTable(); bool getPnpInfoNamesFromValues(const std::string &vendorValue, std::string &vendorName, const std::string &productValue, std::string &productName); /** represents 'DefaultAdapterChanged' signal of org.bluez.Adapter*/ GDBusCXX::SignalWatch1 m_adapterChanged; /** flag to indicate whether the calls are all returned */ bool m_done; }; SE_END_CXX #endif // BLUEZ_MANAGER_H syncevolution_1.4/src/dbus/server/client.cpp000066400000000000000000000043201230021373600213710ustar00rootroot00000000000000 /* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "client.h" #include "session.h" #include "server.h" SE_BEGIN_CXX Client::~Client() { SE_LOG_DEBUG(NULL, "D-Bus client %s is destructing", m_ID.c_str()); // explicitly detach all resources instead of just freeing the // list, so that the special behavior for sessions in detach() is // triggered while (!m_resources.empty()) { detach(m_resources.front().get()); } } void Client::detach(Resource *resource) { for (Resources_t::iterator it = m_resources.begin(); it != m_resources.end(); ++it) { if (it->get() == resource) { if (it->unique()) { // client was the last owner, and thus the session must be idle (otherwise // it would also be referenced as active session) boost::shared_ptr session = boost::dynamic_pointer_cast(*it); if (session) { // give clients a chance to query the session m_server.delaySessionDestruction(session); // allow other sessions to start session->done(false); } } // this will trigger removal of the resource if // the client was the last remaining owner m_resources.erase(it); return; } } SE_THROW_EXCEPTION(InvalidCall, "cannot detach from resource that client is not attached to"); } SE_END_CXX syncevolution_1.4/src/dbus/server/client.h000066400000000000000000000073551230021373600210510ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef CLIENT_H #define CLIENT_H #include #include "gdbus-cxx-bridge.h" #include SE_BEGIN_CXX class Server; class Resource; /** * Tracks a single client and all sessions and connections that it is * connected to. Referencing them ensures that they stay around as * long as needed. */ class Client { Server &m_server; typedef std::list< boost::shared_ptr > Resources_t; Resources_t m_resources; /** counts how often a client has called Attach() without Detach() */ int m_attachCount; /** current client setting for notifications (see HAS_NOTIFY) */ bool m_notificationsEnabled; public: const GDBusCXX::Caller_t m_ID; Client(Server &server, const GDBusCXX::Caller_t &ID) : m_server(server), m_attachCount(0), m_notificationsEnabled(true), m_ID(ID) {} ~Client(); void increaseAttachCount() { ++m_attachCount; } void decreaseAttachCount() { --m_attachCount; } int getAttachCount() const { return m_attachCount; } void setNotificationsEnabled(bool enabled) { m_notificationsEnabled = enabled; } bool getNotificationsEnabled() const { return m_notificationsEnabled; } /** * Attach a specific resource to this client. As long as the * resource is attached, it cannot be freed. Can be called * multiple times, which means that detach() also has to be called * the same number of times to finally detach the resource. */ void attach(boost::shared_ptr resource) { m_resources.push_back(resource); } /** * Detach once from the given resource. Has to be called as * often as attach() to really remove all references to the * session. It's an error to call detach() more often than * attach(). */ void detach(Resource *resource); void detach(boost::shared_ptr resource) { detach(resource.get()); } /** * Remove all references to the given resource, regardless whether * it was referenced not at all or multiple times. */ void detachAll(Resource *resource) { Resources_t::iterator it = m_resources.begin(); while (it != m_resources.end()) { if (it->get() == resource) { it = m_resources.erase(it); } else { ++it; } } } void detachAll(boost::shared_ptr resource) { detachAll(resource.get()); } /** * return corresponding smart pointer for a certain resource, * empty pointer if not found */ boost::shared_ptr findResource(Resource *resource) { for (Resources_t::iterator it = m_resources.begin(); it != m_resources.end(); ++it) { if (it->get() == resource) { // got it return *it; } } return boost::shared_ptr(); } }; SE_END_CXX #endif // CLIENT_H syncevolution_1.4/src/dbus/server/cmdline-wrapper.h000066400000000000000000000042171230021373600226560ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef CMD_LINE_WRAPPER_H #define CMD_LINE_WRAPPER_H #include #include "dbus-sync.h" #include "exceptions.h" #include "session-helper.h" SE_BEGIN_CXX /** * A wrapper to maintain the execution of command line arguments from * dbus clients. It creates the DBusSync instance when required and * sets up the same environment as in the D-Bus client. */ class CmdlineWrapper : public Cmdline { virtual SyncContext* createSyncClient() { SessionCommon::SyncParams params; params.m_config = getConfigName(); return new DBusSync(params, m_helper); } SessionHelper &m_helper; /** environment variables passed from client */ map m_envVars; public: CmdlineWrapper(SessionHelper &helper, const vector &args, const map &vars) : Cmdline(args), m_helper(helper), m_envVars(vars) {} bool run() { //temporarily set environment variables and restore them after running list > changes; BOOST_FOREACH(const StringPair &var, m_envVars) { changes.push_back(boost::shared_ptr(new ScopedEnvChange(var.first, var.second))); } bool success = Cmdline::run(); return success; } }; SE_END_CXX #endif // CMD_LINE_WRAPPER_H syncevolution_1.4/src/dbus/server/connection.cpp000066400000000000000000000654771230021373600222760ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "server.h" #include "connection.h" #include "client.h" #include #include #include using namespace GDBusCXX; SE_BEGIN_CXX void Connection::failed(const std::string &reason) { SE_LOG_DEBUG(NULL, "Connection %s: failed: %s (old state %s)", m_sessionID.c_str(), reason.c_str(), SessionCommon::ConnectionStateToString(m_state).c_str()); if (m_failure.empty()) { m_failure = reason; if (m_session) { m_session->setStubConnectionError(reason); } } // notify client abort(); // ensure that state is failed m_state = SessionCommon::FAILED; // tell helper (again) m_statusSignal(reason); // A "failed" connection is dead, no further operations on it // are allowed, in particular not the normal Connection.Close(). // Therefore remove ourselves. // // But don't delete ourselves while some code of the Connection still // runs. Instead let server do that as part of its event loop. boost::shared_ptr c = m_me.lock(); if (c) { m_server.delayDeletion(c); m_server.detach(this); } } std::string Connection::buildDescription(const StringMap &peer) { StringMap::const_iterator desc = peer.find("description"), id = peer.find("id"), trans = peer.find("transport"), trans_desc = peer.find("transport_description"); std::string buffer; buffer.reserve(256); if (desc != peer.end()) { buffer += desc->second; } if (id != peer.end() || trans != peer.end()) { if (!buffer.empty()) { buffer += " "; } buffer += "("; if (id != peer.end()) { buffer += id->second; if (trans != peer.end()) { buffer += " via "; } } if (trans != peer.end()) { buffer += trans->second; if (trans_desc != peer.end()) { buffer += " "; buffer += trans_desc->second; } } buffer += ")"; } return buffer; } static bool IsSyncML(const std::string &messageType) { return messageType == TransportAgent::m_contentTypeSyncML || messageType == TransportAgent::m_contentTypeSyncWBXML; } void Connection::process(const Caller_t &caller, const GDBusCXX::DBusArray &message, const std::string &message_type) { SE_LOG_DEBUG(NULL, "Connection %s: D-Bus client %s sends %lu bytes, %s (old state %s)", m_sessionID.c_str(), caller.c_str(), (unsigned long)message.first, message_type.c_str(), SessionCommon::ConnectionStateToString(m_state).c_str()); boost::shared_ptr client(m_server.findClient(caller)); if (!client) { SE_THROW("unknown client"); } boost::shared_ptr myself = boost::static_pointer_cast(client->findResource(this)); if (!myself) { SE_THROW("client does not own connection"); } // any kind of error from now on terminates the connection try { switch (m_state) { case SessionCommon::SETUP: { std::string config; std::string peerDeviceID; bool serverMode = false; bool serverAlerted = false; // check message type, determine whether we act // as client or server, choose config if (message_type == "HTTP Config") { // type used for testing, payload is config name config.assign(reinterpret_cast(message.second), message.first); } else if (message_type == TransportAgent::m_contentTypeServerAlertedNotificationDS) { serverAlerted = true; sysync::SanPackage san; if (san.PassSan(const_cast(message.second), message.first, 2) || san.GetHeader()) { // We are very tolerant regarding the content of the message. // If it doesn't parse, try to do something useful anyway. // only for SAN 1.2, for SAN 1.0/1.1 we can not be sure // whether it is a SAN package or a normal sync pacakge if (message_type == TransportAgent::m_contentTypeServerAlertedNotificationDS) { config = "default"; SE_LOG_DEBUG(NULL, "SAN parsing failed, falling back to 'default' config"); } } else { //Server alerted notification case // Extract server ID and match it against a server // configuration. Multiple different peers might use the // same serverID ("PC Suite"), so check properties of the // of our configs first before going back to the name itself. std::string serverID = san.fServerID; SyncConfig::ConfigList servers = SyncConfig::getConfigs(); BOOST_FOREACH(const SyncConfig::ConfigList::value_type &server, servers) { SyncConfig conf(server.first); vector urls = conf.getSyncURL(); BOOST_FOREACH (const string &url, urls) { if (url == serverID) { config = server.first; break; } } if (!config.empty()) { break; } } // for Bluetooth transports match against mac address. StringMap::const_iterator id = m_peer.find("id"), trans = m_peer.find("transport"); if (trans != m_peer.end() && id != m_peer.end()) { if (trans->second == "org.openobex.obexd") { m_peerBtAddr = id->second.substr(0, id->second.find("+")); BOOST_FOREACH(const SyncConfig::ConfigList::value_type &server, servers) { SyncConfig conf(server.first); vector urls = conf.getSyncURL(); BOOST_FOREACH (string &url, urls){ url = url.substr (0, url.find("+")); SE_LOG_DEBUG(NULL, "matching against %s",url.c_str()); if (url.find ("obex-bt://") ==0 && url.substr(strlen("obex-bt://"), url.npos) == m_peerBtAddr) { config = server.first; break; } } if (!config.empty()){ break; } } } } if (config.empty()) { BOOST_FOREACH(const SyncConfig::ConfigList::value_type &server, servers) { if (server.first == serverID) { config = serverID; break; } } } // create a default configuration name if none matched if (config.empty()) { config = serverID+"_"+getCurrentTime(); SE_LOG_DEBUG(NULL, "SAN Server ID '%s' unknown, falling back to automatically created '%s' config", serverID.c_str(), config.c_str()); } SE_LOG_DEBUG(NULL, "SAN sync with config %s", config.c_str()); m_SANContent.reset (new SANContent ()); // extract number of sources int numSources = san.fNSync; int syncType; uint32_t contentType; std::string serverURI; if (!numSources) { SE_LOG_DEBUG(NULL, "SAN message with no sources, using selected modes"); // Synchronize all known sources with the default mode. if (san.GetNthSync(0, syncType, contentType, serverURI)) { SE_LOG_DEBUG(NULL, "SAN invalid header, using default modes"); } else if (syncType < SYNC_FIRST || syncType > SYNC_LAST) { SE_LOG_DEBUG(NULL, "SAN invalid sync type %d, using default modes", syncType); } else { m_syncMode = PrettyPrintSyncMode(SyncMode(syncType), true); SE_LOG_DEBUG(NULL, "SAN sync mode for all configured sources: %s", m_syncMode.c_str()); } } else { for (int sync = 1; sync <= numSources; sync++) { if (san.GetNthSync(sync, syncType, contentType, serverURI)) { SE_LOG_DEBUG(NULL, "SAN invalid sync entry #%d", sync); } else if (syncType < SYNC_FIRST || syncType > SYNC_LAST) { SE_LOG_DEBUG(NULL, "SAN invalid sync type %d for entry #%d, ignoring entry", syncType, sync); } else { std::string syncMode = PrettyPrintSyncMode(SyncMode(syncType), true); m_SANContent->m_syncType.push_back (syncMode); m_SANContent->m_serverURI.push_back (serverURI); m_SANContent->m_contentType.push_back (contentType); } } } } // TODO: use the session ID set by the server if non-null } else if (// relaxed checking for XML: ignore stuff like "; CHARSET=UTF-8" IsSyncML(message_type.substr(0, message_type.find(';')))) { // run a new SyncML session as server serverMode = true; if (m_peer.find("config") == m_peer.end() && !m_peer["config"].empty()) { SE_LOG_DEBUG(NULL, "ignoring pre-chosen config '%s'", m_peer["config"].c_str()); } // peek into the data to extract the locURI = device ID, // then use it to find the configuration SyncContext::SyncMLMessageInfo info; info = SyncContext::analyzeSyncMLMessage(reinterpret_cast(message.second), message.first, message_type); if (info.m_deviceID.empty()) { // TODO: proper exception SE_THROW("could not extract LocURI=deviceID from initial message"); } BOOST_FOREACH(const SyncConfig::ConfigList::value_type &entry, SyncConfig::getConfigs()) { SyncConfig peer(entry.first); if (info.m_deviceID == peer.getRemoteDevID()) { config = entry.first; SE_LOG_INFO(NULL, "matched %s against config %s (%s)", info.toString().c_str(), entry.first.c_str(), entry.second.c_str()); // Stop searching. Other peer configs might have the same remoteDevID. // We go with the first one found, which because of the sort order // of getConfigs() ensures that "foo" is found before "foo.old". break; } } if (config.empty()) { // TODO: proper exception SE_THROW(string("no configuration found for ") + info.toString()); } // identified peer, still need to abort previous sessions below peerDeviceID = info.m_deviceID; } else { SE_THROW(StringPrintf("message type '%s' not supported for starting a sync", message_type.c_str())); } // run session as client or server m_state = SessionCommon::PROCESSING; m_session = Session::createSession(m_server, peerDeviceID, config, m_sessionID); m_session->activate(); if (serverMode) { m_session->initServer(SharedBuffer(reinterpret_cast(message.second), message.first), message_type); } m_session->setServerAlerted(serverAlerted); m_session->setPriority(Session::PRI_CONNECTION); m_session->setStubConnection(myself); // this will be reset only when the connection shuts down okay // or overwritten with the error given to us in // Connection::close() m_session->setStubConnectionError("closed prematurely"); // Now abort all earlier sessions, if necessary. The new // session will be enqueued below and thus won't get // killed. It also won't run unless all other sessions // before it terminate, therefore we don't need to check // for success. if (!peerDeviceID.empty()) { // TODO: On failure we should kill the connection (beware, // it might go away before killing completes and/or // fails - need to use shared pointer tracking). // // boost::shared_ptr c = m_me.lock(); // if (!c) { // SE_THROW("internal error: Connection::process() cannot lock its own instance"); // } m_server.killSessionsAsync(peerDeviceID, SimpleResult(SuccessCb_t(), ErrorCb_t())); } m_server.enqueue(m_session); break; } case SessionCommon::PROCESSING: SE_THROW("protocol error: already processing a message"); break; case SessionCommon::WAITING: m_incomingMsg = SharedBuffer(reinterpret_cast(message.second), message.first); m_incomingMsgType = message_type; m_messageSignal(DBusArray(m_incomingMsg.size(), reinterpret_cast(m_incomingMsg.get())), m_incomingMsgType); m_state = SessionCommon::PROCESSING; m_timeout.deactivate(); break; case SessionCommon::FINAL: SE_THROW("protocol error: final reply sent, no further message processing possible"); case SessionCommon::DONE: SE_THROW("protocol error: connection closed, no further message processing possible"); break; case SessionCommon::FAILED: SE_THROW(m_failure); break; default: SE_THROW("protocol error: unknown internal state"); break; } } catch (const std::exception &error) { failed(error.what()); throw; } catch (...) { failed("unknown exception in Connection::process"); throw; } } void Connection::send(const DBusArray buffer, const std::string &type, const std::string &url) { SE_LOG_DEBUG(NULL, "Connection %s: send %lu bytes, %s, %s (old state %s)", m_sessionID.c_str(), (unsigned long)buffer.first, type.c_str(), url.c_str(), SessionCommon::ConnectionStateToString(m_state).c_str()); if (m_state != SessionCommon::PROCESSING) { SE_THROW_EXCEPTION(TransportException, "cannot send to our D-Bus peer"); } // Change state in advance. If we fail while replying, then all // further resends will fail with the error above. m_state = SessionCommon::WAITING; activateTimeout(); m_incomingMsg = SharedBuffer(); // TODO: turn D-Bus exceptions into transport exceptions StringMap meta; meta["URL"] = url; reply(buffer, type, meta, false, m_sessionID); } void Connection::sendFinalMsg() { SE_LOG_DEBUG(NULL, "Connection %s: shut down (old state %s)", m_sessionID.c_str(), SessionCommon::ConnectionStateToString(m_state).c_str()); if (m_state == SessionCommon::PROCESSING) { // send final, empty message and wait for close m_state = SessionCommon::FINAL; reply(GDBusCXX::DBusArray(0, 0), "", StringMap(), true, m_sessionID); } } void Connection::close(const Caller_t &caller, bool normal, const std::string &error) { SE_LOG_DEBUG(NULL, "Connection %s: client %s closes connection %s %s%s%s (old state %s)", m_sessionID.c_str(), caller.c_str(), getPath(), normal ? "normally" : "with error", error.empty() ? "" : ": ", error.c_str(), SessionCommon::ConnectionStateToString(m_state).c_str()); boost::shared_ptr client(m_server.findClient(caller)); if (!client) { SE_THROW("unknown client"); } // Remove reference to us from client, will destruct *this* // instance. To let us finish our work safely, keep a reference // that the server will unref when everything is idle again. boost::shared_ptr c = m_me.lock(); if (!c) { SE_THROW("connection already destructing"); } m_server.delayDeletion(c); client->detach(this); if (!normal || m_state != SessionCommon::FINAL) { std::string err = error.empty() ? "connection closed unexpectedly" : error; m_statusSignal(err); if (m_session) { m_session->setStubConnectionError(err); } failed(err); } else { m_state = SessionCommon::DONE; m_statusSignal(""); if (m_session) { m_session->setStubConnectionError(""); } } // TODO (?): errors during shutdown of the helper will not get // reported back to the client, which sees the operation as // completed successfully once this call returns. } void Connection::abort() { if (!m_abortSent) { SE_LOG_DEBUG(NULL, "Connection %s: send abort to client (state %s)", m_sessionID.c_str(), SessionCommon::ConnectionStateToString(m_state).c_str()); sendAbort(); m_abortSent = true; } else { SE_LOG_DEBUG(NULL, "Connection %s: not sending abort to client, already done (state %s)", m_sessionID.c_str(), SessionCommon::ConnectionStateToString(m_state).c_str()); } } void Connection::shutdown() { SE_LOG_DEBUG(NULL, "Connection %s: self-destructing (state %s)", m_sessionID.c_str(), SessionCommon::ConnectionStateToString(m_state).c_str()); // trigger removal of this connection by removing all // references to it m_server.detach(this); } Connection::Connection(Server &server, const DBusConnectionPtr &conn, const std::string &sessionID, const StringMap &peer, bool must_authenticate) : DBusObjectHelper(conn, std::string("/org/syncevolution/Connection/") + sessionID, "org.syncevolution.Connection", boost::bind(&Server::autoTermCallback, &server)), m_server(server), m_peer(peer), m_mustAuthenticate(must_authenticate), m_state(SessionCommon::SETUP), m_sessionID(sessionID), m_timeoutSeconds(-1), sendAbort(*this, "Abort"), m_abortSent(false), reply(*this, "Reply"), m_description(buildDescription(peer)) { add(this, &Connection::process, "Process"); add(this, &Connection::close, "Close"); add(sendAbort); add(reply); m_server.autoTermRef(); SE_LOG_DEBUG(NULL, "Connection %s: created", m_sessionID.c_str()); } boost::shared_ptr Connection::createConnection(Server &server, const DBusConnectionPtr &conn, const std::string &sessionID, const StringMap &peer, bool must_authenticate) { boost::shared_ptr c(new Connection(server, conn, sessionID, peer, must_authenticate)); c->m_me = c; return c; } Connection::~Connection() { SE_LOG_DEBUG(NULL, "Connection %s: done with '%s'%s%s%s (old state %s)", m_sessionID.c_str(), m_description.c_str(), m_state == SessionCommon::DONE ? ", normal shutdown" : " unexpectedly", m_failure.empty() ? "" : ": ", m_failure.c_str(), SessionCommon::ConnectionStateToString(m_state).c_str()); try { if (m_state != SessionCommon::DONE) { abort(); m_state = SessionCommon::FAILED; } // DBusTransportAgent waiting? Wake it up. m_statusSignal(m_failure); m_session.reset(); } catch (...) { // log errors, but do not propagate them because we are // destructing Exception::handle(); } m_server.autoTermUnref(); } void Connection::ready() { SE_LOG_DEBUG(NULL, "Connection %s: ready to run (old state %s)", m_sessionID.c_str(), SessionCommon::ConnectionStateToString(m_state).c_str()); //if configuration not yet created std::string configName = m_session->getConfigName(); SyncConfig config (configName); if (!config.exists() && m_SANContent) { SE_LOG_DEBUG(NULL, "Configuration %s not exists for a runnable session in a SAN context, create it automatically", configName.c_str()); ReadOperations::Config_t from; const std::string templateName = "SyncEvolution"; // TODO: support SAN from other well known servers ReadOperations ops(templateName, m_server); ops.getConfig(true , from); if (!m_peerBtAddr.empty()){ from[""]["SyncURL"] = string ("obex-bt://") + m_peerBtAddr; } m_session->setConfig (false, false, from); } // As we cannot resend messages via D-Bus even if running as // client (API not designed for it), let's use the hard server // timeout from RetryDuration here. m_timeoutSeconds = config.getRetryDuration(); const SyncContext context (configName); std::list sources = context.getSyncSources(); if (m_SANContent && !m_SANContent->m_syncType.empty()) { // check what the server wants us to synchronize // and only synchronize that m_syncMode = "disabled"; for (size_t sync=0; syncm_syncType.size(); sync++) { std::string syncMode = m_SANContent->m_syncType[sync]; std::string serverURI = m_SANContent->m_serverURI[sync]; //uint32_t contentType = m_SANContent->m_contentType[sync]; bool found = false; BOOST_FOREACH(const std::string &source, sources) { boost::shared_ptr sourceConfig(context.getSyncSourceConfig(source)); // prefix match because the local // configuration might contain // additional parameters (like date // range selection for events) if (boost::starts_with(sourceConfig->getURINonEmpty(), serverURI)) { SE_LOG_DEBUG(NULL, "SAN entry #%d = source %s with mode %s", (int)sync, source.c_str(), syncMode.c_str()); m_sourceModes[source] = syncMode; found = true; break; } } if (!found) { SE_LOG_DEBUG(NULL, "SAN entry #%d with mode %s ignored because Server URI %s is unknown", (int)sync, syncMode.c_str(), serverURI.c_str()); } } if (m_sourceModes.empty()) { SE_LOG_DEBUG(NULL, "SAN message with no known entries, falling back to default"); m_syncMode = ""; } } if (m_SANContent) { m_session->setRemoteInitiated(true); } // proceed with sync now that our session is ready m_session->sync(m_syncMode, m_sourceModes); } void Connection::activateTimeout() { if (m_timeoutSeconds >= 0) { m_timeout.runOnce(m_timeoutSeconds, boost::bind(&Connection::timeoutCb, this)); } else { m_timeout.deactivate(); } } void Connection::timeoutCb() { SE_LOG_DEBUG(NULL, "Connection %s: timed out after %ds (state %s)", m_sessionID.c_str(), m_timeoutSeconds, SessionCommon::ConnectionStateToString(m_state).c_str()); failed(StringPrintf("timed out after %ds", m_timeoutSeconds)); } SE_END_CXX syncevolution_1.4/src/dbus/server/connection.h000066400000000000000000000132241230021373600217220ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef CONNECTION_H #define CONNECTION_H #include "session.h" #include "session-common.h" #include "resource.h" #include #include #include SE_BEGIN_CXX class Server; class Session; /** * Represents and implements the Connection interface. * * The connection interacts with a Session by creating the Session and * exchanging data with it. For that, the connection registers itself * with the Session and unregisters again when it goes away. * * In contrast to clients, the Session only keeps a weak_ptr, which * becomes invalid when the referenced object gets deleted. Typically * this means the Session has to abort, unless reconnecting is * supported. */ class Connection : public GDBusCXX::DBusObjectHelper, public Resource { private: Server &m_server; boost::weak_ptr m_me; StringMap m_peer; bool m_mustAuthenticate; SessionCommon::ConnectionState m_state; std::string m_failure; /** first parameter for Session::sync() */ std::string m_syncMode; /** second parameter for Session::sync() */ SessionCommon::SourceModes_t m_sourceModes; const std::string m_sessionID; boost::shared_ptr m_session; /** * Defines the timeout in seconds. -1 and thus "no timeout" by default. * * The timeout is acticated each time the connection goes into * WAITING mode. Once it triggers, the connection is put into * the FAILED and queued for delayed deletion in the server. */ int m_timeoutSeconds; Timeout m_timeout; void activateTimeout(); void timeoutCb(); /** * buffer for received data, waiting here for engine to ask * for it via DBusTransportAgent::getReply(). */ SharedBuffer m_incomingMsg; std::string m_incomingMsgType; struct SANContent { std::vector m_syncType; std::vector m_contentType; std::vector m_serverURI; }; /** * The content of a parsed SAN package to be processed via * connection.ready */ boost::shared_ptr m_SANContent; std::string m_peerBtAddr; /** * records the reason for the failure, sends Abort signal and puts * the connection into the FAILED state. */ void failed(const std::string &reason); /** * returns " ( via )" */ static std::string buildDescription(const StringMap &peer); /** Connection.Process() */ void process(const GDBusCXX::Caller_t &caller, const GDBusCXX::DBusArray &message, const std::string &message_type); /** Connection.Close() */ void close(const GDBusCXX::Caller_t &caller, bool normal, const std::string &error); /** wrapper around sendAbort */ void abort(); /** Connection.Abort */ GDBusCXX::EmitSignal0 sendAbort; bool m_abortSent; /** Connection.Reply */ GDBusCXX::EmitSignal5 &, const std::string &, const StringMap &, bool, const std::string &> reply; Connection(Server &server, const GDBusCXX::DBusConnectionPtr &conn, const std::string &session_num, const StringMap &peer, bool must_authenticate); public: const std::string m_description; static boost::shared_ptr createConnection(Server &server, const GDBusCXX::DBusConnectionPtr &conn, const std::string &session_num, const StringMap &peer, bool must_authenticate); ~Connection(); /** session requested by us is ready to run a sync */ void ready(); /** send outgoing message via connection */ void send(const GDBusCXX::DBusArray buffer, const std::string &type, const std::string &url); /** send last, empty message and enter FINAL state */ void sendFinalMsg(); /** connection is no longer needed, ensure that it gets deleted */ void shutdown(); /** peer is not trusted, must authenticate as part of SyncML */ bool mustAuthenticate() const { return m_mustAuthenticate; } /** new incoming message ready */ typedef boost::signals2::signal &, const std::string &)> MessageSignal_t; MessageSignal_t m_messageSignal; /** connection went down (empty string) or failed (error message) */ typedef boost::signals2::signal StatusSignal_t; StatusSignal_t m_statusSignal; }; SE_END_CXX #endif // CONNECTION_H syncevolution_1.4/src/dbus/server/connman-client.cpp000066400000000000000000000072601230021373600230260ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "connman-client.h" #include "server.h" #include "presence-status.h" SE_BEGIN_CXX ConnmanClient::ConnmanClient(Server &server): DBusRemoteObject(!strcmp(getEnv("DBUS_TEST_CONNMAN", ""), "none") ? NULL : /* simulate missing ConnMan */ GDBusCXX::dbus_get_bus_connection(!strcmp(getEnv("DBUS_TEST_CONNMAN", ""), "session") ? "SESSION" : /* use our own ConnMan stub */ "SYSTEM" /* use real ConnMan */, NULL, true, NULL), "/", "net.connman.Manager", "net.connman", true), m_available(false), m_server(server), m_propertyChanged(*this, "PropertyChanged") { if (getConnection()) { typedef std::map > PropDict; GDBusCXX::DBusClientCall1 getProp(*this,"GetProperties"); getProp.start(boost::bind(&ConnmanClient::getPropCb, this, _1, _2)); m_propertyChanged.activate(boost::bind(&ConnmanClient::propertyChanged, this, _1, _2)); }else{ SE_LOG_DEBUG(NULL, "DBus connection setup for connman failed"); } } void ConnmanClient::getPropCb (const std::map >& props, const string &error){ if (!error.empty()) { m_available = false; if (error == "org.freedesktop.DBus.Error.ServiceUnknown") { // ensure there is still first set of singal set in case of no // connman available m_server.getPresenceStatus().updatePresenceStatus (true, PresenceStatus::HTTP_TRANSPORT); SE_LOG_DEBUG(NULL, "No connman service available %s", error.c_str()); return; } SE_LOG_DEBUG(NULL, "error in connmanCallback %s", error.c_str()); return; } m_available = true; typedef std::pair > element; bool httpPresence = false; BOOST_FOREACH (element entry, props) { // just check online state, we don't care how about the underlying technology if (entry.first == "State") { std::string state = boost::get(entry.second); httpPresence = state == "online"; break; } } //now delivering the signals m_server.getPresenceStatus().updatePresenceStatus (httpPresence, PresenceStatus::HTTP_TRANSPORT); } void ConnmanClient::propertyChanged(const std::string &name, const boost::variant, std::string> &prop) { if (name == "State") { std::string state = boost::get(prop); m_server.getPresenceStatus().updatePresenceStatus(state == "online", PresenceStatus::HTTP_TRANSPORT); } } SE_END_CXX syncevolution_1.4/src/dbus/server/connman-client.h000066400000000000000000000032521230021373600224700ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef CONNMAN_CLIENT_H #define CONNMAN_CLIENT_H #include #include "gdbus-cxx-bridge.h" #include SE_BEGIN_CXX class Server; /* * Implements org.connman.Manager * GetProperty : getPropCb * PropertyChanged: propertyChanged **/ class ConnmanClient : public GDBusCXX::DBusRemoteObject { public: ConnmanClient (Server &server); void propertyChanged(const std::string &name, const boost::variant, std::string> &prop); void getPropCb(const std::map >& props, const std::string &error); /** TRUE if watching ConnMan status */ bool isAvailable() { return m_available; } private: bool m_available; Server &m_server; GDBusCXX::SignalWatch2 > m_propertyChanged; }; SE_END_CXX #endif // CONNMAN_CLIENT_H syncevolution_1.4/src/dbus/server/dbus-callbacks.cpp000066400000000000000000000040161230021373600227670ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "dbus-callbacks.h" #include "session-common.h" #include #include SE_BEGIN_CXX uint32_t dbusErrorCallback(const boost::shared_ptr &result) { try { // If there is no pending exception, the process will abort // with "terminate called without an active exception"; // dbusErrorCallback() should only be called when there is // a pending exception. // TODO: catch this misuse in a better way throw; } catch (...) { // let D-Bus parent log the error std::string explanation; uint32_t error = Exception::handle(explanation, HANDLE_EXCEPTION_NO_ERROR); try { result->failed(GDBusCXX::dbus_error(SessionCommon::SERVER_IFACE, explanation)); } catch (...) { // Ignore failures while sending the reply. This can // happen when our caller dropped the connection before we // could reply. Exception::handle(HANDLE_EXCEPTION_NO_ERROR); } return error; } // keep compiler happy return 500; } ErrorCb_t createDBusErrorCb(const boost::shared_ptr &result) { return boost::bind(dbusErrorCallback, result); } SE_END_CXX syncevolution_1.4/src/dbus/server/dbus-callbacks.h000066400000000000000000000162421230021373600224400ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_DBUS_CALLBACKS #define INCL_DBUS_CALLBACKS #include #include #include #include #include #include SE_BEGIN_CXX /** * Any method inside syncevo-dbus-server which might block for * extended periods of time must be asynchronous. It has to alert the * caller of success (with a custom callback) or failure (with the * ErrorCb_t callback) once it is done with executing the triggered * operation. * * The error callback is invoked inside an exception handler. The * callback then needs to rethrow the exection to determine what the * real error is and react accordingly. A default error callback which * relays the error back to the D-Bus caller is provided below * (dbusErrorCallback(), used by createDBusErrorCb()). * * Asynchronous functions have to take care that exactly those * exceptions which indicate a failure of the requested operation * invoke the error callback. There might be other exceptions, usually * related to fatal problems in the process itself. * * The caller of an asynchronous method doesn't have to (and in fact, * shouldn't!) catch these exceptions and leave handling of them to * the top-level catch clauses. In return it may assume that the error * callback is invoked only in relation to the requested operation and * that the server is able to continue to run. * Only one of these two callbacks gets invoked, and only once. Empty * callbacks are allowed. * * It is the responsibility of the caller to ensure that any objects * bound to the callback are still around when the callback gets * invoked. One simple way of doing that is via BoostHelper.h and * binding to a boost::weak_ptr that tracks the instance to which * the callback belongs. * * The recommended naming is to use the "Async" suffix in the function * name and a "const SimpleResult &result" as last parameter. Example: * * void killSessionsAsync(const std::string &peerDeviceID, * const SimpleResult &result); * * Some asynchronous methods might also take a D-Bus result pointer * plus a success callback, then deal with errors internally by * relaying them to the D-Bus client. Example: * * void runOperationAsync(RunOperation op, * const boost::shared_ptr &dbusResult, * const SuccessCb_t &helperReady) * ... * useHelperAsync(SimpleResult(helperReady, * boost::bind(&Session::failureCb, this))); * * Session::failureCb() in this example then does some work on its own * and finally calls dbusErrorCallback(dbusResult). */ typedef boost::function ErrorCb_t; /** * Because callbacks always come in pairs, the following * utility class is usually used in asynchronous * calls. It's parameterized with the prototype of * the success call. */ template class Result { boost::function

    m_onSuccess; ErrorCb_t m_onError; public: Result(const boost::function

    &onSuccess, const ErrorCb_t &onError) : m_onSuccess(onSuccess), m_onError(onError) {} boost::function

    getOnSuccess() const { return m_onSuccess; } ErrorCb_t getOnError() const { return m_onError; } void done() const { if (m_onSuccess) m_onSuccess(); } template void done(const A1 &a1) const { if (m_onSuccess) m_onSuccess(a1); } template void done(const A1 &a1, const A2 &a2) const { if (m_onSuccess) m_onSuccess(a1, a2); } template void done(const A1 &a1, const A2 &a2, const A3 &a3) const { if (m_onSuccess) m_onSuccess(a1, a2, a3); } void failed() const { if (m_onError) m_onError(); } }; /** * Convenience function for creating a ResultCb for a pair of success * and failure callbacks. Determines type automatically based on type * of success callback. */ template Result

    makeCb(const boost::function

    &onSuccess, const ErrorCb_t &onFailure) { return Result

    (onSuccess, onFailure); } /** * Implements the error callback, can also be called directly inside a * catch clause as a general utility function in other error callbacks. * * @param result failed() is called here * @return status code (see SyncML.h) */ uint32_t dbusErrorCallback(const boost::shared_ptr &result); /** * Creates an error callback which can be used to return a pending * exception as a D-Bus error. Template call which is parameterized * with the GDBusCXX::Result* class that takes the error. */ ErrorCb_t createDBusErrorCb(const boost::shared_ptr &result); /** * Creates a result object which passes back results and turns * exceptions into dbus_error instances with the given interface * name. * TODO: interface name */ template Result createDBusCb(const boost::shared_ptr< GDBusCXX::Result1 > &result) { return Result(boost::bind(&GDBusCXX::Result1::done, result, _1), createDBusErrorCb(result)); } /** * Creates a result object which passes back zero results and turns * exceptions into dbus_error instances with the given interface name. * TODO: interface name */ static inline Result createDBusCb(const boost::shared_ptr< GDBusCXX::Result0 > &result) { return Result(boost::bind(&GDBusCXX::Result0::done, result), createDBusErrorCb(result)); } /** * a generic "operation successful" callback with no parameters */ typedef boost::function SuccessCb_t; /** * A generic "operation completed/failed" result pair (no parameters * for completion). Same as Result, but because it doesn't * have overloaded done() template methods the done method can be used * in boost::bind(). */ class SimpleResult { public: SuccessCb_t m_onSuccess; ErrorCb_t m_onError; public: SimpleResult(const SuccessCb_t &onSuccess, const ErrorCb_t &onError) : m_onSuccess(onSuccess), m_onError(onError) {} void done() const { if (m_onSuccess) m_onSuccess(); } void failed() const { if (m_onError) m_onError(); } }; SE_END_CXX #endif // INCL_DBUS_CALLBACKS syncevolution_1.4/src/dbus/server/dbus-sync.cpp000066400000000000000000000310751230021373600220310ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "dbus-sync.h" #include "session-helper.h" #include "dbus-transport-agent.h" #include #include #include SE_BEGIN_CXX DBusSync::DBusSync(const SessionCommon::SyncParams ¶ms, SessionHelper &helper) : SyncContext(params.m_config, true), m_helper(helper), m_params(params), m_waiting(false) { setUserInterface(this); setServerAlerted(params.m_serverAlerted); if (params.m_serverMode) { initServer(params.m_sessionID, params.m_initialMessage, params.m_initialMessageType); } if (params.m_remoteInitiated) { setRemoteInitiated(true); } // Watch status of parent and our own process and cancel // any pending password request if parent or we go down. boost::shared_ptr forkexec = m_helper.getForkExecChild(); if (forkexec) { m_parentWatch = forkexec->m_onQuit.connect(boost::bind(&DBusSync::passwordResponse, this, true, false, "")); } m_suspendFlagsWatch = SuspendFlags::getSuspendFlags().m_stateChanged.connect(boost::bind(&DBusSync::suspendFlagsChanged, this, _1)); // Apply temporary config filters. The parameters of this function // override the source filters, if set. setConfigFilter(true, "", params.m_syncFilter); FilterConfigNode::ConfigFilter filter; filter = params.m_sourceFilter; if (!params.m_mode.empty()) { if (params.m_mode == "ephemeral") { makeEphemeral(); } else if (params.m_mode == "pbap") { // Batched writing is off by default, explicitly enable it for PBAP. SE_LOG_DEBUG(NULL, "enabling SYNCEVOLUTION_EDS_ACCESS_MODE=batched"); setenv("SYNCEVOLUTION_EDS_ACCESS_MODE", "batched", true); // "pbap" may only be used by caller when it knows that // the mode is safe to use. makeEphemeral(); const char *sync = getenv("SYNCEVOLUTION_PBAP_SYNC"); if (!sync) { SE_LOG_DEBUG(NULL, "enabling default SYNCEVOLUTION_PBAP_SYNC=incremental"); setenv("SYNCEVOLUTION_PBAP_SYNC", "incremental", true); } else { SE_LOG_DEBUG(NULL, "using SYNCEVOLUTION_PBAP_SYNC=%s from environment", sync); } } else { filter["sync"] = params.m_mode; } } setConfigFilter(false, "", filter); BOOST_FOREACH(const std::string &source, getSyncSources()) { SessionCommon::SourceFilters_t::const_iterator fit = params.m_sourceFilters.find(source); filter = fit == params.m_sourceFilters.end() ? FilterConfigNode::ConfigFilter() : fit->second; SessionCommon::SourceModes_t::const_iterator it = params.m_sourceModes.find(source); if (it != params.m_sourceModes.end()) { filter["sync"] = it->second; } setConfigFilter(false, source, filter); } // Create source status and progress entries for each source in // the parent. See Session::sourceProgress(). BOOST_FOREACH(const std::string source, getSyncSources()) { m_helper.emitSourceProgress(sysync::PEV_PREPARING, source, SYNC_NONE, 0, 0, 0); } // Forward the SourceSyncedSignal via D-Bus. m_sourceSyncedSignal.connect(boost::bind(m_helper.emitSourceSynced, _1, _2)); } DBusSync::~DBusSync() { m_parentWatch.disconnect(); m_suspendFlagsWatch.disconnect(); } boost::shared_ptr DBusSync::createTransportAgent() { if (m_params.m_serverAlerted || m_params.m_serverMode) { // Use the D-Bus Connection to send and receive messages. boost::shared_ptr agent(new DBusTransportAgent(m_helper)); // Hook up agent with D-Bus in the helper. The agent may go // away at any time, so use instance tracking. m_helper.m_messageSignal.connect(SessionHelper::MessageSignal_t::slot_type(&DBusTransportAgent::storeMessage, agent.get(), _1, _2).track(agent)); m_helper.m_connectionStateSignal.connect(SessionHelper::ConnectionStateSignal_t::slot_type(&DBusTransportAgent::storeState, agent.get(), _1).track(agent)); if (m_params.m_serverAlerted) { // A SAN message was sent to us, need to reply. agent->serverAlerted(); } else if (m_params.m_serverMode) { // Let transport return initial message to engine. agent->storeMessage(GDBusCXX::DBusArray(m_params.m_initialMessage.size(), reinterpret_cast(m_params.m_initialMessage.get())), m_params.m_initialMessageType); } return agent; } else { // no connection, use HTTP via libsoup/GMainLoop GMainLoop *loop = m_helper.getLoop(); boost::shared_ptr agent = SyncContext::createTransportAgent(loop); return agent; } } void DBusSync::displaySyncProgress(sysync::TProgressEventEnum type, int32_t extra1, int32_t extra2, int32_t extra3) { SyncContext::displaySyncProgress(type, extra1, extra2, extra3); m_helper.emitSyncProgress(type, extra1, extra2, extra3); } bool DBusSync::displaySourceProgress(SyncSource &source, const SyncSourceEvent &event, bool flush) { bool cached = SyncContext::displaySourceProgress(source, event, flush); if (!cached) { m_helper.emitSourceProgress(event.m_type, source.getName(), source.getFinalSyncMode(), event.m_extra1, event.m_extra2, event.m_extra3); } return cached; } void DBusSync::reportStepCmd(sysync::uInt16 stepCmd) { switch(stepCmd) { case sysync::STEPCMD_SENDDATA: case sysync::STEPCMD_RESENDDATA: case sysync::STEPCMD_NEEDDATA: // sending or waiting if (!m_waiting) { m_helper.emitWaiting(true); m_waiting = true; } break; default: // otherwise, processing if (m_waiting) { m_helper.emitWaiting(false); m_waiting = false; } break; } } void DBusSync::syncSuccessStart() { m_helper.emitSyncSuccessStart(); } string DBusSync::askPassword(const string &passwordName, const string &descr, const ConfigPasswordKey &key) { std::string password; std::string error; askPasswordAsync(passwordName, descr, key, boost::bind(static_cast(&std::string::assign), &password, _1), boost::bind(static_cast(&Exception::handle), boost::ref(error), HANDLE_EXCEPTION_NO_ERROR)); // We know that askPasswordAsync() is done when it cleared the // callback functors. while (m_passwordSuccess) { g_main_context_iteration(NULL, true); } if (!error.empty()) { Exception::tryRethrow(error); SE_THROW(StringPrintf("password request failed: %s", error.c_str())); } return password; } void DBusSync::askPasswordAsync(const std::string &passwordName, const std::string &descr, const ConfigPasswordKey &key, const boost::function &success, const boost::function &failureException) { // cannot handle more than one password request at a time m_passwordSuccess.clear(); m_passwordFailure.clear(); m_passwordDescr = descr; InitStateString password; if (GetLoadPasswordSignal()(getKeyring(), passwordName, descr, key, password) && password.wasSet()) { // handled success(password); return; } try { SE_LOG_DEBUG(NULL, "asking parent for password"); m_passwordSuccess = success; m_passwordFailure = failureException; m_helper.emitPasswordRequest(descr, key); if (!m_helper.connected()) { SE_LOG_DEBUG(NULL, "password request failed, lost connection"); SE_THROW_EXCEPTION_STATUS(StatusException, StringPrintf("Could not get the '%s' password from user, no connection to UI.", descr.c_str()), STATUS_PASSWORD_TIMEOUT); } if (SuspendFlags::getSuspendFlags().getState() != SuspendFlags::NORMAL) { SE_LOG_DEBUG(NULL, "password request failed, was asked to terminate"); SE_THROW_EXCEPTION_STATUS(StatusException, StringPrintf("Could not get the '%s' password from user, was asked to shut down.", descr.c_str()), STATUS_PASSWORD_TIMEOUT); } } catch (...) { m_passwordSuccess.clear(); m_passwordFailure.clear(); failureException(); } } void DBusSync::passwordResponse(bool timedOut, bool aborted, const std::string &password) { boost::function success; boost::function failureException; std::swap(success, m_passwordSuccess); std::swap(failureException, m_passwordFailure); if (success && failureException) { SE_LOG_DEBUG(NULL, "password result: %s", timedOut ? "timeout or parent gone" : aborted ? "user abort" : password.empty() ? "empty password" : "valid password"); try { if (timedOut) { SE_THROW_EXCEPTION_STATUS(StatusException, StringPrintf("Could not get the '%s' password from user.", m_passwordDescr.c_str()), STATUS_PASSWORD_TIMEOUT); } else if (aborted) { SE_THROW_EXCEPTION_STATUS(StatusException, StringPrintf("User did not provide the '%s' password.", m_passwordDescr.c_str()), SyncMLStatus(sysync::LOCERR_USERABORT)); } else { success(password); } } catch (...) { failureException(); } } } void DBusSync::suspendFlagsChanged(SuspendFlags &flags) { if (flags.getState() != SuspendFlags::NORMAL) { passwordResponse(true, false, ""); } } bool DBusSync::savePassword(const std::string &passwordName, const std::string &password, const ConfigPasswordKey &key) { if (GetSavePasswordSignal()(getKeyring(), passwordName, password, key)) { return true; } // not saved return false; } void DBusSync::readStdin(std::string &content) { // might get called, must be avoided by user SE_THROW("reading from stdin not supported when running with daemon, use --daemon=no"); } SE_END_CXX syncevolution_1.4/src/dbus/server/dbus-sync.h000066400000000000000000000064271230021373600215010ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef DBUS_SYNC_H #define DBUS_SYNC_H #include "session-common.h" #include #include #include #include #include SE_BEGIN_CXX class SessionHelper; namespace SessionCommon { struct SyncParams; } /** * Maps sync events to D-Bus signals in SessionHelper. * Does password requests by sending out a request for * them via SessionHelper and waiting until a reply (positive * or negative) is received. */ class DBusSync : public SyncContext, private UserInterface { SessionHelper &m_helper; SessionCommon::SyncParams m_params; bool m_waiting; boost::function m_passwordSuccess; boost::function m_passwordFailure; std::string m_passwordDescr; boost::signals2::connection m_parentWatch; boost::signals2::connection m_suspendFlagsWatch; void suspendFlagsChanged(SuspendFlags &flags); public: DBusSync(const SessionCommon::SyncParams ¶ms, SessionHelper &helper); ~DBusSync(); /** to be called by SessionHelper when it gets a response via D-Bus */ void passwordResponse(bool timedOut, bool aborted, const std::string &password); protected: virtual boost::shared_ptr createTransportAgent(); virtual void displaySyncProgress(sysync::TProgressEventEnum type, int32_t extra1, int32_t extra2, int32_t extra3); virtual bool displaySourceProgress(SyncSource &source, const SyncSourceEvent &event, bool flush); virtual void reportStepCmd(sysync::uInt16 stepCmd); virtual void syncSuccessStart(); string askPassword(const string &passwordName, const string &descr, const ConfigPasswordKey &key); virtual void askPasswordAsync(const std::string &passwordName, const std::string &descr, const ConfigPasswordKey &key, const boost::function &success, const boost::function &failureException); virtual bool savePassword(const std::string &passwordName, const std::string &password, const ConfigPasswordKey &key); virtual void readStdin(std::string &content); }; SE_END_CXX #endif // DBUS_SYNC_H syncevolution_1.4/src/dbus/server/dbus-transport-agent.cpp000066400000000000000000000143171230021373600242050ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "dbus-transport-agent.h" #include "session-helper.h" SE_BEGIN_CXX DBusTransportAgent::DBusTransportAgent(SessionHelper &helper) : m_helper(helper), m_state(SessionCommon::SETUP) { } void DBusTransportAgent::serverAlerted() { SE_LOG_DEBUG(NULL, "D-Bus transport: server alerted (old state: %s, %s)", SessionCommon::ConnectionStateToString(m_state).c_str(), m_error.c_str()); if (m_state == SessionCommon::SETUP) { m_state = SessionCommon::PROCESSING; } else { SE_THROW_EXCEPTION(TransportException, "setting 'server alerted' only allowed during setup"); } } void DBusTransportAgent::storeMessage(const GDBusCXX::DBusArray &buffer, const std::string &type) { SE_LOG_DEBUG(NULL, "D-Bus transport: store incoming message, %ld bytes, %s (old state: %s, %s)", (long)buffer.first, type.c_str(), SessionCommon::ConnectionStateToString(m_state).c_str(), m_error.c_str()); if (m_state == SessionCommon::SETUP || m_state == SessionCommon::WAITING) { m_incomingMsg = SharedBuffer(reinterpret_cast(buffer.second), buffer.first); m_incomingMsgType = type; m_state = SessionCommon::PROCESSING; } else if (m_state == SessionCommon::PROCESSING && m_incomingMsgType == type && m_incomingMsg.size() == buffer.first && !memcmp(m_incomingMsg.get(), buffer.second, buffer.first)) { // Exactly the same message, accept resend without error, and // without doing anything. } else { SE_THROW_EXCEPTION(TransportException, "unexpected message"); } } void DBusTransportAgent::storeState(const std::string &error) { SE_LOG_DEBUG(NULL, "D-Bus transport: got error '%s', current error is '%s', state %s", error.c_str(), m_error.c_str(), SessionCommon::ConnectionStateToString(m_state).c_str()); if (!error.empty()) { // specific error encountered m_state = SessionCommon::FAILED; if (m_error.empty()) { m_error = error; } } else if (m_state == SessionCommon::FINAL) { // expected loss of connection m_state = SessionCommon::DONE; } else { // unexpected loss of connection m_state = SessionCommon::FAILED; } } void DBusTransportAgent::send(const char *data, size_t len) { SE_LOG_DEBUG(NULL, "D-Bus transport: outgoing message %ld bytes, %s, %s", (long)len, m_type.c_str(), m_url.c_str()); if (m_state != SessionCommon::PROCESSING) { SE_THROW_EXCEPTION(TransportException, "cannot send to our D-Bus peer"); } m_state = SessionCommon::WAITING; m_incomingMsg = SharedBuffer(); m_helper.emitMessage(GDBusCXX::DBusArray(len, reinterpret_cast(data)), m_type, m_url); } void DBusTransportAgent::shutdown() { SE_LOG_DEBUG(NULL, "D-Bus transport: shut down (old state: %s, %s)", SessionCommon::ConnectionStateToString(m_state).c_str(), m_error.c_str()); if (m_state == SessionCommon::PROCESSING) { m_state = SessionCommon::FINAL; m_helper.emitShutdown(); } } void DBusTransportAgent::doWait() { SE_LOG_DEBUG(NULL, "D-Bus transport: wait - old state: %s, %s", SessionCommon::ConnectionStateToString(m_state).c_str(), m_error.c_str()); if (SuspendFlags::getSuspendFlags().getState() == SuspendFlags::NORMAL) { // Block for one iteration. Both D-Bus calls and signals (thanks // to the SuspendFlags guard in the running sync session) will // wake us up. g_main_context_iteration(NULL, true); } SE_LOG_DEBUG(NULL, "D-Bus transport: wait - new state: %s, %s", SessionCommon::ConnectionStateToString(m_state).c_str(), m_error.c_str()); } DBusTransportAgent::Status DBusTransportAgent::wait(bool noReply) { switch (m_state) { case SessionCommon::PROCESSING: return GOT_REPLY; break; case SessionCommon::FINAL: doWait(); // if the connection is still available, then keep waiting if (m_state == SessionCommon::FINAL) { return ACTIVE; } else if (m_error.empty()) { return INACTIVE; } else { SE_THROW_EXCEPTION(TransportException, m_error); return FAILED; } break; case SessionCommon::WAITING: if (noReply) { // message is sent as far as we know, so return return INACTIVE; } doWait(); // tell caller to check again return ACTIVE; break; case SessionCommon::DONE: if (!noReply) { SE_THROW_EXCEPTION(TransportException, "internal error: transport has shut down, can no longer receive reply"); } return CLOSED; default: SE_THROW_EXCEPTION(TransportException, "send() on connection which is not ready"); break; } return FAILED; } void DBusTransportAgent::getReply(const char *&data, size_t &len, std::string &contentType) { data = m_incomingMsg.get(); len = m_incomingMsg.size(); contentType = m_incomingMsgType; } SE_END_CXX syncevolution_1.4/src/dbus/server/dbus-transport-agent.h000066400000000000000000000055541230021373600236550ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef DBUS_TRANSPORT_AGENT_H #define DBUS_TRANSPORT_AGENT_H #include #include #include #include #include #include "session-common.h" SE_BEGIN_CXX class Connection; class SessionHelper; /** * A proxy for a Connection instance in the syncevo-dbus-server. The * Connection instance can go away (weak pointer, must be locked and * and checked each time it is needed). The agent must remain * available as long as the engine needs and basically becomes * unusuable once the connection dies. That information is relayed * to it via the D-Bus API. * * Reconnecting is not currently supported. */ class DBusTransportAgent : public TransportAgent { SessionHelper &m_helper; /** information about outgoing message, provided by user of this instance */ std::string m_url; std::string m_type; /** latest message sent to us */ SharedBuffer m_incomingMsg; std::string m_incomingMsgType; /** explanation for problem, sent to us by syncevo-dbus-server */ std::string m_error; /** * Current state. Changed by us as messages are sent and received * and by syncevo-dbus-server: * - connectionState with error -> failed * - connectionState without error -> closed */ SessionCommon::ConnectionState m_state; void doWait(); public: DBusTransportAgent(SessionHelper &helper); void serverAlerted(); void storeMessage(const GDBusCXX::DBusArray &buffer, const std::string &type); void storeState(const std::string &error); virtual void setURL(const std::string &url) { m_url = url; } virtual void setContentType(const std::string &type) { m_type = type; } virtual void send(const char *data, size_t len); virtual void cancel() {} virtual void shutdown(); virtual Status wait(bool noReply = false); virtual void setTimeout(int seconds) {} virtual void getReply(const char *&data, size_t &len, std::string &contentType); }; SE_END_CXX #endif // DBUS_TRANSPORT_AGENT_H syncevolution_1.4/src/dbus/server/dbus-user-interface.cpp000066400000000000000000000036151230021373600237700ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "dbus-user-interface.h" #include SE_BEGIN_CXX DBusUserInterface::DBusUserInterface(const InitStateTri &keyring) : m_keyring(keyring) { } std::string DBusUserInterface::askPassword(const std::string &passwordName, const std::string &descr, const ConfigPasswordKey &key) { InitStateString password; if (GetLoadPasswordSignal()(m_keyring, passwordName, descr, key, password) && password.wasSet()) { // handled return password; } //if not found, return empty return ""; } bool DBusUserInterface::savePassword(const std::string &passwordName, const std::string &password, const ConfigPasswordKey &key) { if (GetSavePasswordSignal()(m_keyring, passwordName, password, key)) { return true; } // not saved return false; } void DBusUserInterface::readStdin(std::string &content) { SE_THROW("reading stdin in D-Bus server not supported, use --daemon=no in command line"); } SE_END_CXX syncevolution_1.4/src/dbus/server/dbus-user-interface.h000066400000000000000000000036551230021373600234410ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef DBUS_USER_INTERFACE_H #define DBUS_USER_INTERFACE_H #include #include #include SE_BEGIN_CXX /** * This class is mainly to implement two virtual functions 'askPassword' * and 'savePassword' of ConfigUserInterface. The main functionality is * to only get and save passwords in the gnome keyring. */ class DBusUserInterface : public UserInterface { public: DBusUserInterface(const InitStateTri &keyring); /* * Ask password from keyring, if not found, empty string * is returned */ std::string askPassword(const std::string &passwordName, const std::string &descr, const ConfigPasswordKey &key); //save password to keyring, if not successful, false is returned. bool savePassword(const std::string &passwordName, const std::string &password, const ConfigPasswordKey &key); /** * Read stdin via InfoRequest/Response. */ void readStdin(std::string &content); private: InitStateTri m_keyring; }; SE_END_CXX #endif // DBUS_USER_INTERFACE_H syncevolution_1.4/src/dbus/server/exceptions.cpp000066400000000000000000000031421230021373600222750ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "exceptions.h" SE_BEGIN_CXX /** * implement syncevolution exception handler * to cover its default implementation */ GDBusCXX::message_type *SyncEvoHandleException(GDBusCXX::message_type *msg) { /** give an opportunity to let syncevolution handle exception */ Exception::handle(); try { throw; } catch (const GDBusCXX::dbus_error &ex) { return DBUS_NEW_ERROR_MSG(msg, ex.dbusName().c_str(), "%s", ex.what()); } catch (const GDBusCXX::DBusCXXException &ex) { return DBUS_NEW_ERROR_MSG(msg, ex.getName().c_str(), "%s", ex.getMessage()); } catch (const std::runtime_error &ex) { return DBUS_NEW_ERROR_MSG(msg, "org.syncevolution.Exception", "%s", ex.what()); } catch (...) { return DBUS_NEW_ERROR_MSG(msg, "org.syncevolution.Exception", "unknown"); } } SE_END_CXX syncevolution_1.4/src/dbus/server/exceptions.h000066400000000000000000000071011230021373600217410ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef SYNCEVO_EXCEPTIONS_H #define SYNCEVO_EXCEPTIONS_H #include #include "config.h" // Can't use the GDBusCXX::message_type typedef here because it's not // defined till we include gdbus-cxx-bridge.h below. #ifdef WITH_GIO_GDBUS // Forward decleration can't be used here due to later typedef _GDBusMessage GDBusMessage. #include #else struct DBusMessage; #endif SE_BEGIN_CXX #ifdef WITH_GIO_GDBUS GDBusMessage *SyncEvoHandleException(GDBusMessage *msg); #else DBusMessage *SyncEvoHandleException(DBusMessage *msg); #endif SE_END_CXX // This needs to be defined before including gdbus-cxx-bridge.h! #define DBUS_CXX_EXCEPTION_HANDLER SyncEvo::SyncEvoHandleException #include "gdbus-cxx-bridge.h" #include SE_BEGIN_CXX class DBusSyncException : public GDBusCXX::DBusCXXException, public Exception { public: DBusSyncException(const std::string &file, int line, const std::string &what) : Exception(file, line, what) {} /** * get exception name, used to convert to dbus error name * subclasses should override it */ virtual std::string getName() const { return "org.syncevolution.Exception"; } virtual const char* getMessage() const { return Exception::what(); } }; /** * exceptions classes deriving from DBusException * org.syncevolution.NoSuchConfig */ class NoSuchConfig: public DBusSyncException { public: NoSuchConfig(const std::string &file, int line, const std::string &error): DBusSyncException(file, line, error) {} virtual std::string getName() const { return "org.syncevolution.NoSuchConfig";} }; /** * org.syncevolution.NoSuchSource */ class NoSuchSource : public DBusSyncException { public: NoSuchSource(const std::string &file, int line, const std::string &error): DBusSyncException(file, line, error) {} virtual std::string getName() const { return "org.syncevolution.NoSuchSource";} }; /** * org.syncevolution.InvalidCall */ class InvalidCall : public DBusSyncException { public: InvalidCall(const std::string &file, int line, const std::string &error): DBusSyncException(file, line, error) {} virtual std::string getName() const { return "org.syncevolution.InvalidCall";} }; /** * org.syncevolution.SourceUnusable * CheckSource will use this when the source cannot be used for whatever reason */ class SourceUnusable : public DBusSyncException { public: SourceUnusable(const std::string &file, int line, const std::string &error): DBusSyncException(file, line, error) {} virtual std::string getName() const { return "org.syncevolution.SourceUnusable";} }; SE_END_CXX #endif // SYNCEVO_EXCEPTIONS_H syncevolution_1.4/src/dbus/server/info-req.cpp000066400000000000000000000055601230021373600216420ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "info-req.h" #include "server.h" using namespace GDBusCXX; SE_BEGIN_CXX InfoReq::InfoReq(Server &server, const string &type, const InfoMap ¶meters, const string &sessionPath, uint32_t timeout) : m_server(server), m_sessionPath(sessionPath), m_id(server.getNextInfoReq()), m_timeoutSeconds(timeout), m_infoState(IN_REQ), m_status(ST_RUN), m_type(type), m_param(parameters) { m_server.emitInfoReq(*this); m_timeout.runOnce(m_timeoutSeconds, boost::bind(boost::ref(m_timeoutSignal))); m_param.clear(); } InfoReq::~InfoReq() { m_handler = ""; done(); } string InfoReq::statusToString(Status status) { switch(status) { case ST_RUN: return "running"; case ST_OK: return "ok"; case ST_CANCEL: return "cancelled"; case ST_TIMEOUT: return "timeout"; default: return ""; }; } string InfoReq::infoStateToString(InfoState state) { switch(state) { case IN_REQ: return "request"; case IN_WAIT: return "waiting"; case IN_DONE: return "done"; default: return ""; } } void InfoReq::setResponse(const Caller_t &caller, const string &state, const InfoMap &response) { if(m_status != ST_RUN) { return; } else if(m_infoState == IN_REQ && state == "working") { m_handler = caller; m_infoState = IN_WAIT; m_server.emitInfoReq(*this); //reset the timer, used to check timeout m_timeout.runOnce(m_timeoutSeconds, boost::bind(boost::ref(m_timeoutSignal))); } else if ((m_infoState == IN_WAIT || m_infoState == IN_REQ) && state == "response") { m_response = response; m_handler = caller; m_status = ST_OK; m_responseSignal(m_response); done(); } } string InfoReq::getSessionPath() const { return m_sessionPath; } void InfoReq::done() { if (m_infoState != IN_DONE) { m_infoState = IN_DONE; m_server.emitInfoReq(*this); } m_server.removeInfoReq(getId()); } SE_END_CXX syncevolution_1.4/src/dbus/server/info-req.h000066400000000000000000000073261230021373600213110ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INFO_REQ_H #define INFO_REQ_H #include #include "gdbus-cxx-bridge.h" #include "timeout.h" #include #include SE_BEGIN_CXX class Server; /** * A wrapper for handling info request and response. */ class InfoReq { public: typedef std::map InfoMap; // status of current request enum Status { ST_RUN, // request is running ST_OK, // ok, response is gotten ST_TIMEOUT, // timeout ST_CANCEL // request is cancelled }; /** * constructor * The default timeout is 120 seconds */ InfoReq(Server &server, const std::string &type, const InfoMap ¶meters, const std::string &sessionPath, uint32_t timeout = 120); ~InfoReq(); std::string getId() const { return m_id; } std::string getSessionPath() const; std::string getInfoStateStr() const { return infoStateToString(m_infoState); } std::string getHandler() const { return m_handler; } std::string getType() const { return m_type; } const InfoMap& getParam() const { return m_param; } /** * Connect to this signal to be notified that a final response has * been received. */ typedef boost::signals2::signal ResponseSignal_t; ResponseSignal_t m_responseSignal; /** * Connect to this signal to be notified when it is considered timed out. * The timeout counting restarts each time any client sends any kind of * response. */ typedef boost::signals2::signal TimeoutSignal_t; TimeoutSignal_t m_timeoutSignal; /** get current status in string format */ std::string getStatusStr() const { return statusToString(m_status); } /** set response from dbus clients */ void setResponse(const GDBusCXX::Caller_t &caller, const std::string &state, const InfoMap &response); private: static std::string statusToString(Status status); enum InfoState { IN_REQ, //request IN_WAIT, // waiting IN_DONE // done }; static std::string infoStateToString(InfoState state); Server &m_server; /** caller's session, might be NULL */ const std::string m_sessionPath; /** unique id of this info request */ std::string m_id; /** times out this amount of seconds after last interaction with client */ uint32_t m_timeoutSeconds; Timeout m_timeout; /** info req state defined in dbus api */ InfoState m_infoState; /** status to indicate the info request is timeout, ok, abort, etc */ Status m_status; /** the handler of the responsed dbus client */ GDBusCXX::Caller_t m_handler; /** the type of the info request */ std::string m_type; /** parameters from info request callers */ InfoMap m_param; /** response returned from dbus clients */ InfoMap m_response; void done(); }; SE_END_CXX #endif // INFO_REQ_H syncevolution_1.4/src/dbus/server/localed-listener.cpp000066400000000000000000000174251230021373600233530ustar00rootroot00000000000000/* * Copyright (C) 2013 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "localed-listener.h" #include #include #include #include #include #include #include SE_BEGIN_CXX static const char LOCALED_PATH[] = "/org/freedesktop/locale1"; static const char LOCALED_INTERFACE[] = "org.freedesktop.locale1"; static const char LOCALED_DESTINATION[] = "org.freedesktop.locale1"; static const char LOCALED_LOCALE_PROPERTY[] = "Locale"; /** * Must be a complete list, because we need to know which variables * we have to unset if not set remotely. * * Localed intentionally does not support LC_ALL. As localed.c says: * "We don't list LC_ALL here on purpose. People should be using LANG instead." */ static const char * const LOCALED_ENV_VARS[] = { "LANG", "LC_CTYPE", "LC_NUMERIC", "LC_TIME", "LC_COLLATE", "LC_MONETARY", "LC_MESSAGES", "LC_PAPER", "LC_NAME", "LC_ADDRESS", "LC_TELEPHONE", "LC_MEASUREMENT", "LC_IDENTIFICATION" }; static const char PROPERTIES_INTERFACE[] = "org.freedesktop.DBus.Properties"; static const char PROPERTIES_CHANGED_SIGNAL[] = "PropertiesChanged"; static const char PROPERTIES_GET[] = "Get"; LocaledListener::LocaledListener(): GDBusCXX::DBusRemoteObject(!strcmp(getEnv("SYNCEVOLUTION_LOCALED", ""), "none") ? NULL : /* simulate missing localed */ GDBusCXX::dbus_get_bus_connection(!strcmp(getEnv("SYNCEVOLUTION_LOCALED", ""), "session") ? "SESSION" : /* use our own localed stub */ "SYSTEM" /* use real localed */, NULL, false, NULL), LOCALED_PATH, PROPERTIES_INTERFACE, LOCALED_DESTINATION), m_propertiesChanged(*this, PROPERTIES_CHANGED_SIGNAL), m_propertiesGet(*this, PROPERTIES_GET) { if (getConnection()) { m_propertiesChanged.activate(boost::bind(&LocaledListener::onPropertiesChange, this, _1, _2, _3)); } else { SE_LOG_DEBUG(NULL, "localed: not activating, no connection"); } }; boost::shared_ptr LocaledListener::create() { static boost::weak_ptr singleton; boost::shared_ptr self = singleton.lock(); if (!self) { self.reset(new LocaledListener()); self->m_self = self; singleton = self; } return self; }; void LocaledListener::onPropertiesChange(const std::string &interface, const Properties &properties, const Invalidated &invalidated) { if (interface == LOCALED_INTERFACE) { boost::function result(boost::bind(&LocaledListener::emitLocaleEnv, m_self, _1)); BOOST_FOREACH (const Properties::value_type &entry, properties) { if (entry.first == LOCALED_LOCALE_PROPERTY) { const LocaleEnv *locale = boost::get(&entry.second); if (locale) { SE_LOG_DEBUG(NULL, "localed: got new Locale"); processLocaleProperty(*locale, "", false, result); } else { SE_LOG_DEBUG(NULL, "localed: got new Locale of invalid type?! Ignore."); } return; } } if (std::find(invalidated.begin(), invalidated.end(), LOCALED_LOCALE_PROPERTY) != invalidated.end()) { SE_LOG_DEBUG(NULL, "localed: Locale changed, need to get new value"); m_propertiesGet.start(std::string(LOCALED_INTERFACE), std::string(LOCALED_LOCALE_PROPERTY), boost::bind(&LocaledListener::processLocaleProperty, m_self, _1, _2, false, result)); } SE_LOG_DEBUG(NULL, "localed: ignoring irrelevant property change"); } } void LocaledListener::processLocaleProperty(const LocaleVariant &variant, const std::string &error, bool mustCall, const ProcessLocalePropCB_t &result) { SE_LOG_DEBUG(NULL, "localed: got Locale property: %s", error.empty() ? "<>" : error.c_str()); const LocaleEnv *locale = error.empty() ? boost::get(&variant) : NULL; LocaleEnv current; if (!locale && mustCall) { SE_LOG_DEBUG(NULL, "localed: using current environment as fallback"); BOOST_FOREACH (const char *name, LOCALED_ENV_VARS) { const char *value = getenv(name); if (value) { current.push_back(StringPrintf("%s=%s", name, value)); } } locale = ¤t; } if (locale) { result(*locale); } } void LocaledListener::emitLocaleEnv(const LocaleEnv &env) { SE_LOG_DEBUG(NULL, "localed: got environment: %s", boost::join(env, " ").c_str()); m_localeValues(env); } void LocaledListener::check(const boost::function &result) { if (getConnection()) { SE_LOG_DEBUG(NULL, "localed: get current Locale property"); m_propertiesGet.start(std::string(LOCALED_INTERFACE), std::string(LOCALED_LOCALE_PROPERTY), boost::bind(&LocaledListener::processLocaleProperty, m_self, _1, _2, true, result)); } else { processLocaleProperty(LocaleVariant(), "no D-Bus connection", true, result); } } void LocaledListener::setLocale(const LocaleEnv &locale) { bool modified = false; BOOST_FOREACH (const char *name, LOCALED_ENV_VARS) { const char *value = getenv(name); std::string assignment = StringPrintf("%s=", name); LocaleEnv::const_iterator instance = std::find_if(locale.begin(), locale.end(), boost::bind(boost::starts_with, _1, name)); const char *newvalue = instance != locale.end() ? instance->c_str() + assignment.size() : NULL; if ((value && newvalue && strcmp(value, newvalue)) || (!value && newvalue)) { modified = true; setenv(name, newvalue, true); SE_LOG_DEBUG(NULL, "localed: %s = %s -> %s", name, value ? value : "", newvalue); } else if (value && !newvalue) { modified = true; unsetenv(name); SE_LOG_DEBUG(NULL, "localed: %s = %s -> ", name, value); } } SE_LOG_DEBUG(NULL, "localed: environment %s", modified ? "changed" : "unchanged"); if (modified) { m_localeChanged(); } } SE_END_CXX syncevolution_1.4/src/dbus/server/localed-listener.h000066400000000000000000000072151230021373600230140ustar00rootroot00000000000000/* * Copyright (C) 2013 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_LOCALED_LISTENER #define INCL_LOCALED_LISTENER #include #include #include #include #include SE_BEGIN_CXX /** * The D-Bus binding for http://www.freedesktop.org/wiki/Software/systemd/localed/ */ class LocaledListener : public GDBusCXX::DBusRemoteObject { public: /** * Singleton - at most one instance of LocaledListener will exist. * It lives as long as one of the create() callers keeps the reference. */ static boost::shared_ptr create(); /** * array of var=value, for example LANG, LC_NUMERIC, etc. */ typedef std::vector LocaleEnv; /** * Emitted for each new set of env variables from localed. * May or may not be different from what we have already. */ boost::signals2::signal m_localeValues; /** * The result callback is guaranteed to be invoked once, * either with the current settings from localed or, if * retrieving those fails, with the current environment. */ void check(const boost::function &result); /** * Updates current environment to match the one in the parameter. * Emits m_localeChanged if and only if something really changed. * * Not called by default. To ensure that the current environment * matches localed, do: * - use current settings * - m_localeValues -> setLocale * - check -> setLocale * * Alternatively, one could wait until check() completes and only * then use the current settings. */ void setLocale(const LocaleEnv &locale); typedef boost::signals2::signal LocaleChangedSignal; /** * Emitted by setLocale() only if something really changed in the * local environment. */ LocaleChangedSignal m_localeChanged; private: boost::weak_ptr m_self; typedef boost::variant LocaleVariant; typedef std::map Properties; typedef std::vector Invalidated; GDBusCXX::SignalWatch3 m_propertiesChanged; GDBusCXX::DBusClientCall1 m_propertiesGet; LocaledListener(); void onPropertiesChange(const std::string &interface, const Properties &properties, const Invalidated &invalidated); typedef boost::function ProcessLocalePropCB_t; void processLocaleProperty(const LocaleVariant &locale, const std::string &error, bool mustCall, const ProcessLocalePropCB_t &result); void emitLocaleEnv(const LocaleEnv &env); }; SE_END_CXX #endif // INCL_LOCALED_LISTENER syncevolution_1.4/src/dbus/server/main.cpp000066400000000000000000000215301230021373600210410ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "server.h" #include "restart.h" #include "session-common.h" #include #include #include #include #include #include #ifdef USE_DLT # include #endif using namespace SyncEvo; using namespace GDBusCXX; namespace { GMainLoop *loop = NULL; bool shutdownRequested = false; const char * const execName = "syncevo-dbus-server"; const char * const debugEnv = "SYNCEVOLUTION_DEBUG"; void niam(int sig) { shutdownRequested = true; SuspendFlags::getSuspendFlags().handleSignal(sig); g_main_loop_quit (loop); } bool parseDuration(int &duration, const char* value) { if(value == NULL) { return false; } else if (boost::iequals(value, "unlimited")) { duration = -1; return true; } else if ((duration = atoi(value)) > 0) { return true; } else { return false; } } } // anonymous namespace static Logger::Level checkLogLevel(const char *option, int logLevel) { switch (logLevel) { case 0: return Logger::NONE; case 1: return Logger::ERROR; case 2: return Logger::INFO; case 3: return Logger::DEBUG; default: SE_THROW(StringPrintf("invalid parameter value %d for %s: must be one of 0, 1, 2 or 3", logLevel, option)); return Logger::NONE; } } int main(int argc, char **argv, char **envp) { // remember environment for restart boost::shared_ptr restart; restart.reset(new Restart(argv, envp)); // Internationalization for auto sync messages. setlocale(LC_ALL, ""); bindtextdomain(GETTEXT_PACKAGE, getEnv("SYNCEVOLUTION_LOCALE_DIR", SYNCEVOLUTION_LOCALEDIR)); bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); textdomain(GETTEXT_PACKAGE); try { gchar *durationString = NULL; int duration = 600; int logLevel = 1; int logLevelDBus = 2; gboolean stdoutEnabled = false; gboolean syslogEnabled = true; #ifdef USE_DLT gboolean dltEnabled = false; #endif #ifdef ENABLE_DBUS_PIM gboolean startPIM = false; #endif GOptionEntry entries[] = { { "duration", 'd', 0, G_OPTION_ARG_STRING, &durationString, "Shut down automatically when idle for this duration", "seconds/'unlimited'" }, { "verbosity", 'v', 0, G_OPTION_ARG_INT, &logLevel, "Choose amount of output, 0 = no output, 1 = errors, 2 = info, 3 = debug; default is 1.", "level" }, { "dbus-verbosity", 'v', 0, G_OPTION_ARG_INT, &logLevelDBus, "Choose amount of output via D-Bus signals, 0 = no output, 1 = errors, 2 = info, 3 = debug; default is 2.", "level" }, { "stdout", 'o', 0, G_OPTION_ARG_NONE, &stdoutEnabled, "Enable printing to stdout (result of operations) and stderr (errors/info/debug).", NULL }, { "no-syslog", 's', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &syslogEnabled, "Disable printing to syslog.", NULL }, #ifdef USE_DLT { "dlt", 0, 0, G_OPTION_ARG_NONE, &dltEnabled, "Enable logging via GENIVI Diagnostic Log and Trace.", NULL }, #endif #ifdef ENABLE_DBUS_PIM { "start-pim", 'p', 0, G_OPTION_ARG_NONE, &startPIM, "Activate the PIM Manager (= unified address book) immediately.", NULL }, #endif { NULL } }; GErrorCXX gerror; static GOptionContext *context = g_option_context_new("- SyncEvolution D-Bus Server"); g_option_context_add_main_entries(context, entries, GETTEXT_PACKAGE); bool success = g_option_context_parse(context, &argc, &argv, gerror); PlainGStr durationOwner(durationString); if (!success) { gerror.throwError("parsing command line options"); } if (durationString && !parseDuration(duration, durationString)) { SE_THROW(StringPrintf("invalid parameter value '%s' for --duration/-d: must be positive number of seconds or 'unlimited'", durationString)); } Logger::Level level = checkLogLevel("--debug", logLevel); Logger::Level levelDBus = checkLogLevel("--dbus-debug", logLevelDBus); // Temporarily set G_DBUS_DEBUG. Hopefully GIO will read and // remember it, because we don't want to keep it set // permanently, lest it gets passed on to other processes. const char *gdbus = getenv("SYNCEVOLUTION_DBUS_SERVER_GDBUS"); if (gdbus) { setenv("G_DBUS_DEBUG", gdbus, 1); } SyncContext::initMain(execName); loop = g_main_loop_new (NULL, FALSE); setvbuf(stderr, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); // Redirect output and optionally log to syslog. PushLogger redirect(new LogRedirect(LogRedirect::STDERR_AND_STDOUT)); redirect->setLevel(stdoutEnabled ? level : Logger::NONE); #ifdef USE_DLT PushLogger loggerdlt; if (dltEnabled) { // DLT logging with default log level DLT_LOG_WARN. This // default was chosen because DLT's own default, // DLT_LOG_INFO, leads to too much output given that a lot // of the standard messages in SyncEvolution and // libsynthesis are labelled informational. setenv("SYNCEVOLUTION_USE_DLT", StringPrintf("%d", DLT_LOG_WARN).c_str(), true); loggerdlt.reset(new LoggerDLT(DLT_SYNCEVO_DBUS_SERVER_ID, "SyncEvolution D-Bus server")); } else { unsetenv("SYNCEVOLUTION_USE_DLT"); } #endif PushLogger syslogger; if (syslogEnabled && level > Logger::NONE) { syslogger.reset(new LoggerSyslog(execName)); syslogger->setLevel(level); } // syncevo-dbus-server should hardly ever produce output that // is relevant for end users, so include the somewhat cryptic // process name for developers in this process, and not in // syncevo-dbus-helper. Logger::setProcessName("syncevo-dbus-server"); SE_LOG_DEBUG(NULL, "syncevo-dbus-server: catch SIGINT/SIGTERM in our own shutdown function"); signal(SIGTERM, niam); signal(SIGINT, niam); boost::shared_ptr guard = SuspendFlags::getSuspendFlags().activate(); DBusErrorCXX err; DBusConnectionPtr conn = dbus_get_bus_connection("SESSION", SessionCommon::SERVICE_NAME, true, &err); if (!conn) { err.throwFailure("dbus_get_bus_connection()", " failed - server already running?"); } // make this object the main owner of the connection boost::scoped_ptr obj(new DBusObject(conn, "foo", "bar", true)); boost::shared_ptr server(new SyncEvo::Server(loop, shutdownRequested, restart, conn, duration)); server->setDBusLogLevel(levelDBus); server->activate(); #ifdef ENABLE_DBUS_PIM boost::shared_ptr manager(SyncEvo::CreateContactManager(server, startPIM)); #endif if (gdbus) { unsetenv("G_DBUS_DEBUG"); } dbus_bus_connection_undelay(conn); server->run(); SE_LOG_DEBUG(NULL, "cleaning up"); #ifdef ENABLE_DBUS_PIM manager.reset(); #endif server.reset(); obj.reset(); guard.reset(); SE_LOG_DEBUG(NULL, "flushing D-Bus connection"); conn.flush(); conn.reset(); SE_LOG_INFO(NULL, "terminating, closing logging"); syslogger.reset(); redirect.reset(); SE_LOG_INFO(NULL, "terminating"); return 0; } catch ( const std::exception &ex ) { SE_LOG_ERROR(NULL, "%s", ex.what()); } catch (...) { SE_LOG_ERROR(NULL, "unknown error"); } return 1; } syncevolution_1.4/src/dbus/server/network-manager-client.cpp000066400000000000000000000077111230021373600244770ustar00rootroot00000000000000 /* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "network-manager-client.h" #include "server.h" #include "presence-status.h" SE_BEGIN_CXX NetworkManagerClient::NetworkManagerClient(Server &server) : DBusRemoteObject(!strcmp(getEnv("DBUS_TEST_NETWORK_MANAGER", ""), "none") ? NULL : /* simulate missing Network Manager */ GDBusCXX::dbus_get_bus_connection(!strcmp(getEnv("DBUS_TEST_NETWORK_MANAGER", ""), "session") ? "SESSION" : /* use our own Network Manager stub */ "SYSTEM" /* use real Network Manager */, NULL, true, NULL), "/org/freedesktop/NetworkManager", "org.freedesktop.NetworkManager", "org.freedesktop.NetworkManager", true), m_available(false), m_server(server), m_stateChanged(*this, "StateChanged"), m_properties(*this) { if (getConnection()) { m_properties.get(); m_stateChanged.activate(boost::bind( &NetworkManagerClient::stateChanged, this, _1)); } else { SE_LOG_DEBUG(NULL, "DBus connection setup for NetworkManager failed"); } } void NetworkManagerClient::stateChanged(uint32_t uiState) { switch (uiState) { case NM_STATE_ASLEEP: case NM_STATE_DISCONNECTED: case NM_STATE_DISCONNECTING: case NM_STATE_CONNECTING: case NM_STATE_ASLEEP_DEPRECATED: case NM_STATE_CONNECTING_DEPRECATED: case NM_STATE_DISCONNECTED_DEPRECATED: SE_LOG_DEBUG(NULL, "NetworkManager disconnected"); m_server.getPresenceStatus().updatePresenceStatus( false, PresenceStatus::HTTP_TRANSPORT); break; default: SE_LOG_DEBUG(NULL, "NetworkManager connected"); m_server.getPresenceStatus().updatePresenceStatus( true, PresenceStatus::HTTP_TRANSPORT); } } NetworkManagerClient::NetworkManagerProperties::NetworkManagerProperties( NetworkManagerClient& manager) : GDBusCXX::DBusRemoteObject(manager.getConnection(), "/org/freedesktop/NetworkManager", "org.freedesktop.DBus.Properties", "org.freedesktop.NetworkManager"), m_manager(manager) { } void NetworkManagerClient::NetworkManagerProperties::get() { GDBusCXX::DBusClientCall1 > get(*this, "Get"); get.start(std::string(m_manager.getInterface()), std::string("State"), boost::bind(&NetworkManagerProperties::getCallback, this, _1, _2)); } void NetworkManagerClient::NetworkManagerProperties::getCallback( const boost::variant &prop, const std::string &error) { if(!error.empty()) { SE_LOG_DEBUG(NULL, "Error in calling Get of Interface org.freedesktop.DBus.Properties : %s", error.c_str()); } else { // Now, and only now, do we know that NetworkManager is running. m_manager.m_available = true; m_manager.stateChanged(boost::get(prop)); } } SE_END_CXX syncevolution_1.4/src/dbus/server/network-manager-client.h000066400000000000000000000050651230021373600241440ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef NETWORK_MANAGER_CLIENT_H #define NETWORK_MANAGER_CLIENT_H #include #include "gdbus-cxx-bridge.h" #include SE_BEGIN_CXX class Server; /** * Client for org.freedesktop.NetworkManager * The initial state of NetworkManager is queried via * org.freedesktop.DBus.Properties. Dynamic changes are listened via * org.freedesktop.NetworkManager - StateChanged signal */ class NetworkManagerClient : public GDBusCXX::DBusRemoteObject { public: enum NM_State { NM_STATE_UNKNOWN = 0, /* following values for NM < 0.9 */ NM_STATE_ASLEEP_DEPRECATED = 1, NM_STATE_CONNECTING_DEPRECATED = 2, NM_STATE_CONNECTED_DEPRECATED = 3, NM_STATE_DISCONNECTED_DEPRECATED = 4, /* following values for NM >= 0.9 */ NM_STATE_ASLEEP = 10, NM_STATE_DISCONNECTED = 20, NM_STATE_DISCONNECTING = 30, NM_STATE_CONNECTING = 40, NM_STATE_CONNECTED_LOCAL = 50, NM_STATE_CONNECTED_SITE = 60, NM_STATE_CONNECTED_GLOBAL = 70, }; public: NetworkManagerClient(Server& server); void stateChanged(uint32_t uiState); /** TRUE if watching Network Manager status */ bool isAvailable() { return m_available; } private: bool m_available; class NetworkManagerProperties : public DBusRemoteObject { public: NetworkManagerProperties(NetworkManagerClient& manager); void get(); void getCallback(const boost::variant &prop, const std::string &error); private: NetworkManagerClient &m_manager; }; Server &m_server; GDBusCXX::SignalWatch1 m_stateChanged; NetworkManagerProperties m_properties; }; SE_END_CXX #endif // NETWORK_MANAGER_CLIENT_H syncevolution_1.4/src/dbus/server/notification-backend-base.h000066400000000000000000000023101230021373600245400ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef __NOTIFICATION_BACKEND_BASE_H #define __NOTIFICATION_BACKEND_BASE_H #include "syncevo/declarations.h" #include SE_BEGIN_CXX class NotificationBackendBase { public: virtual bool init() = 0; virtual void publish(const std::string& summary, const std::string& body, const std::string& viewParams = std::string()) = 0; }; SE_END_CXX #endif syncevolution_1.4/src/dbus/server/notification-backend-libnotify.cpp000066400000000000000000000137731230021373600261770ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #if HAS_NOTIFY #include "notification-backend-libnotify.h" #include "syncevo/util.h" #include "syncevo/GLibSupport.h" #include #include #include #include #include #ifdef NOTIFY_COMPATIBILITY # include #endif SE_BEGIN_CXX #ifdef NOTIFY_COMPATIBILITY /** * set to real old C notify_notification_new() (with widget pointer) or new one (without); * because of the x86/AMD64 calling conventions, calling the newer function with * one extra parameter is okay */ gboolean (*notify_init)(const char *app_name); GList *(*notify_get_server_caps)(void); NotifyNotification *(*notify_notification_new)(const char *summary, const char *body, const char *icon, void *widget); void (*notify_notification_add_action)(NotifyNotification *notification, const char *action, const char *label, NotifyActionCallback callback, gpointer user_data, GFreeFunc free_func); void (*notify_notification_clear_actions)(NotifyNotification *notification); gboolean (*notify_notification_close)(NotifyNotification *notification, GError **error); gboolean (*notify_notification_show)(NotifyNotification *notification, GError **error); static bool NotFound(const char *func) { SE_LOG_DEBUG(NULL, "%s: not found", func); return false; } #endif NotificationBackendLibnotify::NotificationBackendLibnotify() : m_initialized(false), m_acceptsActions(false), m_notification(NULL) { } NotificationBackendLibnotify::~NotificationBackendLibnotify() { } void NotificationBackendLibnotify::notifyAction( NotifyNotification *notify, gchar *action, gpointer userData) { if(boost::iequals(action, "view")) { pid_t pid; if((pid = fork()) == 0) { // search sync-ui from $PATH if(execlp("sync-ui", "sync-ui", (const char*)0) < 0) { exit(0); } } } // if dismissed, ignore. } bool NotificationBackendLibnotify::init() { #ifdef NOTIFY_COMPATIBILITY void *dlhandle = NULL; int i; for (i = 1; i <= 4; i++) { dlhandle = dlopen(StringPrintf("libnotify.so.%d", i).c_str(), RTLD_LAZY|RTLD_GLOBAL); if (!dlhandle) { SE_LOG_DEBUG(NULL, "failed to load libnotify.so.%d: %s", i, dlerror()); } else { break; } } if (!dlhandle) { return false; } #define LOOKUP(_x) ((_x = reinterpret_cast(dlsym(dlhandle, #_x))) || \ NotFound(#_x)) if (!LOOKUP(notify_init) || !LOOKUP(notify_get_server_caps) || !LOOKUP(notify_notification_new) || !LOOKUP(notify_notification_add_action) || !LOOKUP(notify_notification_clear_actions) || !LOOKUP(notify_notification_close) || !LOOKUP(notify_notification_show)) { return false; } SE_LOG_DEBUG(NULL, "using libnotify.so.%d", i); #endif m_initialized = notify_init("SyncEvolution"); if(m_initialized) { GStringListFreeCXX list(notify_get_server_caps()); BOOST_FOREACH (const char *cap, list) { if(boost::iequals(cap, "actions")) { m_acceptsActions = true; } } return true; } return false; } void NotificationBackendLibnotify::publish( const std::string& summary, const std::string& body, const std::string& viewParams) { if(!m_initialized) return; if(m_notification) { notify_notification_clear_actions(m_notification); notify_notification_close(m_notification, NULL); } #ifndef NOTIFY_CHECK_VERSION # define NOTIFY_CHECK_VERSION(_x,_y,_z) 0 #endif #if !NOTIFY_CHECK_VERSION(0,7,0) || defined(NOTIFY_COMPATIBILITY) m_notification = notify_notification_new(summary.c_str(), body.c_str(), NULL, NULL); #else m_notification = notify_notification_new(summary.c_str(), body.c_str(), NULL); #endif //if actions are not supported, don't add actions //An example is Ubuntu Notify OSD. It uses an alert box //instead of a bubble when a notification is appended with actions. //the alert box won't be closed until user inputs. //so disable it in case of no support of actions if(m_acceptsActions) { notify_notification_add_action(m_notification, "view", _("View"), notifyAction, (gpointer)viewParams.c_str(), NULL); // Use "default" as ID because that is what mutter-moblin // recognizes: it then skips the action instead of adding it // in addition to its own "Dismiss" button (always added). notify_notification_add_action(m_notification, "default", _("Dismiss"), notifyAction, (gpointer)viewParams.c_str(), NULL); } notify_notification_show(m_notification, NULL); } SE_END_CXX #endif // HAS_NOTIFY syncevolution_1.4/src/dbus/server/notification-backend-libnotify.h000066400000000000000000000040201230021373600256250ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef __NOTIFICATION_BACKEND_LIBNOTIFY_H #define __NOTIFICATION_BACKEND_LIBNOTIFY_H #ifdef HAVE_CONFIG_H #include "config.h" #endif #if HAS_NOTIFY #include "syncevo/declarations.h" #include "notification-backend-base.h" #include SE_BEGIN_CXX class NotificationBackendLibnotify : public NotificationBackendBase { public: NotificationBackendLibnotify(); virtual ~NotificationBackendLibnotify(); /** * Callback for the notification action. */ static void notifyAction(NotifyNotification *notify, gchar *action, gpointer userData); bool init(); void publish(const std::string& summary, const std::string& body, const std::string& viewParams = std::string()); private: /** * Flag to indicate whether libnotify has been successfully * initialized. */ bool m_initialized; /** * Flag to indicate whether libnotify accepts actions. */ bool m_acceptsActions; /** * The current notification. */ NotifyNotification *m_notification; }; SE_END_CXX #endif // HAS_NOTIFY #endif // __NOTIFICATION_BACKEND_LIBNOTIFY_H syncevolution_1.4/src/dbus/server/notification-backend-mlite.cpp000066400000000000000000000036121230021373600253010ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #ifdef HAS_MLITE #include "notification-backend-mlite.h" #include #include SE_BEGIN_CXX /// TODO: these should really be in a common place. #define SYNCEVOLUTION_SERVICE_NAME "org.syncevolution" #define SYNCEVOLUTION_OBJECT_PATH "/org/syncevolution/Server" #define SYNCEVOLUTION_INTERFACE "org.syncevolution.Server" NotificationBackendMLite::NotificationBackendMLite() { } NotificationBackendMLite::~NotificationBackendMLite() { } bool NotificationBackendMLite::init() { return true; } void NotificationBackendMLite::publish( const std::string& summary, const std::string& body, const std::string& viewParams) { MNotification n ("Sync"); n.setSummary(QString::fromStdString(summary)); n.setBody(QString::fromStdString(body)); n.setImage("image://themedimage/icons/settings/sync"); MRemoteAction action(SYNCEVOLUTION_SERVICE_NAME, SYNCEVOLUTION_OBJECT_PATH, SYNCEVOLUTION_INTERFACE, "NotificationAction"); n.setAction(action); n.publish(); } SE_END_CXX #endif syncevolution_1.4/src/dbus/server/notification-backend-mlite.h000066400000000000000000000024701230021373600247470ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef __NOTIFICATION_BACKEND_MLITE_H #define __NOTIFICATION_BACKEND_MLITE_H #include "syncevo/declarations.h" #include "notification-backend-base.h" SE_BEGIN_CXX class NotificationBackendMLite : public NotificationBackendBase { public: NotificationBackendMLite(); virtual ~NotificationBackendMLite(); bool init(); void publish(const std::string& summary, const std::string& body, const std::string& viewParams = std::string()); }; SE_END_CXX #endif // __NOTIFICATION_BACKEND_MLITE_H syncevolution_1.4/src/dbus/server/notification-backend-noop.cpp000066400000000000000000000022101230021373600251330ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "notification-backend-noop.h" SE_BEGIN_CXX NotificationBackendNoop::NotificationBackendNoop() { } NotificationBackendNoop::~NotificationBackendNoop() { } bool NotificationBackendNoop::init() { return true; } void NotificationBackendNoop::publish( const std::string& summary, const std::string& body, const std::string& viewParams) { } SE_END_CXX syncevolution_1.4/src/dbus/server/notification-backend-noop.h000066400000000000000000000024621230021373600246110ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef __NOTIFICATION_BACKEND_NOOP_H #define __NOTIFICATION_BACKEND_NOOP_H #include "syncevo/declarations.h" #include "notification-backend-base.h" SE_BEGIN_CXX class NotificationBackendNoop : public NotificationBackendBase { public: NotificationBackendNoop(); virtual ~NotificationBackendNoop(); bool init(); void publish(const std::string& summary, const std::string& body, const std::string& viewParams = std::string()); }; SE_END_CXX #endif // __NOTIFICATION_BACKEND_NOOP_H syncevolution_1.4/src/dbus/server/notification-manager-base.h000066400000000000000000000030641230021373600245720ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef __NOTIFICATION_MANAGER_BASE_H #define __NOTIFICATION_MANAGER_BASE_H #include "syncevo/declarations.h" #include SE_BEGIN_CXX class NotificationManagerBase { public: /** Initializes the backend. */ virtual bool init() { return true; } /** Publishes the notification through the backend. * @param summary The summary of the notification. * @param body The main content of the notification. * @param viewParams Parameters used by sync-ui (opened when the * notification activated. */ virtual void publish(const std::string& summary, const std::string& body, const std::string& viewParams = std::string()) {} }; SE_END_CXX #endif syncevolution_1.4/src/dbus/server/notification-manager-factory.cpp000066400000000000000000000040151230021373600256570ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "notification-manager-factory.h" #include "notification-backend-noop.h" #include "notification-backend-mlite.h" #include "notification-backend-libnotify.h" #include "notification-manager.h" #include SE_BEGIN_CXX #define SYNC_UI_PATH "/usr/bin/sync-ui" boost::shared_ptr NotificationManagerFactory::createManager() { boost::shared_ptr mgr; /* Detect what kind of manager we need: if /usr/bin/sync-ui * doesn't exists, we shall use the MLite backend; otherwise, if * libnotify is enabled, we shall use the libnotify backend; * if everything fails, then we'll use the no-op backend. */ if(access(SYNC_UI_PATH, F_OK) != 0) { // i.e. it does not exist #if defined(HAS_MLITE) mgr.reset(new NotificationManager()); #elif defined(HAS_NOTIFY) mgr.reset(new NotificationManager()); #endif } else { // it exists #if defined(HAS_NOTIFY) mgr.reset(new NotificationManager()); #endif } // Fallback if no manager was created if(mgr.get() == 0) mgr.reset(new NotificationManager()); return mgr; } SE_END_CXX syncevolution_1.4/src/dbus/server/notification-manager-factory.h000066400000000000000000000025771230021373600253370ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef __NOTIFICATION_MANAGER_FACTORY_H #define __NOTIFICATION_MANAGER_FACTORY_H #include "syncevo/declarations.h" #include "notification-manager.h" #include SE_BEGIN_CXX class NotificationBackendBase; class NotificationManagerFactory { public: /** Creates the appropriate NotificationManager for the current * platform. * Note: NotificationManagerFactory does not take ownership of * the returned pointer: the user must delete it when done. */ static boost::shared_ptr createManager(); }; SE_END_CXX #endif syncevolution_1.4/src/dbus/server/notification-manager.cpp000066400000000000000000000025711230021373600242170ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "notification-manager.h" #include "syncevo/declarations.h" #include SE_BEGIN_CXX template NotificationManager::NotificationManager() { } template NotificationManager::~NotificationManager() { } template bool NotificationManager::init() { return m_backend->init(); } template void NotificationManager::publish(const std::string& summary, const std::string& body, const std::string& viewParams) { m_backend->publish(summary, body, viewParams); } SE_END_CXX syncevolution_1.4/src/dbus/server/notification-manager.h000066400000000000000000000034701230021373600236630ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef __NOTIFICATION_MANAGER_H #define __NOTIFICATION_MANAGER_H #include "notification-manager-base.h" #include "syncevo/declarations.h" #include SE_BEGIN_CXX template class NotificationManager : public NotificationManagerBase { public: NotificationManager(); virtual ~NotificationManager(); bool init(); void publish(const std::string& summary, const std::string& body, const std::string& viewParams = std::string()); private: T m_backend; }; template NotificationManager::NotificationManager() { } template NotificationManager::~NotificationManager() { } template bool NotificationManager::init() { return m_backend.init(); } template void NotificationManager::publish(const std::string& summary, const std::string& body, const std::string& viewParams) { m_backend.publish(summary, body, viewParams); } SE_END_CXX #endif syncevolution_1.4/src/dbus/server/org.syncevolution.service.in000066400000000000000000000001501230021373600251020ustar00rootroot00000000000000[D-BUS Service] Name=org.syncevolution Exec=@libexecdir@/syncevo-dbus-server @SYNCEVO_DBUS_SERVER_ARGS@ syncevolution_1.4/src/dbus/server/pim/000077500000000000000000000000001230021373600201755ustar00rootroot00000000000000syncevolution_1.4/src/dbus/server/pim/README000066400000000000000000000624351230021373600210670ustar00rootroot00000000000000Overview ======== The code in this directory only gets compiled when SyncEvolution is configured with --enable-dbus-service-pim. It uses libfolks and the PBAP backend to implement a unified address book. This additional functionality is provided via the org._01.pim D-Bus API. That API is independent of libfolks and SyncEvolution. That it is implemented inside syncevo-dbus-server merely simplifies the implementation, by reusing some code provided by SyncEvolution and the core Server class: * C++ D-Bus bindings * logging * D-Bus server life cycle control: delay shut down while clients make calls, inhibit shutdown while clients are registered, restart when files change on disk during system update, ... * direct access to SyncEvolution instead having to go through the http://api.syncevolution.org D-Bus API Compilation =========== Use --enable-dbus-service-pim --enable-pbap --enable-ebook. --enable-ebook is already the default at the moment. It must not be disabled. The PBAP backend can be disabled. However, then fetching data from phones via PBAP obviously does not work. More information ================ Public discussion started here, comments can be sent via the gmane "reply" feature: http://comments.gmane.org/gmane.comp.mobile.syncevolution/4009 Issues specific to PIM Manager can be found in this graph: https://bugs.freedesktop.org/showdependencygraph.cgi?id=55916&display=web&rankdir=TB Dependencies ============ A fairly recent libfolks > 0.7.4 is required. The 837a88 commit is required and several other pending changes are recommended: https://bugzilla.gnome.org/show_bug.cgi?id=686693 writing birthday lacks conversion from UTC https://bugzilla.gnome.org/show_bug.cgi?id=685401 linking by email https://bugzilla.gnome.org/show_bug.cgi?id=686695 support nickname in add_persona_from_details Other requirements: * Evolution Data Server >= 3.6 * boost::locale and libphonenumber (when using the default sorting and searching) * glib >= 2.30 * Python >= 2.7 (only for testing) Known issues ============ None at the moment. Extending the implementation ============================ Sorting and searching can be replaced with a different implementation at compile time via --enable-dbus-service-pim[=]: it uses the file src/dbus/server/pim/locale-factory-.cpp to implement sorting and searching. Any additional library dependencies for that file need to be added to the main configure.ac. The default implementation is based on boost::locale and libphonenumber and is described below. Sorting ======= The sort order can be "first/last", "last/first", "fullname". "first/last" sorts based on the first name stored in the "name" property, with the last name used to break ties between equal first names. "last/first" reverts that comparison. "fullname" sorts based on the full name chosen for the contact if there is such a string, otherwise it uses the concatenation of the individual name componts without prefix (= "[] [] [] [])" as fallback. In other words, it sorts correctly when either all contacts have a full name explicitly set or the full names that were set following the same pattern as the fallback. Sorting is case-insensitive. The default is "last/first" if not set earlier. In addition, an empty string as sort order picks a simple, ASCII-based "last/first" sorting. This is used for testing. Searching ========= Supported searches: [ ] - An empty list matches all contacts. [ [ ], [ 'limit', ] ] - a 'limit' search term with a number as parameter (formatted as string) can be added at the top level term to truncate the search results after a certain number of contacts. Example: Search([['any-contains', 'Joe'], ['limit', '10']]) => return the first 10 Joes. As with any other search, the resulting view will be updated if contact data changes. The limit must not be changed in a RefineSearch(). A 'limit' term may (but doesn't have to) be given. If it is given, its value must match the value set when creating the search. This limitation simplifies the implementation and its testing. The limitation could be removed if there is sufficient demand. [ [ ] ] - the same as [ ] [ 'or', , , ... ] - combines 0 to n other searches and results in a match if any of the sub-searches matches. ['or'] without any sub-search does not match. [ 'and', , , ... ] - like 'or', but matches if and only if all of the sub-searches match. [ 'phone', '' ] - Look up a valid phone number (= "caller ID"). The country code for the current locale is added if no country code was given in the number. Phone numbers in the unified address book must start with the resulting full number, after being normalized the same way. In other words: - Formatting does not matter. - Alpha characters are aliases for numbers on the keypad and match their corresponding number. - Additional digits in the address book are ignored, only the prefix must match (extensions may or may not be included in ). - Phone numbers in the address book which cannot be normalized cannot be matched. [ 'is|contains|begins-with|ends-with', '', '', '' ] - compares a specified field against the search text. For the 'is' operation, the entire field must match, for 'contains' anywhere inside the value, for 'begins_with' at the beginning and for 'ends_with' at the end. Fields are referenced as in the contact dictionary (see below), using multiple path components if necessary. Supported for matching are: 'full-name' - string 'nickname' - string 'structured-name/family' - string 'structured-name/given' - string 'structured-name/additional' - string 'phones/value' - telephone number 'emails/value' - string 'addresses/po-box' - string 'addresses/extension' - string 'addresses/street' - string 'addresses/locality' - string 'addresses/region' - string 'addresses/postal-code' - string 'addresses/country' - string The fields referencing value lists ('phones', 'email', 'address') check against any of the entries in these lists. Except for 'phones/value', all values are treated as text values. For text values, the default search without explicit flags is very tolerant, meaning that it ignores quite a few differences between search term and value. The default search: - transliterates any foreign script in search term and values to Latin before comparison, thus finding 江 when searching for Jiang and vice-versa - is case-insensitive - is accent-insensitive Case and accent differences get removed after the optional transliteration. Spaces between words always matter. This behavior can be modified by giving additional, optional flags after the search value: 'case-insensitive' - force case-insensitive search (available for the sake of consistency and just in case, should the default ever change) 'case-sensitive' - force case-sensitive search 'accent-insensitive', 'accent-sensitive' - same for accents 'transliteration' - force transliteration, i.e. explicitly choose the current default 'no-transliteration' - disable transliteration For telephone numbers, only digits are compared. Latin alphabetic characters are treated as aliases for digits as they typically occur on a keypad or old rotary dial phones ('A', 'b', 'c' map to '1', etc.). If the full name was not set explicitly for a contact, the concatenation of the given, middle and family with a space as separator is used instead when matching against the 'full-name' field. Using the current syntax it is not possible to define searches where the *same* value in a value list must meet different criteria ("cell phone number containing the digits 1234"). Something like that could be added as a future extensions, for example by allowing search values to have more complex types than the simple ''. [ 'any-contains', '', ] - Sub-string search for in the following contact values: first, middle or last name, formatted name, nick name, phone number, or email address. Optional flags include: 'case-insensitive' (the default), 'case-sensitive'. This search is equivalent to: [ 'or', [ 'contains', 'structured-name/given', '', ], [ 'contains', 'structured-name/additional', '', ], [ 'contains', 'structured-name/family', '', ], [ 'contains', 'full-name', '', ], [ 'contains', 'emails/value', '', ], [ 'contains', 'phones/value', ''] ] Note that lookup and search are different: the former is based on a valid number, the later on user input. A 'phone' lookup can compare normalized numbers including the country code, to ensure that the lookup is exact and does not mismatch numbers from different countries. Heuristics like suffix matching do not do this correctly in all cases. An 'any-contains' search is based on user input, which might contain just some digits in the middle of the phone number. The search ignores formatting in both input and address book. Compound searches with 'and' and 'or' are evaluated lazily, from the first to the last sub-search. Therefore it makes sense to list sub-searches that are more likely to match first. Phone number lookup =================== A "phone" search must return results quickly (<30ms with 10000 contacts) under all circumstances, including the period of time where the unified address book is still getting assembled in memory. To achieve this, SyncEvolution searches directly in the active address books and presents these results until the ones from the unified address book are ready. A quiescence signal will be sent when: 1. results from EDS are complete before the ones from the unified address book 2. results from the unified address book are complete. The first signal will be skipped and the EDS results discarded if EDS turns out to be slower than the unified address book. Results from different EDS address books are not unified, for the sake of simplicity. They get sorted according to the sort order that was active when starting the search. Changing the sort order while the search runs will only affect the final results from the unified address book. Refining such a search is not supported because refining a phone number lookup is not useful. Peers ===== The following keys are supported for the configuration of a peer: - "protocol" - defines how to access the address book. "PBAB" (phone book access protocol) and "file" (read vCard files from directory) are implemented. "SyncML" and "CardDAV" could be added easily. - "transport" - defines how to establish the connection. The only supported value is "Bluetooth" (for protocol=PBAP or SyncML), which is also the default if not given explicitly. - "address" - the Bluetooth MAC address in the aa:bb:cc:dd:ee:ff format (for transport=Bluetooth). - "database" - empty or unset for the internal address book (protocol=PBAP), or the path to the directory (protocol=file). - "logdir" - a directory in which directories are created with debug information about sync session. - "maxsessions" - number of sessions that are allowed to exist after a sync (>= 0): 0 is special and means unlimited, 1 for just the latest, etc.; old sessions are pruned heuristically (for example, keep sessions where something changed instead of some where nothing changed), so there is no hard guarantee that the last n sessions are present. Not supported via the API at the moment: - selecting a specific phone address book - selecting which vCard properties get cached Syncing ======= SetSync() in SyncEvolution will return a dict with all of the following entries set: "modified": boolean - data was modified "added" : integer - number of new contacts "updated" : integer - number of updated contacts "removed" : integer - number of deleted contacts In other words, the caller can reliably detect when nothing changed, but when contacts were modified or added, it needs to read them to determine which kind of properties were modified or added. The SyncProgress is triggered by SyncEvolution with three different keys (in this order, with "modified" occuring zero or more times): "started" "modified"* "done" "started" and "done" send an empty data dictionary. "modified" sends the same dictionary as the one returned by SyncPeer(), if contact data was modified. So by definition, "modified" will be True in the dictionary, but is included anyway for the sake of consistency. Contact Data ============ A contact dictionary has the following key/value pairs. More properties may be added later. contact dictionary: "id" - string, see API description "source" - string, see API description "full-name" - string, formatted by the user or automatically (vCard FN) "nickname" - string "structured-name" - name dictionary (vCard N) "photo" - string, the URL (usually of a local file; EDS strips all inline photo data in vCards and puts them into local files, leading to URLs like: file:///home/user/.local/share/evolution/addressbook/system/photos/pas_id_5012983E0000065A_photo-file0.image%2Fjpeg) "birthday" - birthday tuple "emails" - value list with strings as value "phones" - value list with strings as value "urls" - value list with strings as value "notes" - list of strings; in practice only one entry is supported "addresses" - value list with an address dictionary as value "roles" - list of role dictionaries "groups" - list strings representing the names of groups the contact belongs to (CATEGORIES in vCard) "location" - a pair of doubles, representing latitude + longitude (in this order, see GEO in vCard); typically based on WGS84 name dictionary: "family" - string, the last name "given" - string, the fist name "additional" - string, middle names "prefixes" - string, name prefix like "Mr." "suffixes" - string, name suffix like "Sr." birthday tuple: (yy, mm, dd) - integer values for year, month, and day role dictionary: "organisation" - main organization ("Foo ACME") "title" - title inside that organization ("vice president") "role" - role as part of that origanization ("adviser") value list: [ (value, [parameter, ...]), (value, ...) ] value - depends on the property parameter - a string, with values again depending on the property; a value may have zero to n different parameters phone parameters: "voice" "fax" "car" "cell" "pager" "pref" "home" "work" "other" (might not be set explicitly) address parameters: "home" "work" "other" (might not be set explicitly) url parameters: "x-home-page" "x-blog" "x-free-busy" - public calendar free/busy URL "x-video" - video chat URL address dictionary: "po-box" - string, post office box "extension" - string, address extension "street" - string, street name "locality" - string, city name "region" - string, area name "postal-code" - string "country" - string Note: all of the strings and values above are defined by SyncEvolution in individual-traits.cpp, except for parameter strings. Those come directly from folks, more specifically AbstractFieldDetails: http://telepathy.freedesktop.org/doc/folks/vala/Folks.AbstractFieldDetails.html Here is an example contact dictionary, using Python syntax: { 'full-name': 'John Doe', 'nickname': 'Johnny', 'groups': ['friends', 'soccer'], 'location': (30.12, -130.34), 'structured-name': {'given': 'John', 'family': 'Doe'}, 'birthday': (2006, 1, 8), 'photo': 'file:///home/user/file.png', 'roles': [ { 'organisation': 'Test Inc.', 'role': 'professional test case', 'title': 'Senior Tester', }, ], 'source': [ ('test-dbus-foo', luids[0]) ], 'id': 'xyz', 'notes': [ 'This is a test case which uses almost all Evolution fields.', ], 'emails': [ ('john.doe@home.priv', ['home']), ('john.doe@other.world', ['other']), ('john.doe@work.com', ['work']), ('john.doe@yet.another.world', ['other']), ], 'phones': [ ('business 1', ['voice', 'work']), ('businessfax 4', ['fax', 'work']), ('car 7', ['car']), ('home 2', ['home', 'voice']), ('homefax 5', ['fax', 'home']), ('mobile 3', ['cell']), ('pager 6', ['pager']), ('primary 8', ['pref']), ], 'addresses': [ ({'country': 'New Testonia', 'locality': 'Test Megacity', 'po-box': 'Test Box #3', 'postal-code': '12347', 'region': 'Test County', 'street': 'Test Drive 3'}, []), ({'country': 'Old Testovia', 'locality': 'Test Town', 'po-box': 'Test Box #2', 'postal-code': '12346', 'region': 'Upper Test County', 'street': 'Test Drive 2'}, ['work']), ({'country': 'Testovia', 'locality': 'Test Village', 'po-box': 'Test Box #1', 'postal-code': '12345', 'region': 'Lower Test County', 'street': 'Test Drive 1'}, ['home']), ], 'urls': [ ('chat', ['x-video']), ('free/busy', ['x-free-busy']), ('http://john.doe.com', ['x-home-page']), ('web log', ['x-blog']), ], } Configuration and data handling =============================== The configuration of peers is mapped to normal SyncEvolution configurations. Every peer gets its own context, which contains both the local source definition for the peer and sync plus target configs. The special name space prefix "pim-manager-" used to identify them and avoid potential conflicts with normal SyncEvolution configurations. Look in ~/.config/syncevolution/pim-manager-* to find the configuration files. Local EDS databases are created with a fixed UID and name that also have that prefix. One could go one step further than it is currently done and set these databases to "disabled" in the ESourceRegistry, which would hide them in Evolution. The SyncEvolution command line can be used to view or manipulate these databases: https://syncevolution.org/wiki/item-operations The sort order and set of active address books are stored persistently in ~/.config/syncevolution/pim-manager.ini as: - "sort" = same value as in the API - "active" = space, comma or tab separated EDS source UUIDs Usage ===== The PIM manager is part of the syncevo-dbus-server. It can be started automatically via D-Bus or explicitly. When started automatically, it logs error messages to syslog. The PIM manager starts as idle and needs to be started via the D-Bus Start() API before it begins assembling the unified address book. SyncEvolution installs an XDG autostart file (/etc/xdg/autostart/syncevo-dbus-server.desktop) which, on systems supporting that mechanism, ensures that syncevo-dbus-server is started once. This is used for automatic syncing, which is not active when using only the PIM Manager. Therefore syncevo-dbus-server will terminate again after some idle period. It's recommended to disable this mechanism or patch syncevo-dbus-server.desktop to start the server with different options. In particular the "--start-pim" option may be useful to start up the UI and the server in parallel. Here is a list of supported options: Help Options: -h, --help Show help options Application Options: -d, --duration=seconds/'unlimited' Shut down automatically when idle for this duration (default 300 seconds) -v, --verbosity=level Choose amount of output, 0 = no output, 1 = errors, 2 = info, 3 = debug; default is 1. -o, --stdout Enable printing to stdout (result of operations) and stderr (errors/info/debug). -s, --no-syslog Disable printing to syslog. -p, --start-pim Activate the PIM Manager (= unified address book) immediately. Limitations =========== There are no hard-coded limits. In practice the number of contacts, parallel searches, peers, etc. is limited by CPU performance, amount of RAM and disk space. Functionality ============= Caching ------- Caching of contacts is done roughly like this: 1. The PBAP backend reads all contact data. See src/backends/pbap. 2. As part of a local sync (see main SyncEvolution README), a local-cache-slow sync is done. In that mode all local data is compared against the incoming data. Matches are found according to the properties that are defined as compare="always" or compare="slowsync" in src/syncevo/configs/datatypes/00vcard-fieldlist.xml. In the default file, these are the first, middle and last name and the organization. 3. Local entries which have a match are updated with data from that match, unmatched local entries are removed and unmatched incoming ones are created. This means that changing "less important" properties will be turned into local updates. This has the desirable effect of not changing the local ID of the existing contact, which might become important when attaching local meta data to that contact via its local ID (not supported at the moment; folks itself has a favorite flag which is stored like that). It also reduces disk IO. If matching fails, a contact will get deleted and recreated. The end result in the unified address book is still the same, because folks does not rely on the ID for linking. Supported fields ---------------- The PBAP backend always downloads the entire vCard. It supports restricting the vCard to specific properties, see src/backend/pbap/README. However, this is not used by the PIM Manager. In particular, PHOTO data is always included in the download right away. A mode where caching only works without PHOTO in the initial pass and then adds that in a second step could be added. Internally a vCard is represented as a Synthesis field list (again, see 00vcard-fieldlist.xml). X- extensions in the incoming vCard are preserved. Unified address book -------------------- The unified address book is assembled by folks based on properties that it considers "linkable". Linkable properties must be unique for a person. A phone number is not linkable, because different persons living at the same place might share a phone. For EDS, the linkable properties are defined in folk's backends/eds/lib/edsf-persona.vala and currently are: - instant messaging handles - email addresses - local IDs (not used in this context) - web service addresses (not used in this context) folks supports heuristics for identifying persons which are likely to be the same. This is not used by the PIM Manager. If it was, then links would have to be established based on the local IDs and keeping those stable became more important. Localization ------------ The usual environment variables are used to determine the locale: LC_CTYPE, LC_ALL, and LANG in that order (i.e. LC_CTYPE first and LANG last). There is no support for changing that once syncevo-dbus-server runs. boost::locale is used for collation while sorting contacts. The secondary collation level is used, which means that case, punctuation are ignored while accents are relevant. This is hard-coded in locale-factory-boost.cpp as DEFAULT_COLLATION_LEVEL. Searching is either done with a strict text comparison (case sensitive) or after using case folding (case insensitive, see http://www.boost.org/doc/libs/1_51_0/libs/locale/doc/html/glossary.html#term_case_folding). A 'phone' lookup uses libphonenumber to compare the free-form text field for telephone numbers against a caller ID. The text field must be in a format that libphonenumber understands, otherwise it will be ignored. A country code is optional. If missing, the country code of the current locale will be added before the comparison. Testing ======= Tests are mostly written in Python. See test/test-dbus.py (base classes and testing of the normal SyncEvolution D-Bus API) and src/dbus/server/pim/testpim.py. These operations manipulate the system address book. To avoid unexpected data loss when a developer runs the script in his normal environment, check that testpim.py gets run like this: XDG_CONFIG_HOME=`pwd`/temp-testpim/config \ XDG_DATA_HOME=`pwd`/temp-testpim/local/cache \ PATH=/bin:/libexec:$PATH \ /test/dbus-session.sh \ /src/dbus/server/pim/testpim.py This will use temp-testpim in addition to temp-testdbus in the current directory for temporary files. If TEST_DBUS_PBAP_PHONE is set to the Bluetooth MAC address (like A0:4E:04:1E:AD:30) of a phone, PBAP syncing with that phone is tested. The phone must be paired, connected and support SyncML in addition to PBAP. The test hard-codes Nokia SyncML settings to keep it simpe and because Nokia phones are most likely to be used. syncevolution_1.4/src/dbus/server/pim/edsf-view.cpp000066400000000000000000000137111230021373600225750ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "edsf-view.h" #include SE_GOBJECT_TYPE(EdsfPersona); SE_BEGIN_CXX EDSFView::EDSFView(const ESourceRegistryCXX ®istry, const std::string &uuid, const std::string &query) : m_registry(registry), m_uuid(uuid), m_query(query) { } void EDSFView::init(const boost::shared_ptr &self) { m_self = self; } boost::shared_ptr EDSFView::create(const ESourceRegistryCXX ®istry, const std::string &uuid, const std::string &query) { boost::shared_ptr view(new EDSFView(registry, uuid, query)); view->init(view); return view; } void EDSFView::doStart() { // This function may get entered again, see retry code in opened() below. ESourceCXX source(e_source_registry_ref_source(m_registry, m_uuid.c_str()), TRANSFER_REF); if (!source) { SE_LOG_DEBUG(NULL, "edsf %s: address book not found", m_uuid.c_str()); return; } m_store = EdsfPersonaStoreCXX::steal(edsf_persona_store_new_with_source_registry(m_registry, source)); GErrorCXX gerror; #ifdef HAVE_E_BOOK_CLIENT_CONNECT_DIRECT_SYNC // TODO: use asynchronous version, once there is one in EDS if (!getenv("SYNCEVOLUTION_NO_PIM_EDS_DIRECT")) { while (!m_ebook) { SE_LOG_DEBUG(NULL, "edsf %s: synchronously connecting direct", m_uuid.c_str()); m_ebook = EBookClientCXX::steal(E_BOOK_CLIENT(e_book_client_connect_direct_sync(m_registry, source, NULL, gerror))); if (!m_ebook) { SE_LOG_DEBUG(NULL, "edsf %s: no DRA client for address book: %s", m_uuid.c_str(), gerror ? gerror->message : "???"); if (gerror && g_error_matches(gerror, E_CLIENT_ERROR, E_CLIENT_ERROR_BUSY)) { SE_LOG_DEBUG(NULL, "edsf %s: try again", m_uuid.c_str()); gerror.clear(); } else { return; } } } // Already opened by call above, proceed immediately. opened(true, NULL); return; } #endif SE_LOG_DEBUG(NULL, "edsf %s: new client", m_uuid.c_str()); m_ebook = EBookClientCXX::steal(e_book_client_new(source, gerror)); if (!m_ebook) { SE_LOG_DEBUG(NULL, "edsf %s: no normal client for address book: %s", m_uuid.c_str(), gerror ? gerror->message : "???"); return; } SE_LOG_DEBUG(NULL, "edsf %s: asynchronous open", m_uuid.c_str()); SYNCEVO_GLIB_CALL_ASYNC(e_client_open, boost::bind(&EDSFView::opened, m_self, _1, _2), E_CLIENT(m_ebook.get()), false, NULL); } void EDSFView::opened(gboolean success, const GError *gerror) throw() { try { if (!success) { SE_LOG_DEBUG(NULL, "edsf %s: opening failed: %s", m_uuid.c_str(), gerror ? gerror->message : "???"); if (gerror && g_error_matches(gerror, E_CLIENT_ERROR, E_CLIENT_ERROR_BUSY)) { SE_LOG_DEBUG(NULL, "edsf %s: try again", m_uuid.c_str()); doStart(); return; } } SE_LOG_DEBUG(NULL, "edsf %s: opened successfully, reading contacts asynchronously: %s", m_uuid.c_str(), m_query.c_str()); SYNCEVO_GLIB_CALL_ASYNC(e_book_client_get_contacts, boost::bind(&EDSFView::read, m_self, _1, _2, _3), m_ebook.get(), m_query.c_str(), NULL); } catch (...) { Exception::handle(HANDLE_EXCEPTION_NO_ERROR); } } void EDSFView::read(gboolean success, GSList *contactslist, const GError *gerror) throw() { try { SE_LOG_DEBUG(NULL, "edsf %s: reading contacts completed: %s", m_uuid.c_str(), success ? "success" : gerror ? gerror->message : "failed without error"); GListCXX contacts(contactslist); if (!success) { SE_LOG_DEBUG(NULL, "edsf %s: reading failed: %s", m_uuid.c_str(), gerror->message); return; } BOOST_FOREACH (EContact *contact, contacts) { EdsfPersonaCXX persona(edsf_persona_new(m_store, contact), TRANSFER_REF); GeeHashSetCXX personas(gee_hash_set_new(G_TYPE_OBJECT, g_object_ref, g_object_unref, NULL, NULL, NULL, NULL, NULL, NULL), TRANSFER_REF); gee_collection_add(GEE_COLLECTION(personas.get()), persona.get()); FolksIndividualCXX individual(folks_individual_new(GEE_SET(personas.get())), TRANSFER_REF); m_addedSignal(individual); } m_isQuiescent = true; m_quiescenceSignal(); } catch (...) { Exception::handle(HANDLE_EXCEPTION_NO_ERROR); } } SE_END_CXX syncevolution_1.4/src/dbus/server/pim/edsf-view.h000066400000000000000000000044671230021373600222520ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ /** * Search in an EBook once. Uses folks-eds (= EDSF) to turn EContacts * into FolksPersonas and from that into FolksIndividuals. Results * will be sorted after the search is complete, then they will be * advertised with the "added" signal. */ #ifndef INCL_SYNCEVO_DBUS_SERVER_PIM_EDSF_VIEW #define INCL_SYNCEVO_DBUS_SERVER_PIM_EDSF_VIEW #include "view.h" #include #include #include SE_GOBJECT_TYPE(EBookClient) SE_GOBJECT_TYPE(EdsfPersonaStore); #include SE_BEGIN_CXX class EDSFView : public StreamingView { boost::weak_ptr m_self; ESourceRegistryCXX m_registry; std::string m_uuid; std::string m_query; EBookClientCXX m_ebook; EdsfPersonaStoreCXX m_store; Bool m_isQuiescent; EDSFView(const ESourceRegistryCXX ®istry, const std::string &uuid, const std::string &query); void init(const boost::shared_ptr &self); void opened(gboolean success, const GError *gerror) throw(); void read(gboolean success, GSList *contactlist, const GError *gerror) throw(); public: static boost::shared_ptr create(const ESourceRegistryCXX ®istry, const std::string &uuid, const std::string &query); virtual bool isQuiescent() const { return m_isQuiescent; } protected: virtual void doStart(); }; SE_END_CXX #endif // INCL_SYNCEVO_DBUS_SERVER_PIM_EDSF_VIEW syncevolution_1.4/src/dbus/server/pim/examples/000077500000000000000000000000001230021373600220135ustar00rootroot00000000000000syncevolution_1.4/src/dbus/server/pim/examples/search.py000077500000000000000000000345001230021373600236370ustar00rootroot00000000000000#! /usr/bin/python -u # -*- coding: utf-8 -*- # vim: set fileencoding=utf-8 :# # # Copyright (C) 2012 Intel Corporation # # 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) version 3. # # 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 Street, Fifth Floor, Boston, MA # 02110-1301 USA # Changes sorting, active address books and/or searches. # Run with no arguments to see the current state. # # Examples: # search.py --order=last/first # search.py --order=first/last # search.py --active-address-book '' --active-address-book 'peer-foobar' # search.py --search "[]" # search.py --search "[('any-contains', 'Joe')]" # search.py --search "[('phone', '+49891234')]" # # Use perf to analyze startup and reading all data, using a populated # system address book: # search.py -a 'peer-aa_bb_cc_dd_ee_ff' \ # -o first/last \ # -s '[]' \ # --verbosity 1 \ # --read-all \ # --start-operation 'set -x; sudo perf record -a -o %(operation)s.perf & sleep 2 && pidof perf' \ # --end-operation 'set -x; sudo killall -INT perf && sleep 2' # sudo perf report -i search.perf # # When searching, the script will print the results as they come in. # To continue waiting for changes until interrupted via CTRL-C, use # --monitor. import dbus import dbus.service import gobject from dbus.mainloop.glib import DBusGMainLoop import functools import subprocess import sys import time import traceback import itertools from optparse import OptionParser VERBOSITY_INFO = 0 VERBOSITY_NOTIFICATIONS = 1 VERBOSITY_DATA_SUMMARY = 2 VERBOSITY_DATA_FULL = 3 VERBOSITY_DEBUG = 4 parser = OptionParser() parser.add_option("-a", "--active-address-book", dest="address_books", action="append", default=[], help="Set one active address book, repeat to activate more than one. " "Default is to leave the current set unchanged.", metavar="ADDRESS-BOOK-ID") parser.add_option("-s", "--search", dest="search", default=None, help="Search expression in Python syntax. " "Default is to not search at all.", metavar="SEARCH-EXPRESSION") parser.add_option("-m", "--monitor", default=False, action="store_true", help="Keep running after receiving initial set of search results.") parser.add_option("-o", "--order", dest="order", default=None, help="Set new global sort order. Default is to use the existing one.", metavar="ORDER-NAME") parser.add_option("-r", "--read-all", default=False, action="store_true", help="Read entire view content each time the view is stable. " "If false, then modified or added contacts are requested right away.") parser.add_option("--start-operation", default=None, help="A shell command which gets invoked when the script enters a new phase. " "%(operation) gets replaced by a single word which describes that phase. " "See checkpoint() calls in the source for details. " "For benchmarking it is recommended to combine this with --read-all. " "Example value: set -x; sudo perf record -a -o %(operation)s.perf & sleep 2 && pidof perf") parser.add_option("--end-operation", default=None, help="A shell command which gets invoked when the script exits a phase. " "Example value: set -x; sudo killall -INT perf && sleep 2") parser.add_option("--verbosity", default=VERBOSITY_DATA_FULL, type="int", help="Determine what is printed. " "0 = only minimal progress messages. " "1 = also summary of view notifications. " "2 = also a one-line entry per contact. " "3 = also a dump of the contact. " "4 = also debug output for the script itself. " ) (options, args) = parser.parse_args() DBusGMainLoop(set_as_default=True) bus = dbus.SessionBus() loop = gobject.MainLoop() # The example does all calls to D-Bus with a very long timeout. A # real app should instead either never time out (because all of the # calls can, in theory, take arbitrarily long to complete) or be # prepared to deal with timeouts. timeout = 100000 # Contact PIM Manager. manager = dbus.Interface(bus.get_object('org._01.pim.contacts', '/org/01/pim/contacts'), 'org._01.pim.contacts.Manager') # Simplify the output of values returned via D-Bus by replacing # types like dbus.Dictionary with a normal Python dictionary # and by sorting lists (the order of all list entries never matters # in the contact dictionary). dbus_type_mapping = { dbus.Array: list, dbus.Boolean: bool, dbus.Byte: int, dbus.Dictionary: dict, dbus.Double: float, dbus.Int16: int, dbus.Int32: int, dbus.Int64: long, dbus.ObjectPath: str, dbus.Signature: str, dbus.String: unicode, dbus.Struct: tuple, dbus.UInt16: int, dbus.UInt32: int, dbus.UInt64: long, dbus.UTF8String: unicode } def strip_dbus(instance): base = dbus_type_mapping.get(type(instance), None) if base == dict or isinstance(instance, dict): return dict([(strip_dbus(k), strip_dbus(v)) for k, v in instance.iteritems()]) if base == list or isinstance(instance, list): l = [strip_dbus(v) for v in instance] l.sort() return l if base == tuple or isinstance(instance, tuple): return tuple([strip_dbus(v) for v in instance]) if base == None: return instance if base == unicode: # try conversion to normal string try: return str(instance) except UnicodeEncodeError: pass return base(instance) begin = time.time() starttime = begin running = None def checkpoint(operation): global starttime, running if running: now = time.time() print "+%04.3fs %s stopping '%s', duration %fs" % (now - begin, time.ctime(now), running, now - starttime) if options.end_operation: subprocess.check_call(options.end_operation % {'operation': running}, shell=True) running = None if operation: if options.end_operation: subprocess.check_call(options.start_operation % {'operation': operation}, shell=True) now = time.time() print "+%04.3fs %s starting '%s'" % (now - begin, time.ctime(now), operation) starttime = now running = operation def nothrow(fn): '''Function decorator which dumps exceptions to stdout. Use for callbacks.''' @functools.wraps(fn) def wrapper(*a, **b): try: fn(*a, **b) except: print traceback.format_exc() return wrapper class ContactsView(dbus.service.Object): '''Implements ViewAgent. Logs changes to stdout and maintains+shows the current content.''' def __init__(self): '''Create ViewAgent with the chosen path.''' # A real app would have to ensure that the path is unique for the # process. This example only has one ViewAgent and thus can use # a fixed path. self.path = '/org/syncevolution/search' # Currently known contact data, size matches view. self.contacts = [] dbus.service.Object.__init__(self, dbus.SessionBus(), self.path) def getIDs(self, start, count): '''Return just the IDs for a range of contacts in the current view.''' return [isinstance(x, dict) and x['id'] or x for x in self.contacts[start:start + count]] def search(self, filter): '''Start a search.''' print 'searching: %s' % filter self.viewPath = manager.Search(filter, self.path, timeout=100000) # This example uses the ViewControl to read contact data. # It does not close the view explicitly when # terminating. Instead it relies on the PIM Manager to # detect that the client disconnects from D-Bus. # Alternatively a client can also remove only its ViewAgent, # which will be noticed by the PIM Manager the next time it # tries to send a change. self.view = dbus.Interface(bus.get_object(manager.bus_name, self.viewPath), 'org._01.pim.contacts.ViewControl') def read(self, ids): '''Read contact data which was modified or added.''' self.view.ReadContacts(ids, timeout=100000, reply_handler=lambda x: self.ContactsRead(ids, x), error_handler=lambda x: self.ReadFailed(ids, x)) def dump(self, start, count): '''Show content of view. Highlight the contacts in the given range.''' if options.verbosity < VERBOSITY_DATA_SUMMARY: return for index, contact in enumerate(self.contacts): if start == index: # empty line with marker where range starts print '=> ' print '%s %03d %s' % \ (start != None and index >= start and index < start + count and '*' or ' ', index, isinstance(contact, dict) and contact.get('full-name', '<>') or '<>') if options.verbosity >= VERBOSITY_DATA_FULL: print ' ', strip_dbus(contact) print @nothrow def ContactsRead(self, ids, contacts): if options.verbosity >= VERBOSITY_DATA_FULL: print 'got contact data %s => %s ' % (ids, strip_dbus(contacts)) min = len(contacts) max = -1 for index, contact in contacts: if index >= 0: self.contacts[index] = contact if min > index: min = index if max < index: max = index if max > 0: self.dump(min, max - min + 1) if options.read_all and not options.monitor: loop.quit() @nothrow def ReadFailed(self, ids, error): print 'request for contact data %s failed: %s' % \ (ids, error) @nothrow @dbus.service.method(dbus_interface='org._01.pim.contacts.ViewAgent', in_signature='oias', out_signature='') def ContactsModified(self, view, start, ids): if options.verbosity >= VERBOSITY_NOTIFICATIONS: print 'contacts modified: %s, start %d, count %d, ids %s' % \ (view, start, len(ids), options.verbosity >= VERBOSITY_DATA_SUMMARY and strip_dbus(ids) or '<...>') self.contacts[start:start + len(ids)] = ids self.dump(start, len(ids)) if not options.read_all: self.read(ids) @nothrow @dbus.service.method(dbus_interface='org._01.pim.contacts.ViewAgent', in_signature='oias', out_signature='') def ContactsAdded(self, view, start, ids): if options.verbosity >= VERBOSITY_NOTIFICATIONS: print 'contacts added: %s, start %d, count %d, ids %s' % \ (view, start, len(ids), options.verbosity >= VERBOSITY_DATA_SUMMARY and strip_dbus(ids) or '<...>') self.contacts[start:start] = ids self.dump(start, len(ids)) if not options.read_all: self.read(ids) @nothrow @dbus.service.method(dbus_interface='org._01.pim.contacts.ViewAgent', in_signature='oii', out_signature='') def ContactsRemoved(self, view, start, count): if options.verbosity >= VERBOSITY_NOTIFICATIONS: print 'contacts removed: %s, start %d, count %d, ids %s' % \ (view, start, len(ids), options.verbosity >= VERBOSITY_DATA_SUMMARY and strip_dbus(ids) or '<...>') # Remove obsolete entries. del self.contacts[start:start + len(ids)] self.dump(start, 0) @nothrow @dbus.service.method(dbus_interface='org._01.pim.contacts.ViewAgent', in_signature='o', out_signature='') def Quiescent(self, view): if options.verbosity >= VERBOSITY_NOTIFICATIONS: print 'view is stable' if options.read_all: # Avoid reading in parallel, if quiescence signal repeats. if running != 'read': checkpoint('read') self.read(self.getIDs(0, len(self.contacts))) elif not options.monitor: loop.quit() checkpoint('getallpeers') peers = strip_dbus(manager.GetAllPeers()) print 'peers: %s' % peers print 'available databases: %s' % ([''] + ['peer-' + uid for uid in peers.keys()]) checkpoint('getactiveaddressbooks') address_books = strip_dbus(manager.GetActiveAddressBooks()) if options.address_books: print 'active address books %s -> %s' % (address_books, options.address_books) checkpoint('setactiveaddressbooks') manager.SetActiveAddressBooks(options.address_books) else: print 'active address books: %s' % options.address_books checkpoint('getsortorder') order = strip_dbus(manager.GetSortOrder()) if options.order: print 'active sort order %s -> %s' % (order, options.order) checkpoint('setsortorder') manager.SetSortOrder(options.order) else: print 'active sort order: %s' % order if options.search != None: checkpoint('search') view = ContactsView() view.search(eval(options.search)) loop.run() else: print 'no search expression given, quitting' checkpoint(None) syncevolution_1.4/src/dbus/server/pim/examples/sync.py000077500000000000000000000135621230021373600233530ustar00rootroot00000000000000#! /usr/bin/python -u # -*- coding: utf-8 -*- # vim: set fileencoding=utf-8 :# # # Copyright (C) 2012 Intel Corporation # # 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) version 3. # # 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 Street, Fifth Floor, Boston, MA # 02110-1301 USA # Configure, sync and/or remove a PBAP-capable phone. # The peer UID is set to the Bluetooth MAC address. # # Examples: # sync.py --bt-mac A0:4E:04:1E:AD:30 --configure # sync.py --bt-mac A0:4E:04:1E:AD:30 --sync # sync.py --bt-mac A0:4E:04:1E:AD:30 --remove # sync.py --bt-mac A0:4E:04:1E:AD:30 --configure --sync --remove import dbus import dbus.service import gobject from dbus.mainloop.glib import DBusGMainLoop import functools import sys import traceback import itertools from optparse import OptionParser parser = OptionParser() parser.add_option("-b", "--bt-mac", dest="mac", default=None, help="Set the Bluetooth MAC address and thus UID of the phone peer.", metavar="aa:bb:cc:dd:ee:ff") parser.add_option("-d", "--debug", action="store_true", default=False, help="Print debug output coming from SyncEvolution server.") parser.add_option("-c", "--configure", action="store_true", default=False, help="Enable configuring the peer.") parser.add_option("-s", "--sync", action="store_true", default=False, help="Cache data of peer.") parser.add_option("-r", "--remove", action="store_true", default=False, help="Remove peer configuration and data.") (options, args) = parser.parse_args() if options.configure or options.sync or options.remove: if not options.mac: sys.exit('--bt-mac parameter must be given') # Use MAC address as UID of peer, but with underscores instead of colons # and all in lower case. See https://bugs.freedesktop.org/show_bug.cgi?id=56436 peername = options.mac.replace(':', '').lower() DBusGMainLoop(set_as_default=True) bus = dbus.SessionBus() loop = gobject.MainLoop() # Contact PIM Manager. manager = dbus.Interface(bus.get_object('org._01.pim.contacts', '/org/01/pim/contacts'), 'org._01.pim.contacts.Manager') # Capture and print debug output. def log_output(path, level, output, component): print '%s %s: %s' % (level, (component or 'sync'), output) if options.debug: bus.add_signal_receiver(log_output, "LogOutput", "org.syncevolution.Server", "org.syncevolution", None) # Simplify the output of values returned via D-Bus by replacing # types like dbus.Dictionary with a normal Python dictionary # and by sorting lists (the order of all list entries never matters # in the contact dictionary). dbus_type_mapping = { dbus.Array: list, dbus.Boolean: bool, dbus.Byte: int, dbus.Dictionary: dict, dbus.Double: float, dbus.Int16: int, dbus.Int32: int, dbus.Int64: long, dbus.ObjectPath: str, dbus.Signature: str, dbus.String: unicode, dbus.Struct: tuple, dbus.UInt16: int, dbus.UInt32: int, dbus.UInt64: long, dbus.UTF8String: unicode } def strip_dbus(instance): base = dbus_type_mapping.get(type(instance), None) if base == dict or isinstance(instance, dict): return dict([(strip_dbus(k), strip_dbus(v)) for k, v in instance.iteritems()]) if base == list or isinstance(instance, list): l = [strip_dbus(v) for v in instance] l.sort() return l if base == tuple or isinstance(instance, tuple): return tuple([strip_dbus(v) for v in instance]) if base == None: return instance if base == unicode: # try conversion to normal string try: return str(instance) except UnicodeEncodeError: pass return base(instance) # Call all methods asynchronously, to avoid timeouts and # to capture debug output while the methods run. error = None result = None def failed(err): global error error = err loop.quit() def done(*args): global result loop.quit() if len(args) == 1: result = args[0] elif len(args) > 1: result = args def run(): global result result = None loop.run() if error: print print error print return result async_args = { 'reply_handler': done, 'error_handler': failed, 'timeout': 100000, # very large, infinite doesn't seem to be supported by Python D-Bus bindings } manager.GetAllPeers(**async_args) peers = strip_dbus(run()) print 'peers: %s' % peers print 'available databases: %s' % ([''] + ['peer-' + uid for uid in peers.keys()]) if not error and options.configure: peer = {'protocol': 'PBAP', 'address': options.mac} print 'adding peer config %s = %s' % (peername, peer) manager.SetPeer(peername, peer, **async_args) run() if not error and options.sync: print 'syncing peer %s' % peername manager.SyncPeer(peername, **async_args) run() if not error and options.remove: print 'removing peer %s' % peername manager.RemovePeer(peername, **async_args) run() if options.debug: print "waiting for further debug output, press CTRL-C to stop" loop.run() syncevolution_1.4/src/dbus/server/pim/filtered-view.cpp000066400000000000000000000367041230021373600234610ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "filtered-view.h" #include #include #include #include SE_BEGIN_CXX FilteredView::FilteredView(const boost::shared_ptr &parent, const boost::shared_ptr &filter) : m_parent(parent), m_filter(filter) { setName("filtered view"); } void FilteredView::init(const boost::shared_ptr &self) { m_self = self; m_parent->m_quiescenceSignal.connect(boost::bind(&FilteredView::parentQuiescent, m_self)); } void FilteredView::parentQuiescent() { // State of the parent is stable again. Check if we queued a "fill view" // operation and do it now, before forwarding the quiescent signal. // This gives us the chance to add a contact before a previous remove // signal is sent, which then enables the combination of two signals // into one. if (m_fillViewOnIdle) { fillViewCb(); m_fillViewOnIdle.deactivate(); } m_quiescenceSignal(); } boost::shared_ptr FilteredView::create(const boost::shared_ptr &parent, const boost::shared_ptr &filter) { boost::shared_ptr view(new FilteredView(parent, filter)); view->init(view); return view; } void FilteredView::doStart() { m_parent->start(); // Add initial content. Our processing of the new contact must not // cause changes to the parent view, otherwise the result will not // be inconsistent. for (int index = 0; !isFull() && index < m_parent->size(); index++) { addIndividual(index, *m_parent->getContact(index)); } // Start listening to signals. m_parent->m_addedSignal.connect(ChangeSignal_t::slot_type(boost::bind(&FilteredView::addIndividual, this, _1, _2)).track(m_self)); m_parent->m_modifiedSignal.connect(ChangeSignal_t::slot_type(boost::bind(&FilteredView::modifyIndividual, this, _1, _2)).track(m_self)); m_parent->m_removedSignal.connect(ChangeSignal_t::slot_type(boost::bind(&FilteredView::removeIndividual, this, _1, _2)).track(m_self)); } bool FilteredView::isFull(const Entries_t &local2parent, const boost::shared_ptr &filter) { size_t newEndIndex = local2parent.end() - local2parent.begin(); return !filter->isIncluded(newEndIndex); } void FilteredView::fillViewCb() { // Can we add back contacts which were excluded because of the // maximum number of results? SE_LOG_DEBUG(NULL, "filtered view %s: fill view on idle", getName()); int candidate = m_local2parent.empty() ? 0 : m_local2parent.back() + 1; while (!isFull() && candidate < m_parent->size()) { const IndividualData *data = m_parent->getContact(candidate); addIndividual(candidate, *data); candidate++; } } void FilteredView::fillView() { if (!m_fillViewOnIdle) { m_fillViewOnIdle.runOnce(-1, boost::bind(&FilteredView::fillViewCb, this)); } } void FilteredView::replaceFilter(const boost::shared_ptr &individualFilter, bool refine) { // Keep number of results the same, to avoid additional corner // cases. if (individualFilter->getMaxResults() != -1 && individualFilter->getMaxResults() != m_filter->getMaxResults()) { SE_THROW("refining the search must not change the maximum number of results"); } individualFilter->setMaxResults(m_filter->getMaxResults()); if (refine) { // Take advantage of the hint that the search is more strict: // we know that we can limit searching to the contacts which // already matched the previous search. bool removed = false; size_t index = 0; while (index < m_local2parent.size()) { const IndividualData *data = m_parent->getContact(m_local2parent[index]); if (individualFilter->matches(*data)) { // Still matched, just skip it. ++index; } else { // No longer matched, remove it. m_local2parent.erase(m_local2parent.begin() + index); m_removedSignal(index, *data); removed = true; } } m_filter = individualFilter; if (removed) { fillView(); } } else { // Brute-force approach. // // Here is an example of old and new mapping: // index into local2parent old value new value // 0 10 10 // 1 20 30 // 2 30 40 // 3 50 50 // 4 70 60 // 5 - 70 // 6 - 80 // // The LCS (see below) is: // (0, 0, 10) (2, 1, 30) (3, 3, 50) (4, 5, 70) // // The expected change signals for this transition are: // "removed", 1 // "added", 2 // "added", 4 // "added", 6 // // Note that this example does not include all corner cases. // Also relevant is adding or removing multiple entries at the // same index. // // One could also emit a "modified" signal for each index if // it is different, but then a single insertion or deletion // would led to invalidating the entire view. // // 1. build new result list. Entries_t local2parent; int candidate = 0; while (!isFull(local2parent, individualFilter) && candidate < m_parent->size()) { const IndividualData *data = m_parent->getContact(candidate); if (individualFilter->matches(*data)) { local2parent.push_back(candidate); } candidate++; } // 2. morph existing one into new one. // // Uses the SyncEvolution longest-common-subsequence // algorithm. Because all entries are different, there can be // only one solution and thus there is no need for a cost // function to find "better" solutions. std::vector< LCS::Entry > common; common.reserve(std::min(m_local2parent.size(), local2parent.size())); LCS::lcs(m_local2parent, local2parent, std::back_inserter(common), LCS::accessor_sequence()); // To emit the discovered changes as "added" and "removed" // signals, we need to look at identical entries. // The "delta" here always represents the value "new = b" - // "old = a" which needs to be added to the old index to get // the new one. It summarizes the change signals emitted so // far. For each common entry, we need to check if the delta // is different from what we have told the agent so far. int delta = 0; // The "shift" is the "old modified" - "old original" that // tells us how many entries were added (positive value) or // removed (negative value) via signals. int shift = 0; // Old and new index represent the indices of the previous // common element plus 1; in other words, the expected next // common element. size_t oldIndex = 0, newIndex = 0; BOOST_FOREACH (const LCS::Entry &entry, common) { int new_delta = (ssize_t)entry.index_b - (ssize_t)entry.index_a; if (delta != new_delta) { // When emitting "added" or "removed" signals, // be careful about getting the original index // in the old set right. It needs to be adjusted // to include the already sent changes. if (delta < new_delta) { size_t change = new_delta - delta; for (size_t i = 0; i < change; i++) { const IndividualData *data = m_parent->getContact(local2parent[newIndex + i]); // Keep adding at new indices, one new element // after the other. m_addedSignal(oldIndex - shift + i, *data); } shift -= change; } else if (delta > new_delta) { size_t change = delta - new_delta; shift += change; for (size_t i = 0; i < change; i++) { size_t index = oldIndex - shift; const IndividualData *data = m_parent->getContact(m_local2parent[index + i]); // Keep removing at the same index, because // that's how it'll look to the recipient. m_removedSignal(index, *data); } } new_delta = delta; } oldIndex = entry.index_a + 1; newIndex = entry.index_b + 1; } // Now deal with entries after the latest common entry, in // both arrays. for (size_t index = oldIndex; index < m_local2parent.size(); index++) { const IndividualData *data = m_parent->getContact(m_local2parent[index]); m_removedSignal(oldIndex - shift, *data); } for (size_t index = newIndex; index < local2parent.size(); index++) { const IndividualData *data = m_parent->getContact(local2parent[index]); m_addedSignal(index, *data); } // - swap std::swap(m_local2parent, local2parent); } // If the parent is currently busy, then we can delay sending the // signal until it is no longer busy. if (isQuiescent()) { parentQuiescent(); } } void FilteredView::addIndividual(int parentIndex, const IndividualData &data) { // We can use binary search to find the insertion point. // Check last entry first, because that is going to be // very common when adding via doStart(). Entries_t::iterator it; if (!m_local2parent.empty() && m_local2parent.back() < parentIndex) { it = m_local2parent.end(); } else { it = std::lower_bound(m_local2parent.begin(), m_local2parent.end(), parentIndex); } // Adding a contact in the parent changes values in our // mapping array, regardless whether the new contact also // gets and entry in it. Shift all following indices. for (Entries_t::iterator it2 = it; it2 != m_local2parent.end(); ++it2) { (*it2)++; } if (m_filter->matches(data)) { size_t index = it - m_local2parent.begin(); if (m_filter->isIncluded(index)) { // Remove first if necessary, to ensure that recipient // never has more entries in its view than requested. size_t newEndIndex = m_local2parent.end() - m_local2parent.begin(); if (newEndIndex > index && !m_filter->isIncluded(newEndIndex)) { const IndividualData *data = m_parent->getContact(m_local2parent.back()); SE_LOG_DEBUG(NULL, "%s: removed at #%ld/%ld to make room for new entry", getName(), (long)(newEndIndex - 1), (long)m_local2parent.size()); m_local2parent.pop_back(); m_removedSignal(newEndIndex - 1, *data); // Iterator might have pointed to removed entry, which may have // invalidated it. Get fresh iterator based on index. it = m_local2parent.begin() + index; } m_local2parent.insert(it, parentIndex); SE_LOG_DEBUG(NULL, "%s: added at #%ld/%ld", getName(), (long)index, (long)m_local2parent.size()); m_addedSignal(index, data); } else { SE_LOG_DEBUG(NULL, "%s: not added at #%ld/%ld because outside of result range", getName(), (long)index, (long)m_local2parent.size()); } } } void FilteredView::removeIndividual(int parentIndex, const IndividualData &data) { // The entries are sorted. Therefore we can use a binary search // to find the parentIndex or the first entry after it. Entries_t::iterator it = std::lower_bound(m_local2parent.begin(), m_local2parent.end(), parentIndex); // Removing a contact in the parent changes values in our mapping // array, regardless whether the removed contact is part of our // view. Shift all following indices, including the removed entry // if it is part of the view. bool found = it != m_local2parent.end() && *it == parentIndex; for (Entries_t::iterator it2 = it; it2 != m_local2parent.end(); ++it2) { (*it2)--; } if (found) { size_t index = it - m_local2parent.begin(); SE_LOG_DEBUG(NULL, "%s: removed at #%ld/%ld", getName(), (long)index, (long)m_local2parent.size()); m_local2parent.erase(it); m_removedSignal(index, data); // Try adding more contacts from the parent once the parent // is done with sending us changes - in other words, wait until // the process is idle. fillView(); } } void FilteredView::modifyIndividual(int parentIndex, const IndividualData &data) { Entries_t::iterator it = std::lower_bound(m_local2parent.begin(), m_local2parent.end(), parentIndex); bool matches = m_filter->matches(data); if (it != m_local2parent.end() && *it == parentIndex) { // Was matched before the change. size_t index = it - m_local2parent.begin(); if (matches) { // Still matched, merely pass on modification signal. SE_LOG_DEBUG(NULL, "%s: modified at #%ld/%ld", getName(), (long)index, (long)m_local2parent.size()); m_modifiedSignal(index, data); } else { // Removed. SE_LOG_DEBUG(NULL, "%s: removed at #%ld/%ld due to modification", getName(), (long)index, (long)m_local2parent.size()); m_local2parent.erase(it); m_removedSignal(index, data); fillView(); } } else if (matches) { // Was not matched before and is matched now => add it. size_t index = it - m_local2parent.begin(); if (m_filter->isIncluded(index)) { m_local2parent.insert(it, parentIndex); SE_LOG_DEBUG(NULL, "%s: added at #%ld/%ld due to modification", getName(), (long)index, (long)m_local2parent.size()); m_addedSignal(index, data); } } else { // Neither matched before nor now => nothing changed. } } SE_END_CXX syncevolution_1.4/src/dbus/server/pim/filtered-view.h000066400000000000000000000072411230021373600231200ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_SYNCEVO_DBUS_SERVER_PIM_FILTERED_VIEW #define INCL_SYNCEVO_DBUS_SERVER_PIM_FILTERED_VIEW #include "view.h" #include SE_BEGIN_CXX /** * A subset of some other view. Takes input from that view and thus * can rely on individuals being sorted by their index number in the * other view. */ class FilteredView : public IndividualView { boost::weak_ptr m_self; boost::shared_ptr m_parent; boost::shared_ptr m_filter; /** * Maps local indices to indices in parent view. Could be be * optimized to map entire ranges, but for the sake of simplicitly * let's use a 1:1 mapping for now. */ typedef std::vector Entries_t; Entries_t m_local2parent; FilteredView(const boost::shared_ptr &parent, const boost::shared_ptr &filter); void init(const boost::shared_ptr &self); bool isFull() const { return isFull(m_local2parent, m_filter); } static bool isFull(const Entries_t &local2parent, const boost::shared_ptr &filter); /** * Request filling up the filtered view once things are stable again. */ void fillView(); /** * internal helper for fillView(), do not call directly */ void fillViewCb(); Timeout m_fillViewOnIdle; void parentQuiescent(); public: /** * Creates an idle IndividualAggregator. Configure it and * subscribe to signals, then call start(). */ static boost::shared_ptr create(const boost::shared_ptr &parent, const boost::shared_ptr &filter); /** * Mirrors the quiesent state of the underlying view. */ virtual bool isQuiescent() const { return m_parent->isQuiescent(); } /** * Add a FolksIndividual if it matches the filter. Tracking of * changes to individuals is done in parent view. */ void addIndividual(int parentIndex, const IndividualData &data); /** * Removes a FolksIndividual. Might not have been added at all. */ void removeIndividual(int parentIndex, const IndividualData &data); /** * Check whether a changed individual still belongs into the view. */ void modifyIndividual(int parentIndex, const IndividualData &data); // from IndividualView virtual void doStart(); virtual void replaceFilter(const boost::shared_ptr &individualFilter, bool refine); virtual int size() const { return (int)m_local2parent.size(); } virtual const IndividualData *getContact(int index) { return (index >= 0 && (unsigned)index < m_local2parent.size()) ? m_parent->getContact(m_local2parent[index]) : NULL; } }; SE_END_CXX #endif // INCL_SYNCEVO_DBUS_SERVER_PIM_FILTERED_VIEW syncevolution_1.4/src/dbus/server/pim/folks.cpp000066400000000000000000000655011230021373600220260ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include "folks.h" #include "full-view.h" #include "filtered-view.h" #include "individual-traits.h" #include "persona-details.h" #include #include "test.h" #include #include #include #include SE_BEGIN_CXX /** * Generic error callback. There really isn't much that can be done if * libfolks fails, except for logging the problem. */ static void logResult(const GError *gerror, const char *operation) { if (gerror) { SE_LOG_ERROR(NULL, "%s: %s", operation, gerror->message); } else { SE_LOG_DEBUG(NULL, "%s: done", operation); } } class CompareFormattedName : public IndividualCompare { bool m_reversed; bool m_firstLast; public: CompareFormattedName(bool reversed = false, bool firstLast = false) : m_reversed(reversed), m_firstLast(firstLast) { } virtual void createCriteria(FolksIndividual *individual, Criteria_t &criteria) const { FolksStructuredName *fn = folks_name_details_get_structured_name(FOLKS_NAME_DETAILS(individual)); if (fn) { const char *family = folks_structured_name_get_family_name(fn); const char *given = folks_structured_name_get_given_name(fn); SE_LOG_DEBUG(NULL, "criteria: formatted name: %s, %s", family, given); if (m_firstLast) { criteria.push_back(given ? given : ""); criteria.push_back(family ? family : ""); } else { criteria.push_back(family ? family : ""); criteria.push_back(given ? given : ""); } } else { SE_LOG_DEBUG(NULL, "criteria: no formatted"); } } virtual bool compare(const Criteria_t &a, const Criteria_t &b) const { return m_reversed ? IndividualCompare::compare(b, a) : IndividualCompare::compare(a, b); } }; boost::shared_ptr IndividualCompare::defaultCompare() { boost::shared_ptr compare(new CompareFormattedName); return compare; } bool IndividualData::init(const IndividualCompare *compare, const LocaleFactory *locale, FolksIndividual *individual) { bool precomputedModified = false; m_individual = FolksIndividualCXX(individual, ADD_REF); if (compare) { m_criteria.clear(); compare->createCriteria(individual, m_criteria); } if (locale) { precomputedModified = locale->precompute(individual, m_precomputed); } return precomputedModified; } bool IndividualCompare::compare(const Criteria_t &a, const Criteria_t &b) const { Criteria_t::const_iterator ita = a.begin(), itb = b.begin(); while (itb != b.end()) { if (ita == a.end()) { // a is shorter return true; } int cmp = ita->compare(*itb); if (cmp < 0) { // String comparison shows that a is less than b. return true; } else if (cmp > 0) { // Is greater, so definitely not less => don't compare // rest of the criteria. return false; } else { // Equal, continue comparing. ++ita; ++itb; } } // a is not less b return false; } IndividualAggregator::IndividualAggregator(const boost::shared_ptr &locale) : m_locale(locale), m_databases(gee_hash_set_new(G_TYPE_STRING, (GBoxedCopyFunc) g_strdup, g_free, NULL, NULL, NULL, NULL, NULL, NULL), TRANSFER_REF) { } void IndividualAggregator::init(boost::shared_ptr &self) { m_self = self; m_backendStore = FolksBackendStoreCXX::steal(folks_backend_store_dup()); // Ignore some known harmless messages from folks. LogRedirect::addIgnoreError("Error preparing Backend 'ofono'"); LogRedirect::addIgnoreError("Error preparing Backend 'telepathy'"); SYNCEVO_GLIB_CALL_ASYNC(folks_backend_store_prepare, boost::bind(&IndividualAggregator::storePrepared, m_self), m_backendStore); m_folks = FolksIndividualAggregatorCXX::steal(folks_individual_aggregator_new_with_backend_store(m_backendStore)); } boost::shared_ptr IndividualAggregator::create(const boost::shared_ptr &locale) { boost::shared_ptr aggregator(new IndividualAggregator(locale)); aggregator->init(aggregator); return aggregator; } std::string IndividualAggregator::dumpDatabases() { std::string res; BOOST_FOREACH (const gchar *tmp, GeeStringCollection(GEE_COLLECTION(m_databases.get()), ADD_REF)) { if (!res.empty()) { res += ", "; } res += tmp; } return res; } void IndividualAggregator::storePrepared() { SE_LOG_DEBUG(NULL, "backend store is prepared"); // Have to hard-code the list of known backends that we don't want. SYNCEVO_GLIB_CALL_ASYNC(folks_backend_store_disable_backend, boost::bind(logResult, (const GError *)NULL, "folks_backend_store_disable_backend"), m_backendStore, "telepathy"); SYNCEVO_GLIB_CALL_ASYNC(folks_backend_store_disable_backend, boost::bind(logResult, (const GError *)NULL, "folks_backend_store_disable_backend"), m_backendStore, "tracker"); SYNCEVO_GLIB_CALL_ASYNC(folks_backend_store_disable_backend, boost::bind(logResult, (const GError *)NULL, "folks_backend_store_disable_backend"), m_backendStore, "key-file"); SYNCEVO_GLIB_CALL_ASYNC(folks_backend_store_disable_backend, boost::bind(logResult, (const GError *)NULL, "folks_backend_store_disable_backend"), m_backendStore, "libsocialweb"); // Explicitly enable EDS, just to be sure. SYNCEVO_GLIB_CALL_ASYNC(folks_backend_store_enable_backend, boost::bind(logResult, (const GError *)NULL, "folks_backend_store_enable_backend"), m_backendStore, "eds"); // Start loading backends right away. Assumes that the // asynchronous operations above will be done first. SYNCEVO_GLIB_CALL_ASYNC(folks_backend_store_load_backends, boost::bind(&IndividualAggregator::backendsLoaded, m_self), m_backendStore); } void IndividualAggregator::backendsLoaded() { SE_LOG_DEBUG(NULL, "backend store has loaded backends"); GeeCollectionCXX coll(folks_backend_store_list_backends(m_backendStore), TRANSFER_REF); BOOST_FOREACH (FolksBackend *backend, GeeCollCXX(coll)) { SE_LOG_DEBUG(NULL, "folks backend: %s", folks_backend_get_name(backend)); } m_eds = FolksBackendCXX::steal(folks_backend_store_dup_backend_by_name(m_backendStore, "eds")); if (m_eds) { // Remember system store, for writing contacts. GeeMap *stores = folks_backend_get_persona_stores(m_eds); FolksPersonaStore *systemStore = static_cast(gee_map_get(stores, "system-address-book")); m_systemStore = FolksPersonaStoreCXX(systemStore, TRANSFER_REF); // Tell the backend which databases we want. SE_LOG_DEBUG(NULL, "backends loaded: setting EDS persona stores: [%s]", dumpDatabases().c_str()); folks_backend_set_persona_stores(m_eds, GEE_SET(m_databases.get())); if (m_view) { // We were started, prepare aggregator. SYNCEVO_GLIB_CALL_ASYNC(folks_individual_aggregator_prepare, boost::bind(logResult, _1, "folks_individual_aggregator_prepare"), getFolks()); } // Execute delayed work. m_backendsLoadedSignal(); } else { SE_LOG_ERROR(NULL, "EDS backend not active?!"); } } void IndividualAggregator::setDatabases(std::set &databases) { gee_collection_clear(GEE_COLLECTION(m_databases.get())); BOOST_FOREACH (const std::string &database, databases) { gee_collection_add(GEE_COLLECTION(m_databases.get()), database.c_str()); } if (m_eds) { // Backend is loaded, tell it about the change. SE_LOG_DEBUG(NULL, "backends already loaded: setting EDS persona stores directly: [%s]", dumpDatabases().c_str()); folks_backend_set_persona_stores(m_eds, GEE_SET(m_databases.get())); } else { SE_LOG_DEBUG(NULL, "backends not loaded yet: setting EDS persona stores delayed: [%s]", dumpDatabases().c_str()); } } void IndividualAggregator::setCompare(const boost::shared_ptr &compare) { // Don't start main view. Instead rememeber the compare instance // for start(). m_compare = compare; if (m_view) { m_view->setCompare(compare); } } void IndividualAggregator::setLocale(const boost::shared_ptr &locale) { m_locale = locale; if (m_view) { m_view->setLocale(m_locale); } } void IndividualAggregator::start() { if (!m_view) { m_view = FullView::create(m_folks, m_locale); if (m_compare) { m_view->setCompare(m_compare); } if (m_eds) { // Backend was loaded and configured, we can prepare the aggregator. SYNCEVO_GLIB_CALL_ASYNC(folks_individual_aggregator_prepare, boost::bind(logResult, _1, "folks_individual_aggregator_prepare"), getFolks()); } } } bool IndividualAggregator::isRunning() const { return m_view; } boost::shared_ptr IndividualAggregator::getMainView() { if (!m_view) { start(); } return m_view; } void IndividualAggregator::addContact(const Result &result, const PersonaDetails &details) { // Called directly by D-Bus client. Need fully functional system address book. runWithAddressBook(boost::bind(&IndividualAggregator::doAddContact, this, result, details), result.getOnError()); } void IndividualAggregator::doAddContact(const Result &result, const PersonaDetails &details) { SYNCEVO_GLIB_CALL_ASYNC(folks_persona_store_add_persona_from_details, boost::bind(&IndividualAggregator::addContactDone, this, _2, _1, result), m_systemStore, details.get()); } void IndividualAggregator::addContactDone(const GError *gerror, FolksPersona *persona, const Result &result) throw() { try { // Handle result of folks_persona_store_add_persona_from_details(). if (!persona || gerror) { GErrorCXX::throwError("add contact", gerror); } const gchar *uid = folks_persona_get_uid(persona); if (uid) { gchar *backend, *storeID, *personaID; folks_persona_split_uid(uid, &backend, &storeID, &personaID); PlainGStr tmp1(backend), tmp2(storeID), tmp3(personaID); result.done(personaID); } else { SE_THROW("new persona has empty UID"); } } catch (...) { result.failed(); } } void IndividualAggregator::modifyContact(const Result &result, const std::string &localID, const PersonaDetails &details) { runWithPersona(boost::bind(&IndividualAggregator::doModifyContact, this, result, _1, details), localID, result.getOnError()); } void IndividualAggregator::doModifyContact(const Result &result, FolksPersona *persona, const PersonaDetails &details) throw() { try { // Asynchronously modify the persona. This will be turned into // EDS updates by folks. Details2Persona(result, details, persona); } catch (...) { result.failed(); } } void IndividualAggregator::removeContact(const Result &result, const std::string &localID) { runWithPersona(boost::bind(&IndividualAggregator::doRemoveContact, this, result, _1), localID, result.getOnError()); } void IndividualAggregator::doRemoveContact(const Result &result, FolksPersona *persona) throw() { try { SYNCEVO_GLIB_CALL_ASYNC(folks_persona_store_remove_persona, boost::bind(&IndividualAggregator::removeContactDone, this, _1, result), m_systemStore, persona); } catch (...) { result.failed(); } } void IndividualAggregator::removeContactDone(const GError *gerror, const Result &result) throw() { try { if (gerror) { GErrorCXX::throwError("remove contact", gerror); } result.done(); } catch (...) { result.failed(); } } void IndividualAggregator::runWithAddressBook(const boost::function &operation, const ErrorCb_t &onError) throw() { try { if (m_eds) { runWithAddressBookHaveEDS(boost::signals2::connection(), operation, onError); } else { // Do it later. m_backendsLoadedSignal.connect_extended(boost::bind(&IndividualAggregator::runWithAddressBookHaveEDS, this, _1, operation, onError)); } } catch (...) { onError(); } } void IndividualAggregator::runWithAddressBookHaveEDS(const boost::signals2::connection &conn, const boost::function &operation, const ErrorCb_t &onError) throw() { try { // Called after we obtained EDS backend. Need system store // which is prepared. m_backendsLoadedSignal.disconnect(conn); if (!m_systemStore) { SE_THROW("system address book not found"); } if (folks_persona_store_get_is_prepared(m_systemStore)) { runWithAddressBookPrepared(NULL, operation, onError); } else { SYNCEVO_GLIB_CALL_ASYNC(folks_persona_store_prepare, boost::bind(&IndividualAggregator::runWithAddressBookPrepared, this, _1, operation, onError), m_systemStore); } } catch (...) { onError(); } } void IndividualAggregator::runWithAddressBookPrepared(const GError *gerror, const boost::function &operation, const ErrorCb_t &onError) throw() { try { // Called after EDS system store is prepared, successfully or unsuccessfully. if (gerror) { GErrorCXX::throwError("prepare EDS system address book", gerror); } operation(); } catch (...) { onError(); } } void IndividualAggregator::runWithPersona(const boost::function &operation, const std::string &localID, const ErrorCb_t &onError) throw() { try { runWithAddressBook(boost::bind(&IndividualAggregator::doRunWithPersona, this, operation, localID, onError), onError); } catch (...) { onError(); } } void IndividualAggregator::doRunWithPersona(const boost::function &operation, const std::string &localID, const ErrorCb_t &onError) throw() { try { typedef GeeCollCXX< GeeMapEntryWrapper > Coll; Coll personas(folks_persona_store_get_personas(m_systemStore), ADD_REF); BOOST_FOREACH (const Coll::value_type &entry, personas) { // key seems to be : const gchar *key = entry.key(); const gchar *colon = strchr(key, ':'); if (colon && localID == colon + 1) { operation(entry.value()); return; } } SE_THROW(StringPrintf("contact with local ID '%s' not found in system address book", localID.c_str())); } catch (...) { onError(); } } #ifdef ENABLE_UNIT_TESTS class FolksTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(FolksTest); CPPUNIT_TEST(open); CPPUNIT_TEST(asyncError); CPPUNIT_TEST(gvalue); CPPUNIT_TEST_SUITE_END(); private: static void asyncCB(const GError *gerror, const char *func, bool &failed, bool &done) { done = true; if (gerror) { failed = true; SE_LOG_ERROR(NULL, "%s: %s", func, gerror->message); } } void open() { FolksIndividualAggregatorCXX aggregator(folks_individual_aggregator_new(), TRANSFER_REF); bool done = false, failed = false; SYNCEVO_GLIB_CALL_ASYNC(folks_individual_aggregator_prepare, boost::bind(asyncCB, _1, "folks_individual_aggregator_prepare", boost::ref(failed), boost::ref(done)), aggregator); while (!done) { g_main_context_iteration(NULL, true); } CPPUNIT_ASSERT(!failed); while (!folks_individual_aggregator_get_is_quiescent(aggregator)) { g_main_context_iteration(NULL, true); } GeeMap *individuals = folks_individual_aggregator_get_individuals(aggregator); typedef GeeCollCXX< GeeMapEntryWrapper > Coll; Coll coll(individuals, ADD_REF); SE_LOG_DEBUG(NULL, "%d individuals", gee_map_get_size(individuals)); GeeMapIteratorCXX it(gee_map_map_iterator(individuals), TRANSFER_REF); while (gee_map_iterator_next(it)) { PlainGStr id(reinterpret_cast(gee_map_iterator_get_key(it))); FolksIndividualCXX individual(reinterpret_cast(gee_map_iterator_get_value(it)), TRANSFER_REF); GValueStringCXX fullname; g_object_get_property(G_OBJECT(individual.get()), "full-name", &fullname); SE_LOG_DEBUG(NULL, "map: id %s name %s = %s", id.get(), fullname.toString().c_str(), fullname.get()); } GeeIteratorCXX it2(gee_iterable_iterator(GEE_ITERABLE(individuals)), TRANSFER_REF); while (gee_iterator_next(it2)) { GeeMapEntryCXX entry(reinterpret_cast(gee_iterator_get(it2)), TRANSFER_REF); gchar *id(reinterpret_cast(const_cast(gee_map_entry_get_key(entry)))); FolksIndividual *individual(reinterpret_cast(const_cast(gee_map_entry_get_value(entry)))); GValueStringCXX fullname; g_object_get_property(G_OBJECT(individual), "full-name", &fullname); SE_LOG_DEBUG(NULL, "iterable: id %s name %s = %s", id, fullname.toString().c_str(), fullname.get()); } Coll::const_iterator curr = coll.begin(); Coll::const_iterator end = coll.end(); if (curr != end) { const gchar *id = (*curr).key(); FolksIndividual *individual((*curr).value()); GValueStringCXX fullname; g_object_get_property(G_OBJECT(individual), "full-name", &fullname); SE_LOG_DEBUG(NULL, "first: id %s name %s = %s", id, fullname.toString().c_str(), fullname.get()); ++curr; } BOOST_FOREACH (Coll::value_type &entry, coll) { const gchar *id = entry.key(); FolksIndividual *individual(entry.value()); // GValueStringCXX fullname; // g_object_get_property(G_OBJECT(individual), "full-name", &fullname); const gchar *fullname = folks_name_details_get_full_name(FOLKS_NAME_DETAILS(individual)); SE_LOG_DEBUG(NULL, "boost: id %s %s name %s", id, fullname ? "has" : "has no", fullname); typedef GeeCollCXX EmailColl; EmailColl emails(folks_email_details_get_email_addresses(FOLKS_EMAIL_DETAILS(individual)), ADD_REF); SE_LOG_DEBUG(NULL, " %d emails", gee_collection_get_size(GEE_COLLECTION(emails.get()))); BOOST_FOREACH (FolksEmailFieldDetails *email, emails) { SE_LOG_DEBUG(NULL, " %s", reinterpret_cast(folks_abstract_field_details_get_value(FOLKS_ABSTRACT_FIELD_DETAILS(email)))); } } aggregator.reset(); } void gvalue() { GValueBooleanCXX b(true); SE_LOG_DEBUG(NULL, "GValueBooleanCXX(true) = %s", b.toString().c_str()); GValueBooleanCXX b2(b); CPPUNIT_ASSERT_EQUAL(b.get(), b2.get()); b2.set(false); CPPUNIT_ASSERT_EQUAL(b.get(), (gboolean)!b2.get()); b2 = b; CPPUNIT_ASSERT_EQUAL(b.get(), b2.get()); GValueStringCXX str("foo bar"); SE_LOG_DEBUG(NULL, "GValueStringCXX(\"foo bar\") = %s", str.toString().c_str()); CPPUNIT_ASSERT(!strcmp(str.get(), "foo bar")); GValueStringCXX str2(str); CPPUNIT_ASSERT(!strcmp(str.get(), str2.get())); CPPUNIT_ASSERT(str.get() != str2.get()); str2.set("foo"); CPPUNIT_ASSERT(strcmp(str.get(), str2.get())); CPPUNIT_ASSERT(str.get() != str2.get()); str2 = str; CPPUNIT_ASSERT(!strcmp(str.get(), str2.get())); CPPUNIT_ASSERT(str.get() != str2.get()); str2.take(g_strdup("bar")); CPPUNIT_ASSERT(strcmp(str.get(), str2.get())); CPPUNIT_ASSERT(str.get() != str2.get()); const char *fixed = "fixed"; str2.setStatic(fixed); CPPUNIT_ASSERT(!strcmp(str2.get(), fixed)); CPPUNIT_ASSERT(str2.get() == fixed); } void asyncError() { bool done = false, failed = false; SYNCEVO_GLIB_CALL_ASYNC(folks_individual_aggregator_remove_individual, boost::bind(asyncCB, _1, "folks_individual_aggregator_remove_individual", boost::ref(failed), boost::ref(done)), NULL, NULL); while (!done) { g_main_context_iteration(NULL, true); } // Invalid parameters are not reported! CPPUNIT_ASSERT(!failed); // using simpler macro GErrorCXX gerror; SYNCEVO_GLIB_CALL_SYNC(NULL, gerror, folks_individual_aggregator_remove_individual, NULL, NULL); // Invalid parameters are not reported! CPPUNIT_ASSERT(!gerror); } static void individualSignal(std::ostringstream &out, const char *action, int index, const IndividualData &data) { out << action << ": " << index << " " << folks_name_details_get_full_name(FOLKS_NAME_DETAILS(data.m_individual.get())) << std::endl; } static void monitorView(IndividualView &view, std::ostringstream &out) { view.m_addedSignal.connect(boost::bind(individualSignal, boost::ref(out), "added", _1, _2)); view.m_removedSignal.connect(boost::bind(individualSignal, boost::ref(out), "removed", _1, _2)); view.m_modifiedSignal.connect(boost::bind(individualSignal, boost::ref(out), "modified", _1, _2)); } }; SYNCEVOLUTION_TEST_SUITE_REGISTRATION(FolksTest); #endif SE_END_CXX syncevolution_1.4/src/dbus/server/pim/folks.h000066400000000000000000000301671230021373600214730ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ /** * The classes in this file implement sorting, searching and * configuring of the unified address book based on libfolks. * * This is pure C++ code. The D-Bus IPC binding for it is * implemented separately in pim-manager.h/cpp. */ #ifndef INCL_SYNCEVO_DBUS_SERVER_IVI_FOLKS #define INCL_SYNCEVO_DBUS_SERVER_IVI_FOLKS #include #include "locale-factory.h" #include "../dbus-callbacks.h" #include "../timeout.h" #include #include #include #include #include #include SE_GOBJECT_TYPE(FolksIndividualAggregator) SE_GOBJECT_TYPE(FolksIndividual) SE_GOBJECT_TYPE(FolksEmailFieldDetails) SE_GOBJECT_TYPE(FolksBackendStore) SE_GOBJECT_TYPE(FolksBackend) SE_GOBJECT_TYPE(FolksPersonaStore) SE_GOBJECT_TYPE(FolksAbstractFieldDetails) SE_GOBJECT_TYPE(FolksRoleFieldDetails) SE_GOBJECT_TYPE(FolksRole) SE_GOBJECT_TYPE(FolksPostalAddress) SE_GOBJECT_TYPE(FolksNoteFieldDetails) SE_GOBJECT_TYPE(FolksPostalAddressFieldDetails) SE_GOBJECT_TYPE(FolksPersona) SE_GOBJECT_TYPE(FolksLocation) SE_GOBJECT_TYPE(GeeHashSet) SE_GLIB_TYPE(GHashTable, g_hash_table) #include SE_BEGIN_CXX class PersonaDetails; /** * Abstract interface for comparing two FolksIndividual instances. * The properties of a folks individual may change at any time. * Therefore the key properties which determine the sort order * must be copied from the individual. They will be updated when * the individual changes. * * The other advantage is that complex, derived keys only need * to be computed once instead of each time during a comparison. * For example, boost::locale::collator::transform() can be used * to generate the keys. */ class IndividualCompare { public: static boost::shared_ptr defaultCompare(); virtual ~IndividualCompare() {} typedef std::vector Criteria_t; /** * Calculate an ordered set of criteria for comparing * individuals. The default comparison will start with the initial * criteria and move on until a difference is found. * * This is necessary because a single string of "Doe, John" is * treated differently in a collation than the pair of strings * "Doe" and "John". * * @param individual the individual for which sort keys are to be created * @retval criteria cleared before the call, filled afterwards */ virtual void createCriteria(FolksIndividual *individual, Criteria_t &criteria) const = 0; /** * Partial sort order: true if a smaller than b. * * Default implementation uses normal std::string::compare(). */ virtual bool compare(const Criteria_t &a, const Criteria_t &b) const; }; /** * A FolksIndividual plus its sort criteria and search cache. */ struct IndividualData { /** * Sets all members to match the given individual, using the * compare instance to compute values. Both compare and locale may * be NULL. * * Returns true if the precomputed values changed. */ bool init(const IndividualCompare *compare, const LocaleFactory *locale, FolksIndividual *individual); FolksIndividualCXX m_individual; IndividualCompare::Criteria_t m_criteria; LocaleFactory::Precomputed m_precomputed; }; /** * wraps an IndividualCompare for std algorithms on IndividualData */ class IndividualDataCompare : public std::binary_function { boost::shared_ptr m_compare; public: IndividualDataCompare(const boost::shared_ptr &compare) : m_compare(compare) {} IndividualDataCompare(const IndividualDataCompare &other) : m_compare(other.m_compare) {} bool operator () (const IndividualData &a, const IndividualData &b) const { return m_compare->compare(a.m_criteria, b.m_criteria); } }; /** * Abstract interface for filtering (aka searching) FolksIndividual * instances. */ class IndividualFilter { int m_maxResults; public: IndividualFilter() : m_maxResults(-1) {} virtual ~IndividualFilter() {} /** Maximum number of results. -1 for unlimited. */ int getMaxResults() const { return m_maxResults; } void setMaxResults(int maxResults) { m_maxResults = maxResults; } /** * True if within the number of expected results. */ bool isIncluded(size_t index) const { return m_maxResults == -1 || index < (size_t)m_maxResults; } /** * The corresponding EBook query string * (http://developer.gnome.org/libebook/stable/libebook-e-book-query.html#e-book-query-to-string) * for the filter, if there is one. Empty if not. */ virtual std::string getEBookFilter() const { return ""; } /** true if the contact matches the filter */ virtual bool matches(const IndividualData &data) const = 0; }; /** * A filter which just enforces a maximum number of results, * something that FullView cannot do. */ class MatchAll : public IndividualFilter { public: virtual bool matches(const IndividualData &data) const { return true; } }; /** * A fake filter which just carries the maximum result parameter. * Separate type because the dynamic_cast<> can be used to detect * this special case. */ class ParamFilter : public MatchAll { }; class FullView; /** * Is a normal FolksIndividualAggregator and adds sorting, searching * and browsing to it. At least the full sorted view always exists. */ class IndividualAggregator { /** empty when not started yet */ boost::shared_ptr m_view; boost::shared_ptr m_compare; boost::shared_ptr m_locale; boost::weak_ptr m_self; FolksIndividualAggregatorCXX m_folks; FolksBackendStoreCXX m_backendStore; /** * NULL when backends haven't been loaded yet. * Set by backendsLoaded(). */ FolksBackendCXX m_eds; /** * Set by backendsLoaded(), if possible. If m_eds != NULL * and m_systemStore == NULL, no system address book is * available. If m_eds == NULL, hook into m_backendsLoadedSignal * to be notified. */ FolksPersonaStoreCXX m_systemStore; boost::signals2::signal m_backendsLoadedSignal; /** * The set of enabled EDS databases, referenced by the UUID. * Empty by default. */ GeeHashSetCXX m_databases; /** string representation for debugging */ std::string dumpDatabases(); IndividualAggregator(const boost::shared_ptr &locale); void init(boost::shared_ptr &self); /** * Called when backend store is prepared. At that point, backends * can be disabled or enabled and loading them can be kicked of. */ void storePrepared(); /** * Called when all Folks backends are loaded, before the * aggregator does its work. Now is the right time to initialize * the set of databases and prepare the aggregator, if start() was * already called. */ void backendsLoaded(); /** * Executes the given operation when the EDS system address book * is prepared. The operation may throw an exception, which (like * all other errors) is reported as failure for the asynchronous * operation. */ void runWithAddressBook(const boost::function &operation, const ErrorCb_t &onError) throw(); void runWithAddressBookHaveEDS(const boost::signals2::connection &conn, const boost::function &operation, const ErrorCb_t &onError) throw(); void runWithAddressBookPrepared(const GError *gerror, const boost::function &operation, const ErrorCb_t &onError) throw(); /** * Executes the given operation after looking up the FolksPersona * in the system address book, which must be prepared and loaded * at that point. */ void runWithPersona(const boost::function &operation, const std::string &localID, const ErrorCb_t &onError) throw(); void doRunWithPersona(const boost::function &operation, const std::string &localID, const ErrorCb_t &onError) throw(); /** the operation for runWithAddressBook() */ void doAddContact(const Result &result, const PersonaDetails &details); /** handle result of adding contact */ void addContactDone(const GError *gerror, FolksPersona *persona, const Result &result) throw(); void doModifyContact(const Result &result, FolksPersona *persona, const PersonaDetails &details) throw(); void doRemoveContact(const Result &result, FolksPersona *persona) throw(); void removeContactDone(const GError *gerror, const Result &result) throw(); public: /** * Creates an idle IndividualAggregator. Configure it and * subscribe to signals, then call start(). */ static boost::shared_ptr create(const boost::shared_ptr &locale); /** * Access to FolksIndividualAggregator which is owned by * the aggregator. */ FolksIndividualAggregator *getFolks() const { return m_folks.get(); } /** * Set sorting without starting the view just yet. */ void setCompare(const boost::shared_ptr &compare); /** * Change current locale. Must be followed by setCompare() to update * any pre-computed data. */ void setLocale(const boost::shared_ptr &locale); /** * Starts pulling and sorting of contacts. * Creates m_view and starts populating it. * Can be called multiple times. * * See also org.01.pim.contacts.Manager.Start(). */ void start(); /** * start() was called, or something caused it to be called. */ bool isRunning() const; /** * configure active databases * * @param set of EDS database UUIDs, empty string for the default * system address book */ void setDatabases(std::set &databases); /** * Each aggregator has exactly one full view on the data. This * method grants access to it and its change signals. * * @return never empty, start() will be called if necessary */ boost::shared_ptr getMainView(); /** * Add contact to system address book. Returns new local ID * as result. */ void addContact(const Result &result, const PersonaDetails &details); /** * Modify contact in system address book. */ void modifyContact(const Result &result, const std::string &localID, const PersonaDetails &details); /** * Remove contact in system address book. */ void removeContact(const Result &result, const std::string &localID); }; SE_END_CXX #endif // INCL_SYNCEVO_DBUS_SERVER_IVI_FOLKS syncevolution_1.4/src/dbus/server/pim/full-view.cpp000066400000000000000000000333731230021373600226240ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "full-view.h" #include #include #include SE_BEGIN_CXX FullView::FullView(const FolksIndividualAggregatorCXX &folks, const boost::shared_ptr &locale) : m_folks(folks), m_locale(locale), m_localeChanged(false), // set only after explicit setLocale() m_isQuiescent(false), // Ensure that there is a sort criteria. m_compare(IndividualCompare::defaultCompare()) { setName("full view"); } void FullView::init(const boost::shared_ptr &self) { m_self = self; } void FullView::doStart() { // Populate view from current set of data. Usually FullView // gets instantiated when the aggregator is idle, in which // case there won't be any contacts yet. // // Optimize the initial loading by filling a vector and sorting it // more efficiently, then adding it all in one go. // Use pointers in array, to speed up sorting. boost::ptr_vector individuals; IndividualData data; typedef GeeCollCXX< GeeMapEntryWrapper > Coll; GeeMap *map = folks_individual_aggregator_get_individuals(m_folks); Coll coll(map, ADD_REF); guint size = gee_map_get_size(map); individuals.reserve(size); SE_LOG_DEBUG(NULL, "starting with %u individuals", size); BOOST_FOREACH (const Coll::value_type &entry, coll) { FolksIndividual *individual = entry.value(); data.init(m_compare.get(), m_locale.get(), individual); individuals.push_back(new IndividualData(data)); } individuals.sort(IndividualDataCompare(m_compare)); // Copy the sorted data into the view in one go. m_entries.insert(m_entries.begin(), individuals.begin(), individuals.end()); // Avoid loop if no-one is listening. if (!m_addedSignal.empty()) { for (size_t index = 0; index < m_entries.size(); index++) { m_addedSignal(index, m_entries[index]); } } // Connect to changes. Aggregator might live longer than we do, so // bind to weak pointer and check our existence at runtime. m_folks.connectSignal("individuals-changed", boost::bind(&FullView::individualsChanged, m_self, _2, _3, _4, _5, _6)); // Track state as part of normal event processing. Don't check the // state directly, because then we might get into an inconsistent // state (changes still pending in our queue, function call // already returns true). m_isQuiescent = folks_individual_aggregator_get_is_quiescent(m_folks.get()); m_folks.connectSignal("notify::is-quiescent", boost::bind(&FullView::quiescenceChanged, m_self)); } boost::shared_ptr FullView::create(const FolksIndividualAggregatorCXX &folks, const boost::shared_ptr &locale) { boost::shared_ptr view(new FullView(folks, locale)); view->init(view); return view; } void FullView::setLocale(const boost::shared_ptr &locale) { m_locale = locale; m_localeChanged = true; // Don't recompute all IndividualData content. That will be done // as part of setCompare(), which must be called later. } void FullView::individualsChanged(GeeSet *added, GeeSet *removed, gchar *message, FolksPersona *actor, FolksGroupDetailsChangeReason reason) { SE_LOG_DEBUG(NULL, "individuals changed, %s, %d added, %d removed, message: %s", actor ? folks_persona_get_display_id(actor) : "<>", added ? gee_collection_get_size(GEE_COLLECTION(added)) : 0, removed ? gee_collection_get_size(GEE_COLLECTION(removed)) : 0, message); typedef GeeCollCXX Coll; // Remove first, to match the "remove + added = modified" change optimization // in Manager::handleChange(). if (removed) { BOOST_FOREACH (FolksIndividual *individual, Coll(removed, ADD_REF)) { removeIndividual(individual); } } if (added) { // TODO (?): Optimize adding many new individuals by pre-sorting them, // then using that information to avoid comparisons in addIndividual(). BOOST_FOREACH (FolksIndividual *individual, Coll(added, ADD_REF)) { addIndividual(individual); } } } void FullView::individualModified(gpointer gobject, GParamSpec *pspec) { SE_LOG_DEBUG(NULL, "individual %p modified", gobject); FolksIndividual *individual = FOLKS_INDIVIDUAL(gobject); // Delay the expensive modification check until the process is // idle, because in practice we get several change signals for // each contact change in EDS. // // See https://bugzilla.gnome.org/show_bug.cgi?id=684764 // "too many FolksIndividual modification signals" m_pendingModifications.insert(FolksIndividualCXX(individual, ADD_REF)); waitForIdle(); } void FullView::quiescenceChanged() { bool quiescent = folks_individual_aggregator_get_is_quiescent(m_folks); SE_LOG_DEBUG(NULL, "aggregator is %s", quiescent ? "quiescent" : "busy"); // In practice, libfolks only switches from "busy" to "quiescent" // once. See https://bugzilla.gnome.org/show_bug.cgi?id=684766 // "enter and leave quiescence state". if (quiescent) { int seconds = atoi(getEnv("SYNCEVOLUTION_PIM_DELAY_FOLKS", "0")); if (seconds > 0) { // Delay the quiescent state change as requested. SE_LOG_DEBUG(NULL, "delay aggregrator quiescence by %d seconds", seconds); m_quiescenceDelay.runOnce(seconds, boost::bind(&FullView::quiescenceChanged, this)); unsetenv("SYNCEVOLUTION_PIM_DELAY_FOLKS"); return; } m_isQuiescent = true; m_quiescenceSignal(); } } void FullView::doAddIndividual(Entries_t::auto_type &data) { // Binary search to find insertion point. Entries_t::iterator it = std::lower_bound(m_entries.begin(), m_entries.end(), *data, IndividualDataCompare(m_compare)); size_t index = it - m_entries.begin(); it = m_entries.insert(it, data.release()); SE_LOG_DEBUG(NULL, "full view: added at #%ld/%ld", (long)index, (long)m_entries.size()); m_addedSignal(index, *it); waitForIdle(); // Monitor individual for changes. it->m_individual.connectSignal("notify", boost::bind(&FullView::individualModified, m_self, _1, _2)); } void FullView::addIndividual(FolksIndividual *individual) { Entries_t::auto_type data(new IndividualData); data->init(m_compare.get(), m_locale.get(), individual); doAddIndividual(data); } void FullView::modifyIndividual(FolksIndividual *individual) { // Brute-force search for the individual. Pointer comparison is // sufficient, libfolks will not change instances without // announcing it. for (Entries_t::iterator it = m_entries.begin(); it != m_entries.end(); ++it) { if (it->m_individual.get() == individual) { size_t index = it - m_entries.begin(); Entries_t::auto_type data(new IndividualData); data->init(m_compare.get(), m_locale.get(), individual); if (data->m_criteria != it->m_criteria && ((it != m_entries.begin() && !m_compare->compare((it - 1)->m_criteria, data->m_criteria)) || (it + 1 != m_entries.end() && !m_compare->compare(data->m_criteria, (it + 1)->m_criteria)))) { // Sort criteria changed in such a way that the old // sorting became invalid => move the entry. Do it // as simple as possible, because this is not expected // to happen often. SE_LOG_DEBUG(NULL, "full view: temporarily removed at #%ld/%ld", (long)index, (long)m_entries.size()); Entries_t::auto_type old = m_entries.release(it); m_removedSignal(index, *old); doAddIndividual(data); } else { SE_LOG_DEBUG(NULL, "full view: modified at #%ld/%ld", (long)index, (long)m_entries.size()); // Use potentially modified pre-computed data. m_entries.replace(it, data.release()); m_modifiedSignal(index, *it); waitForIdle(); } return; } } // Not a bug: individual might have been removed before we got // around to processing the modification notification. SE_LOG_DEBUG(NULL, "full view: modified individual not found"); } void FullView::removeIndividual(FolksIndividual *individual) { for (Entries_t::iterator it = m_entries.begin(); it != m_entries.end(); ++it) { if (it->m_individual.get() == individual) { size_t index = it - m_entries.begin(); SE_LOG_DEBUG(NULL, "full view: removed at #%ld/%ld", (long)index, (long)m_entries.size()); Entries_t::auto_type data = m_entries.release(it); m_removedSignal(index, *data); waitForIdle(); return; } } // A bug?! SE_LOG_DEBUG(NULL, "full view: individual to be removed not found"); } void FullView::onIdle() { SE_LOG_DEBUG(NULL, "full view: process is idle"); // Process delayed contact modifications. BOOST_FOREACH (const FolksIndividualCXX &individual, m_pendingModifications) { modifyIndividual(const_cast(individual.get())); } m_pendingModifications.clear(); // If not quiescent at the moment, then we can rely on getting // that signal triggered by folks and don't need to send it now. if (isQuiescent()) { m_quiescenceSignal(); } } void FullView::waitForIdle() { if (!m_waitForIdle) { // Run this after all other idle callbacks that we may have added, // like the "fill view on idle" callback in the filtered view. m_waitForIdle.runOnce(boost::bind(&FullView::onIdle, this), Timeout::PRIORITY_LOW); } } void FullView::setCompare(const boost::shared_ptr &compare) { m_compare = compare ? compare : IndividualCompare::defaultCompare(); // Make a copy of the original order. The actual instances // continue to be owned by m_entries. boost::scoped_array old(new IndividualData *[m_entries.size()]); memcpy(old.get(), m_entries.c_array(), sizeof(IndividualData *) * m_entries.size()); // Change sort criteria and sort. // Optionally also re-compute locale-dependent values, if // the locale changed (see setLocale()). LocaleFactory *locale = m_localeChanged ? m_locale.get() : NULL; m_localeChanged = false; boost::dynamic_bitset modified(locale ? m_entries.size() : 0); for (size_t i = 0; i < m_entries.size(); i++ ) { IndividualData &data = m_entries[i]; bool preComputedModified = data.init(m_compare.get(), locale, data.m_individual); if (locale && preComputedModified) { modified.set(i); } } m_entries.sort(IndividualDataCompare(m_compare)); // Now check for changes. for (size_t index = 0; index < m_entries.size(); index++) { IndividualData &previous = *old[index], ¤t = m_entries[index]; if (previous.m_individual != current.m_individual || (locale && modified[index])) { // Contact at the index changed. Don't try to find out // where it came from now. The effect is that temporarily // the same contact might be shown at two different // indices. m_modifiedSignal(index, current); } } // Current status is stable again (?), send out all modifications. if (isQuiescent()) { m_quiescenceSignal(); } } SE_END_CXX syncevolution_1.4/src/dbus/server/pim/full-view.h000066400000000000000000000115641230021373600222670ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_SYNCEVO_DBUS_SERVER_FULL_VIEW #define INCL_SYNCEVO_DBUS_SERVER_FULL_VIEW #include "view.h" #include SE_BEGIN_CXX /** * The view which takes input directly from IndividualAggregator * and maintains a sorted set of contacts as result. */ class FullView : public IndividualView { FolksIndividualAggregatorCXX m_folks; boost::shared_ptr m_locale; bool m_localeChanged; bool m_isQuiescent; boost::weak_ptr m_self; Timeout m_waitForIdle; std::set m_pendingModifications; Timeout m_quiescenceDelay; /** * Sorted vector. Sort order is maintained by this class. */ typedef boost::ptr_vector Entries_t; Entries_t m_entries; /** * The sort object to be used. */ boost::shared_ptr m_compare; FullView(const FolksIndividualAggregatorCXX &folks, const boost::shared_ptr &locale); void init(const boost::shared_ptr &self); /** * Run via m_waitForIdle if (and only if) something * changed. */ void onIdle(); /** * Ensure that onIdle() gets invoked. */ void waitForIdle(); /** * Adds the new individual to m_entries, transfers ownership * (data == NULL afterwards). */ void doAddIndividual(Entries_t::auto_type &data); public: /** * @param folks the aggregator to use */ static boost::shared_ptr create(const FolksIndividualAggregatorCXX &folks, const boost::shared_ptr &locale); /** * Change locale. Updating pre-computed data must be triggered by * calling setCompare() later. */ void setLocale(const boost::shared_ptr &locale); /** FolksIndividualAggregator "individuals-changed" slot */ void individualsChanged(GeeSet *added, GeeSet *removed, gchar *message, FolksPersona *actor, FolksGroupDetailsChangeReason reason = FOLKS_GROUP_DETAILS_CHANGE_REASON_NONE); /** GObject "notify" slot */ void individualModified(gpointer gobject, GParamSpec *pspec); /** * FolksIndividualAggregator "is-quiescent" property change slot. * * It turned out that "quiescence" is only set to true once in * FolksIndividualAggregator. The code which watches that signal * is still in place, but it will only get invoked once. * * Therefore the main mechanism for emitting m_quiescenceSignal in * FullView is an idle callback which gets invoked each time the * daemon has nothing to do, which implies that (at least for now) * libfolks has no pending work to do. */ void quiescenceChanged(); /** * Mirrors the FolksIndividualAggregator "is-quiesent" state: * false initially, then true for the rest of the run. */ virtual bool isQuiescent() const { return m_isQuiescent; } /** * Add a FolksIndividual. Starts monitoring it for changes. */ void addIndividual(FolksIndividual *individual); /** * Deal with FolksIndividual modification. */ void modifyIndividual(FolksIndividual *individual); /** * Remove a FolksIndividual. */ void removeIndividual(FolksIndividual *individual); /** * Set new sort method. Reorders current set of entries on the * fly. Default is lexicographical comparison of the single-string * full name. * * @param compare the new ordering or NULL for the builtin default (last/first with ASCII lexicographic comparison) */ void setCompare(const boost::shared_ptr &compare); // from IndividualView virtual void doStart(); virtual int size() const { return (int)m_entries.size(); } virtual const IndividualData *getContact(int index) { return (index >= 0 && (unsigned)index < m_entries.size()) ? &m_entries[index] : NULL; } }; SE_END_CXX #endif // INCL_SYNCEVO_DBUS_SERVER_PIM_FULL_VIEW syncevolution_1.4/src/dbus/server/pim/individual-traits.cpp000066400000000000000000001512011230021373600243350ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ /** * The D-Bus IPC binding for folks.h. Maps FolksIndividual to and * from the D-Bus dict described in pim-manager-api.txt. */ #include "individual-traits.h" #include "persona-details.h" #include "folks.h" SE_GLIB_TYPE(GDateTime, g_date_time) SE_GOBJECT_TYPE(GTimeZone) SE_GOBJECT_TYPE(GObject) SE_BEGIN_CXX typedef GValueDynTypedCXX GValueDateTimeCXX; static const char * const CONTACT_HASH_FULL_NAME = "full-name"; static const char * const CONTACT_HASH_NICKNAME = "nickname"; static const char * const CONTACT_HASH_STRUCTURED_NAME = "structured-name"; static const char * const CONTACT_HASH_STRUCTURED_NAME_FAMILY = "family"; static const char * const CONTACT_HASH_STRUCTURED_NAME_GIVEN = "given"; static const char * const CONTACT_HASH_STRUCTURED_NAME_ADDITIONAL = "additional"; static const char * const CONTACT_HASH_STRUCTURED_NAME_PREFIXES = "prefixes"; static const char * const CONTACT_HASH_STRUCTURED_NAME_SUFFIXES = "suffixes"; static const char * const CONTACT_HASH_ALIAS = "alias"; static const char * const CONTACT_HASH_PHOTO = "photo"; static const char * const CONTACT_HASH_BIRTHDAY = "birthday"; static const char * const CONTACT_HASH_LOCATION = "location"; static const char * const CONTACT_HASH_EMAILS = "emails"; static const char * const CONTACT_HASH_PHONES = "phones"; static const char * const CONTACT_HASH_URLS = "urls"; static const char * const CONTACT_HASH_NOTES = "notes"; static const char * const CONTACT_HASH_ADDRESSES = "addresses"; static const char * const CONTACT_HASH_ADDRESSES_PO_BOX = "po-box"; static const char * const CONTACT_HASH_ADDRESSES_EXTENSION = "extension"; static const char * const CONTACT_HASH_ADDRESSES_STREET = "street"; static const char * const CONTACT_HASH_ADDRESSES_LOCALITY = "locality"; static const char * const CONTACT_HASH_ADDRESSES_REGION = "region"; static const char * const CONTACT_HASH_ADDRESSES_POSTAL_CODE = "postal-code"; static const char * const CONTACT_HASH_ADDRESSES_COUNTRY = "country"; static const char * const CONTACT_HASH_ROLES = "roles"; static const char * const CONTACT_HASH_ROLES_ORGANISATION = "organisation"; static const char * const CONTACT_HASH_ROLES_TITLE = "title"; static const char * const CONTACT_HASH_ROLES_ROLE = "role"; static const char * const CONTACT_HASH_GROUPS = "groups"; static const char * const CONTACT_HASH_SOURCE = "source"; static const char * const CONTACT_HASH_ID = "id"; static const char * const INDIVIDUAL_DICT = "a{sv}"; static const char * const INDIVIDUAL_DICT_ENTRY = "{sv}"; /** * Checks whether a certain value is the default value and thus * can be skipped when converting to D-Bus. * * class B provides additional type information, which is necessary * to check a GeeSet containing FolksRoleFieldDetails differently * than other GeeSets. */ template struct IsNonDefault { template static bool check(V value) { // Default version uses normal C/C++ rules, for example pointer non-NULL. // For bool and integer, the value will only be sent if true or non-zero. return value; } static bool check(const gchar *value) { // Don't send empty strings. return value && value[0]; } static bool check(GeeSet *value) { // Don't send empty sets. return value && gee_collection_get_size(GEE_COLLECTION(value)); } }; template <> struct IsNonDefault< GeeCollCXX > { static bool check(GeeSet *value) { // Don't send empty set and set which contains only empty roles. if (value) { BOOST_FOREACH (FolksRoleFieldDetails *value, GeeCollCXX(value, ADD_REF)) { FolksRole *role = static_cast(const_cast((folks_abstract_field_details_get_value(FOLKS_ABSTRACT_FIELD_DETAILS(value))))); if (IsNonDefault::check(folks_role_get_organisation_name(role)) || IsNonDefault::check(folks_role_get_title(role)) || IsNonDefault::check(folks_role_get_role(role))) { return true; } } } return false; } }; /** * Adds a dict entry to the builder, with 'key' as string key and the * result of 'get()' as value. */ template void SerializeFolks(GDBusCXX::builder_type &builder, O *obj, V (*get)(O *), const char *key) { if (!obj) { SE_THROW("casting to base class failed"); } V value = get(obj); if (IsNonDefault::check(value)) { g_variant_builder_open(&builder, G_VARIANT_TYPE(INDIVIDUAL_DICT_ENTRY)); // dict entry GDBusCXX::dbus_traits::append(builder, key); g_variant_builder_open(&builder, G_VARIANT_TYPE("v")); // variant GDBusCXX::dbus_traits::append(builder, value); g_variant_builder_close(&builder); // variant g_variant_builder_close(&builder); // dict entry } } template void SerializeFolks(GDBusCXX::builder_type &builder, O *obj, V (*get)(O *), B *, // dummy parameter, determines base type const char *key) { if (!obj) { SE_THROW("casting to base class failed"); } V value = get(obj); B coll(value, ADD_REF); if (IsNonDefault::check(value)) { g_variant_builder_open(&builder, G_VARIANT_TYPE(INDIVIDUAL_DICT_ENTRY)); // dict entry GDBusCXX::dbus_traits::append(builder, key); g_variant_builder_open(&builder, G_VARIANT_TYPE("v")); // variant GDBusCXX::dbus_traits::append(builder, coll); g_variant_builder_close(&builder); // variant g_variant_builder_close(&builder); // dict entry } } SE_END_CXX namespace GDBusCXX { template <> struct dbus_traits { static void append(builder_type &builder, FolksStructuredName *value) { g_variant_builder_open(&builder, G_VARIANT_TYPE(INDIVIDUAL_DICT)); // dict SyncEvo::SerializeFolks(builder, value, folks_structured_name_get_family_name, CONTACT_HASH_STRUCTURED_NAME_FAMILY); SyncEvo::SerializeFolks(builder, value, folks_structured_name_get_given_name, CONTACT_HASH_STRUCTURED_NAME_GIVEN); SyncEvo::SerializeFolks(builder, value, folks_structured_name_get_additional_names, CONTACT_HASH_STRUCTURED_NAME_ADDITIONAL); SyncEvo::SerializeFolks(builder, value, folks_structured_name_get_prefixes, CONTACT_HASH_STRUCTURED_NAME_PREFIXES); SyncEvo::SerializeFolks(builder, value, folks_structured_name_get_suffixes, CONTACT_HASH_STRUCTURED_NAME_SUFFIXES); g_variant_builder_close(&builder); // dict } }; /** Path to icon (the expected case) or uninitialized string (not set or not a file). */ static InitStateString ExtractFilePath(GLoadableIcon *value) { if (value && G_IS_FILE_ICON(value)) { GFileIcon *fileIcon = G_FILE_ICON(value); GFile *file = g_file_icon_get_file(fileIcon); if (file) { PlainGStr uri(g_file_get_uri(file)); // Have a path. return InitStateString(uri.get(), true); } } // No path. return InitStateString(); } template <> struct dbus_traits { static void append(builder_type &builder, GLoadableIcon *value) { InitStateString path = ExtractFilePath(value); // EDS is expected to only work with URIs for the PHOTO // property, therefore we shouldn't get here without a valid // path. Either way, we need to store something. GDBusCXX::dbus_traits::append(builder, path.c_str()); } }; template <> struct dbus_traits { static void append(builder_type &builder, FolksPostalAddress *value) { g_variant_builder_open(&builder, G_VARIANT_TYPE(INDIVIDUAL_DICT)); // dict SyncEvo::SerializeFolks(builder, value, folks_postal_address_get_po_box, CONTACT_HASH_ADDRESSES_PO_BOX); SyncEvo::SerializeFolks(builder, value, folks_postal_address_get_extension, CONTACT_HASH_ADDRESSES_EXTENSION); SyncEvo::SerializeFolks(builder, value, folks_postal_address_get_street, CONTACT_HASH_ADDRESSES_STREET); SyncEvo::SerializeFolks(builder, value, folks_postal_address_get_locality, CONTACT_HASH_ADDRESSES_LOCALITY); SyncEvo::SerializeFolks(builder, value, folks_postal_address_get_region, CONTACT_HASH_ADDRESSES_REGION); SyncEvo::SerializeFolks(builder, value, folks_postal_address_get_postal_code, CONTACT_HASH_ADDRESSES_POSTAL_CODE); SyncEvo::SerializeFolks(builder, value, folks_postal_address_get_country, CONTACT_HASH_ADDRESSES_COUNTRY); // Not used by EDS. The tracker backend in folks was able to provide such a uid. // SyncEvo::SerializeFolks(builder, value, folks_postal_address_get_address_format, CONTACT_HASH_ADDRESSES_FORMAT); // SyncEvo::SerializeFolks(builder, value, folks_postal_address_get_uid, "uid"); g_variant_builder_close(&builder); // dict } }; template <> struct dbus_traits { static void append(builder_type &builder, FolksRole *value) { g_variant_builder_open(&builder, G_VARIANT_TYPE(INDIVIDUAL_DICT)); // dict // Other parts of ORG are not currently supported by libfolks. SyncEvo::SerializeFolks(builder, value, folks_role_get_organisation_name, CONTACT_HASH_ROLES_ORGANISATION); SyncEvo::SerializeFolks(builder, value, folks_role_get_title, CONTACT_HASH_ROLES_TITLE); SyncEvo::SerializeFolks(builder, value, folks_role_get_role, CONTACT_HASH_ROLES_ROLE); // Not used: // SyncEvo::SerializeFolks(builder, value, folks_role_get_uid, "uid"); g_variant_builder_close(&builder); // dict } }; // TODO: put into global header file static const char * const MANAGER_PREFIX = "pim-manager-"; template <> struct dbus_traits { static void append(builder_type &builder, FolksPersona *value) { g_variant_builder_open(&builder, G_VARIANT_TYPE("(ss)")); // pair of peer ID and local ID const gchar *uid = folks_persona_get_uid(value); if (uid) { gchar *backend, *storeID, *personaID; folks_persona_split_uid(uid, &backend, &storeID, &personaID); PlainGStr tmp1(backend), tmp2(storeID), tmp3(personaID); if (boost::starts_with(storeID, MANAGER_PREFIX)) { dbus_traits::append(builder, storeID + strlen(MANAGER_PREFIX)); } else { // Must be the system address book. dbus_traits::append(builder, ""); } dbus_traits::append(builder, personaID); } else { dbus_traits::append(builder, ""); dbus_traits::append(builder, ""); } g_variant_builder_close(&builder); // pair } }; // Only use this with FolksAbstractFieldDetails instances where // the value is a string. template <> struct dbus_traits { static void append(builder_type &builder, FolksAbstractFieldDetails *value) { g_variant_builder_open(&builder, G_VARIANT_TYPE("(sas)")); // pair of string and string list gconstpointer v = folks_abstract_field_details_get_value(value); dbus_traits::append(builder, v ? (const char *)v : ""); g_variant_builder_open(&builder, G_VARIANT_TYPE("as")); GeeMultiMap *map = folks_abstract_field_details_get_parameters(value); if (map) { GeeCollCXX coll(gee_multi_map_get(map, FOLKS_ABSTRACT_FIELD_DETAILS_PARAM_TYPE), TRANSFER_REF); BOOST_FOREACH (const char *type, coll) { dbus_traits::append(builder, type); } } g_variant_builder_close(&builder); // string list g_variant_builder_close(&builder); // pair } }; template <> struct dbus_traits { static void append(builder_type &builder, FolksPostalAddressFieldDetails *value) { g_variant_builder_open(&builder, G_VARIANT_TYPE(StringPrintf("(%sas)", INDIVIDUAL_DICT).c_str())); // pair of dict and string list FolksAbstractFieldDetails *fieldDetails = FOLKS_ABSTRACT_FIELD_DETAILS(value); gconstpointer v = folks_abstract_field_details_get_value(fieldDetails); dbus_traits::append(builder, static_cast(const_cast(v))); g_variant_builder_open(&builder, G_VARIANT_TYPE("as")); GeeMultiMap *map = folks_abstract_field_details_get_parameters(fieldDetails); if (map) { GeeCollCXX coll(gee_multi_map_get(map, "type"), TRANSFER_REF); BOOST_FOREACH (const char *type, coll) { dbus_traits::append(builder, type); } } g_variant_builder_close(&builder); // string list g_variant_builder_close(&builder); // pair } }; template <> struct dbus_traits { static void append(builder_type &builder, FolksNoteFieldDetails *value) { FolksAbstractFieldDetails *fieldDetails = FOLKS_ABSTRACT_FIELD_DETAILS(value); gconstpointer v = folks_abstract_field_details_get_value(fieldDetails); dbus_traits::append(builder, static_cast(v)); // plain string // Ignore parameters. LANGUAGE is hardly ever set. } }; template <> struct dbus_traits { static void append(builder_type &builder, FolksRoleFieldDetails *value) { FolksAbstractFieldDetails *fieldDetails = FOLKS_ABSTRACT_FIELD_DETAILS(value); gconstpointer v = folks_abstract_field_details_get_value(fieldDetails); dbus_traits::append(builder, static_cast(const_cast((v)))); // Ignore parameters. LANGUAGE is hardly ever set. } }; // Used for types like V = FolksEmailFieldDetails * template struct dbus_traits< GeeCollCXX > { static void append(builder_type &builder, const GeeCollCXX &collection) { g_variant_builder_open(&builder, G_VARIANT_TYPE("av")); // array of variants BOOST_FOREACH (V value, collection) { g_variant_builder_open(&builder, G_VARIANT_TYPE("v")); // variant dbus_traits::append(builder, value); g_variant_builder_close(&builder); // variant } g_variant_builder_close(&builder); // array of variants } }; template <> struct dbus_traits { static void append(builder_type &builder, GDateTime *value) { // Extract local date from UTC date + time + UTC offset. // // The libfolks EDS backend does date + 00:00 in local time, // then converts to UTC. We need to hard-code the stripping // of the time. Folks should make it easier to extract the date, see // https://bugzilla.gnome.org/show_bug.cgi?id=684905 // // TODO: if folks doesn't get fixed, then we should cache the // GTimeZone local = g_time_zone_new_local() // and use that throughout the runtime of the process, like // folks-eds does. GDateTimeCXX local(g_date_time_to_local(value), TRANSFER_REF); gint year, month, day; g_date_time_get_ymd(local.get(), &year, &month, &day); g_variant_builder_open(&builder, G_VARIANT_TYPE("(iii)")); // tuple with year, month, day GDBusCXX::dbus_traits::append(builder, year); GDBusCXX::dbus_traits::append(builder, month); GDBusCXX::dbus_traits::append(builder, day); g_variant_builder_close(&builder); // tuple } }; template <> struct dbus_traits { static void append(builder_type &builder, FolksLocation *value) { g_variant_builder_open(&builder, G_VARIANT_TYPE("(dd)")); // tuple with latitude + longitude GDBusCXX::dbus_traits::append(builder, value->latitude); GDBusCXX::dbus_traits::append(builder, value->longitude); g_variant_builder_close(&builder); // tuple } }; } // namespace GDBusCXX SE_BEGIN_CXX static guint FolksAbstractFieldDetailsHash(gconstpointer v, void *d) { return folks_abstract_field_details_hash((FolksAbstractFieldDetails *)v); } static gboolean FolksAbstractFieldDetailsEqual(gconstpointer a, gconstpointer b, void *d) { return folks_abstract_field_details_equal((FolksAbstractFieldDetails *)a, (FolksAbstractFieldDetails *)b); } /** * Copy from D-Bus into a type derived from FolksAbstractFieldDetails, * including type flags. */ static void DBus2AbstractField(GDBusCXX::ExtractArgs &context, GVariantIter &valueIter, PersonaDetails &details, GType detailType, FolksPersonaDetail detail, FolksAbstractFieldDetails *(*fieldNew)(const gchar *, GeeMultiMap *)) { // type of emails, urls, etc. typedef std::vector< std::pair< std::string, std::vector > > Details_t; Details_t value; GDBusCXX::dbus_traits::get(context, valueIter, value); GeeHashSetCXX set(gee_hash_set_new(detailType, g_object_ref, g_object_unref, FolksAbstractFieldDetailsHash, NULL, NULL, FolksAbstractFieldDetailsEqual, NULL, NULL), TRANSFER_REF); BOOST_FOREACH (const Details_t::value_type &entry, value) { const Details_t::value_type::first_type &val = entry.first; const Details_t::value_type::second_type &flags = entry.second; FolksAbstractFieldDetailsCXX field(fieldNew(val.c_str(), NULL), TRANSFER_REF); BOOST_FOREACH (const std::string &type, flags) { folks_abstract_field_details_add_parameter(field.get(), FOLKS_ABSTRACT_FIELD_DETAILS_PARAM_TYPE, type.c_str()); } gee_collection_add(GEE_COLLECTION(set.get()), field.get()); } g_hash_table_insert(details.get(), const_cast(folks_persona_store_detail_key(detail)), new GValueObjectCXX(set.get())); } /** * Copy from D-Bus into a type derived from FolksAbstractFieldDetails, * excluding type flags. */ static void DBus2SimpleAbstractField(GDBusCXX::ExtractArgs &context, GVariantIter &valueIter, PersonaDetails &details, GType detailType, FolksPersonaDetail detail, FolksAbstractFieldDetails *(*fieldNew)(const gchar *, GeeMultiMap *)) { // type of notes typedef std::vector Details_t; Details_t value; GDBusCXX::dbus_traits::get(context, valueIter, value); GeeHashSetCXX set(gee_hash_set_new(detailType, g_object_ref, g_object_unref, FolksAbstractFieldDetailsHash, NULL, NULL, FolksAbstractFieldDetailsEqual, NULL, NULL), TRANSFER_REF); BOOST_FOREACH (const std::string &val, value) { FolksAbstractFieldDetailsCXX field(fieldNew(val.c_str(), NULL), TRANSFER_REF); gee_collection_add(GEE_COLLECTION(set.get()), field.get()); } g_hash_table_insert(details.get(), const_cast(folks_persona_store_detail_key(detail)), new GValueObjectCXX(set.get())); } /** * Copy from D-Bus into FolksRoleFieldDetails. */ static void DBus2Role(GDBusCXX::ExtractArgs &context, GVariantIter &valueIter, PersonaDetails &details) { // type of role typedef std::vector Details_t; Details_t value; GDBusCXX::dbus_traits::get(context, valueIter, value); GeeHashSetCXX set(gee_hash_set_new(FOLKS_TYPE_ROLE_FIELD_DETAILS, g_object_ref, g_object_unref, FolksAbstractFieldDetailsHash, NULL, NULL, FolksAbstractFieldDetailsEqual, NULL, NULL), TRANSFER_REF); BOOST_FOREACH (const StringMap &entry, value) { FolksRoleCXX role(folks_role_new(NULL, NULL, NULL), TRANSFER_REF); BOOST_FOREACH (const StringPair &aspect, entry) { const std::string &k = aspect.first; const std::string &v = aspect.second; if (k == CONTACT_HASH_ROLES_ORGANISATION) { folks_role_set_organisation_name(role, v.c_str()); } else if (k == CONTACT_HASH_ROLES_TITLE) { folks_role_set_title(role, v.c_str()); } else if (k == CONTACT_HASH_ROLES_ROLE) { folks_role_set_role(role, v.c_str()); } } FolksRoleFieldDetailsCXX field(folks_role_field_details_new(role.get(), NULL), TRANSFER_REF); gee_collection_add(GEE_COLLECTION(set.get()), field.get()); } g_hash_table_insert(details.get(), const_cast(folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_ROLES)), new GValueObjectCXX(set.get())); } /** * Copy from D-Bus into a set of strings. */ static void DBus2Groups(GDBusCXX::ExtractArgs &context, GVariantIter &valueIter, PersonaDetails &details) { std::list value; GDBusCXX::dbus_traits::get(context, valueIter, value); GeeHashSetCXX set(gee_hash_set_new(G_TYPE_STRING, (GBoxedCopyFunc)g_strdup, g_free, NULL, NULL, NULL, NULL, NULL, NULL), TRANSFER_REF); BOOST_FOREACH(const std::string &entry, value) { gee_collection_add(GEE_COLLECTION(set.get()), entry.c_str()); } g_hash_table_insert(details.get(), const_cast(folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_GROUPS)), new GValueObjectCXX(set.get())); } /** * Copy from D-Bus into a FolksAddressFieldDetails. */ static void DBus2Addr(GDBusCXX::ExtractArgs &context, GVariantIter &valueIter, PersonaDetails &details) { // type of CONTACT_HASH_ADDRESSES typedef std::vector< std::pair< StringMap, std::vector > > Details_t; Details_t value; GDBusCXX::dbus_traits::get(context, valueIter, value); GeeHashSetCXX set(gee_hash_set_new(FOLKS_TYPE_POSTAL_ADDRESS_FIELD_DETAILS, g_object_ref, g_object_unref, FolksAbstractFieldDetailsHash, NULL, NULL, FolksAbstractFieldDetailsEqual, NULL, NULL), TRANSFER_REF); BOOST_FOREACH (const Details_t::value_type &entry, value) { const StringMap &fields = entry.first; const std::vector &flags = entry.second; FolksPostalAddressCXX address(folks_postal_address_new(GetWithDef(fields, CONTACT_HASH_ADDRESSES_PO_BOX).c_str(), GetWithDef(fields, CONTACT_HASH_ADDRESSES_EXTENSION).c_str(), GetWithDef(fields, CONTACT_HASH_ADDRESSES_STREET).c_str(), GetWithDef(fields, CONTACT_HASH_ADDRESSES_LOCALITY).c_str(), GetWithDef(fields, CONTACT_HASH_ADDRESSES_REGION).c_str(), GetWithDef(fields, CONTACT_HASH_ADDRESSES_POSTAL_CODE).c_str(), GetWithDef(fields, CONTACT_HASH_ADDRESSES_COUNTRY).c_str(), NULL /* address format */, NULL /* uid */), TRANSFER_REF); FolksAbstractFieldDetailsCXX field(FOLKS_ABSTRACT_FIELD_DETAILS(folks_postal_address_field_details_new(address.get(), NULL)), TRANSFER_REF); BOOST_FOREACH (const std::string &type, flags) { folks_abstract_field_details_add_parameter(field.get(), FOLKS_ABSTRACT_FIELD_DETAILS_PARAM_TYPE, type.c_str()); } gee_collection_add(GEE_COLLECTION(set.get()), field.get()); } g_hash_table_insert(details.get(), const_cast(folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_POSTAL_ADDRESSES)), new GValueObjectCXX(set.get())); } void DBus2PersonaDetails(GDBusCXX::ExtractArgs &context, GDBusCXX::reader_type &iter, PersonaDetails &details) { GDBusCXX::GVariantCXX var(g_variant_iter_next_value(&iter)); if (var == NULL || !g_variant_type_is_subtype_of(g_variant_get_type(var), G_VARIANT_TYPE_ARRAY)) { SE_THROW("D-Bus contact hash: unexpected content"); } GVariantIter contIter; GDBusCXX::GVariantCXX child; g_variant_iter_init(&contIter, var); // array while((child = g_variant_iter_next_value(&contIter)) != NULL) { GVariantIter childIter; g_variant_iter_init(&childIter, child); // dict entry std::string key; GDBusCXX::dbus_traits::get(context, childIter, key); GDBusCXX::GVariantCXX valueVarient(g_variant_iter_next_value(&childIter)); if (valueVarient == NULL || !g_variant_type_equal(g_variant_get_type(valueVarient), G_VARIANT_TYPE_VARIANT)) { SE_THROW("D-Bus contact hash entry: value must be variant"); } GVariantIter valueIter; g_variant_iter_init(&valueIter, valueVarient); if (key == CONTACT_HASH_FULL_NAME) { std::string value; GDBusCXX::dbus_traits::get(context, valueIter, value); g_hash_table_insert(details.get(), const_cast(folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_FULL_NAME)), new GValueStringCXX(value.c_str())); } else if (key == CONTACT_HASH_STRUCTURED_NAME) { StringMap value; GDBusCXX::dbus_traits::get(context, valueIter, value); g_hash_table_insert(details.get(), const_cast(folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_STRUCTURED_NAME)), new GValueObjectCXX(folks_structured_name_new(GetWithDef(value, CONTACT_HASH_STRUCTURED_NAME_FAMILY).c_str(), GetWithDef(value, CONTACT_HASH_STRUCTURED_NAME_GIVEN).c_str(), GetWithDef(value, CONTACT_HASH_STRUCTURED_NAME_ADDITIONAL).c_str(), GetWithDef(value, CONTACT_HASH_STRUCTURED_NAME_PREFIXES).c_str(), GetWithDef(value, CONTACT_HASH_STRUCTURED_NAME_SUFFIXES).c_str()), TRANSFER_REF)); } else if (key == CONTACT_HASH_PHOTO) { std::string value; GDBusCXX::dbus_traits::get(context, valueIter, value); GFileCXX file(g_file_new_for_uri(value.c_str()), TRANSFER_REF); g_hash_table_insert(details.get(), const_cast(folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_AVATAR)), new GValueObjectCXX(g_file_icon_new(file.get()), TRANSFER_REF)); } else if (key == CONTACT_HASH_BIRTHDAY) { boost::tuple value; GDBusCXX::dbus_traits::get(context, valueIter, value); GDateTimeCXX local(g_date_time_new_local(value.get<0>(), value.get<1>(), value.get<2>(), 0, 0, 0), TRANSFER_REF); g_hash_table_insert(details.get(), const_cast(folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_BIRTHDAY)), new GValueDateTimeCXX(g_date_time_to_utc(local.get()), TRANSFER_REF)); } else if (key == CONTACT_HASH_LOCATION) { boost::tuple value; GDBusCXX::dbus_traits::get(context, valueIter, value); FolksLocationCXX location(folks_location_new(value.get<0>(), value.get<1>()), TRANSFER_REF); g_hash_table_insert(details.get(), const_cast(folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_LOCATION)), new GValueObjectCXX(location.get())); } else if (key == CONTACT_HASH_EMAILS) { DBus2AbstractField(context, valueIter, details, FOLKS_TYPE_EMAIL_FIELD_DETAILS, FOLKS_PERSONA_DETAIL_EMAIL_ADDRESSES, reinterpret_cast(folks_email_field_details_new)); } else if (key == CONTACT_HASH_PHONES) { DBus2AbstractField(context, valueIter, details, FOLKS_TYPE_PHONE_FIELD_DETAILS, FOLKS_PERSONA_DETAIL_PHONE_NUMBERS, reinterpret_cast(folks_phone_field_details_new)); } else if (key == CONTACT_HASH_URLS) { DBus2AbstractField(context, valueIter, details, FOLKS_TYPE_URL_FIELD_DETAILS, FOLKS_PERSONA_DETAIL_URLS, reinterpret_cast(folks_url_field_details_new)); } else if (key == CONTACT_HASH_NOTES) { DBus2SimpleAbstractField(context, valueIter, details, FOLKS_TYPE_NOTE_FIELD_DETAILS, FOLKS_PERSONA_DETAIL_NOTES, reinterpret_cast(folks_note_field_details_new)); } else if (key == CONTACT_HASH_ROLES) { DBus2Role(context, valueIter, details); } else if (key == CONTACT_HASH_GROUPS) { DBus2Groups(context, valueIter, details); } else if (key == CONTACT_HASH_ADDRESSES) { DBus2Addr(context, valueIter, details); } } } struct Pending { Pending(const Result &result, FolksPersona *persona) : m_result(result), m_persona(persona, ADD_REF), m_current(0) {} Result m_result; FolksPersonaCXX m_persona; typedef boost::function AsyncDone; typedef boost::function Prepare; typedef boost::tuple > Change; typedef std::vector Changes; Changes m_changes; size_t m_current; }; static bool GeeCollectionEqual(GeeCollection *a, GeeCollection *b) { return gee_collection_get_size(a) == gee_collection_get_size(b) && gee_collection_contains_all(a, b); } /** * Gets called by event loop. All errors must be reported back to the caller. */ static void Details2PersonaStep(const GError *gerror, const boost::shared_ptr &pending) throw () { try { if (gerror) { GErrorCXX::throwError("modifying property", gerror); } size_t current = pending->m_current++; if (current < pending->m_changes.size()) { // send next change, as determined earlier Pending::Change &change = pending->m_changes[current]; SE_LOG_DEBUG(NULL, "modification step %d/%d: %s", (int)current, (int)pending->m_changes.size(), boost::get<1>(change)); boost::get<0>(change)(new Pending::AsyncDone(boost::bind(Details2PersonaStep, _1, pending))); } else { pending->m_result.done(); } } catch (...) { // Tell caller about specific error. pending->m_result.failed(); } } void Details2Persona(const Result &result, const PersonaDetails &details, FolksPersona *persona) { boost::shared_ptr pending(new Pending(result, persona)); #define PUSH_CHANGE(_prepare) \ SE_LOG_DEBUG(NULL, "queueing new change: %s", #_prepare); \ pending->m_changes.push_back(Pending::Change(boost::bind(_prepare, \ details, \ value, \ SYNCEVO_GLIB_CALL_ASYNC_CXX(_prepare)::handleGLibResult, \ _1), \ #_prepare, \ tracker)) const GValue *gvalue; boost::shared_ptr tracker; gvalue = static_cast(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_FULL_NAME))); { const gchar *value; if (gvalue) { value = g_value_get_string(gvalue); tracker = boost::shared_ptr(g_strdup(value), g_free); } else { value = NULL; } FolksNameDetails *details = FOLKS_NAME_DETAILS(persona); if (g_strcmp0(value, folks_name_details_get_full_name(details))) { PUSH_CHANGE(folks_name_details_change_full_name); } } gvalue = static_cast(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_STRUCTURED_NAME))); { FolksStructuredName *value; if (gvalue) { value = FOLKS_STRUCTURED_NAME(g_value_get_object(gvalue)); tracker = boost::shared_ptr(g_object_ref(value), g_object_unref); } else { tracker = boost::shared_ptr(folks_structured_name_new("", "", "", "", ""), g_object_unref); value = static_cast(tracker.get()); } FolksNameDetails *details = FOLKS_NAME_DETAILS(persona); if (!folks_structured_name_equal(value, folks_name_details_get_structured_name(details))) { PUSH_CHANGE(folks_name_details_change_structured_name); } } gvalue = static_cast(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_AVATAR))); { FolksAvatarDetails *details = FOLKS_AVATAR_DETAILS(persona); GLoadableIcon *value; if (gvalue) { value = G_LOADABLE_ICON(g_value_get_object(gvalue)); tracker = boost::shared_ptr(g_object_ref(value), g_object_unref); } else { tracker = boost::shared_ptr(); value = NULL; } InitStateString newPath = GDBusCXX::ExtractFilePath(value); InitStateString oldPath = GDBusCXX::ExtractFilePath(folks_avatar_details_get_avatar(details)); if (newPath.wasSet() != oldPath.wasSet() || newPath != oldPath) { PUSH_CHANGE(folks_avatar_details_change_avatar); } } gvalue = static_cast(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_BIRTHDAY))); { GDateTime *value; if (gvalue) { value = static_cast(g_value_get_boxed(gvalue)); tracker = boost::shared_ptr(g_date_time_ref(value), g_date_time_unref); } else { tracker = boost::shared_ptr(); value = NULL; } FolksBirthdayDetails *details = FOLKS_BIRTHDAY_DETAILS(persona); GDateTime *old = folks_birthday_details_get_birthday(details); if ((value == NULL && old != NULL) || (value != NULL && old == NULL) || !g_date_time_equal(value, old)) { PUSH_CHANGE(folks_birthday_details_change_birthday); } } gvalue = static_cast(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_LOCATION))); { FolksLocation *value; if (gvalue) { value = static_cast(g_value_get_object(gvalue)); tracker = boost::shared_ptr(g_object_ref(value), g_object_unref); } else { tracker = boost::shared_ptr(); value = NULL; } FolksLocationDetails *details = FOLKS_LOCATION_DETAILS(persona); FolksLocation *old = folks_location_details_get_location(details); if ((value == NULL && old != NULL) || (value != NULL && old == NULL) || (value && old && (value->latitude != old->latitude || value->longitude != old->longitude))) { PUSH_CHANGE(folks_location_details_change_location); } } gvalue = static_cast(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_EMAIL_ADDRESSES))); { GeeSet *value; if (gvalue) { value = GEE_SET(g_value_get_object(gvalue)); tracker = boost::shared_ptr(g_object_ref(value), g_object_unref); } else { tracker = boost::shared_ptr(gee_hash_set_new(FOLKS_TYPE_EMAIL_FIELD_DETAILS, g_object_ref, g_object_unref, FolksAbstractFieldDetailsHash, NULL, NULL, FolksAbstractFieldDetailsEqual, NULL, NULL), g_object_unref); value = static_cast(tracker.get()); } FolksEmailDetails *details = FOLKS_EMAIL_DETAILS(persona); if (!GeeCollectionEqual(GEE_COLLECTION(value), GEE_COLLECTION(folks_email_details_get_email_addresses(details)))) { PUSH_CHANGE(folks_email_details_change_email_addresses); } } gvalue = static_cast(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_PHONE_NUMBERS))); { GeeSet *value; if (gvalue) { value = GEE_SET(g_value_get_object(gvalue)); tracker = boost::shared_ptr(g_object_ref(value), g_object_unref); } else { tracker = boost::shared_ptr(gee_hash_set_new(FOLKS_TYPE_PHONE_FIELD_DETAILS, g_object_ref, g_object_unref, FolksAbstractFieldDetailsHash, NULL, NULL, FolksAbstractFieldDetailsEqual, NULL, NULL), g_object_unref); value = static_cast(tracker.get()); } FolksPhoneDetails *details = FOLKS_PHONE_DETAILS(persona); if (!GeeCollectionEqual(GEE_COLLECTION(value), GEE_COLLECTION(folks_phone_details_get_phone_numbers(details)))) { PUSH_CHANGE(folks_phone_details_change_phone_numbers); } } gvalue = static_cast(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_URLS))); { GeeSet *value; if (gvalue) { value = GEE_SET(g_value_get_object(gvalue)); tracker = boost::shared_ptr(g_object_ref(value), g_object_unref); } else { tracker = boost::shared_ptr(gee_hash_set_new(FOLKS_TYPE_URL_FIELD_DETAILS, g_object_ref, g_object_unref, FolksAbstractFieldDetailsHash, NULL, NULL, FolksAbstractFieldDetailsEqual, NULL, NULL), g_object_unref); value = static_cast(tracker.get()); } FolksUrlDetails *details = FOLKS_URL_DETAILS(persona); if (!GeeCollectionEqual(GEE_COLLECTION(value), GEE_COLLECTION(folks_url_details_get_urls(details)))) { PUSH_CHANGE(folks_url_details_change_urls); } } gvalue = static_cast(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_NOTES))); { // At the moment the comparison fails and tiggers a write on each update?! GeeSet *value; if (gvalue) { value = GEE_SET(g_value_get_object(gvalue)); tracker = boost::shared_ptr(g_object_ref(value), g_object_unref); } else { tracker = boost::shared_ptr(gee_hash_set_new(FOLKS_TYPE_NOTE_FIELD_DETAILS, g_object_ref, g_object_unref, FolksAbstractFieldDetailsHash, NULL, NULL, FolksAbstractFieldDetailsEqual, NULL, NULL), g_object_unref); value = static_cast(tracker.get()); } FolksNoteDetails *details = FOLKS_NOTE_DETAILS(persona); if (!GeeCollectionEqual(GEE_COLLECTION(value), GEE_COLLECTION(folks_note_details_get_notes(details)))) { PUSH_CHANGE(folks_note_details_change_notes); } } gvalue = static_cast(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_ROLES))); { GeeSet *value; if (gvalue) { value = GEE_SET(g_value_get_object(gvalue)); tracker = boost::shared_ptr(g_object_ref(value), g_object_unref); } else { tracker = boost::shared_ptr(gee_hash_set_new(FOLKS_TYPE_ROLE_FIELD_DETAILS, g_object_ref, g_object_unref, FolksAbstractFieldDetailsHash, NULL, NULL, FolksAbstractFieldDetailsEqual, NULL, NULL), g_object_unref); value = static_cast(tracker.get()); } FolksRoleDetails *details = FOLKS_ROLE_DETAILS(persona); if (!GeeCollectionEqual(GEE_COLLECTION(value), GEE_COLLECTION(folks_role_details_get_roles(details)))) { PUSH_CHANGE(folks_role_details_change_roles); } } gvalue = static_cast(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_GROUPS))); { GeeSet *value; if (gvalue) { value = GEE_SET(g_value_get_object(gvalue)); tracker = boost::shared_ptr(g_object_ref(value), g_object_unref); } else { tracker = boost::shared_ptr(gee_hash_set_new(G_TYPE_STRING, (GBoxedCopyFunc)g_strdup, g_free, NULL, NULL, NULL, NULL, NULL, NULL), g_object_unref); value = static_cast(tracker.get()); } FolksGroupDetails *details = FOLKS_GROUP_DETAILS(persona); if (!GeeCollectionEqual(GEE_COLLECTION(value), GEE_COLLECTION(folks_group_details_get_groups(details)))) { PUSH_CHANGE(folks_group_details_change_groups); } } gvalue = static_cast(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_POSTAL_ADDRESSES))); { GeeSet *value; if (gvalue) { value = GEE_SET(g_value_get_object(gvalue)); tracker = boost::shared_ptr(g_object_ref(value), g_object_unref); } else { tracker = boost::shared_ptr(gee_hash_set_new(FOLKS_TYPE_POSTAL_ADDRESS_FIELD_DETAILS, g_object_ref, g_object_unref, FolksAbstractFieldDetailsHash, NULL, NULL, FolksAbstractFieldDetailsEqual, NULL, NULL), g_object_unref); value = static_cast(tracker.get()); } FolksPostalAddressDetails *details = FOLKS_POSTAL_ADDRESS_DETAILS(persona); if (!GeeCollectionEqual(GEE_COLLECTION(value), GEE_COLLECTION(folks_postal_address_details_get_postal_addresses(details)))) { PUSH_CHANGE(folks_postal_address_details_change_postal_addresses); } } Details2PersonaStep(NULL, pending); } void FolksIndividual2DBus(const FolksIndividualCXX &individual, GDBusCXX::builder_type &builder) { g_variant_builder_open(&builder, G_VARIANT_TYPE(INDIVIDUAL_DICT)); // dict FolksNameDetails *name = FOLKS_NAME_DETAILS(individual.get()); SerializeFolks(builder, name, folks_name_details_get_full_name, CONTACT_HASH_FULL_NAME); SerializeFolks(builder, name, folks_name_details_get_nickname, CONTACT_HASH_NICKNAME); SerializeFolks(builder, name, folks_name_details_get_structured_name, CONTACT_HASH_STRUCTURED_NAME); // gconstpointer folks_abstract_field_details_get_value (FolksAbstractFieldDetails* self); FolksAliasDetails *alias = FOLKS_ALIAS_DETAILS(individual.get()); SerializeFolks(builder, alias, folks_alias_details_get_alias, CONTACT_HASH_ALIAS); FolksAvatarDetails *avatar = FOLKS_AVATAR_DETAILS(individual.get()); SerializeFolks(builder, avatar, folks_avatar_details_get_avatar, CONTACT_HASH_PHOTO); FolksBirthdayDetails *birthday = FOLKS_BIRTHDAY_DETAILS(individual.get()); SerializeFolks(builder, birthday, folks_birthday_details_get_birthday, CONTACT_HASH_BIRTHDAY); // const gchar* folks_birthday_details_get_calendar_event_id (FolksBirthdayDetails* self); FolksLocationDetails *location = FOLKS_LOCATION_DETAILS(individual.get()); SerializeFolks(builder, location, folks_location_details_get_location, CONTACT_HASH_LOCATION); FolksEmailDetails *emails = FOLKS_EMAIL_DETAILS(individual.get()); SerializeFolks(builder, emails, folks_email_details_get_email_addresses, (GeeCollCXX*)NULL, CONTACT_HASH_EMAILS); FolksPhoneDetails *phones = FOLKS_PHONE_DETAILS(individual.get()); SerializeFolks(builder, phones, folks_phone_details_get_phone_numbers, (GeeCollCXX*)NULL, CONTACT_HASH_PHONES); FolksUrlDetails *urls = FOLKS_URL_DETAILS(individual.get()); SerializeFolks(builder, urls, folks_url_details_get_urls, (GeeCollCXX*)NULL, CONTACT_HASH_URLS); // Doesn't work like this, folks_im_details_get_im_addresses returns // a GeeMultiMap, not a GeeList. Not required anyway. // FolksImDetails *im = FOLKS_IM_DETAILS(individual.get()); // SerializeFolks(builder, im, folks_im_details_get_im_addresses, // (GeeCollCXX*)NULL, "im"); FolksNoteDetails *notes = FOLKS_NOTE_DETAILS(individual.get()); SerializeFolks(builder, notes, folks_note_details_get_notes, (GeeCollCXX*)NULL, CONTACT_HASH_NOTES); FolksPostalAddressDetails *postal = FOLKS_POSTAL_ADDRESS_DETAILS(individual.get()); SerializeFolks(builder, postal, folks_postal_address_details_get_postal_addresses, (GeeCollCXX*)NULL, CONTACT_HASH_ADDRESSES); FolksRoleDetails *roles = FOLKS_ROLE_DETAILS(individual.get()); SerializeFolks(builder, roles, folks_role_details_get_roles, (GeeCollCXX*)NULL, CONTACT_HASH_ROLES); FolksGroupDetails *groups = FOLKS_GROUP_DETAILS(individual.get()); SerializeFolks(builder, groups, folks_group_details_get_groups, (GeeStringCollection*)NULL, CONTACT_HASH_GROUPS); SerializeFolks(builder, individual.get(), folks_individual_get_personas, (GeeCollCXX*)NULL, CONTACT_HASH_SOURCE); SerializeFolks(builder, individual.get(), folks_individual_get_id, CONTACT_HASH_ID); #if 0 // Not exposed via D-Bus. FolksGender folks_gender_details_get_gender (FolksGenderDetails* self); GeeMultiMap* folks_web_service_details_get_web_service_addresses (FolksWebServiceDetails* self); guint folks_interaction_details_get_im_interaction_count (FolksInteractionDetails* self); GDateTime* folks_interaction_details_get_last_im_interaction_datetime (FolksInteractionDetails* self); guint folks_interaction_details_get_call_interaction_count (FolksInteractionDetails* self); GDateTime* folks_interaction_details_get_last_call_interaction_datetime (FolksInteractionDetails* self); GeeSet* folks_local_id_details_get_local_ids (FolksLocalIdDetails* self); const gchar* folks_presence_details_get_default_message_from_type (FolksPresenceType type); FolksPresenceType folks_presence_details_get_presence_type (FolksPresenceDetails* self); const gchar* folks_presence_details_get_presence_message (FolksPresenceDetails* self); const gchar* folks_presence_details_get_presence_status (FolksPresenceDetails* self); #endif g_variant_builder_close(&builder); // dict } SE_END_CXX syncevolution_1.4/src/dbus/server/pim/individual-traits.h000066400000000000000000000065351230021373600240130ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ /** * The D-Bus IPC binding for folks.h. Maps FolksIndividual to and * from the D-Bus dict described in pim-manager-api.txt. */ #ifndef INCL_SYNCEVO_DBUS_SERVER_INDIVIDUAL_TRAITS #define INCL_SYNCEVO_DBUS_SERVER_INDIVIDUAL_TRAITS #include #include "gdbus-cxx-bridge.h" #include "../dbus-callbacks.h" #include "folks.h" #include SE_BEGIN_CXX void DBus2PersonaDetails(GDBusCXX::ExtractArgs &context, GDBusCXX::reader_type &iter, PersonaDetails &details); void FolksIndividual2DBus(const FolksIndividualCXX &individual, GDBusCXX::builder_type &builder); void Details2Persona(const Result &result, const PersonaDetails &details, FolksPersona *persona); SE_END_CXX namespace GDBusCXX { using namespace SyncEvo; /** * The mapping between the internal representation of a * FolksIndividual and D-Bus. Only a subset of the internal * properties are supported. * * The D-Bus side is a mapping from string keys to values which * are either plain text or another such mapping. Boost * cannot represent such a variant, so we cheat and only declare * std::string as content. It doesn't matter for the D-Bus * signature and decoding/encoding is done differently anyway. * * Like the rest of the code for the org._01 API, this code only * works with GDBus GIO. */ template <> struct dbus_traits : public dbus_traits< std::map > > { typedef FolksIndividualCXX host_type; typedef const FolksIndividualCXX &arg_type; static void append(GDBusCXX::builder_type &builder, arg_type individual) { FolksIndividual2DBus(individual, builder); } }; /** * The corresponding mapping from D-Bus to a GeeMap for add_persona_from_details. * See http://telepathy.freedesktop.org/doc/folks/vala/Folks.PersonaStore.add_persona_from_details.html * and http://telepathy.freedesktop.org/doc/folks-eds/vala/Edsf.PersonaStore.add_persona_from_details.html */ template <> struct dbus_traits : public dbus_traits< std::map > > { typedef PersonaDetails host_type; typedef const PersonaDetails &arg_type; static void get(ExtractArgs &context, GDBusCXX::reader_type &iter, host_type &individual) { DBus2PersonaDetails(context, iter, individual); } }; } #endif // INCL_SYNCEVO_DBUS_SERVER_INDIVIDUAL_TRAITS syncevolution_1.4/src/dbus/server/pim/locale-factory-boost.cpp000066400000000000000000001407161230021373600247420ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ /** * The boost::locale based implementation of locale-factory.h. */ #include "locale-factory.h" #include "folks.h" #include #include #include #include #include #include #include #include #include SE_GLIB_TYPE(EBookQuery, e_book_query) SE_BEGIN_CXX /** * Use higher levels to break ties between strings which are * considered equal at the lower levels. For example, "Façade" and * "facade" would be compared as equal when using only base * characters, but compare differently when also considering a higher * level which includes accents. * * The drawback of higher levels is that they are computationally more * expensive (transformation is slower and leads to longer transformed * strings, thus a longer string comparisons during compare). * * The default here pays attention to accents, case, and * punctuation. According to * http://userguide.icu-project.org/collation/concepts, it is required * for Japanese. */ static const boost::locale::collator_base::level_type DEFAULT_COLLATION_LEVEL = boost::locale::collator_base::quaternary; class CompareBoost : public IndividualCompare, private boost::noncopyable { std::locale m_locale; const boost::locale::collator &m_collator; std::auto_ptr m_trans; public: CompareBoost(const std::locale &locale); std::string transform(const char *string) const; std::string transform(const std::string &string) const; }; CompareBoost::CompareBoost(const std::locale &locale) : m_locale(locale), m_collator(std::use_facet< boost::locale::collator >(m_locale)) { std::string language = std::use_facet(m_locale).language(); if (language == "zh") { // Hard-code Pinyin sorting for all Chinese countries. // // There are three different ways of sorting Chinese and Western names: // 1. Sort Chinese characters in pinyin order, but separate from Latin // 2. Sort them interleaved with Latin, by the first character. // 3. Sort them fully interleaved with Latin. // Source: Mark Davis, ICU, http://sourceforge.net/mailarchive/forum.php?thread_name=CAJ2xs_GEnN-u3%3D%2B7P5puaF1%2BU__fX-4tuA-kEybThN9xsw577Q%40mail.gmail.com&forum_name=icu-support // // Either 2 or 3 is what apparently more people expect. Implementing 2 is // harder, whereas 3 fits into the "generate keys, compare keys" concept // of IndividualCompare, so we kind of arbitrarily implement that. SE_LOG_DEBUG(NULL, "enabling Pinyin"); UErrorCode status = U_ZERO_ERROR; icu::Transliterator *trans = icu::Transliterator::createInstance("Han-Latin", UTRANS_FORWARD, status); m_trans.reset(trans); if (U_FAILURE(status)) { SE_LOG_WARNING(NULL, "creating ICU Han-Latin Transliterator for Pinyin failed, error code %s; falling back to normal collation", u_errorName(status)); m_trans.reset(); } else if (!trans) { SE_LOG_WARNING(NULL, "creating ICU Han-Latin Transliterator for Pinyin failed, no error code; falling back to normal collation"); } } } std::string CompareBoost::transform(const char *string) const { if (!string) { return ""; } return transform(std::string(string)); } std::string CompareBoost::transform(const std::string &string) const { // TODO: use e_collator_generate_key if (m_trans.get()) { // std::string result; // m_trans->transliterate(icu::StringPiece(string), icu::StringByteSink(&result)); icu::UnicodeString buffer(string.c_str()); m_trans->transliterate(buffer); std::string result; buffer.toUTF8String(result); result = m_collator.transform(DEFAULT_COLLATION_LEVEL, result); return result; } else { return m_collator.transform(DEFAULT_COLLATION_LEVEL, string); } } class CompareFirstLastBoost : public CompareBoost { public: CompareFirstLastBoost(const std::locale &locale) : CompareBoost(locale) {} virtual void createCriteria(FolksIndividual *individual, Criteria_t &criteria) const { FolksStructuredName *fn = folks_name_details_get_structured_name(FOLKS_NAME_DETAILS(individual)); if (fn) { const char *family = folks_structured_name_get_family_name(fn); const char *given = folks_structured_name_get_given_name(fn); criteria.push_back(transform(given)); criteria.push_back(transform(family)); } } }; class CompareLastFirstBoost : public CompareBoost { public: CompareLastFirstBoost(const std::locale &locale) : CompareBoost(locale) {} virtual void createCriteria(FolksIndividual *individual, Criteria_t &criteria) const { FolksStructuredName *fn = folks_name_details_get_structured_name(FOLKS_NAME_DETAILS(individual)); if (fn) { const char *family = folks_structured_name_get_family_name(fn); const char *given = folks_structured_name_get_given_name(fn); criteria.push_back(transform(family)); criteria.push_back(transform(given)); } } }; class CompareFullnameBoost : public CompareBoost { public: CompareFullnameBoost(const std::locale &locale) : CompareBoost(locale) {} virtual void createCriteria(FolksIndividual *individual, Criteria_t &criteria) const { const char *fullname = folks_name_details_get_full_name(FOLKS_NAME_DETAILS(individual)); if (fullname) { criteria.push_back(transform(fullname)); } else { FolksStructuredName *fn = folks_name_details_get_structured_name(FOLKS_NAME_DETAILS(individual)); if (fn) { const char *given = folks_structured_name_get_given_name(fn); const char *middle = folks_structured_name_get_additional_names(fn); const char *family = folks_structured_name_get_family_name(fn); const char *suffix = folks_structured_name_get_suffixes(fn); std::string buffer; buffer.reserve(256); #define APPEND(_str) \ if (_str && *_str) { \ if (!buffer.empty()) { \ buffer += _str; \ } \ } APPEND(given); APPEND(middle); APPEND(family); APPEND(suffix); #undef APPEND criteria.push_back(transform(buffer)); } } } }; /** * Implements 'any-contains' and acts as utility base class * for the other text comparison operators. */ class AnyContainsBoost : public IndividualFilter { public: enum Mode { EXACT = 0, CASE_INSENSITIVE = 1<<0, ACCENT_INSENSITIVE = 1<<1, TRANSLITERATE = 1<<2, ALL = CASE_INSENSITIVE| ACCENT_INSENSITIVE| TRANSLITERATE| 0 }; AnyContainsBoost(const std::locale &locale, const std::string &searchValue, int mode) : m_locale(locale), // For performance reasons we use ICU directly and thus need // an ICU::Locale. // m_ICULocale(std::use_facet(m_locale).language().c_str(), // std::use_facet(m_locale).country().c_str(), // std::use_facet(m_locale).variant().c_str()), // m_collator(std::use_facet(locale)), m_searchValue(searchValue), m_mode(mode) { if (mode & TRANSLITERATE) { UErrorCode status = U_ZERO_ERROR; m_transliterator.reset(Transliterator::createInstance ("Any-Latin", UTRANS_FORWARD, status)); if (!m_transliterator || U_FAILURE(status)) { SE_LOG_WARNING(NULL, "creating ICU Any-Latin Transliterator failed, error code %s; falling back to not transliterating", u_errorName(status)); m_transliterator.reset(); mode ^= TRANSLITERATE; m_mode = mode; } } switch (mode) { case EXACT: break; default: m_searchValueTransformed = transform(m_searchValue); break; } m_searchValueTel = normalizePhoneText(m_searchValue.c_str()); } /** * Turn filter arguments into bit field. */ static int getFilterMode(const std::vector &terms, size_t start); /** simplify according to mode */ std::string transform(const char *in) const; std::string transform(const std::string &in) const { return transform(in.c_str()); } /** * The search text is not necessarily a full phone number, * therefore we cannot parse it with libphonenumber. Instead * do a sub-string search after telephone specific normalization, * to let the search ignore irrelevant formatting aspects: * * - Map ASCII characters to the corresponding digit. * - Reduce to just the digits before comparison (no spaces, no * punctuation). * * Example: +1-800-FOOBAR -> 1800366227 */ static std::string normalizePhoneText(const char *tel) { static const i18n::phonenumbers::PhoneNumberUtil &util(*i18n::phonenumbers::PhoneNumberUtil::GetInstance()); std::string res; char c; bool haveAlpha = false; while ((c = *tel) != '\0') { if (isdigit(c)) { res += c; } else if (isalpha(c)) { haveAlpha = true; res += c; } ++tel; } // Only scan through the string again if we really have to. if (haveAlpha) { util.ConvertAlphaCharactersInNumber(&res); } return res; } bool containsSearchText(const char *text) const { if (!text) { return false; } switch (m_mode) { case EXACT: return boost::contains(text, m_searchValue); break; default: { std::string transformed = transform(text); return boost::contains(transformed, m_searchValueTransformed); break; } } // not reached return false; } bool containsSearchTel(const char *text) const { std::string tel = normalizePhoneText(text); return boost::contains(tel, m_searchValueTel); } bool isSearchText(const char *text) const { if (!text) { return false; } switch (m_mode) { case EXACT: return boost::equals(text, m_searchValue); break; default: { std::string transformed = transform(text); return boost::equals(transformed, m_searchValueTransformed); break; } } // not reached return false; } bool isSearchTel(const char *text) const { std::string tel = normalizePhoneText(text); return boost::equals(tel, m_searchValueTel); } bool beginsWithSearchText(const char *text) const { if (!text) { return false; } switch (m_mode) { case EXACT: return boost::starts_with(text, m_searchValue); break; default: { std::string transformed = transform(text); return boost::starts_with(transformed, m_searchValueTransformed); break; } } // not reached return false; } bool beginsWithSearchTel(const char *text) const { std::string tel = normalizePhoneText(text); return boost::starts_with(tel, m_searchValueTel); } bool endsWithSearchText(const char *text) const { if (!text) { return false; } switch (m_mode) { case EXACT: return boost::ends_with(text, m_searchValue); break; default: { std::string transformed = transform(text); return boost::ends_with(transformed, m_searchValueTransformed); break; } } // not reached return false; } bool endsWithSearchTel(const char *text) const { std::string tel = normalizePhoneText(text); return boost::ends_with(tel, m_searchValueTel); } virtual bool matches(const IndividualData &data) const { FolksIndividual *individual = data.m_individual.get(); FolksNameDetails *name = FOLKS_NAME_DETAILS(individual); const char *fullname = folks_name_details_get_full_name(name); if (containsSearchText(fullname)) { return true; } const char *nickname = folks_name_details_get_nickname(name); if (containsSearchText(nickname)) { return true; } FolksStructuredName *fn = folks_name_details_get_structured_name(FOLKS_NAME_DETAILS(individual)); if (fn) { const char *given = folks_structured_name_get_given_name(fn); if (containsSearchText(given)) { return true; } const char *middle = folks_structured_name_get_additional_names(fn); if (containsSearchText(middle)) { return true; } const char *family = folks_structured_name_get_family_name(fn); if (containsSearchText(family)) { return true; } } FolksEmailDetails *emailDetails = FOLKS_EMAIL_DETAILS(individual); GeeSet *emails = folks_email_details_get_email_addresses(emailDetails); BOOST_FOREACH (FolksAbstractFieldDetails *email, GeeCollCXX(emails, ADD_REF)) { const gchar *value = reinterpret_cast(folks_abstract_field_details_get_value(email)); if (containsSearchText(value)) { return true; } } FolksPhoneDetails *phoneDetails = FOLKS_PHONE_DETAILS(individual); GeeSet *phones = folks_phone_details_get_phone_numbers(phoneDetails); BOOST_FOREACH (FolksAbstractFieldDetails *phone, GeeCollCXX(phones, ADD_REF)) { const gchar *value = reinterpret_cast(folks_abstract_field_details_get_value(phone)); if (containsSearchTel(value)) { return true; } } return false; } private: std::locale m_locale; // icu::Locale m_ICULocale; boost::shared_ptr m_transliterator; std::string m_searchValue; std::string m_searchValueTransformed; std::string m_searchValueTel; int m_mode; // const bool (*m_contains)(const std::string &, const std::string &, const std::locale &); }; std::string AnyContainsBoost::transform(const char *in) const { icu::UnicodeString unicode = icu::UnicodeString::fromUTF8(in); if (m_mode & TRANSLITERATE) { m_transliterator->transliterate(unicode); } if (m_mode & CASE_INSENSITIVE) { unicode.foldCase(); } std::string utf8; unicode.toUTF8String(utf8); if (m_mode & ACCENT_INSENSITIVE) { // Haven't found an easy way to do this with ICU. // Use e_util_utf8_remove_accents(), which also ensures // consistency with EDS. PlainGStr res = e_util_utf8_remove_accents(utf8.c_str()); return std::string(res); } else { return utf8; } } int AnyContainsBoost::getFilterMode(const std::vector &terms, size_t start) { int mode = ALL; for (size_t i = start; i < terms.size(); i++) { const std::string flag = LocaleFactory::getFilterString(terms[i], "any-contains flag"); if (flag == "case-sensitive") { mode &= ~CASE_INSENSITIVE; } else if (flag == "case-insensitive") { mode |= CASE_INSENSITIVE; } else if (flag == "accent-sensitive") { mode &= ~ACCENT_INSENSITIVE; } else if (flag == "accent-insensitive") { mode |= ACCENT_INSENSITIVE; } else if (flag == "no-transliteration") { mode &= ~TRANSLITERATE; } else if (flag == "transliteration") { mode |= TRANSLITERATE; } else { SE_THROW("unsupported filter flag: " + flag); } } return mode; } class FilterFullName : public AnyContainsBoost { bool (AnyContainsBoost::*m_operation)(const char *text) const; public: FilterFullName(const std::locale &locale, const std::string &searchValue, int mode, bool (AnyContainsBoost::*operation)(const char *text) const) : AnyContainsBoost(locale, searchValue, mode), m_operation(operation) { } virtual bool matches(const IndividualData &data) const { FolksIndividual *individual = data.m_individual.get(); FolksNameDetails *name = FOLKS_NAME_DETAILS(individual); const char *fullname = folks_name_details_get_full_name(name); return (this->*m_operation)(fullname); } }; class FilterNickname : public AnyContainsBoost { bool (AnyContainsBoost::*m_operation)(const char *text) const; public: FilterNickname(const std::locale &locale, const std::string &searchValue, int mode, bool (AnyContainsBoost::*operation)(const char *text) const) : AnyContainsBoost(locale, searchValue, mode), m_operation(operation) { } virtual bool matches(const IndividualData &data) const { FolksIndividual *individual = data.m_individual.get(); FolksNameDetails *name = FOLKS_NAME_DETAILS(individual); const char *fullname = folks_name_details_get_nickname(name); return (this->*m_operation)(fullname); } }; class FilterFamilyName : public AnyContainsBoost { bool (AnyContainsBoost::*m_operation)(const char *text) const; public: FilterFamilyName(const std::locale &locale, const std::string &searchValue, int mode, bool (AnyContainsBoost::*operation)(const char *text) const) : AnyContainsBoost(locale, searchValue, mode), m_operation(operation) { } virtual bool matches(const IndividualData &data) const { FolksIndividual *individual = data.m_individual.get(); FolksStructuredName *fn = folks_name_details_get_structured_name(FOLKS_NAME_DETAILS(individual)); if (fn) { const char *name = folks_structured_name_get_family_name(fn); return (this->*m_operation)(name); } else { return false; } } }; class FilterGivenName : public AnyContainsBoost { bool (AnyContainsBoost::*m_operation)(const char *text) const; public: FilterGivenName(const std::locale &locale, const std::string &searchValue, int mode, bool (AnyContainsBoost::*operation)(const char *text) const) : AnyContainsBoost(locale, searchValue, mode), m_operation(operation) { } virtual bool matches(const IndividualData &data) const { FolksIndividual *individual = data.m_individual.get(); FolksStructuredName *fn = folks_name_details_get_structured_name(FOLKS_NAME_DETAILS(individual)); if (fn) { const char *name = folks_structured_name_get_given_name(fn); return (this->*m_operation)(name); } else { return false; } } }; class FilterAdditionalName : public AnyContainsBoost { bool (AnyContainsBoost::*m_operation)(const char *text) const; public: FilterAdditionalName(const std::locale &locale, const std::string &searchValue, int mode, bool (AnyContainsBoost::*operation)(const char *text) const) : AnyContainsBoost(locale, searchValue, mode), m_operation(operation) { } virtual bool matches(const IndividualData &data) const { FolksIndividual *individual = data.m_individual.get(); FolksStructuredName *fn = folks_name_details_get_structured_name(FOLKS_NAME_DETAILS(individual)); if (fn) { const char *name = folks_structured_name_get_additional_names(fn); return (this->*m_operation)(name); } else { return false; } } }; class FilterEmails : public AnyContainsBoost { bool (AnyContainsBoost::*m_operation)(const char *text) const; public: FilterEmails(const std::locale &locale, const std::string &searchValue, int mode, bool (AnyContainsBoost::*operation)(const char *text) const) : AnyContainsBoost(locale, searchValue, mode), m_operation(operation) { } virtual bool matches(const IndividualData &data) const { FolksIndividual *individual = data.m_individual.get(); FolksEmailDetails *emailDetails = FOLKS_EMAIL_DETAILS(individual); GeeSet *emails = folks_email_details_get_email_addresses(emailDetails); BOOST_FOREACH (FolksAbstractFieldDetails *email, GeeCollCXX(emails, ADD_REF)) { const gchar *value = reinterpret_cast(folks_abstract_field_details_get_value(email)); if ((this->*m_operation)(value)) { return true; } } return false; } }; class FilterTel : public AnyContainsBoost { bool (AnyContainsBoost::*m_operation)(const char *text) const; public: FilterTel(const std::locale &locale, const std::string &searchValue, bool (AnyContainsBoost::*operation)(const char *text) const) : AnyContainsBoost(locale, searchValue, 0 /* doesn't matter */), m_operation(operation) { } virtual bool matches(const IndividualData &data) const { FolksIndividual *individual = data.m_individual.get(); FolksPhoneDetails *phoneDetails = FOLKS_PHONE_DETAILS(individual); GeeSet *phones = folks_phone_details_get_phone_numbers(phoneDetails); BOOST_FOREACH (FolksAbstractFieldDetails *phone, GeeCollCXX(phones, ADD_REF)) { const gchar *value = reinterpret_cast(folks_abstract_field_details_get_value(phone)); if ((this->*m_operation)(value)) { return true; } } return false; } }; class FilterAddr : public AnyContainsBoost { protected: bool (AnyContainsBoost::*m_operation)(const char *text) const; public: FilterAddr(const std::locale &locale, const std::string &searchValue, int mode, bool (AnyContainsBoost::*operation)(const char *text) const) : AnyContainsBoost(locale, searchValue, mode), m_operation(operation) { } virtual bool matches(const IndividualData &data) const { FolksIndividual *individual = data.m_individual.get(); FolksPostalAddressDetails *addressDetails = FOLKS_POSTAL_ADDRESS_DETAILS(individual); GeeSet *addresses = folks_postal_address_details_get_postal_addresses(addressDetails); BOOST_FOREACH (FolksPostalAddressFieldDetails *address, GeeCollCXX(addresses, ADD_REF)) { const FolksPostalAddress *value = reinterpret_cast(folks_abstract_field_details_get_value(FOLKS_ABSTRACT_FIELD_DETAILS(address))); if (matchesAddr(value)) { return true; } } return false; } virtual bool matchesAddr(const FolksPostalAddress *addr) const = 0; }; class FilterAddrPOBox : public FilterAddr { public: FilterAddrPOBox(const std::locale &locale, const std::string &searchValue, int mode, bool (AnyContainsBoost::*operation)(const char *text) const) : FilterAddr(locale, searchValue, mode, operation) { } virtual bool matchesAddr(const FolksPostalAddress *addr) const { const char *attr = folks_postal_address_get_po_box(const_cast(addr)); return (this->*m_operation)(attr); } }; class FilterAddrExtension : public FilterAddr { public: FilterAddrExtension(const std::locale &locale, const std::string &searchValue, int mode, bool (AnyContainsBoost::*operation)(const char *text) const) : FilterAddr(locale, searchValue, mode, operation) { } virtual bool matchesAddr(const FolksPostalAddress *addr) const { const char *attr = folks_postal_address_get_extension(const_cast(addr)); return (this->*m_operation)(attr); } }; class FilterAddrStreet : public FilterAddr { public: FilterAddrStreet(const std::locale &locale, const std::string &searchValue, int mode, bool (AnyContainsBoost::*operation)(const char *text) const) : FilterAddr(locale, searchValue, mode, operation) { } virtual bool matchesAddr(const FolksPostalAddress *addr) const { const char *attr = folks_postal_address_get_street(const_cast(addr)); return (this->*m_operation)(attr); } }; class FilterAddrLocality : public FilterAddr { public: FilterAddrLocality(const std::locale &locale, const std::string &searchValue, int mode, bool (AnyContainsBoost::*operation)(const char *text) const) : FilterAddr(locale, searchValue, mode, operation) { } virtual bool matchesAddr(const FolksPostalAddress *addr) const { const char *attr = folks_postal_address_get_locality(const_cast(addr)); return (this->*m_operation)(attr); } }; class FilterAddrRegion : public FilterAddr { public: FilterAddrRegion(const std::locale &locale, const std::string &searchValue, int mode, bool (AnyContainsBoost::*operation)(const char *text) const) : FilterAddr(locale, searchValue, mode, operation) { } virtual bool matchesAddr(const FolksPostalAddress *addr) const { const char *attr = folks_postal_address_get_region(const_cast(addr)); return (this->*m_operation)(attr); } }; class FilterAddrPostalCode : public FilterAddr { public: FilterAddrPostalCode(const std::locale &locale, const std::string &searchValue, int mode, bool (AnyContainsBoost::*operation)(const char *text) const) : FilterAddr(locale, searchValue, mode, operation) { } virtual bool matchesAddr(const FolksPostalAddress *addr) const { const char *attr = folks_postal_address_get_postal_code(const_cast(addr)); return (this->*m_operation)(attr); } }; class FilterAddrCountry : public FilterAddr { public: FilterAddrCountry(const std::locale &locale, const std::string &searchValue, int mode, bool (AnyContainsBoost::*operation)(const char *text) const) : FilterAddr(locale, searchValue, mode, operation) { } virtual bool matchesAddr(const FolksPostalAddress *addr) const { const char *attr = folks_postal_address_get_country(const_cast(addr)); return (this->*m_operation)(attr); } }; /** * Search value must be a valid caller ID (with or without a country * code). The telephone numbers in the contacts may or may not be * valid; only valid ones will match. The user is expected to clean up * that data to get exact matches for the others. * * The matching uses the same semantic as EQUALS_NATIONAL_PHONE_NUMBER: * - If both numbers have an explicit country code, that code must be * the same for a match. * - If one or both numbers have no country code, matching the national * part is enough for a match. */ class PhoneStartsWith : public IndividualFilter { public: PhoneStartsWith(const std::locale &m_locale, const std::string &tel) : m_phoneNumberUtil(*i18n::phonenumbers::PhoneNumberUtil::GetInstance()), m_simpleEDSSearch(getenv("SYNCEVOLUTION_PIM_EDS_SUBSTRING") || !e_phone_number_is_supported()), m_country(std::use_facet(m_locale).country()) { i18n::phonenumbers::PhoneNumber number; switch (m_phoneNumberUtil.Parse(tel, m_country, &number)) { case i18n::phonenumbers::PhoneNumberUtil::NO_PARSING_ERROR: // okay break; case i18n::phonenumbers::PhoneNumberUtil::INVALID_COUNTRY_CODE_ERROR: SE_THROW("boost locale factory: invalid country code"); break; case i18n::phonenumbers::PhoneNumberUtil::NOT_A_NUMBER: SE_THROW("boost locale factory: not a caller ID: " + tel); break; case i18n::phonenumbers::PhoneNumberUtil::TOO_SHORT_AFTER_IDD: SE_THROW("boost locale factory: too short after IDD: " + tel); break; case i18n::phonenumbers::PhoneNumberUtil::TOO_SHORT_NSN: SE_THROW("boost locale factory: too short NSN: " + tel); break; case i18n::phonenumbers::PhoneNumberUtil::TOO_LONG_NSN: SE_THROW("boost locale factory: too long NSN: " + tel); break; } m_number.m_countryCode = number.country_code(); m_number.m_nationalNumber = number.national_number(); } virtual bool matches(const IndividualData &data) const { BOOST_FOREACH(const SimpleE164 &number, data.m_precomputed.m_phoneNumbers) { // National part must always match, country code only if // set explicitly in both (NSN_MATCH in libphonenumber, // EQUALS_NATIONAL_PHONE_NUMBER in EDS). if (number.m_nationalNumber == m_number.m_nationalNumber && (!number.m_countryCode || !m_number.m_countryCode || number.m_countryCode == m_number.m_countryCode)) { return true; } } return false; } virtual std::string getEBookFilter() const { std::string tel = m_number.toString(); size_t len = std::min((size_t)4, tel.size()); EBookQueryCXX query(m_simpleEDSSearch ? // A suffix match with a limited number of digits is most // likely to find the right contacts. e_book_query_field_test(E_CONTACT_TEL, E_BOOK_QUERY_ENDS_WITH, tel.substr(tel.size() - len, len).c_str()) : // We use EQUALS_NATIONAL_PHONE_NUMBER // instead of EQUALS_PHONE_NUMBER here, // because it will also match contacts // were the country code was not set // explicitly. EQUALS_PHONE_NUMBER would // do a stricter comparison and not match // those. // // If the contact has a country code set, // then EQUALS_NATIONAL_PHONE_NUMBER will // check that and not return a false match // if the country code is different. // // We try to pass the E164 string here. If // the search term had no country code, // that's a bit difficult because we can't // just add the default country code. // That would break the // NATIONAL_PHONE_NUMBER semantic because // EDS wouldn't know that the search term // had no country code. We resort to the // format of SimpleE164.toString(), which // is passing the national number // formatted as string. e_book_query_field_test(E_CONTACT_TEL, E_BOOK_QUERY_EQUALS_NATIONAL_PHONE_NUMBER, tel.c_str()), TRANSFER_REF); PlainGStr filter(e_book_query_to_string(query.get())); return filter.get(); } private: const i18n::phonenumbers::PhoneNumberUtil &m_phoneNumberUtil; bool m_simpleEDSSearch; std::string m_country; SimpleE164 m_number; }; class PhoneNumberLogger : public i18n::phonenumbers::Logger { const char *getPrefix() { switch (level()) { case i18n::phonenumbers::LOG_FATAL: return "phonenumber fatal"; break; case i18n::phonenumbers::LOG_ERROR: return "phonenumber error"; break; case i18n::phonenumbers::LOG_WARNING: return "phonenumber warning"; break; case i18n::phonenumbers::LOG_INFO: return "phonenumber info"; break; case i18n::phonenumbers::LOG_DEBUG: return "phonenumber debug"; break; default: return "phonenumber ???"; break; } } public: virtual void WriteMessage(const std::string &msg) { SE_LOG(getPrefix(), level() == i18n::phonenumbers::LOG_FATAL ? SyncEvo::Logger::ERROR : SyncEvo::Logger::DEBUG, "%s", msg.c_str()); } }; class LocaleFactoryBoost : public LocaleFactory { const i18n::phonenumbers::PhoneNumberUtil &m_phoneNumberUtil; bool m_edsSupportsPhoneNumbers; std::locale m_locale; std::string m_country; std::string m_defaultCountryCode; PhoneNumberLogger m_logger; public: LocaleFactoryBoost() : m_phoneNumberUtil(*i18n::phonenumbers::PhoneNumberUtil::GetInstance()), m_edsSupportsPhoneNumbers(e_phone_number_is_supported() && !getenv("SYNCEVOLUTION_PIM_EDS_NO_E164")), m_locale(genLocale()), m_country(std::use_facet(m_locale).country()), m_defaultCountryCode(StringPrintf("+%d", m_phoneNumberUtil.GetCountryCodeForRegion(m_country))) { // Redirect output of libphonenumber and make it a bit quieter // than it is by default. We map fatal libphonenumber errors // to ERROR and everything else to DEBUG. i18n::phonenumbers::PhoneNumberUtil::SetLogger(&m_logger); } static std::locale genLocale() { // Get current locale from environment. Configure the // generated locale so that it supports what we need and // nothing more. boost::locale::generator gen; gen.characters(boost::locale::char_facet); gen.categories(boost::locale::collation_facet | boost::locale::convert_facet | boost::locale::information_facet); // Hard-code "phonebook" collation for certain languages // where we know that it is desirable. We could use it // in all cases, except that ICU has a bug where it does not // fall back properly to the base collation. See // http://sourceforge.net/mailarchive/message.php?msg_id=30802924 // and http://bugs.icu-project.org/trac/ticket/10149 std::locale locale = gen(""); std::string name = std::use_facet(locale).name(); std::string language = std::use_facet(locale).language(); std::string country = std::use_facet(locale).country(); SE_LOG_DEV(NULL, "PIM Manager running with locale %s = language %s in country %s", name.c_str(), language.c_str(), country.c_str()); if (language == "de" || language == "fi") { SE_LOG_DEV(NULL, "enabling phonebook collation for language %s", language.c_str()); locale = gen(name + "@collation=phonebook"); } return locale; } virtual boost::shared_ptr createCompare(const std::string &order) { boost::shared_ptr res; if (order == "first/last") { res.reset(new CompareFirstLastBoost(m_locale)); } else if (order == "last/first") { res.reset(new CompareLastFirstBoost(m_locale)); } else if (order == "fullname") { res.reset(new CompareFullnameBoost(m_locale)); } else { SE_THROW("boost locale factory: sort order '" + order + "' not supported"); } return res; } virtual boost::shared_ptr createFilter(const Filter_t &filter, int level) { boost::shared_ptr res; try { const std::vector &terms = getFilterArray(filter, "array of terms"); // Only handle arrays where the first entry is a string // that we recognize. All other cases are handled by the generic // LocaleFactory. if (!terms.empty() && boost::get(&terms[0])) { const std::string &operation = getFilterString(terms[0], "operation name"); // Pick default operation. Will be replaced with // telephone-specific operation once we know that the // field is 'phones/value'. bool (AnyContainsBoost::*func)(const char *text) const = NULL; if (operation == "contains") { func = &AnyContainsBoost::containsSearchText; } else if (operation == "is") { func = &AnyContainsBoost::isSearchText; } else if (operation == "begins-with") { func = &AnyContainsBoost::beginsWithSearchText; } else if (operation == "ends-with") { func = &AnyContainsBoost::endsWithSearchText; } if (func) { switch (terms.size()) { case 1: SE_THROW("missing field name and search value"); break; case 2: SE_THROW("missing search value"); break; } const std::string &field = getFilterString(terms[1], "search field"); const std::string &value = getFilterString(terms[2], "search string"); if (field == "phones/value") { if (terms.size() > 3) { SE_THROW("Additional entries after 'phones/value' field filter not allowed."); } // Use the telephone specific functions. res.reset(new FilterTel(m_locale, value, func == &AnyContainsBoost::containsSearchText ? &AnyContainsBoost::containsSearchTel : func == &AnyContainsBoost::isSearchText ? &AnyContainsBoost::isSearchTel : func == &AnyContainsBoost::beginsWithSearchText ? &AnyContainsBoost::beginsWithSearchTel : func == &AnyContainsBoost::endsWithSearchText ? &AnyContainsBoost::endsWithSearchTel : func)); } else { int mode = AnyContainsBoost::getFilterMode(terms, 3); if (field == "full-name") { res.reset(new FilterFullName(m_locale, value, mode, func)); } else if (field == "nickname") { res.reset(new FilterNickname(m_locale, value, mode, func)); } else if (field == "structured-name/family") { res.reset(new FilterFamilyName(m_locale, value, mode, func)); } else if (field == "structured-name/given") { res.reset(new FilterGivenName(m_locale, value, mode, func)); } else if (field == "structured-name/additional") { res.reset(new FilterAdditionalName(m_locale, value, mode, func)); } else if (field == "emails/value") { res.reset(new FilterEmails(m_locale, value, mode, func)); } else if (field == "addresses/po-box") { res.reset(new FilterAddrPOBox(m_locale, value, mode, func)); } else if (field == "addresses/extension") { res.reset(new FilterAddrExtension(m_locale, value, mode, func)); } else if (field == "addresses/street") { res.reset(new FilterAddrStreet(m_locale, value, mode, func)); } else if (field == "addresses/locality") { res.reset(new FilterAddrLocality(m_locale, value, mode, func)); } else if (field == "addresses/region") { res.reset(new FilterAddrRegion(m_locale, value, mode, func)); } else if (field == "addresses/postal-code") { res.reset(new FilterAddrPostalCode(m_locale, value, mode, func)); } else if (field == "addresses/country") { res.reset(new FilterAddrCountry(m_locale, value, mode, func)); } else { SE_THROW("Unknown field name: " + field); } } } else if (operation == "any-contains") { if (terms.size() < 2) { SE_THROW("missing search value"); } const std::string &value = getFilterString(terms[1], "search string"); int mode = AnyContainsBoost::getFilterMode(terms, 2); res.reset(new AnyContainsBoost(m_locale, value, mode)); } else if (operation == "phone") { if (terms.size() != 2) { SE_THROW("'phone' filter needs exactly one parameter."); } const std::string &value = getFilterString(terms[1], "search string"); res.reset(new PhoneStartsWith(m_locale, value)); } } } catch (const Exception &ex) { handleFilterException(filter, level, &ex.m_file, ex.m_line); } catch (...) { handleFilterException(filter, level, NULL, 0); } // Let base class handle it if we didn't recognize the operation. return res ? res : LocaleFactory::createFilter(filter, level); } virtual bool precompute(FolksIndividual *individual, Precomputed &precomputed) const { LocaleFactory::Precomputed old; std::swap(old, precomputed); FolksPhoneDetails *phoneDetails = FOLKS_PHONE_DETAILS(individual); GeeSet *phones = folks_phone_details_get_phone_numbers(phoneDetails); precomputed.m_phoneNumbers.reserve(gee_collection_get_size(GEE_COLLECTION(phones))); BOOST_FOREACH (FolksAbstractFieldDetails *phone, GeeCollCXX(phones, ADD_REF)) { const gchar *value = reinterpret_cast(folks_abstract_field_details_get_value(phone)); if (value) { if (m_edsSupportsPhoneNumbers) { // Check X-EVOLUTION-E164 (made lowercase by folks!). // // It has the format ,, // where happens to be in quotation marks. // This ends up being split into individual values which // are returned in random order by folks (a bug?!). // // Example: TEL;X-EVOLUTION-E164=891234,"+49":+49-89-1234 // => value '+49-89-1234', params [ '+49', '891234' ]. // // We restore the right order by sorting, which puts the // country code first, and then joining. GeeCollectionCXX coll(folks_abstract_field_details_get_parameter_values(phone, "x-evolution-e164"), TRANSFER_REF); if (coll) { std::vector components; components.reserve(2); BOOST_FOREACH (const gchar *component, GeeStringCollection(coll)) { // Empty component represents an unset // country code. Note that it is not // certain whether we get to see the empty // component. At the moment (EDS 3.7, // folks 0.9.1), someone swallows it. components.push_back(component); } if (!components.empty()) { // Only one component? We must still miss the country code. if (components.size() == 1) { components.push_back(""); } std::sort(components.begin(), components.end()); try { SimpleE164 number; number.m_countryCode = components[0].empty() ? 0 : boost::lexical_cast(components[0]); number.m_nationalNumber = components[1].empty() ? 0 : boost::lexical_cast(components[1]); precomputed.m_phoneNumbers.push_back(number); } catch (const boost::bad_lexical_cast &ex) { SE_LOG_WARNING(NULL, "ignoring malformed X-EVOLUTION-E164 (sorted): %s", boost::join(components, ", ").c_str()); } } } // Either EDS had a normalized value or there is none because // the value is not a phone number. No need to try parsing again. continue; } i18n::phonenumbers::PhoneNumber number; i18n::phonenumbers::PhoneNumberUtil::ErrorType error = m_phoneNumberUtil.Parse(value, m_country, &number); if (error == i18n::phonenumbers::PhoneNumberUtil::NO_PARSING_ERROR) { SimpleE164 e164; e164.m_countryCode = number.country_code(); e164.m_nationalNumber = number.national_number(); precomputed.m_phoneNumbers.push_back(e164); } } } // Now check if any phone number changed. return old != precomputed; } }; boost::shared_ptr LocaleFactory::createFactory() { return boost::shared_ptr(new LocaleFactoryBoost()); } SE_END_CXX syncevolution_1.4/src/dbus/server/pim/locale-factory.cpp000066400000000000000000000177361230021373600236230ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ /** * Common code for sorting and searching. */ #include "locale-factory.h" #include "folks.h" #include #include SE_BEGIN_CXX std::string SimpleE164::toString() const { std::ostringstream out; if (m_countryCode) { out << "+" << m_countryCode; } if (m_nationalNumber) { out << m_nationalNumber; } return out.str(); } bool LocaleFactory::Precomputed::operator == (const LocaleFactory::Precomputed &other) const { if (other.m_phoneNumbers.size() != m_phoneNumbers.size()) { return false; } PhoneNumbers::const_iterator ita = other.m_phoneNumbers.begin(); PhoneNumbers::const_iterator itb = m_phoneNumbers.begin(); while (ita != other.m_phoneNumbers.end()) { if (*ita != *itb) { return false; } ++ita; ++itb; } return true; } class Filter2StringVisitor : public boost::static_visitor { std::ostringstream m_out; public: void operator () (const std::string &str) { m_out << "'" << str << "'"; } void operator () (const std::vector &filter) { m_out << "["; for (size_t i = 0; i < filter.size(); i++) { if (i == 0) { m_out << " "; } else { m_out << ", "; } boost::apply_visitor(*this, filter[i]); } m_out << " ]"; } std::string toString() { return m_out.str(); } }; std::string LocaleFactory::Filter2String(const Filter_t &filter) { Filter2StringVisitor visitor; boost::apply_visitor(visitor, filter); return visitor.toString(); } template const V &getFilter(const LocaleFactory::Filter_t &filter, const char *expected) { const V *value = boost::get(&filter); if (!value) { throw std::runtime_error(StringPrintf("expected %s, got instead: %s", expected, LocaleFactory::Filter2String(filter).c_str())); } return *value; } const std::string &LocaleFactory::getFilterString(const Filter_t &filter, const char *expected) { return getFilter(filter, expected); } const std::vector &LocaleFactory::getFilterArray(const Filter_t &filter, const char *expected) { return getFilter< std::vector >(filter, expected); } void LocaleFactory::handleFilterException(const Filter_t &filter, int level, const std::string *file, int line) { std::string what; Exception::handle(what, HANDLE_EXCEPTION_NO_ERROR); what = StringPrintf("%s nesting level %d: %s\n%s", level == 0 ? "Error while parsing a search filter.\nMost specific term comes last, then the error message:\n" : "", level, Filter2String(filter).c_str(), what.c_str()); if (file) { throw Exception(*file, line, what); } else { throw std::runtime_error(what); } } class LogicFilter : public IndividualFilter { protected: std::vector< boost::shared_ptr > m_subFilter; public: void addFilter(const boost::shared_ptr &filter) { m_subFilter.push_back(filter); } }; class OrFilter : public LogicFilter { public: virtual bool matches(const IndividualData &data) const { BOOST_FOREACH (const boost::shared_ptr &filter, m_subFilter) { if (filter->matches(data)) { return true; } } return false; } }; class AndFilter : public LogicFilter { public: virtual bool matches(const IndividualData &data) const { BOOST_FOREACH (const boost::shared_ptr &filter, m_subFilter) { if (!filter->matches(data)) { return false; } } // Does not match if empty, just like 'or'. return !m_subFilter.empty(); } }; boost::shared_ptr LocaleFactory::createFilter(const Filter_t &filter, int level) { boost::shared_ptr res; try { const std::vector &terms = getFilterArray(filter, "array of terms"); if (terms.empty()) { res.reset(new MatchAll()); return res; } // Array of arrays? // May contain search parameters ('limit') and one // filter expression. if (boost::get< std::vector >(&terms[0])) { boost::shared_ptr params; BOOST_FOREACH (const Filter_t &subfilter, terms) { boost::shared_ptr tmp = createFilter(subfilter, level + 1); if (dynamic_cast(tmp.get())) { // New parameter overwrites old one. If we ever // want to support more than one parameter, we // need to be more selective here. params = tmp; } else if (!res) { res = tmp; } else { SE_THROW("Filter can only be combined with other filters inside a logical operation."); } } if (params) { if (res) { // Copy parameter(s) to real filter. res->setMaxResults(params->getMaxResults()); } else { // Or just use it as-is because no filter was // given. It'll work like MatchAll. res = params; } } } else { // Not an array, so must be string. const std::string &operation = getFilterString(terms[0], "operation name"); if (operation == "limit") { // Level 0 is the [] containing the ['limit', ...]. // We thus expect it at level 1. if (level != 1) { SE_THROW("'limit' parameter only allowed at top level."); } if (terms.size() != 2) { SE_THROW("'limit' needs exactly one parameter."); } const std::string &limit = getFilterString(terms[1], "'filter' value as string"); int maxResults = boost::lexical_cast(limit); res.reset(new ParamFilter()); res->setMaxResults(maxResults); } else if (operation == "or" || operation == "and") { boost::shared_ptr logicFilter(operation == "or" ? static_cast(new OrFilter()) : static_cast(new AndFilter())); for (size_t i = 1; i < terms.size(); i++ ) { logicFilter->addFilter(createFilter(terms[i], level + 1)); } res = logicFilter; } else { SE_THROW(StringPrintf("Unknown operation '%s'", operation.c_str())); } } } catch (const Exception &ex) { handleFilterException(filter, level, &ex.m_file, ex.m_line); } catch (...) { handleFilterException(filter, level, NULL, 0); } return res; } SE_END_CXX syncevolution_1.4/src/dbus/server/pim/locale-factory.h000066400000000000000000000127741230021373600232650ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ /** * Abstract definition of sorting and searching plugin. Used * by folks.cpp, must be provided by exactly one implementation * which is chosen at compile time. */ #ifndef INCL_SYNCEVO_DBUS_SERVER_PIM_LOCALE_FACTORY #define INCL_SYNCEVO_DBUS_SERVER_PIM_LOCALE_FACTORY #include #include #include #include #include SE_BEGIN_CXX class IndividualCompare; class IndividualFilter; /** * Normalized phone numbers (E164). A subset of the full * i18n::phonenumbers::PhoneNumber data. */ class SimpleE164 { public: SimpleE164() : m_countryCode(0), m_nationalNumber(0) {} typedef int32_t CountryCode_t; typedef uint64_t NationalNumber_t; /** * Country code (for example, 49 for the +49 prefix of Germany), * 0 if unknown/unset. */ CountryCode_t m_countryCode; /** * Phone number after the country code as it would appear in E.164 * after the + prefix, again given as binary value, * 0 if unknown/unset. */ NationalNumber_t m_nationalNumber; /** * + if country code and national number are set, * + if only country code is set, * if only national number is set, * empty string if both are unset. */ std::string toString() const; bool operator == (const SimpleE164 &other) const { return m_countryCode == other.m_countryCode && m_nationalNumber == other.m_nationalNumber; } bool operator != (const SimpleE164 &other) const { return !(*this == other); } }; /** * Factory for everything related to the current locale: sorting and * searching. */ class LocaleFactory { public: /** * Exactly one factory can be created, chosen at compile time. */ static boost::shared_ptr createFactory(); /** * Creates a compare instance or throws an error when that is not * possible. * * @param order factory-specific string which chooses one of * the orderings supported by the factory * @return a valid instance, must not be NULL */ virtual boost::shared_ptr createCompare(const std::string &order) = 0; /** * A recursive definition of a search expression. * All operand names, field names and values are strings. */ typedef boost::make_recursive_variant< std::string, std::vector< boost::recursive_variant_ > >::type Filter_t; /** * Simplified JSON representation (= no escaping of special characters), * for debugging and error reporting. */ static std::string Filter2String(const Filter_t &filter); /** * Throws "expected , got instead: " when * conversion to V fails. */ static const std::string &getFilterString(const Filter_t &filter, const char *expected); static const std::vector &getFilterArray(const Filter_t &filter, const char *expected); /** * Creates a filter instance or throws an error when that is not * possible. * * @param represents a (sub-)filter * @level 0 at the root of the filter, incremented by one for each * non-trivial indirection; i.e., [ [ ] ] still * treats as if it was the root search * * @return a valid instance, must not be NULL */ virtual boost::shared_ptr createFilter(const Filter_t &filter, int level) = 0; /** * To be called when parsing a Filter_t caused an exception. * Will add information about the filter and a preamble, if * called at the top level. */ static void handleFilterException(const Filter_t &filter, int level, const std::string *file, int line); /** * Pre-computed data for a single FolksIndividual which will be needed * for searching. Strictly speaking, this should be an opaque pointer * whose content is entirely owned by the implementation of LocaleFactory. * For the sake of performance and simplicity, we define a struct instead * which can be embedded inside IndividualData. Leads to better memory * locality and reduces overall memory consumption/usage. */ struct Precomputed { typedef std::vector PhoneNumbers; PhoneNumbers m_phoneNumbers; bool operator == (const Precomputed &other) const; bool operator != (const Precomputed &other) const { return !(*this == other); } }; /** * (Re)set pre-computed data for an individual. */ virtual bool precompute(FolksIndividual *individual, Precomputed &precomputed) const = 0; }; SE_END_CXX #endif // INCL_SYNCEVO_DBUS_SERVER_PIM_LOCALE_FACTORY syncevolution_1.4/src/dbus/server/pim/localed.py000066400000000000000000000050321230021373600221520ustar00rootroot00000000000000import dbus.service import os class Localed(dbus.service.Object): """a fake localed systemd implementation""" LOCALED_INTERFACE = "org.freedesktop.locale1" LOCALED_NAME = "org.freedesktop.locale1" LOCALED_PATH = "/org/freedesktop/locale1" LOCALED_LOCALE = "Locale" def __init__(self, bus=None): if bus is None: bus = dbus.SystemBus() bus_name = dbus.service.BusName(self.LOCALED_NAME, bus=bus) dbus.service.Object.__init__(self, bus_name, self.LOCALED_PATH) locale = [] for name in ( "LANG", "LC_CTYPE", "LC_NUMERIC", "LC_TIME", "LC_COLLATE", "LC_MONETARY", "LC_MESSAGES", "LC_PAPER", "LC_NAME", "LC_ADDRESS", "LC_TELEPHONE", "LC_MEASUREMENT", "LC_IDENTIFICATION", ): value = os.environ.get(name, None) if value != None: locale.append('%s=%s' % (name, value)) self.properties = { self.LOCALED_LOCALE : locale, } def SetLocale(self, locale, invalidate=False): self.properties[self.LOCALED_LOCALE] = locale if invalidate: self.PropertiesChanged(self.LOCALED_INTERFACE, { }, [ self.LOCALED_LOCALE ]) else: self.PropertiesChanged(self.LOCALED_INTERFACE, { self.LOCALED_LOCALE: locale }, []) # Properties @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature='ss', out_signature='v') def Get(self, interface_name, property_name): return self.GetAll(interface_name)[property_name] @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature='s', out_signature='a{sv}') def GetAll(self, interface_name): if interface_name == self.LOCALED_INTERFACE: return self.properties else: raise dbus.exceptions.DBusException( 'org.syncevolution.UnknownInterface', 'The fake localed object does not implement the %s interface' % interface_name) @dbus.service.signal(dbus.PROPERTIES_IFACE, signature='sa{sv}as') def PropertiesChanged(self, interface_name, changed_properties, invalidated_properties): pass syncevolution_1.4/src/dbus/server/pim/manager.cpp000066400000000000000000001751641230021373600223310ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "manager.h" #include "individual-traits.h" #include "persona-details.h" #include "filtered-view.h" #include "full-view.h" #include "merge-view.h" #include "edsf-view.h" #include "../resource.h" #include "../client.h" #include "../session.h" #include "../localed-listener.h" #include #include #include #include #include #include #include SE_BEGIN_CXX static const char * const MANAGER_SERVICE = "org._01.pim.contacts"; static const char * const MANAGER_PATH = "/org/01/pim/contacts"; static const char * const MANAGER_IFACE = "org._01.pim.contacts.Manager"; static const char * const MANAGER_ERROR_ABORTED = "org._01.pim.contacts.Manager.Aborted"; static const char * const MANAGER_ERROR_BAD_STATUS = "org._01.pim.contacts.Manager.BadStatus"; static const char * const MANAGER_ERROR_ALREADY_EXISTS = "org._01.pim.contacts.Manager.AlreadyExists"; static const char * const MANAGER_ERROR_NOT_FOUND = "org._01.pim.contacts.Manager.NotFound"; static const char * const AGENT_IFACE = "org._01.pim.contacts.ViewAgent"; static const char * const CONTROL_IFACE = "org._01.pim.contacts.ViewControl"; static const char * const MANAGER_CONFIG_SORT_PROPERTY = "sort"; static const char * const MANAGER_CONFIG_ACTIVE_ADDRESS_BOOKS_PROPERTY = "active"; /** * Prefix for peer databases ("peer-") */ static const char * const DB_PEER_PREFIX = "peer-"; /** * Name prefix for SyncEvolution config contexts used by PIM manager. * Used in combination with the uid string provided by the PIM manager * client, like this: * * eds@pim-manager- source 'eds' syncs with target-config@pim-manager- * source 'remote' for PBAP. * * eds@pim-manager- source 'local' syncs with a SyncML peer directly. */ static const char * const MANAGER_PREFIX = "pim-manager-"; static const char * const MANAGER_LOCAL_CONFIG = "eds"; static const char * const MANAGER_LOCAL_SOURCE = "local"; static const char * const MANAGER_REMOTE_CONFIG = "target-config"; static const char * const MANAGER_REMOTE_SOURCE = "remote"; Manager::Manager(const boost::shared_ptr &server) : DBusObjectHelper(server->getConnection(), MANAGER_PATH, MANAGER_IFACE), m_mainThread(g_thread_self()), m_server(server), m_locale(LocaleFactory::createFactory()), m_localedListener(LocaledListener::create()), emitSyncProgress(*this, "SyncProgress") { // Update our own environment and sorting on each locale change. m_localedListener->m_localeValues.connect(boost::bind(&LocaledListener::setLocale, m_localedListener.get(), _1)); m_localedListener->m_localeChanged.connect(boost::bind(&Manager::localeChanged, this)); // Get the environment once from localed, just to be sure. m_localedListener->check(boost::bind(&LocaledListener::setLocale, boost::weak_ptr(m_localedListener), _1)); } Manager::~Manager() { // Clear the pending queue before self-desctructing, because the // entries hold pointers to this instance. m_pending.clear(); if (m_preventingAutoTerm) { m_server->autoTermUnref(); } } #ifdef PIM_MANAGER_TEST_THREADING static gpointer StartManager(gpointer data) { Manager *manager = static_cast(data); manager->start(); return NULL; } #endif void Manager::init() { // Restore sort order and active databases. m_configNode.reset(new IniFileConfigNode(SubstEnvironment("${XDG_CONFIG_HOME}/syncevolution"), "pim-manager.ini", false)); InitStateString order = m_configNode->readProperty(MANAGER_CONFIG_SORT_PROPERTY); m_sortOrder = order.wasSet() ? order : "last/first"; InitStateString active = m_configNode->readProperty(MANAGER_CONFIG_ACTIVE_ADDRESS_BOOKS_PROPERTY); m_enabledEBooks.clear(); BOOST_FOREACH(const std::string &entry, boost::tokenizer< boost::char_separator >(active, boost::char_separator(", \t"))) { if (!entry.empty()) { m_enabledEBooks.insert(entry); } } initFolks(); try { initSorting(m_sortOrder); } catch (...) { std::string explanation; Exception::handle(explanation, HANDLE_EXCEPTION_NO_ERROR); SE_LOG_WARNING(NULL, "Restoring sort order from config failed, falling back to default: %s", explanation.c_str()); m_sortOrder = "last/first"; initSorting(m_sortOrder); } initDatabases(); add(this, &Manager::start, "Start"); add(this, &Manager::stop, "Stop"); add(this, &Manager::isRunning, "IsRunning"); add(this, &Manager::setSortOrder, "SetSortOrder"); add(this, &Manager::getSortOrder, "GetSortOrder"); add(this, &Manager::search, "Search"); add(this, &Manager::getActiveAddressBooks, "GetActiveAddressBooks"); add(this, &Manager::setActiveAddressBooks, "SetActiveAddressBooks"); add(this, &Manager::modifyPeer, "SetPeer"); // The original method name, keep it for backwards compatibility. add(this, &Manager::createPeer, "CreatePeer"); // Strict version: uid must be new. add(this, &Manager::removePeer, "RemovePeer"); add(this, &Manager::syncPeer, "SyncPeer"); add(this, &Manager::stopSync, "StopSync"); add(this, &Manager::getAllPeers, "GetAllPeers"); add(this, &Manager::addContact, "AddContact"); add(this, &Manager::modifyContact, "ModifyContact"); add(this, &Manager::removeContact, "RemoveContact"); add(emitSyncProgress); // Ready, make it visible via D-Bus. activate(); // Claim MANAGER_SERVICE name on connection. // We don't care about the result. GDBusCXX::DBusConnectionPtr(getConnection()).ownNameAsync(MANAGER_SERVICE, boost::function()); #ifdef PIM_MANAGER_TEST_THREADING GThread *thread = g_thread_new("start", StartManager, this); g_thread_unref(thread); #endif } struct TaskForMain { GMutex m_mutex; GCond m_cond; bool m_done; boost::function m_operation; boost::function m_rethrow; void runTaskOnIdle() { g_mutex_lock(&m_mutex); // Exceptions must be reported back to the original thread. // This is done by serializing them as string, then using the // existing Exception::tryRethrow() to turn that string back // into an instance of the right class. try { m_operation(); } catch (...) { std::string explanation; Exception::handle(explanation); m_rethrow = boost::bind(Exception::tryRethrow, explanation, true); } // Wake up task. m_done = true; g_cond_signal(&m_cond); g_mutex_unlock(&m_mutex); } }; template void AssignResult(const boost::function &operation, R &res) { res = operation(); } template R Manager::runInMainRes(const boost::function &operation) { // Prepare task. R res; TaskForMain task; g_mutex_init(&task.m_mutex); g_cond_init(&task.m_cond); task.m_done = false; task.m_operation = boost::bind(&AssignResult, boost::cref(operation), boost::ref(res)); // Run in main. Timeout timeout; timeout.runOnce(-1, boost::bind(&TaskForMain::runTaskOnIdle, &task)); g_main_context_wakeup(NULL); g_mutex_lock(&task.m_mutex); while (!task.m_done) { g_cond_wait(&task.m_cond, &task.m_mutex); } g_mutex_unlock(&task.m_mutex); // Rethrow exceptions (optional) and return result. g_cond_clear(&task.m_cond); g_mutex_clear(&task.m_mutex); if (task.m_rethrow) { task.m_rethrow(); } return res; } static int Return1(const boost::function &operation) { operation(); return 1; } void Manager::runInMainVoid(const boost::function &operation) { runInMainRes(boost::bind(Return1, boost::cref(operation))); } void Manager::initFolks() { m_folks = IndividualAggregator::create(m_locale); } void Manager::initSorting(const std::string &order) { // Mirror sorting order in m_folks main view. // Empty string passes NULL pointer to setCompare(), // which chooses the builtin sorting in folks.cpp, // independent of the locale plugin. boost::shared_ptr compare = order.empty() ? IndividualCompare::defaultCompare() : m_locale->createCompare(order); m_folks->setCompare(compare); } void Manager::localeChanged() { // First update locale. m_locale = LocaleFactory::createFactory(); // Change sorting. First install new locale in // IndividualAggregator and through it in FullView. if (m_folks) { m_folks->setLocale(m_locale); } // Then update IndividualData of all loaded individuals by // changing the sort order. initSorting(m_sortOrder); // Now update views. m_localeChanged(m_locale); } boost::shared_ptr Manager::create(const boost::shared_ptr &server) { boost::shared_ptr manager(new Manager(server)); manager->m_self = manager; manager->init(); return manager; } boost::shared_ptr CreateContactManager(const boost::shared_ptr &server, bool start) { boost::shared_ptr manager = Manager::create(server); if (start) { manager->start(); } return manager; } void Manager::start() { if (!isMain()) { runInMainV(&Manager::start); return; } if (!m_preventingAutoTerm) { // Prevent automatic shut down during idle times, because we need // to keep our unified address book available. m_server->autoTermRef(); m_preventingAutoTerm = true; } m_folks->start(); } void Manager::stop() { if (!isMain()) { runInMainV(&Manager::stop); return; } // If there are no active searches, then recreate aggregator. // Instead of tracking open views, use the knowledge that an // idle server has only two references to the main view: // one inside m_folks, one given back to us here. if (m_folks->getMainView().use_count() <= 2) { SE_LOG_DEBUG(NULL, "restarting due to Manager.Stop()"); initFolks(); initDatabases(); initSorting(m_sortOrder); if (m_preventingAutoTerm) { // Allow auto shutdown again. m_server->autoTermUnref(); m_preventingAutoTerm = false; } } } bool Manager::isRunning() { if (!isMain()) { return runInMainR(&Manager::isRunning); } return m_folks->isRunning(); } void Manager::setSortOrder(const std::string &order) { if (!isMain()) { runInMainV(&Manager::setSortOrder, order); return; } if (order == getSortOrder()) { // Nothing to do. return; } // String is checked as part of initSorting, // only store if parsing succeeds. initSorting(order); m_configNode->writeProperty(MANAGER_CONFIG_SORT_PROPERTY, InitStateString(order, true)); m_configNode->flush(); m_sortOrder = order; } std::string Manager::getSortOrder() { if (!isMain()) { return runInMainR(&Manager::getSortOrder); } return m_sortOrder; } /** * Connects a normal IndividualView to a D-Bus client. * Provides the org.01.pim.contacts.ViewControl API. */ class ViewResource : public Resource, public GDBusCXX::DBusObjectHelper { static unsigned int m_counter; boost::weak_ptr m_self; GDBusCXX::DBusRemoteObject m_viewAgent; boost::shared_ptr m_view; boost::shared_ptr m_locale; boost::weak_ptr m_owner; LocaleFactory::Filter_t m_filter; struct Change { Change() : m_start(0), m_call(NULL) {} int m_start; std::deque m_ids; const GDBusCXX::DBusClientCall0 *m_call; } m_pendingChange, m_lastChange; GDBusCXX::DBusClientCall0 m_quiescent; GDBusCXX::DBusClientCall0 m_contactsModified, m_contactsAdded, m_contactsRemoved; ViewResource(const boost::shared_ptr view, const boost::shared_ptr &locale, const boost::shared_ptr &owner, const LocaleFactory::Filter_t &filter, GDBusCXX::connection_type *connection, const GDBusCXX::Caller_t &ID, const GDBusCXX::DBusObject_t &agentPath) : GDBusCXX::DBusObjectHelper(connection, StringPrintf("%s/view%d", MANAGER_PATH, m_counter++), CONTROL_IFACE), m_viewAgent(connection, agentPath, AGENT_IFACE, ID), m_view(view), m_locale(locale), m_owner(owner), m_filter(filter), // use ViewAgent interface m_quiescent(m_viewAgent, "Quiescent"), m_contactsModified(m_viewAgent, "ContactsModified"), m_contactsAdded(m_viewAgent, "ContactsAdded"), m_contactsRemoved(m_viewAgent, "ContactsRemoved") {} /** * Invokes one of m_contactsModified/Added/Removed. A failure of * the asynchronous call indicates that the client is dead and * that its view can be purged. */ template void sendChange(const GDBusCXX::DBusClientCall0 &call, int start, const V &ids) { // Changes get aggregated inside handleChange(). call.start(getObject(), start, ids, boost::bind(ViewResource::sendDone, m_self, _1, &call == &m_contactsModified ? "ContactsModified()" : &call == &m_contactsAdded ? "ContactsAdded()" : &call == &m_contactsRemoved ? "ContactsRemoved()" : "???", true)); } /** * Merge local changes as much as possible, to avoid excessive * D-Bus traffic. Pending changes get flushed each time the * view reports that it is stable again or contact data * needs to be sent back to the client. Flushing in the second * case is necessary, because otherwise the client will not have * an up-to-date view when the requested data arrives. */ void handleChange(const GDBusCXX::DBusClientCall0 &call, int start, const IndividualData &data) { FolksIndividual *individual = data.m_individual.get(); const char *id = folks_individual_get_id(individual); SE_LOG_DEBUG(NULL, "handle change %s: %s, #%d, %s = %s", getPath(), &call == &m_contactsModified ? "modified" : &call == &m_contactsAdded ? "added" : &call == &m_contactsRemoved ? "remove" : "???", start, id, folks_name_details_get_full_name(FOLKS_NAME_DETAILS(individual)) ); int pendingCount = m_pendingChange.m_ids.size(); if (pendingCount == 0) { // Sending a "contact modified" twice for the same range is redundant. // If the recipient read the data since the last signal, m_lastChange // got cleared and we don't do this optimization. if (m_lastChange.m_call == &m_contactsModified && &call == &m_contactsModified && start >= m_lastChange.m_start && start < m_lastChange.m_start + (int)m_lastChange.m_ids.size()) { SE_LOG_DEBUG(NULL, "handle change %s: redundant 'modified' signal, ignore", getPath()); return; } // Nothing pending, delay sending. m_pendingChange.m_call = &call; m_pendingChange.m_start = start; m_pendingChange.m_ids.push_back(id); SE_LOG_DEBUG(NULL, "handle change %s: stored as pending change", getPath()); return; } if (m_pendingChange.m_call == &call) { // Same operation. Can we extend it? if (&call == &m_contactsModified) { // Modification, indices are unchanged. if (start + 1 == m_pendingChange.m_start) { // New modified element at the front. SE_LOG_DEBUG(NULL, "handle change %s: insert modification, #%d + %d and #%d => #%d + %d", getPath(), m_pendingChange.m_start, pendingCount, start, m_pendingChange.m_start - 1, pendingCount + 1); m_pendingChange.m_ids.push_front(id); m_pendingChange.m_start--; return; } else if (start == m_pendingChange.m_start + pendingCount) { // New modified element at the end. SE_LOG_DEBUG(NULL, "handle change %s: append modification, #%d + %d and #%d => #%d + %d", getPath(), m_pendingChange.m_start, pendingCount, start, m_pendingChange.m_start, pendingCount + 1); m_pendingChange.m_ids.push_back(id); return; } else if (start >= m_pendingChange.m_start && start < m_pendingChange.m_start + pendingCount) { // Element modified again => no change, except perhaps for the ID. SE_LOG_DEBUG(NULL, "handle change %s: modification of already modified contact, #%d + %d and #%d => #%d + %d", getPath(), m_pendingChange.m_start, pendingCount, start, m_pendingChange.m_start, pendingCount); m_pendingChange.m_ids[start - m_pendingChange.m_start] = id; return; } } else if (&call == &m_contactsAdded) { // Added individuals. The new start index already includes // the previously added individuals. int newCount = m_pendingChange.m_ids.size() + 1; if (start >= m_pendingChange.m_start) { // Adding in the middle or at the end? int end = m_pendingChange.m_start + pendingCount; if (start == end) { SE_LOG_DEBUG(NULL, "handle change %s: increase count of 'added' individuals at end, #%d + %d and #%d new => #%d + %d", getPath(), m_pendingChange.m_start, pendingCount, start, m_pendingChange.m_start, newCount); m_pendingChange.m_ids.push_back(id); return; } else if (start < end) { SE_LOG_DEBUG(NULL, "handle change %s: increase count of 'added' individuals in the middle, #%d + %d and #%d new => #%d + %d", getPath(), m_pendingChange.m_start, pendingCount, start, m_pendingChange.m_start, newCount); m_pendingChange.m_ids.insert(m_pendingChange.m_ids.begin() + (start - m_pendingChange.m_start), id); return; } } else { // Adding directly before the previous start? if (start + 1 == m_pendingChange.m_start) { SE_LOG_DEBUG(NULL, "handle change %s: reduce start and increase count of 'added' individuals, #%d + %d and #%d => #%d + %d", getPath(), m_pendingChange.m_start, pendingCount, start, start, newCount); m_pendingChange.m_start = start; m_pendingChange.m_ids.push_front(id); return; } } } else { // Removed individuals. The new start was already reduced by // previously removed individuals. int newCount = m_pendingChange.m_ids.size() + 1; if (start == m_pendingChange.m_start) { // Removing directly at end. SE_LOG_DEBUG(NULL, "handle change %s: increase count of 'removed' individuals, #%d + %d and #%d => #%d + %d", getPath(), m_pendingChange.m_start, pendingCount, start, m_pendingChange.m_start, newCount); m_pendingChange.m_ids.push_back(id); return; } else if (start + 1 == m_pendingChange.m_start) { // Removing directly before the previous start. SE_LOG_DEBUG(NULL, "handle change %s: reduce start and increase count of 'removed' individuals, #%d + %d and #%d => #%d + %d", getPath(), m_pendingChange.m_start, pendingCount, start, start, newCount); m_pendingChange.m_start = start; m_pendingChange.m_ids.push_front(id); return; } } } // More complex merging is possible. For example, "removed 1 // at #10" and "added 1 at #10" can be turned into "modified 1 // at #10", if the ID is the same. This happens when a contact // gets modified and folks decides to recreate the // FolksIndividual instead of modifying it. if (m_pendingChange.m_call == &m_contactsRemoved && &call == &m_contactsAdded && start == m_pendingChange.m_start && 1 == m_pendingChange.m_ids.size() && m_pendingChange.m_ids.front() == id) { SE_LOG_DEBUG(NULL, "handle change %s: removed individual was re-added => #%d modified", getPath(), start); m_pendingChange.m_call = &m_contactsModified; return; } // Cannot merge changes. flushChanges(); // Now remember requested change. m_pendingChange.m_call = &call; m_pendingChange.m_start = start; m_pendingChange.m_ids.clear(); m_pendingChange.m_ids.push_back(id); } /** Clear pending state and flush. */ void flushChanges() { int count = m_pendingChange.m_ids.size(); if (count) { SE_LOG_DEBUG(NULL, "send change %s: %s, #%d + %d", getPath(), m_pendingChange.m_call == &m_contactsModified ? "modified" : m_pendingChange.m_call == &m_contactsAdded ? "added" : m_pendingChange.m_call == &m_contactsRemoved ? "remove" : "???", m_pendingChange.m_start, count); m_lastChange = m_pendingChange; m_pendingChange.m_ids.clear(); sendChange(*m_lastChange.m_call, m_lastChange.m_start, m_lastChange.m_ids); } } /** Current state is stable. Flush and tell agent. */ void quiescent() { flushChanges(); m_quiescent.start(getObject(), boost::bind(ViewResource::sendDone, m_self, _1, "Quiescent()", false)); } /** * Used as callback for sending changes to the ViewAgent. Only * holds weak references and thus does not prevent deleting view * or client. */ static void sendDone(const boost::weak_ptr &self, const std::string &error, const char *method, bool required) { if (required && !error.empty()) { // remove view because it is no longer needed SE_LOG_DEBUG(NULL, "ViewAgent %s method call failed, deleting view: %s", method, error.c_str()); boost::shared_ptr r = self.lock(); if (r) { r->close(); } } } void init(boost::shared_ptr self) { m_self = self; // activate D-Bus interface add(this, &ViewResource::readContacts, "ReadContacts"); add(this, &ViewResource::close, "Close"); add(this, &ViewResource::refineSearch, "RefineSearch"); add(this, &ViewResource::replaceSearch, "ReplaceSearch"); activate(); // The view might have been started already, for example when // reconnecting a ViewResource to the persistent full view. // Therefore tell the agent about the current content before // starting, then connect to signals, and finally start. int size = m_view->size(); if (size) { std::vector ids; ids.reserve(size); const IndividualData *data; for (int i = 0; i < size; i++) { data = m_view->getContact(i); ids.push_back(folks_individual_get_id(data->m_individual.get())); } sendChange(m_contactsAdded, 0, ids); } m_view->m_quiescenceSignal.connect(IndividualView::QuiescenceSignal_t::slot_type(&ViewResource::quiescent, this).track(self)); m_view->m_modifiedSignal.connect(IndividualView::ChangeSignal_t::slot_type(&ViewResource::handleChange, this, boost::cref(m_contactsModified), _1, _2).track(self)); m_view->m_addedSignal.connect(IndividualView::ChangeSignal_t::slot_type(&ViewResource::handleChange, this, boost::cref(m_contactsAdded), _1, _2).track(self)); m_view->m_removedSignal.connect(IndividualView::ChangeSignal_t::slot_type(&ViewResource::handleChange, this, boost::cref(m_contactsRemoved), _1, _2).track(self)); m_view->start(); // start() did all the initial work, no further changes expected // if state is considered stable => tell users. if (m_view->isQuiescent()) { quiescent(); } } public: /** returns the integer number that will be used for the next view resource */ static unsigned getNextViewNumber() { return m_counter; } static boost::shared_ptr create(const boost::shared_ptr &view, const boost::shared_ptr &locale, const boost::shared_ptr &owner, const LocaleFactory::Filter_t &filter, GDBusCXX::connection_type *connection, const GDBusCXX::Caller_t &ID, const GDBusCXX::DBusObject_t &agentPath) { boost::shared_ptr viewResource(new ViewResource(view, locale, owner, filter, connection, ID, agentPath)); viewResource->init(viewResource); return viewResource; } /** ViewControl.ReadContacts() */ void readContacts(const std::vector &ids, IndividualView::Contacts &contacts) { if (!ids.empty()) { // Ensure that client's view is up-to-date, then prepare the // data for it. flushChanges(); m_view->readContacts(ids, contacts); // Discard the information about the previous 'modified' signal // if the client now has data in that range. Necessary because // otherwise future 'modified' signals for that range might get // suppressed in handleChange(). int modifiedCount = m_lastChange.m_ids.size(); if (m_lastChange.m_call == &m_contactsModified && modifiedCount) { BOOST_FOREACH (const IndividualView::Contacts::value_type &entry, contacts) { int index = entry.first; if (index >= m_lastChange.m_start && index < m_lastChange.m_start + modifiedCount) { m_lastChange.m_ids.clear(); break; } } } } } /** ViewControl.Close() */ void close() { // Removing the resource from its owner will drop the last // reference and delete it when we return. boost::shared_ptr r = m_self.lock(); if (r) { boost::shared_ptr c = m_owner.lock(); if (c) { c->detach(r.get()); } } } /** ViewControl.RefineSearch() */ void refineSearch(const std::vector &filterArray) { replaceSearch(filterArray, true); } void replaceSearch(const std::vector &filterArray, bool refine) { // Same as in Search(). m_filter = filterArray; redoSearch(refine); } /** * Start filtering again, using the current environment. To be * called after a locale change or when m_filter changed. * * @param refine true only if it is known to the caller that the new result set is * a subset of the current one, false if uncertain */ void redoSearch(bool refine) { boost::shared_ptr individualFilter = m_locale->createFilter(m_filter, 0); m_view->replaceFilter(individualFilter, refine); } /** * Change locale, then refilter because the filter may have changed. */ void setLocale(const boost::shared_ptr &locale) { m_locale = locale; redoSearch(true); } }; unsigned int ViewResource::m_counter; void Manager::search(const boost::shared_ptr< GDBusCXX::Result1 > &result, const GDBusCXX::Caller_t &ID, const boost::shared_ptr &watch, const std::vector &filterVector, const GDBusCXX::DBusObject_t &agentPath) { // TODO: figure out a native, thread-safe API for this. // Start folks in parallel with asking for an ESourceRegistry. start(); // We use a std::vector as outer type to help Python decide how to // send the empty list []. When we declare our parameter as // variant instead of array of variants, as we do now, then the // Python programmer has to use dbus.Array([], signature='s'), // which breaks backwards compatibility (wasn't necessary earlier) // and is not easy to use. // // But before we can pass the filter on, we need to turn it into // a variant containing the vector. LocaleFactory::Filter_t filter; filter = filterVector; // We don't know for sure whether we'll need the ESourceRegistry. // Ask for it, just to be sure. If we need to hurry because we are // doing a caller ID lookup during startup, then we'll need it. EDSRegistryLoader::getESourceRegistryAsync(boost::bind(&Manager::searchWithRegistry, m_self, _1, _2, result, ID, watch, filter, agentPath)); } void Manager::searchWithRegistry(const ESourceRegistryCXX ®istry, const GError *gerror, const boost::shared_ptr< GDBusCXX::Result1 > &result, const GDBusCXX::Caller_t &ID, const boost::shared_ptr &watch, const LocaleFactory::Filter_t &filter, const GDBusCXX::DBusObject_t &agentPath) throw() { try { if (!registry) { GErrorCXX::throwError("create ESourceRegistry", gerror); } doSearch(registry, result, ID, watch, filter, agentPath); } catch (...) { // Tell caller about specific error. dbusErrorCallback(result); } } void Manager::doSearch(const ESourceRegistryCXX ®istry, const boost::shared_ptr< GDBusCXX::Result1 > &result, const GDBusCXX::Caller_t &ID, const boost::shared_ptr &watch, const LocaleFactory::Filter_t &filter, const GDBusCXX::DBusObject_t &agentPath) { // Create and track view which is owned by the caller. boost::shared_ptr client = m_server->addClient(ID, watch); boost::shared_ptr view; view = m_folks->getMainView(); bool quiescent = view->isQuiescent(); std::string ebookFilter; // Always use a filtered view. That way we can implement ReplaceView or RefineView // without having to switch from a FullView to a FilteredView. boost::shared_ptr individualFilter = m_locale->createFilter(filter, 0); ebookFilter = individualFilter->getEBookFilter(); if (quiescent) { // Don't search via EDS directly because the unified // address book is ready. ebookFilter.clear(); } view = FilteredView::create(view, individualFilter); view->setName(StringPrintf("filtered view%u", ViewResource::getNextViewNumber())); SE_LOG_DEBUG(NULL, "preparing %s: EDS search term is '%s', active address books %s", view->getName(), ebookFilter.c_str(), boost::join(m_enabledEBooks, " ").c_str()); if (!ebookFilter.empty() && !m_enabledEBooks.empty()) { // Set up direct searching in all active address books. // These searches are done once, so don't bother to deal // with future changes to the active address books or // the sort order. MergeView::Searches searches; searches.reserve(m_enabledEBooks.size()); boost::shared_ptr compare = m_sortOrder.empty() ? IndividualCompare::defaultCompare() : m_locale->createCompare(m_sortOrder); BOOST_FOREACH (const std::string &uuid, m_enabledEBooks) { searches.push_back(EDSFView::create(registry, uuid, ebookFilter)); searches.back()->setName(StringPrintf("eds view %s %s", uuid.c_str(), ebookFilter.c_str())); } boost::shared_ptr merge(MergeView::create(view, searches, m_locale, compare)); merge->setName(StringPrintf("merge view%u", ViewResource::getNextViewNumber())); view = merge; } boost::shared_ptr viewResource(ViewResource::create(view, m_locale, client, filter, getConnection(), ID, agentPath)); client->attach(boost::shared_ptr(viewResource)); // Redo search when locale changes. m_localeChanged.connect(LocaleChangedSignal::slot_type(&ViewResource::setLocale, viewResource.get(), _1).track(viewResource)); // created local resource result->done(viewResource->getPath()); } void Manager::runInSession(const std::string &config, Server::SessionFlags flags, const boost::shared_ptr &result, const boost::function &session)> &callback) { try { boost::shared_ptr session = m_server->startInternalSession(config, flags, boost::bind(&Manager::doSession, this, _1, result, callback)); if (session->getSyncStatus() == Session::SYNC_QUEUEING) { // Must continue to wait instead of dropping the last reference. m_pending.push_back(std::make_pair(result, session)); } } catch (...) { // Tell caller about specific error. dbusErrorCallback(result); } } void Manager::doSession(const boost::weak_ptr &weakSession, const boost::shared_ptr &result, const boost::function &session)> &callback) { try { boost::shared_ptr session = weakSession.lock(); if (!session) { // Destroyed already? return; } // Drop permanent reference, session will be destroyed when we // return. m_pending.remove(std::make_pair(result, session)); // Now run the operation. callback(session); } catch (...) { // Tell caller about specific error. dbusErrorCallback(result); } } void Manager::getActiveAddressBooks(std::vector &dbIDs) { BOOST_FOREACH (const std::string &uuid, m_enabledEBooks) { if (uuid == "system-address-book") { dbIDs.push_back(""); } else { dbIDs.push_back(DB_PEER_PREFIX + uuid.substr(strlen(MANAGER_PREFIX))); } } } void Manager::setActiveAddressBooks(const std::vector &dbIDs) { // Build list of EDS UUIDs. std::set uuids; BOOST_FOREACH (const std::string &dbID, dbIDs) { if (boost::starts_with(dbID, DB_PEER_PREFIX)) { // Database of a specific peer. std::string uid = dbID.substr(strlen(DB_PEER_PREFIX)); uuids.insert(MANAGER_PREFIX + uid); } else if (dbID.empty()) { // System database. It's UUID is hard-coded here because // it is fixed in practice and managing an ESourceRegistry // just to get the value seems overkill. uuids.insert("system-address-book"); } else { SE_THROW("invalid address book ID: " + dbID); } } // Swap and set in aggregator. std::swap(uuids, m_enabledEBooks); initDatabases(); m_configNode->writeProperty(MANAGER_CONFIG_ACTIVE_ADDRESS_BOOKS_PROPERTY, InitStateString(boost::join(m_enabledEBooks, " "), true)); m_configNode->flush(); } void Manager::initDatabases() { m_folks->setDatabases(m_enabledEBooks); } static void checkPeerUID(const std::string &uid) { const pcrecpp::RE re("[-a-z0-9]*"); if (!re.FullMatch(uid)) { SE_THROW(StringPrintf("invalid peer uid: %s", uid.c_str())); } } void Manager::createPeer(const boost::shared_ptr &result, const std::string &uid, const StringMap &properties) { setPeer(result, uid, properties, CREATE_PEER); } void Manager::modifyPeer(const boost::shared_ptr &result, const std::string &uid, const StringMap &properties) { setPeer(result, uid, properties, SET_PEER); } void Manager::setPeer(const boost::shared_ptr &result, const std::string &uid, const StringMap &properties, ConfigureMode mode) { checkPeerUID(uid); runInSession(StringPrintf("@%s%s", MANAGER_PREFIX, uid.c_str()), Server::SESSION_FLAG_NO_SYNC, result, boost::bind(&Manager::doSetPeer, this, _1, result, uid, properties, mode)); } static const char * const PEER_KEY_PROTOCOL = "protocol"; static const char * const PEER_SYNCML_PROTOCOL = "SyncML"; static const char * const PEER_PBAP_PROTOCOL = "PBAP"; static const char * const PEER_FILES_PROTOCOL = "files"; static const char * const PEER_KEY_TRANSPORT = "transport"; static const char * const PEER_BLUETOOTH_TRANSPORT = "Bluetooth"; static const char * const PEER_IP_TRANSPORT = "IP"; static const char * const PEER_DEF_TRANSPORT = PEER_BLUETOOTH_TRANSPORT; static const char * const PEER_KEY_ADDRESS = "address"; static const char * const PEER_KEY_DATABASE = "database"; static const char * const PEER_KEY_LOGDIR = "logdir"; static const char * const PEER_KEY_MAXSESSIONS = "maxsessions"; static std::string GetEssential(const StringMap &properties, const char *key, bool allowEmpty = false) { InitStateString entry = GetWithDef(properties, key); if (!entry.wasSet() || (!allowEmpty && entry.empty())) { SE_THROW(StringPrintf("peer config: '%s' must be set%s", key, allowEmpty ? "" : " to a non-empty value")); } return entry; } void Manager::doSetPeer(const boost::shared_ptr &session, const boost::shared_ptr &result, const std::string &uid, const StringMap &properties, ConfigureMode mode) { // The session is active now, we have exclusive control over the // databases and the config. Create or update config. std::string protocol = GetEssential(properties, PEER_KEY_PROTOCOL); std::string transport = GetWithDef(properties, PEER_KEY_TRANSPORT, PEER_DEF_TRANSPORT); std::string address = GetEssential(properties, PEER_KEY_ADDRESS); std::string database = GetWithDef(properties, PEER_KEY_DATABASE); std::string logdir = GetWithDef(properties, PEER_KEY_LOGDIR); std::string maxsessions = GetWithDef(properties, PEER_KEY_MAXSESSIONS); unsigned maxLogDirs = 0; if (!maxsessions.empty()) { // https://svn.boost.org/trac/boost/ticket/5494 if (boost::starts_with(maxsessions, "-")) { SE_THROW(StringPrintf("negative 'maxsessions' not allowed: %s", maxsessions.c_str())); } maxLogDirs = boost::lexical_cast(maxsessions); } std::string localDatabaseName = MANAGER_PREFIX + uid; std::string context = StringPrintf("@%s%s", MANAGER_PREFIX, uid.c_str()); SE_LOG_DEBUG(NULL, "%s: creating config for protocol %s", uid.c_str(), protocol.c_str()); if (protocol == PEER_PBAP_PROTOCOL) { if (!database.empty()) { SE_THROW(StringPrintf("peer config: %s=%s: choosing database not supported for %s=%s", PEER_KEY_ADDRESS, address.c_str(), PEER_KEY_PROTOCOL, protocol.c_str())); } if (transport != PEER_BLUETOOTH_TRANSPORT) { SE_THROW(StringPrintf("peer config: %s=%s: only transport %s is supported for %s=%s", PEER_KEY_TRANSPORT, transport.c_str(), PEER_BLUETOOTH_TRANSPORT, PEER_KEY_PROTOCOL, protocol.c_str())); } } if (protocol == PEER_PBAP_PROTOCOL || protocol == PEER_FILES_PROTOCOL) { // Create, modify or set local config. boost::shared_ptr config(new SyncConfig(MANAGER_LOCAL_CONFIG + context)); switch (mode) { case CREATE_PEER: if (config->exists()) { // When creating a config, the uid must not be in use already. result->failed(GDBusCXX::dbus_error(MANAGER_ERROR_ALREADY_EXISTS, StringPrintf("uid %s is already in use", uid.c_str()))); return; } break; case MODIFY_PEER: if (!config->exists()) { // Modifying expects the config to exist already. result->failed(GDBusCXX::dbus_error(MANAGER_ERROR_NOT_FOUND, StringPrintf("uid %s is not in use", uid.c_str()))); return; } break; case SET_PEER: // May or may not exist, doesn't matter. break; } config->setDefaults(); config->prepareConfigForWrite(); config->setPreventSlowSync(false); config->setSyncURL("local://" + context); config->setPeerIsClient(true); config->setDumpData(false); config->setPrintChanges(false); if (!logdir.empty()) { config->setLogDir(logdir); } if (!maxsessions.empty()) { config->setMaxLogDirs(maxLogDirs); } config->setLogLevel(atoi(getEnv("SYNCEVOLUTION_LOGLEVEL", "0"))); boost::shared_ptr source(config->getSyncSourceConfig(MANAGER_LOCAL_SOURCE)); source->setBackend("evolution-contacts"); source->setDatabaseID(localDatabaseName); source->setSync("local-cache"); source->setURI(MANAGER_REMOTE_SOURCE); config->flush(); // Ensure that database exists. SyncSourceParams params(MANAGER_LOCAL_SOURCE, config->getSyncSourceNodes(MANAGER_LOCAL_SOURCE), config, context); boost::scoped_ptr syncSource(SyncSource::createSource(params)); SyncSource::Databases databases = syncSource->getDatabases(); bool found = false; BOOST_FOREACH (const SyncSource::Database &database, databases) { if (database.m_uri == localDatabaseName) { found = true; break; } } if (!found) { syncSource->createDatabase(SyncSource::Database(localDatabaseName, localDatabaseName)); } // Now also create target config, in the same context. config.reset(new SyncConfig(MANAGER_REMOTE_CONFIG + context)); config->setDefaults(); config->prepareConfigForWrite(); config->setPreventSlowSync(false); config->setDumpData(false); config->setPrintChanges(false); if (!logdir.empty()) { config->setLogDir(logdir); } config->setLogLevel(atoi(getEnv("SYNCEVOLUTION_LOGLEVEL", "0"))); if (!maxsessions.empty()) { config->setMaxLogDirs(maxLogDirs); } source = config->getSyncSourceConfig(MANAGER_REMOTE_SOURCE); if (protocol == PEER_PBAP_PROTOCOL) { // PBAP source->setDatabaseID("obex-bt://" + address); source->setBackend("pbap"); } else { // Local sync with files on the target side. // Format is hard-coded to vCard 3.0. source->setDatabaseID("file://" + address); source->setDatabaseFormat("text/vcard"); source->setBackend("file"); } config->flush(); } else { SE_THROW(StringPrintf("peer config: %s=%s not supported", PEER_KEY_PROTOCOL, protocol.c_str())); } // Report success. SE_LOG_DEBUG(NULL, "%s: created config for protocol %s", uid.c_str(), protocol.c_str()); result->done(); } Manager::PeersMap Manager::getAllPeers() { PeersMap peers; SyncConfig::ConfigList configs = SyncConfig::getConfigs(); std::string prefix = StringPrintf("%s@%s", MANAGER_LOCAL_CONFIG, MANAGER_PREFIX); BOOST_FOREACH (const StringPair &entry, configs) { if (boost::starts_with(entry.first, prefix)) { // One of our configs. std::string uid = entry.first.substr(prefix.size()); StringMap &properties = peers[uid]; // Extract relevant properties from configs. SyncConfig localConfig(entry.first); InitState< std::vector > syncURLs = localConfig.getSyncURL(); std::string syncURL; if (!syncURLs.empty()) { syncURL = syncURLs[0]; } if (boost::starts_with(syncURL, "local://")) { // Look at target source to determine protocol. SyncConfig targetConfig(StringPrintf("%s@%s%s", MANAGER_REMOTE_CONFIG, MANAGER_PREFIX, uid.c_str())); boost::shared_ptr source(targetConfig.getSyncSourceConfig(MANAGER_REMOTE_SOURCE)); std::string backend = source->getBackend(); std::string database = source->getDatabaseID(); if (backend == "PBAP Address Book") { properties[PEER_KEY_PROTOCOL] = PEER_PBAP_PROTOCOL; if (boost::starts_with(database, "obex-bt://")) { properties[PEER_KEY_ADDRESS] = database.substr(strlen("obex-bt://")); } } else if (backend == "file") { properties[PEER_KEY_PROTOCOL] = PEER_FILES_PROTOCOL; if (boost::starts_with(database, "file://")) { properties[PEER_KEY_ADDRESS] = database.substr(strlen("file://")); } } } } } return peers; } void Manager::removePeer(const boost::shared_ptr &result, const std::string &uid) { checkPeerUID(uid); runInSession(StringPrintf("@%s%s", MANAGER_PREFIX, uid.c_str()), Server::SESSION_FLAG_NO_SYNC, result, boost::bind(&Manager::doRemovePeer, this, _1, result, uid)); } void Manager::doRemovePeer(const boost::shared_ptr &session, const boost::shared_ptr &result, const std::string &uid) { std::string localDatabaseName = MANAGER_PREFIX + uid; std::string context = StringPrintf("@%s%s", MANAGER_PREFIX, uid.c_str()); // Remove database. This is expected to be noticed by libfolks // once we delete the database without us having to tell it, but // doing so doesn't hurt. m_enabledEBooks.erase(localDatabaseName); initDatabases(); m_configNode->writeProperty(MANAGER_CONFIG_ACTIVE_ADDRESS_BOOKS_PROPERTY, InitStateString(boost::join(m_enabledEBooks, " "), true)); m_configNode->flush(); // Access config via context (includes sync and target config). boost::shared_ptr config(new SyncConfig(context)); // Remove database, if it exists. if (config->exists(CONFIG_LEVEL_CONTEXT)) { boost::shared_ptr source(config->getSyncSourceConfig(MANAGER_LOCAL_SOURCE)); SyncSourceNodes nodes = config->getSyncSourceNodes(MANAGER_LOCAL_SOURCE); if (nodes.dataConfigExists()) { SyncSourceParams params(MANAGER_LOCAL_SOURCE, nodes, config, context); boost::scoped_ptr syncSource(SyncSource::createSource(params)); SyncSource::Databases databases = syncSource->getDatabases(); bool found = false; BOOST_FOREACH (const SyncSource::Database &database, databases) { if (database.m_uri == localDatabaseName) { found = true; break; } } if (found) { syncSource->deleteDatabase(localDatabaseName, SyncSource::REMOVE_DATA_FORCE); } } } // Remove entire context, just in case. Placing the code here also // ensures that nothing except the config itself has the config // nodes open, which would prevent removing them. For the same // reason the SyncConfig is recreated: to clear all references to // sources that were opened via it. config.reset(new SyncConfig(context)); config->remove(); config->flush(); // Report success. result->done(); } void Manager::syncPeer(const boost::shared_ptr > &result, const std::string &uid) { checkPeerUID(uid); runInSession(StringPrintf("%s@%s%s", MANAGER_LOCAL_CONFIG, MANAGER_PREFIX, uid.c_str()), Server::SESSION_FLAG_NO_SYNC, result, boost::bind(&Manager::doSyncPeer, this, _1, result, uid)); } static Manager::SyncResult SyncReport2Result(const SyncReport &report) { Manager::SyncResult result; int added = 0, updated = 0, removed = 0; if (!report.empty()) { const SyncSourceReport &source = report.begin()->second; added = source.getItemStat(SyncSourceReport::ITEM_LOCAL, SyncSourceReport::ITEM_ADDED, SyncSourceReport::ITEM_TOTAL); updated = source.getItemStat(SyncSourceReport::ITEM_LOCAL, SyncSourceReport::ITEM_UPDATED, SyncSourceReport::ITEM_TOTAL); removed = source.getItemStat(SyncSourceReport::ITEM_LOCAL, SyncSourceReport::ITEM_REMOVED, SyncSourceReport::ITEM_TOTAL); } result["modified"] = added || updated || removed; result["added"] = added; result["updated"] = updated; result["removed"] = removed; return result; } static void doneSyncPeer(const boost::shared_ptr > &result, SyncMLStatus status, const SyncReport &report) { if (status == STATUS_OK || status == STATUS_HTTP_OK) { result->done(SyncReport2Result(report)); } else if (status == (SyncMLStatus)sysync::LOCERR_USERABORT) { result->failed(GDBusCXX::dbus_error(MANAGER_ERROR_ABORTED, "running sync aborted, probably by StopSync()")); } else { result->failed(GDBusCXX::dbus_error(MANAGER_ERROR_BAD_STATUS, Status2String(status))); } } void Manager::doSyncPeer(const boost::shared_ptr &session, const boost::shared_ptr > &result, const std::string &uid) { // Keep client informed about progress. emitSyncProgress(uid, "started", SyncResult()); session->m_doneSignal.connect(boost::bind(boost::ref(emitSyncProgress), uid, "done", SyncResult())); session->m_sourceSynced.connect(boost::bind(&Manager::report2SyncProgress, m_self, uid, _1, _2)); // Determine sync mode. "pbap" is valid only when the remote // source uses the PBAP backend. Otherwise we use "ephemeral", // which ensures that absolutely no sync meta data gets written. std::string syncMode = "ephemeral"; std::string context = StringPrintf("@%s%s", MANAGER_PREFIX, uid.c_str()); boost::shared_ptr config(new SyncConfig(MANAGER_REMOTE_CONFIG + context)); boost::shared_ptr source(config->getSyncSourceConfig(MANAGER_REMOTE_SOURCE)); if (source->getBackend() == "PBAP Address Book") { syncMode = "pbap"; } // After sync(), the session is tracked as the active sync session // by the server. It was removed from our own m_pending list by // doSession(). session->sync(syncMode, SessionCommon::SourceModes_t()); // Relay result to caller when done. session->m_doneSignal.connect(boost::bind(doneSyncPeer, result, _1, _2)); } void Manager::report2SyncProgress(const std::string &uid, const std::string &sourceName, const SyncSourceReport &source) { SyncReport report; report.addSyncSourceReport("foo", source); emitSyncProgress(uid, "modified", SyncReport2Result(report)); } void Manager::stopSync(const boost::shared_ptr &result, const std::string &uid) { checkPeerUID(uid); // Fully qualified peer config name. Only used for sync sessions // and thus good enough to identify them. std::string syncConfigName = StringPrintf("%s@%s%s", MANAGER_LOCAL_CONFIG, MANAGER_PREFIX, uid.c_str()); // Remove all pending sessions of the peer. Make a complete // copy of the list, to avoid issues with modifications of the // underlying list while we iterate over it. BOOST_FOREACH (const Pending_t::value_type &entry, Pending_t(m_pending)) { std::string configName = entry.second->getConfigName(); if (configName == syncConfigName) { entry.first->failed(GDBusCXX::dbus_error(MANAGER_ERROR_ABORTED, "pending sync aborted by StopSync()")); m_pending.remove(entry); } } // Stop the currently running sync if it is for the peer. // It may or may not complete, depending on what it is currently // doing. We'll check in doneSyncPeer(). boost::shared_ptr session = m_server->getSyncSession(); bool aborting = false; if (session) { std::string configName = session->getConfigName(); if (configName == syncConfigName) { // Return to caller later, when aborting is done. session->abortAsync(SimpleResult(boost::bind(&GDBusCXX::Result0::done, result), createDBusErrorCb(result))); aborting = true; } } if (!aborting) { result->done(); } } void Manager::addContact(const boost::shared_ptr< GDBusCXX::Result1 > &result, const std::string &addressbook, const PersonaDetails &details) { try { if (!addressbook.empty()) { SE_THROW("only the system address book is writable"); } m_folks->addContact(createDBusCb(result), details); } catch (...) { dbusErrorCallback(result); } } void Manager::modifyContact(const boost::shared_ptr &result, const std::string &addressbook, const std::string &localID, const PersonaDetails &details) { try { if (!addressbook.empty()) { SE_THROW("only the system address book is writable"); } m_folks->modifyContact(createDBusCb(result), localID, details); } catch (...) { dbusErrorCallback(result); } } void Manager::removeContact(const boost::shared_ptr &result, const std::string &addressbook, const std::string &localID) { try { if (!addressbook.empty()) { SE_THROW("only the system address book is writable"); } m_folks->removeContact(createDBusCb(result), localID); } catch (...) { dbusErrorCallback(result); } } SE_END_CXX syncevolution_1.4/src/dbus/server/pim/manager.h000066400000000000000000000226561230021373600217730ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ /** * The D-Bus IPC binding for folks.h and SyncEvolution's * PBAP support. */ #ifndef INCL_SYNCEVO_DBUS_SERVER_PIM_MANAGER #define INCL_SYNCEVO_DBUS_SERVER_PIM_MANAGER #include "folks.h" #include "locale-factory.h" #include "../server.h" #include "../session.h" #include #include SE_BEGIN_CXX class LocaledListener; /** * Implementation of org._01.pim.contacts.Manager. */ class Manager : public GDBusCXX::DBusObjectHelper { GThread *m_mainThread; boost::weak_ptr m_self; boost::shared_ptr m_server; boost::shared_ptr m_folks; boost::shared_ptr m_locale; boost::shared_ptr m_localedListener; /** Stores "sort" property in XDG ~/.config/syncevolution/pim-manager.ini'. */ boost::shared_ptr m_configNode; std::string m_sortOrder; Bool m_preventingAutoTerm; /** * Contains the EDS UUIDs of all address books contributing to the current * unified address book. */ std::set m_enabledEBooks; typedef std::list< std::pair< boost::shared_ptr, boost::shared_ptr > > Pending_t; /** holds the references to pending session requests, see runInSession() */ Pending_t m_pending; Manager(const boost::shared_ptr &server); void init(); void initFolks(); void initDatabases(); void initSorting(const std::string &order); void localeChanged(); typedef boost::signals2::signal &locale)> LocaleChangedSignal; LocaleChangedSignal m_localeChanged; public: /** Manager.Start() */ void start(); /** Manager.Stop() */ void stop(); /** Manager.IsRunning() */ bool isRunning(); /** Manager.SetSortOrder() */ void setSortOrder(const std::string &order); /** Manager.GetSortOrder() */ std::string getSortOrder(); /** Manager.Search() */ void search(const boost::shared_ptr< GDBusCXX::Result1 > &result, const GDBusCXX::Caller_t &ID, const boost::shared_ptr &watch, const std::vector &filter, const GDBusCXX::DBusObject_t &agentPath); private: void searchWithRegistry(const ESourceRegistryCXX ®istry, const GError *gerror, const boost::shared_ptr< GDBusCXX::Result1 > &result, const GDBusCXX::Caller_t &ID, const boost::shared_ptr &watch, const LocaleFactory::Filter_t &filter, const GDBusCXX::DBusObject_t &agentPath) throw(); void doSearch(const ESourceRegistryCXX ®istry, const boost::shared_ptr< GDBusCXX::Result1 > &result, const GDBusCXX::Caller_t &ID, const boost::shared_ptr &watch, const LocaleFactory::Filter_t &filter, const GDBusCXX::DBusObject_t &agentPath); public: /** Manager.GetActiveAddressBooks() */ void getActiveAddressBooks(std::vector &dbIDs); /** Manager.SetActiveAddressBooks() */ void setActiveAddressBooks(const std::vector &dbIDs); /** Manager.CreatePeer() */ void createPeer(const boost::shared_ptr &result, const std::string &uid, const StringMap &properties); /** Manager.ModifyPeer() */ void modifyPeer(const boost::shared_ptr &result, const std::string &uid, const StringMap &properties); private: enum ConfigureMode { SET_PEER, CREATE_PEER, MODIFY_PEER }; void setPeer(const boost::shared_ptr &result, const std::string &uid, const StringMap &properties, ConfigureMode mode); void doSetPeer(const boost::shared_ptr &session, const boost::shared_ptr &result, const std::string &uid, const StringMap &properties, ConfigureMode mode); public: /** Manager.RemovePeer() */ void removePeer(const boost::shared_ptr &result, const std::string &uid); private: void doRemovePeer(const boost::shared_ptr &session, const boost::shared_ptr &result, const std::string &uid); public: typedef std::map > SyncResult; /** Manager.SyncPeer() */ void syncPeer(const boost::shared_ptr > &result, const std::string &uid); /** Manager.SyncProgress */ GDBusCXX::EmitSignal3 emitSyncProgress; private: void doSyncPeer(const boost::shared_ptr &session, const boost::shared_ptr > &result, const std::string &uid); void report2SyncProgress(const std::string &uid, const std::string &sourceName, const SyncSourceReport &source); public: /** Manager.StopSync() */ void stopSync(const boost::shared_ptr &result, const std::string &uid); typedef std::map PeersMap; /** Manager.GetAllPeers() */ PeersMap getAllPeers(); /** Manager.AddContact() */ void addContact(const boost::shared_ptr< GDBusCXX::Result1 > &result, const std::string &addressbook, const PersonaDetails &details); /** Manager.ModifyContact() */ void modifyContact(const boost::shared_ptr &result, const std::string &addressbook, const std::string &localID, const PersonaDetails &details); /** Manager.RemoveContact() */ void removeContact(const boost::shared_ptr &result, const std::string &addressbook, const std::string &localID); private: /** * Starts a session for the given config and with the * given flags, then when it is active, invokes the callback. * Failures will be reported back to via the result * pointer. */ void runInSession(const std::string &config, Server::SessionFlags flags, const boost::shared_ptr &result, const boost::function &session)> &callback); /** * Common boilerplate code for anything that runs inside * an active session in response to some D-Bus method call * (like doSetPeer). */ void doSession(const boost::weak_ptr &session, const boost::shared_ptr &result, const boost::function &session)> &callback); /** true if the current thread is the one handling the event loop and running all operations */ bool isMain() { return g_thread_self() == m_mainThread; } /** * Runs the operation inside the main thread and returns once the * main thread is done with it. */ void runInMainVoid(const boost::function &operation); template R runInMainRes(const boost::function &operation); void runInMainV(void (Manager::*method)()) { runInMainVoid(boost::bind(method, this)); } template R runInMainR(R (Manager::*method)()) { return runInMainRes(boost::bind(method, this)); } template void runInMainV(void (Manager::*method)(B1), A1 a1) { runInMainVoid(boost::bind(method, this, a1)); } template R runInMainR(R (Manager::*method)(B1), A1 a1) { return runInMainRes(boost::bind(method, this, a1)); } public: /** * Creates an instance of the Manager which runs as part * of the given server, using the same D-Bus connection * and using the server's services (tracking clients, * startup/shutdown). * * While the Manager exists, it blocks auto-termination * of the server and serves method calls as part of the * main event loop. */ static boost::shared_ptr create(const boost::shared_ptr &server); ~Manager(); }; SE_END_CXX #endif // INCL_SYNCEVO_DBUS_SERVER_PIM_MANAGER syncevolution_1.4/src/dbus/server/pim/merge-view.cpp000066400000000000000000000174701230021373600227610ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "merge-view.h" #include SE_BEGIN_CXX MergeView::MergeView(const boost::shared_ptr &view, const Searches &searches, const boost::shared_ptr &locale, const boost::shared_ptr &compare) : m_view(view), m_searches(searches), m_locale(locale), m_compare(compare) { } void MergeView::init(const boost::shared_ptr &self) { m_self = self; } boost::shared_ptr MergeView::create(const boost::shared_ptr &view, const Searches &searches, const boost::shared_ptr &locale, const boost::shared_ptr &compare) { boost::shared_ptr merge(new MergeView(view, searches, locale, compare)); merge->init(merge); return merge; } void MergeView::doStart() { BOOST_FOREACH (const Searches::value_type &search, m_searches) { search->m_quiescenceSignal.connect(boost::bind(&MergeView::edsDone, m_self, std::string(search->getName()))); search->m_addedSignal.connect(boost::bind(&MergeView::addEDSIndividual, m_self, _1)); search->start(); } m_view->m_quiescenceSignal.connect(boost::bind(&MergeView::viewReady, m_self)); m_view->start(); if (m_view->isQuiescent()) { // Switch to view directly. viewReady(); } } void MergeView::addEDSIndividual(const FolksIndividualCXX &individual) throw () { try { Entries::auto_type data(new IndividualData); data->init(m_compare.get(), m_locale.get(), individual); // Binary search to find insertion point. Entries::iterator it = std::lower_bound(m_entries.begin(), m_entries.end(), *data, IndividualDataCompare(m_compare)); size_t index = it - m_entries.begin(); it = m_entries.insert(it, data.release()); SE_LOG_DEBUG(NULL, "%s: added at #%ld/%ld", getName(), (long)index, (long)m_entries.size()); m_addedSignal(index, *it); } catch (...) { Exception::handle(HANDLE_EXCEPTION_NO_ERROR); } } void MergeView::edsDone(const std::string &uuid) throw () { try { SE_LOG_DEBUG(NULL, "%s: %s is done", getName(), uuid.c_str()); BOOST_FOREACH (const Searches::value_type &search, m_searches) { if (!search->isQuiescent()) { SE_LOG_DEBUG(NULL, "%s: still waiting for %s", getName(), search->getName()); return; } } SE_LOG_DEBUG(NULL, "%s: all EDS searches done, %s", getName(), m_viewReady ? "folks also done" : "still waiting for folks, send quiescent now"); if (!m_viewReady) { // folks is still busy, this may take a while. Therefore // flush current status. // // TODO (?): it would be good to have a way to signal "done // for now, better results coming" to the client. As // things stand at the moment, it might conclude that // incomplete resuls from EDS is all that there is to show // to the user. Not much of a problem, though, if the // quality of those results is good. m_quiescenceSignal(); } } catch (...) { Exception::handle(HANDLE_EXCEPTION_NO_ERROR); } } static void GetPersonaUIDs(FolksIndividual *individual, std::set &uids) { GeeCollCXX personas(folks_individual_get_personas(individual), ADD_REF); BOOST_FOREACH (FolksPersona *persona, personas) { // Includes backend, address book, and UID inside address book. uids.insert(folks_persona_get_uid(persona)); } } static bool SamePersonas(FolksIndividual *a, FolksIndividual *b) { std::set a_uids, b_uids; GetPersonaUIDs(a, a_uids); GetPersonaUIDs(b, b_uids); if (a_uids.size() == b_uids.size()) { BOOST_FOREACH (const std::string &uid, a_uids) { if (b_uids.find(uid) == b_uids.end()) { break; } } return true; } return false; } void MergeView::viewReady() throw () { try { if (!m_viewReady) { m_viewReady = true; SE_LOG_DEBUG(NULL, "%s: folks is ready: %d entries from EDS, %d from folks", getName(), (int)m_entries.size(), (int)m_view->size()); // Change signals which transform the current view into the final one. int index; for (index = 0; index < m_view->size() && index < (int)m_entries.size(); index++) { const IndividualData &oldData = m_entries[index]; const IndividualData *newData = m_view->getContact(index); // Minimize changes if old and new data are // identical. Instead of checking all data, assume // that if the underlying contacts are identical, then // so must be the data. if (!SamePersonas(oldData.m_individual, newData->m_individual)) { SE_LOG_DEBUG(NULL, "%s: entry #%d modified", getName(), index); m_modifiedSignal(index, *newData); } } for (; index < m_view->size(); index++) { const IndividualData *newData = m_view->getContact(index); SE_LOG_DEBUG(NULL, "%s: entry #%d added", getName(), index); m_addedSignal(index, *newData); } // Index stays the same when removing, because the following // entries get shifted. int removeAt = index; for (; index < (int)m_entries.size(); index++) { const IndividualData &oldData = m_entries[index]; SE_LOG_DEBUG(NULL, "%s: entry #%d removed", getName(), index); m_removedSignal(removeAt, oldData); } // Free resources which are no longer needed. // The expectation is that this will abort loading // from EDS. try { m_searches.clear(); m_entries.clear(); } catch (...) { Exception::handle(HANDLE_EXCEPTION_NO_ERROR); } SE_LOG_DEBUG(NULL, "%s: switched to folks, quiescent", getName()); m_quiescenceSignal(); } } catch (...) { Exception::handle(HANDLE_EXCEPTION_NO_ERROR); } } SE_END_CXX syncevolution_1.4/src/dbus/server/pim/merge-view.h000066400000000000000000000062521230021373600224220ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ /** * Combines results from multiple independent views ("unified address * book light") until the main view is quiescent. Then this view * switches over to mirroring the main view. When switching, it tries * to minimize change signals. * * The independent views don't have to do their own sorting and don't * need store individuals. */ #ifndef INCL_SYNCEVO_DBUS_SERVER_PIM_MERGE_VIEW #define INCL_SYNCEVO_DBUS_SERVER_PIM_MERGE_VIEW #include "view.h" #include SE_BEGIN_CXX class MergeView : public IndividualView { public: typedef std::vector< boost::shared_ptr > Searches; private: boost::weak_ptr m_self; boost::shared_ptr m_view; Searches m_searches; boost::shared_ptr m_locale; boost::shared_ptr m_compare; /** * As soon as this is true, m_entries becomes irrelevant and * MergeView becomes a simple proxy for m_view. */ Bool m_viewReady; /** * Sorted entries from the simple views. */ typedef boost::ptr_vector Entries; Entries m_entries; MergeView(const boost::shared_ptr &view, const Searches &searches, const boost::shared_ptr &locale, const boost::shared_ptr &compare); void init(const boost::shared_ptr &self); void addEDSIndividual(const FolksIndividualCXX &individual) throw (); void edsDone(const std::string &uuid) throw (); void viewReady() throw (); public: static boost::shared_ptr create(const boost::shared_ptr &view, const Searches &searches, const boost::shared_ptr &locale, const boost::shared_ptr &compare); virtual bool isQuiescent() const { return m_view->isQuiescent(); } virtual int size() const { return m_viewReady ? m_view->size() : m_entries.size(); } virtual const IndividualData *getContact(int index) { return m_viewReady ? m_view->getContact(index) : (index >= 0 && (size_t)index < m_entries.size()) ? &m_entries[index] : NULL; } protected: virtual void doStart(); }; SE_END_CXX #endif // INCL_SYNCEVO_DBUS_SERVER_PIM_MERGE_VIEW syncevolution_1.4/src/dbus/server/pim/org._01.pim.contacts.service.in000066400000000000000000000001531230021373600257310ustar00rootroot00000000000000[D-BUS Service] Name=org._01.pim.contacts Exec=@libexecdir@/syncevo-dbus-server @SYNCEVO_DBUS_SERVER_ARGS@ syncevolution_1.4/src/dbus/server/pim/persona-details.h000066400000000000000000000043171230021373600234450ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ /** * The D-Bus IPC binding for folks.h. Maps FolksIndividual to and * from the D-Bus dict described in pim-manager-api.txt. */ #ifndef INCL_SYNCEVO_DBUS_SERVER_PERSONA_DETAILS #define INCL_SYNCEVO_DBUS_SERVER_PERSONA_DETAILS #include #include #include #include #include SE_BEGIN_CXX // This is okay with g++ 4.7 and clang 3.0. // typedef boost::intrusive_ptr PersonaDetails; // However, g++ 4.5 is more picky and rejects // template <> struct dbus_traits : // "template argument 1 is invalid" // // Besides working around this compiler bug, // defining a real class also has the advantage that we can // define D-Bus traits for multiple classes using boost::intrusive_ptr // as base, should that ever be necessary. class PersonaDetails : public boost::intrusive_ptr { public: PersonaDetails() : // Keys are static (from folks_persona_store_detail_key()), values // are dynamically allocated GValueCXX instances owned by the hash. boost::intrusive_ptr(g_hash_table_new_full(g_str_hash, g_str_equal, NULL, GValueCXX::destroy), false) {} }; SE_END_CXX #endif // INCL_SYNCEVO_DBUS_SERVER_PERSONA_DETAILS syncevolution_1.4/src/dbus/server/pim/pim-manager-api.txt000066400000000000000000000423611230021373600237100ustar00rootroot00000000000000Preamble ======== This text describes a D-Bus API. The API implements in-vehicle infotainment (IVI) use cases around contacts: - cache address books from peers (primarily phones connected via Bluetooth) in local address books - provide a unified address book that combines a configurable (and changing) subset of the local address books - fast phone number lookup - browsing and searching in the unified address book Tasks that are expected to be done by the user of this API: - identify peers and their capabilities - decide how and when peer data should be cached - define which data goes into the unified address book In other words, the API provides the mechanisms and the user the policy. Several aspects of this API may differ depending on the implementation. For example, searching for contacts and syncing with peers are not described in the API. Consult the documentation of the API implementation to learn what it supports. For SyncEvolution, that documentation is the src/dbus/server/pim/README file. Datatypes ========= Peers ----- A peer is an entity which has exactly one address book that is meant to be cached locally. Typically a peer is a phone connected via Bluetooth and accessed via PBAP, but it could also be a web service that supports CardDAV or a phone with SyncML support. Peers are identified by a unique string ID. That ID needs to be assigned by the user of this API. The string must not be empty and may only contain characters a-z, 0-9 and hyphen. No other assumptions about its content are made. For example, the phone's Bluetooth MAC address could be used after removing or replacing the colon and using lower case hex characters. For an entity that has more than one address book, multiple peers must be configured. For each peer, enough information must be provided to access its address book. That information is passed via D-Bus as a string-to-string dict, with keys and their values being defined by the implementation. Address books ------------- Address books which mirror data from a specific peer use the string "peer-" as ID, where is the unique ID of that peer. In addition, there is a system address book which is independent of any particular phone. Its ID is the empty string. This naming scheme can be extended later on, to support other kinds of address books. Contact ------- A single contact is transferred via D-Bus as a string->variant dict where the keys are predefined property names and the values represent simple values (a string for "full-name") or more complex structures (list of phone numbers for "phone-numbers", with each list entry itself being a combination of type flags and the actual value). [comment: this mirrors the properties of a libfolks Individual: http://telepathy.freedesktop.org/doc/folks/c/FolksIndividual.html] Some properties of a FolksIndividual only make sense locally and are not transmitted, for example the personas it is derived from. Some other properties provide information not found that way in FolksIndividual: - "source" = list of string pairs, where each pair is a combination of address book ID and local contact ID inside that address book (not necessarily the same as the vCard UID of a contact!) - "id" = an opaque string which identifies the contact while it exists inside any PIM Manager view. See ContactsAdded and ReadContacts. Property values which are large (like photos) are not sent via D-Bus. Instead a link to a local file is sent. For a full definition of contact properties see the implementation documentation. Search results -------------- The goal is to support a UI which: - displays an ordered list of the search result, - can show the initial results with minimal delay, - can load actual content for the display as needed (only load the parts which are visible or will be visible soon). The content of the unified address book can change at any time. The API design takes that into account by using a model/view/controller model. The model is the complete list of contacts, sorted according to the currently configured sort order. Sorting is part of the model to simplify generating views. The view is the subset of the data that a user of the API has requested. In the most extreme case, all contacts are part of the view. Therefore contact data has to be requested explicitly. Contacts are numbered 0 to n-1 in each view, where n is the number of contacts in the view. Sort order is the same as in the underlying model. Change notifications with these index numbers are sent as contacts are added, modified or removed. The controller is the part of the API which allows changing contacts in the system address book, changing the sort order, enabling or disabling address books, etc. Note that removing or adding a contact changes the numbers assigned to other contacts. Example: - A view containing 10 contacts is created. - A notification about "contacts #0 to #9 added" is sent (given as pair of first index and count, not list of numbers). - The five contacts starting with #5 to are read via their ID. - Contact #4 gets removed. The user needs to remember that the data that it has now corresponds to contacts #4 to #8. - Contact #5 gets added, before the contact which had that number before. The user now has contacts #4 and #6 to #9. It should request contact #5 if (or once) it is needed to provide a complete list to the user. [comment: using a view could be simplified by including contact data in the change notifications. This is not planned at the moment because it would not work well for large views. When adding it, there should be an API to restrict which properties of a contact get sent.] Error handling ============== D-Bus error messages are not localized. They are meant for debugging, not for displaying to the user. In cases where the caller may be able to do something about an error, specific error codes are defined as part of the API. However, typically errors are generic and the caller simply has to assume that the PIM storage is currently unusable. Unless noted otherwise, calls return when the requested operation is complete. The following errors are defined. In addition to the D-Bus name of the error they provide a textual error description. org._01.pim.contacts.Manager.Aborted Some operation was intentionally aborted instead of letting it complete. Typically not an error. org._01.pim.contacts.Manager.BadStatus A generic error report. The error description is a string which gives further information for debugging. API === PIM Manager ----------- The PIM manager is used to hold the unified address book in memory, create views on it, change configuration and control data transfers from phones. Service: org._01.pim.contacts Interface: org._01.pim.contacts.Manager Object path: /org/01/pim/contacts Methods: void Start() The PIM manager does not start loading contact data right away. That allows setting the options like sort order first and/or delaying the loading until it is needed. After Start(), changing options that affect the unified address book will take effect immediately. Calling Start() is optional, any method asking for data will automatically do that. void Stop() Explicitly tells the PIM manager to discard the unified address book and free up the memory if possible (= not currently in use). Primarily useful for testing. void SetSortOrder(string mode) "mode" must be one of the values supported by the implementation. string GetSortOrder() Returns the current sort order. list of strings GetActiveAddressBooks() Returns the IDs of the address books which currently contribute to the unified address book. void SetActiveAddressBooks(list of strings) Sets the address books which contribute to the unified address book. void CreatePeer(string uid, dict properties) Creates a peer. Will fail with a org._01.pim.contacts.Manager.AlreadyExists error if the uid is already in use. To change the configuration of a peer, remove and recreate it. This ensures that its data gets removed, too. [A ModifyPeer() might get added if there is demand for it and the desired behavior (remove cached data or keep it?) is better understood.] As a backwards compatibility measure this method is also available as SetPeer() with the semantic that the config is created or modified automatically as needed. void RemovePeer(string uid) Removes a peer and all its cached data. If that data was part of the active address books, it will be removed automatically. dict SyncPeer(string uid) Retrieve contacts from the peer and ensure that the local cache is identical to the address book of the peer. The call returns once the operation is complete. Only if there was no error can the caller assume that the cache is up-to-date. In this case, a string to variant dictionary is returned which provided additional information about the sync. The content of the dictionary is implementation dependent. If the call fails, no dictionary is returned and the local cache may or may not be up-to-date. It may or may not have been updated. The caller needs to check the local cache to find out what it contains. void StopSync(string uid) Stop any running sync for the given peer. The SyncPeer() method which started such a sync will return with an "aborted" error once the sync was stopped. dict of UID to string key/value dict GetAllPeers() Returns information about all currently configured peers. object Search(list filter, object agent) Creates a new view which contains all contacts matching the filter. The call returns the object path of a view object after validating parameters and starting the result gathering, and before completing the search. The view object can be used to control the view via the org._01.pim.contacts.ViewControl interface. The content of the filter is defined by the implementation. Notifications for the view are sent back to the caller by invoking methods from the org._01.pim.contacts.ViewAgent interface on the object whose path is given in the "view" parameter. If any of these method calls fail, the view will automatically be destroyed. In other words, the caller first needs to get ready to process results by registering an object on the bus before calling Search(). [comment: this allows sending results to just one recipient, something that cannot be done easily with the use of signals as in, for example, obexd. In obexd, the initiator of a transfer has to subscribe to org.bluez.obex.Transfer on the object path returned to it when starting the transfer, then check the current status before waiting for signals, because the "Completed" signal might have been sent before it could register for it.] string AddContact(string addressbook, dict contact) Adds a new contact to the given address book. Typically only the system address book is writable. Contact properties which are unknown or cannot be stored are silently ignored. Returns the local ID of the new contact in the address book. Photo data that is sent inline in the dict will be split out into a file that gets associated with the contact. A photo file that gets linked will continue to be owned by the caller; the contact storage may or may not make a copy of it, depending on which storage is used. void ModifyContact(string addressbook, string localid, dict contact) Updates an existing contact. void RemoveContact(string addressbook, string localid) Remove the contact and all of its associated data (like the photo, if the photo file is owned by the contact storage). Signals: SyncProgress(string uid, string event, dict data) Provides information about a running sync for the peer with the given "uid". The "event" string describes what happened and the "data" dictionary provides further information about it with a mapping from event specific string keys to variants as value. Service: org._01.pim.contacts Interface: org._01.pim.contacts.ViewControl Object path: [variable prefix]/{view0,view1,....} Methods: list of (int index, contact dicts) pairs ReadContacts(array ids) Requests the data of the contacts idenfified via their IDs. Only the data of contacts that are still part of the view can be returned. The returned list contains the current index of the requested contact plus its data. -1 and an empty dictionary are returned for contacts which can no longer be read, for example because they were removed from the view in the meantime or because the ID was simply invalid. Note that the caller must process the call response after all events via the ViewAgent interface. Otherwise the index numbers are potentially out of sync and thus unreliable. Doing this call asynchronously and dealing with the response as part of the main event loop will do the right thing automatically, because D-Bus guarantees ordering of messages. Making this explicit by returning data via another org._01.pim.contacts.ViewAgent method was considered and rejected a) for the sake of keeping this API simple and b) to allow simple synchronous calls where it makes sense (testing, for example). void Close() Closes the view and all resources associated with it. Pending ReadContacts() calls will return without any data and no error. void RefineSearch(list filter) Replaces the current filter of the view with a new one. The new filter must be stricter than the old one. Contacts which were already filtered out will not be added back to the view when setting a less restrictive filter (simplifies the implementation and improves performance). void ReplaceSearch(list filter, bool refine) Same as RefineSearch() if refine is true. If refine is false, the new filter can be less restrictive and contacts which did not match the old filter will be added back to the list of matching contacts. Service: [user of the PIM Manager] Interface: org._01.pim.contacts.ViewAgent Object path: [as chosen by user of PIM Manager] Methods: void ContactsModified(object view, int start, array ids) Contacts #start till #start + count (inclusive) have changed. Data that the recipient of the call might have cached became invalid and should be reloaded. It is possible that a contact gets replaced by another with a single "contact modified" signal. In other words, the ID at each position may change and thus the IDs are sent as part of the signal. In cases where a contact changes its position in the view, both a combination of "contact removed" + "contact added" (single contact changes) as well as several "contact modified" signals are possible (contacts swap position, for example when reordering). In the later case, a contact will temporarily appear at two different positions. void ContactsAdded(object view, int start, array ids) New contacts were added to the view. The number of new contacts is given via the size of the ids array. The ID of each new contact is guaranteed to be the same in all views. IDs may get reused after their contact got removed from the last view it was contained in. In particular there is no guarantee that it is persistent across restarts of the PIM manager. The contact which previously had index #start now has index #start + count, etc. void ContactsRemoved(object view, int start, array ids) Some contacts were removed from the view. The contact which previous had index #start + count (if there was one) now has index #start, etc. void Quiescent(object view) The current content of the view is complete. No further updates are expected until something changes again (underlying data, ordering, active address books, filter). Changing data (directly or via syncing) can trigger multiple "Quiescent" signals, depending on when these changes are reported by the underlying storage. Changing multiple settings will trigger one "Quiescent" per change. Implementing the Quiescent() method in a ViewAgent is optional. syncevolution_1.4/src/dbus/server/pim/test-dbus/000077500000000000000000000000001230021373600221075ustar00rootroot00000000000000syncevolution_1.4/src/dbus/server/pim/test-dbus/broken-config/000077500000000000000000000000001230021373600246325ustar00rootroot00000000000000syncevolution_1.4/src/dbus/server/pim/test-dbus/broken-config/config/000077500000000000000000000000001230021373600260775ustar00rootroot00000000000000syncevolution_1.4/src/dbus/server/pim/test-dbus/broken-config/config/syncevolution/000077500000000000000000000000001230021373600310205ustar00rootroot00000000000000syncevolution_1.4/src/dbus/server/pim/test-dbus/broken-config/config/syncevolution/pim-manager.ini000066400000000000000000000000541230021373600337150ustar00rootroot00000000000000sort = foobar active = no-such-addressbook syncevolution_1.4/src/dbus/server/pim/test-dbus/db-active/000077500000000000000000000000001230021373600237455ustar00rootroot00000000000000syncevolution_1.4/src/dbus/server/pim/test-dbus/db-active/config/000077500000000000000000000000001230021373600252125ustar00rootroot00000000000000syncevolution_1.4/src/dbus/server/pim/test-dbus/db-active/config/syncevolution/000077500000000000000000000000001230021373600301335ustar00rootroot00000000000000syncevolution_1.4/src/dbus/server/pim/test-dbus/db-active/config/syncevolution/pim-manager.ini000066400000000000000000000001721230021373600330310ustar00rootroot00000000000000sort = active = system-address-book pim-manager-testactive-testcontacts-a, pim-manager-testactive-testcontacts-c syncevolution_1.4/src/dbus/server/pim/test-dbus/first-last-sort/000077500000000000000000000000001230021373600251645ustar00rootroot00000000000000syncevolution_1.4/src/dbus/server/pim/test-dbus/first-last-sort/config/000077500000000000000000000000001230021373600264315ustar00rootroot00000000000000syncevolution_1.4/src/dbus/server/pim/test-dbus/first-last-sort/config/syncevolution/000077500000000000000000000000001230021373600313525ustar00rootroot00000000000000syncevolution_1.4/src/dbus/server/pim/test-dbus/first-last-sort/config/syncevolution/pim-manager.ini000066400000000000000000000000221230021373600342420ustar00rootroot00000000000000sort = first/last syncevolution_1.4/src/dbus/server/pim/test-dbus/simple-sort/000077500000000000000000000000001230021373600243655ustar00rootroot00000000000000syncevolution_1.4/src/dbus/server/pim/test-dbus/simple-sort/config/000077500000000000000000000000001230021373600256325ustar00rootroot00000000000000syncevolution_1.4/src/dbus/server/pim/test-dbus/simple-sort/config/syncevolution/000077500000000000000000000000001230021373600305535ustar00rootroot00000000000000syncevolution_1.4/src/dbus/server/pim/test-dbus/simple-sort/config/syncevolution/pim-manager.ini000066400000000000000000000000071230021373600334460ustar00rootroot00000000000000sort = syncevolution_1.4/src/dbus/server/pim/testpim.py000077500000000000000000005730541230021373600222550ustar00rootroot00000000000000#! /usr/bin/python -u # -*- coding: utf-8 -*- # vim: set fileencoding=utf-8 :# # # Copyright (C) 2012 Intel Corporation # # 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) version 3. # # 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 Street, Fifth Floor, Boston, MA # 02110-1301 USA # PIM Manager specific tests, using Python unittest as framework. # # Run with "syncevolution", "synccompare" and "syncevo-dbus-server" in # the PATH. # # Uses the normal testdbus.py infrastructure by including that file. # Can be run directly from the SyncEvolution source code or after # copying test/testdbus.py and src/dbus/server/pim/testpim.py into the # same directory. import os import errno import sys import inspect import unittest import time import copy import subprocess import dbus import traceback import re import itertools import codecs import pprint import shutil import localed # Update path so that testdbus.py can be found. pimFolder = os.path.realpath(os.path.abspath(os.path.split(inspect.getfile(inspect.currentframe()))[0])) if pimFolder not in sys.path: sys.path.insert(0, pimFolder) testFolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile(inspect.currentframe()))[0], "../../../../test"))) if testFolder not in sys.path: sys.path.insert(0, testFolder) # Rely on the glib/gobject compatibility import code in test-dbus.py. from testdbus import glib, gobject from testdbus import DBusUtil, timeout, Timeout, property, usingValgrind, xdg_root, bus, logging, NullLogging, loop import testdbus def timeFunction(func, *args1, **args2): start = time.time() res = func(*args1, **args2) end = time.time() return (end - start, res) @unittest.skip("not a real test") class ContactsView(dbus.service.Object, unittest.TestCase): '''Implements ViewAgent, starts a search and mirrors the remote state locally.''' counter = 1 def __init__(self, manager): '''Create ViewAgent with the chosen path.''' self.manager = manager # Ensure unique path across different tests. self.path = '/org/syncevolution/testpim%d' % ContactsView.counter ContactsView.counter = ContactsView.counter + 1 self.view = None # List of encountered errors in ViewAgent, should always be empty. self.errors = [] # Currently known contact data, size matches view. # Entry is a string (just the ID is known), # a tuple (ID + time when reading started), or # a dictionary (actual content known). self.contacts = [] # Change events, as list of ("modified/added/removed", start, count). self.events = [] # Number of times that ViewAgent.Quiescent() was called. self.quiescentCount = 0 self.logging = logging # Called at the end of each view notification. # May throw exceptions, which will be recorded in self.errors. self.check = lambda: True dbus.service.Object.__init__(self, dbus.SessionBus(), self.path) unittest.TestCase.__init__(self) # Set self.check and returns a wrapper for use in runUntil. # The function passed in should not check self.errors, this # will be added by setCheck() for use in runUntil. def setCheck(self, check): if check: self.check = check else: check = lambda: True self.check = check return lambda: (self.assertEqual([], self.errors), check()) def runTest(self): pass def search(self, filter): '''Start a search.''' self.viewPath = self.manager.Search(filter, self.path) self.view = dbus.Interface(bus.get_object(self.manager.bus_name, self.viewPath), 'org._01.pim.contacts.ViewControl') def close(self): self.view.Close() def getIDs(self, start, count): '''Return just the IDs for a range of contacts in the current view.''' return [isinstance(x, dict) and x['id'] or \ isinstance(x, tuple) and x[0] or \ x \ for x in self.contacts[start:start + count]] def countData(self, start, count): '''Number of contacts with data in the given range.''' total = 0 for contact in self.contacts[start:start + count]: if isinstance(contact, dict): total = total + 1 return total def haveData(self, start, count = 1): '''True if all contacts in the range have data.''' return count == self.countData(start, count) def haveNoData(self, start, count = 1): '''True if all contacts in the range have no data.''' return 0 == self.countData(start, count) def processEvent(self, message, event): self.logging.log(message) self.events.append(event) @dbus.service.method(dbus_interface='org._01.pim.contacts.ViewAgent', in_signature='oias', out_signature='') def ContactsModified(self, view, start, ids): count = len(ids) self.processEvent('contacts modified: %s, start %d, ids %s' % (view, start, ids), ('modified', start, count)) try: self.assertEqual(view, self.viewPath) self.assertGreaterEqual(start, 0) self.assertGreater(count, 0) self.assertLessEqual(start + count, len(self.contacts)) # Overwrite valid data with just the (possibly modified) ID. self.contacts[start:start + count] = [str(x) for x in ids] self.logging.printf('contacts modified => %s', self.contacts) self.check() except: error = traceback.format_exc() self.logging.printf('contacts modified: error: %s' % error) self.errors.append(error) @dbus.service.method(dbus_interface='org._01.pim.contacts.ViewAgent', in_signature='oias', out_signature='') def ContactsAdded(self, view, start, ids): count = len(ids) self.processEvent('contacts added: %s, start %d, ids %s' % (view, start, ids), ('added', start, count)) try: self.assertEqual(view, self.viewPath) self.assertGreaterEqual(start, 0) self.assertGreater(count, 0) self.assertLessEqual(start, len(self.contacts)) self.contacts[start:start] = [str(x) for x in ids] self.logging.printf('contacts added => %s', self.contacts) self.check() except: error = traceback.format_exc() self.logging.printf('contacts added: error: %s' % error) self.errors.append(error) @dbus.service.method(dbus_interface='org._01.pim.contacts.ViewAgent', in_signature='oias', out_signature='') def ContactsRemoved(self, view, start, ids): count = len(ids) self.processEvent('contacts removed: %s, start %d, ids %s' % (view, start, ids), ('removed', start, count)) try: self.assertEqual(view, self.viewPath) self.assertGreaterEqual(start, 0) self.assertGreater(count, 0) self.assertLessEqual(start + count, len(self.contacts)) self.assertEqual(self.getIDs(start, count), ids) del self.contacts[start:start + count] self.logging.printf('contacts removed => %s', self.contacts) self.check() except: error = traceback.format_exc() self.logging.printf('contacts removed: error: %s' % error) self.errors.append(error) @dbus.service.method(dbus_interface='org._01.pim.contacts.ViewAgent', in_signature='o', out_signature='') def Quiescent(self, view): # Allow exceptions from self.processEvent to be returned to caller, # because Quiescent() is allowed to fail. testQuiescentOptional # depends on that after hooking into the self.processEvent call. self.quiescentCount = self.quiescentCount + 1 self.processEvent('quiescent: %s' % view, ('quiescent',)) try: self.check() except: error = traceback.format_exc() self.logging.printf('quiescent: error: %s' % error) self.errors.append(error) def read(self, start, count=1): '''Read the specified range of contact data.''' starttime = time.time() ids = [] for index, entry in enumerate(self.contacts[start:start+count]): if isinstance(entry, str): ids.append(entry) self.contacts[start + index] = (entry, starttime) # Avoid composing too large requests because they make the # server unresponsive and trigger our Watchdog. Instead chop up # into pieces and ask for more once we get the response. def step(contacts, start): for index, contact in contacts: if index >= 0: self.contacts[index] = contact if start < len(ids): end = min(start + 50, len(ids)) self.view.ReadContacts(ids[start:end], reply_handler=lambda contacts: step(contacts, end), error_handler=lambda error: self.errors.append(x)) step([], 0) class Watchdog(): '''Send D-Bus queries regularly to the daemon and measure response time.''' def __init__(self, test, manager, threshold=0.1, interval=0.2): self.test = test self.manager = manager self.started = None self.results = [] # tuples of start time + duration self.threshold = threshold self.interval = interval self.timeout = None def start(self): self.timeout = glib.Timeout(int(self.interval * 1000)) self.timeout.set_callback(self._ping) self.timeout.attach(loop.get_context()) if self.threshold < 0: print '\nPinging server at intervals of %fs.' % self.interval def stop(self): if self.timeout: self.timeout.destroy() self.started = None def check(self): '''Assert that all queries were served quickly enough.''' if self.threshold > 0: tooslow = [x for x in self.results if x[1] > self.threshold] self.test.assertEqual([], tooslow) if self.started: self.test.assertLess(time.time() - self.started, self.threshold) def reset(self): self.results = [] self.started = None def checkpoint(self, name): self.check() logging.printf('ping results for %s: %s', name, self.results) if self.threshold < 0: for result in self.results: print '%s: ping duration: %f' % (name, result[1]) self.reset() def _ping(self): if not self.started: # Run with a long timeout. We want to know how long it # takes to reply, even if it is too long. started = time.time() self.started = started self.manager.GetAllPeers(reply_handler=lambda peers: self._done(started, self.results, None), error_handler=lambda error: self._done(started, self.results, error)) return True def _done(self, started, results, error): '''Record result. Intentionally uses the results array from the time when the call started, to handle intermittent checkpoints.''' duration = time.time() - started if self.threshold > 0 and duration > self.threshold or error: logging.printf('ping failure: duration %fs, error %s', duration, error) if error: results.append((started, duration, error)) else: results.append((started, duration)) if self.started == started: self.started = None class TestPIMUtil(DBusUtil): def setUp(self): self.cleanup = [] self.manager = dbus.Interface(bus.get_object('org._01.pim.contacts', '/org/01/pim/contacts'), 'org._01.pim.contacts.Manager') # Determine location of EDS source configs. config = os.environ.get("XDG_CONFIG_HOME", None) if config: self.sourcedir = os.path.join(config, "evolution", "sources") else: self.sourcedir = os.path.expanduser("~/.config/evolution/sources") # SyncEvolution uses a local temp dir. self.configdir = os.path.join(xdg_root, "syncevolution") # Common prefix for peer UIDs. Use different prefixes in each test, # because evolution-addressbook-factory keeps the old instance # open when syncevo-dbus-server stops or crashes and then fails # to work with that database when we remove it. self.uidPrefix = self.testname.replace('_', '-').lower() + '-' # Prefix used by PIM Manager in EDS. self.managerPrefix = 'pim-manager-' # Remove all sources and configs which were created by us # before running the test. removed = False if os.path.exists(self.sourcedir): for source in os.listdir(self.sourcedir): if source.startswith(self.managerPrefix + self.uidPrefix): os.unlink(os.path.join(self.sourcedir, source)) removed = True if removed: # Give EDS time to notice the removal. time.sleep(5) def tearDown(self): for x in self.cleanup: x() def setUpView(self, peers=['foo'], withSystemAddressBook=False, search=[], withLogging=True): '''Set up peers and create a view for them.''' # Ignore all currently existing EDS databases. self.sources = self.currentSources() self.expected = self.sources.copy() self.peers = {} # dummy peer directory self.contacts = os.path.abspath(os.path.join(xdg_root, 'contacts')) os.makedirs(self.contacts) # add peers self.uid = None self.uids = [] for peer in peers: uid = self.uidPrefix + peer if self.uid == None: # Remember first uid for tests which only use one. self.uid = uid self.uids.append(uid) self.peers[uid] = {'protocol': 'PBAP', 'address': 'xxx'} self.manager.SetPeer(uid, self.peers[uid]) self.expected.add(self.managerPrefix + uid) self.assertEqual(self.peers, self.manager.GetAllPeers()) self.assertEqual(self.expected, self.currentSources()) # Delete local data in the cache. logging.log('deleting all items of ' + uid) self.runCmdline(['--delete-items', '@' + self.managerPrefix + uid, 'local', '*']) # Limit active databases to the one we just created. addressbooks = ['peer-' + uid for uid in self.uids] if withSystemAddressBook: addressbooks.append('') # Delete content of system address book. The check at the start of # the script ensures that this is not the real one of the user. logging.log('deleting all items of system address book') self.runCmdline(['--delete-items', 'backend=evolution-contacts', '--luids', '*']) self.manager.SetActiveAddressBooks(addressbooks) self.assertEqual(addressbooks, self.manager.GetActiveAddressBooks(), sortLists=True) # Start view. self.view = ContactsView(self.manager) if not withLogging: self.view.processEvent = lambda message, event: True self.view.logging = NullLogging() # Optional: search and wait for it to be stable. if search != None: self.view.search(search) self.runUntil('empty view', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.quiescentCount > 0) # Clear unknown sequence of events. self.view.events = [] def runCmdline(self, command, **args): '''use syncevolution command line without syncevo-dbus-server, for the sake of keeping code here minimal''' cmdline = testdbus.TestCmdline() cmdline.setUp() try: cmdline.running = True cmdline.session = None # Must use our own self.storedenv here, cmdline doesn't have it. c = [ '--daemon=no' ] + command logging.printf('running syncevolution command line: %s' % c) return cmdline.runCmdline(c, testInstance=self, env=self.storedenv, sessionFlags=None, **args) finally: cmdline.running = False def exportCache(self, uid, filename): '''dump local cache content into file''' self.runCmdline(['--export', filename, '@' + self.managerPrefix + uid, 'local']) contacts = open(filename, 'r').read() # Ignore one empty vcard because the Nokia N97 always sends such a vcard, # despite having deleted everything via SyncML. contacts = re.sub(r'''BEGIN:VCARD\r? VERSION:3.0\r? ((UID|PRODID|REV):.*\r? |N:;;;;\r? |FN:\r? )*END:VCARD(\r|\n)*''', '', contacts, 1) open(filename, 'w').write(contacts) def extractLUIDs(self, out): '''Extract the LUIDs from syncevolution --import/update output.''' r = re.compile(r'''#.*: (\S+)\n''') matches = r.split(out) # Even entry is text (empty here), odd entry is the match group. return matches[1::2] def compareDBs(self, expected, real, ignoreExtensions=True): '''ensure that two sets of items (file or directory) are identical at the semantic level''' env = copy.deepcopy(os.environ) if ignoreExtensions: # Allow the phone to add extensions like X-CLASS=private # (seen with Nokia N97 mini - FWIW, the phone should have # use CLASS=PRIVATE, because it was using vCard 3.0). # Also removes X-EVOLUTION-FILE-AS. env['CLIENT_TEST_STRIP_PROPERTIES'] = 'X-[-_a-zA-Z0-9]*' sub = subprocess.Popen(['synccompare', expected, real], env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) stdout, stderr = sub.communicate() self.assertEqual(0, sub.returncode, msg="env 'CLIENT_TEST_STRIP_PROPERTIES=%s' synccompare %s %s\n%s" % (env.get('CLIENT_TEST_STRIP_PROPERTIES', ''), expected, real, stdout)) def configurePhone(self, phone, uid, contacts): '''set up SyncML for copying all vCard 3.0 files in 'contacts' to the phone, if phone was set''' if phone: self.runCmdline(['--configure', 'syncURL=obex-bt://' + phone, 'backend=file', 'database=file://' + contacts, 'databaseFormat=text/vcard', # Hard-coded Nokia config. 'remoteIdentifier=PC Suite', 'peerIsClient=1', 'uri=Contacts', # Config name and source for syncPhone(). 'phone@' + self.managerPrefix + uid, 'addressbook']) def syncPhone(self, phone, uid, syncMode='refresh-from-local'): '''use SyncML config for copying all vCard 3.0 files in 'contacts' to the phone, if phone was set''' if phone: self.runCmdline(['--sync', syncMode, 'phone@' + self.managerPrefix + uid, 'addressbook']) def run(self, result, serverArgs=[]): # No errors must be logged. During testRead, libphonenumber used to print # [ERROR] Number too short to be viable: 8 # [ERROR] The string supplied did not seem to be a phone number. # to stdout until we reduced the log level. # # ==4039== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 185 from 175) # as printed by valgrind is okay, so don't match that. # # We check both D-Bus messages (which did not contain that # text, but some other error messages) and the servers stdout. def unicodeLog(test, log): open('/tmp/out', 'wb').write(log) print re.match(r'ERROR(?! SUMMARY:)', log) # Using assertNotRegexMatches with a negative lookahead led to unicode errors?! # Therefore stick to plain text checks and avoid false matches against valgind's # 'ERROR SUMMARY' by replacing that first. self.runTestDBusCheck = lambda test, log: test.assertNotIn('ERROR', log.replace('ERROR SUMMARY:', 'error summary:')) self.runTestOutputCheck = self.runTestDBusCheck # We have to clean the xdg_root ourselves. We have to be nice # to EDS and can't just wipe out the entire directory. # Same for GNOME Online Accounts. items = list(os.walk(xdg_root)) items.reverse() for dirname, dirs, files in items: reldir = os.path.relpath(dirname, xdg_root) for dir in dirs: # evolution-source-registry gets confused when we remove # the "sources" directory itself. # GNOME Online Accounts settings and GNOME keyrings must survive. if (reldir == 'config/evolution' and dir == 'sources') or \ (reldir == 'data' and dir == 'keyrings') or \ (reldir == 'config' and dir.startswith('goa')): continue dest = os.path.join(dirname, dir) try: os.rmdir(dest) except OSError, ex: if ex.errno != errno.ENOTEMPTY: raise for file in files: dest = os.path.join(dirname, file) # Don't delete a DB that may still be in use by # evolution-addressbook-factory and that we may still need. # Other DBs can be removed because we are not going to depend on # them anymore thanks to the per-test uid prefix. if reldir == 'data/evolution/addressbook/system' or \ reldir == 'data/keyrings' or \ reldir.startswith('config/goa'): continue os.unlink(dest) # We have to wait until evolution-source-registry catches up # and recognized that the sources are gone, otherwise # evolution-addressbook-factory will keep the .db files open # although we already removed them. while True: out, err = subprocess.Popen(['syncevolution', '--print-databases', '--daemon=no', 'backend=evolution-contacts'], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() self.assertEqual('', err) # Count the number of database entries. An exact # comparison against the output does not work, because the # name of the system address book due to localization and # (to a lesser degree) its UID may change. if len([x for x in out.split('\n') if x.startswith(' ')]) == 1: break else: time.sleep(0.5) # Does not work, Reload()ing a running registry confuses evolution-addressbook-factory. # # for i in range(0, 100): # try: # registry = dbus.Interface(bus.get_object('org.gnome.evolution.dataserver.Sources%d' % i, # '/org/gnome/evolution/dataserver/SourceManager'), # 'org.gnome.evolution.dataserver.SourceManager') # except dbus.exceptions.DBusException, ex: # if ex.get_dbus_name() != 'org.freedesktop.DBus.Error.ServiceUnknown': # raise # registry.Reload() # # Give it some time... # time.sleep(2) # Runtime varies a lot when using valgrind, because # of the need to check an additional process. Allow # a lot more time when running under valgrind. self.runTest(result, own_xdg=False, own_home=False, serverArgs=serverArgs, defTimeout=usingValgrind() and 600 or 20) def currentSources(self): '''returns current set of EDS sources as set of UIDs, without the .source suffix''' return set([os.path.splitext(x)[0] for x in (os.path.exists(self.sourcedir) and os.listdir(self.sourcedir) or [])]) def readManagerIni(self): '''returns content of manager.ini file, split into lines and sorted, None if not found''' filename = os.path.join(xdg_root, "config", "syncevolution", "pim-manager.ini") if os.path.exists(filename): lines = open(filename, "r").readlines() lines.sort() return lines else: return None class TestContacts(TestPIMUtil, unittest.TestCase): """Tests for org._01.pim.contacts API. The tests use the system's EDS, which must be >= 3.6. They create additional databases in EDS under the normal location. This is necessary because the tests cannot tell the EDS source registry daemon to run with a different XDG root. """ def testUIDError(self): '''TestContacts.testUIDError - check that invalid UID is properly detected and reported''' with self.assertRaisesRegexp(dbus.DBusException, 'invalid peer uid: CAPITAL-LETTERS-NOT-ALLOWED'): self.manager.SetPeer('CAPITAL-LETTERS-NOT-ALLOWED', {}) @property("snapshot", "simple-sort") def testConfig(self): '''TestContacts.testConfig - set and remove peers''' sources = self.currentSources() expected = sources.copy() peers = {} # add foo uid = self.uidPrefix + 'foo' peers[uid] = {'protocol': 'PBAP', 'address': 'xxx'} self.manager.SetPeer(uid, peers[uid]) expected.add(self.managerPrefix + uid) self.assertEqual(peers, self.manager.GetAllPeers()) self.assertEqual(expected, self.currentSources()) self.assertEqual(['sort =\n'], self.readManagerIni()) # Check effect of SetActiveAddressBooks() on pim-manager.ini. self.manager.SetActiveAddressBooks(['peer-' + uid]) self.assertEqual(['active = pim-manager-' + uid + '\n', 'sort =\n'], self.readManagerIni()) self.manager.SetActiveAddressBooks([]) self.assertEqual(['active = \n', 'sort =\n'], self.readManagerIni()) # PIM Manager must not allow overwriting an existing config. # Uses the new name for SetPeer(). with self.assertRaisesRegexp(dbus.DBusException, 'org._01.pim.contacts.Manager.AlreadyExists: uid ' + uid + ' is already in use') as cm: self.manager.CreatePeer(uid, peers[uid]) self.assertEqual('org._01.pim.contacts.Manager.AlreadyExists', cm.exception.get_dbus_name()) # TODO: work around EDS bug: e_source_remove_sync() quickly after # e_source_registry_create_sources_sync() leads to an empty .source file: # [Data Source] # DisplayName=Unnamed # Enabled=true # Parent= # # That prevents reusing the same UID. time.sleep(2) # remove foo expected.remove(self.managerPrefix + uid) del peers[uid] self.manager.RemovePeer(uid) self.assertEqual(peers, self.manager.GetAllPeers()) self.assertEqual(expected, self.currentSources()) self.assertEqual(['active = \n', 'sort =\n'], self.readManagerIni()) # add and remove foo again, this time while its address book is active uid = self.uidPrefix + 'foo4' # work around EDS bug with reusing UID peers[uid] = {'protocol': 'PBAP', 'address': 'xxx'} self.manager.SetPeer(uid, peers[uid]) expected.add(self.managerPrefix + uid) self.manager.SetActiveAddressBooks(['peer-' + uid]) self.assertEqual(['active = pim-manager-' + uid + '\n', 'sort =\n'], self.readManagerIni()) self.assertEqual(peers, self.manager.GetAllPeers()) self.assertEqual(expected, self.currentSources()) time.sleep(2) expected.remove(self.managerPrefix + uid) del peers[uid] self.manager.RemovePeer(uid) self.assertEqual(peers, self.manager.GetAllPeers()) self.assertEqual(expected, self.currentSources()) self.assertEqual(['active = \n', 'sort =\n'], self.readManagerIni()) # add foo, bar, xyz addressbooks = [] uid = self.uidPrefix + 'foo2' addressbooks.append('peer-' + uid) peers[uid] = {'protocol': 'PBAP', 'address': 'xxx'} self.manager.SetPeer(uid, peers[uid]) expected.add(self.managerPrefix + uid) self.assertEqual(peers, self.manager.GetAllPeers()) self.assertEqual(expected, self.currentSources()) self.assertEqual(['active = \n', 'sort =\n'], self.readManagerIni()) uid = self.uidPrefix + 'bar' addressbooks.append('peer-' + uid) peers[uid] = {'protocol': 'PBAP', 'address': 'yyy'} self.manager.SetPeer(uid, peers[uid]) expected.add(self.managerPrefix + uid) self.assertEqual(peers, self.manager.GetAllPeers()) self.assertEqual(expected, self.currentSources()) uid = self.uidPrefix + 'xyz' addressbooks.append('peer-' + uid) peers[uid] = {'protocol': 'PBAP', 'address': 'zzz'} self.manager.SetPeer(uid, peers[uid]) expected.add(self.managerPrefix + uid) self.assertEqual(peers, self.manager.GetAllPeers()) self.assertEqual(expected, self.currentSources()) self.assertEqual(['active = \n', 'sort =\n'], self.readManagerIni()) self.manager.SetActiveAddressBooks(addressbooks) addressbooks.sort() self.assertEqual(['active = ' + ' '.join([x.replace('peer-', 'pim-manager-') for x in addressbooks]) + '\n', 'sort =\n'], self.readManagerIni()) # EDS workaround time.sleep(2) # remove yxz, bar, foo expected.remove(self.managerPrefix + uid) del peers[uid] addressbooks.remove('peer-' + uid) self.manager.RemovePeer(uid) self.assertEqual(peers, self.manager.GetAllPeers()) self.assertEqual(expected, self.currentSources()) self.assertEqual(['active = ' + ' '.join([x.replace('peer-', 'pim-manager-') for x in addressbooks]) + '\n', 'sort =\n'], self.readManagerIni()) # EDS workaround time.sleep(2) uid = self.uidPrefix + 'bar' expected.remove(self.managerPrefix + uid) del peers[uid] addressbooks.remove('peer-' + uid) self.manager.RemovePeer(uid) self.assertEqual(peers, self.manager.GetAllPeers()) self.assertEqual(expected, self.currentSources()) self.assertEqual(['active = ' + ' '.join([x.replace('peer-', 'pim-manager-') for x in addressbooks]) + '\n', 'sort =\n'], self.readManagerIni()) # EDS workaround time.sleep(2) uid = self.uidPrefix + 'foo2' expected.remove(self.managerPrefix + uid) del peers[uid] addressbooks.remove('peer-' + uid) self.manager.RemovePeer(uid) self.assertEqual(peers, self.manager.GetAllPeers()) self.assertEqual(expected, self.currentSources()) self.assertEqual(['active = ' + ' '.join([x.replace('peer-', 'pim-manager-') for x in addressbooks]) + '\n', 'sort =\n'], self.readManagerIni()) # EDS workaround time.sleep(2) @property("snapshot", "broken-config") def testBrokenConfig(self): '''TestContacts.testBrokenConfig - start with broken pim-manager.ini''' self.manager.Start() self.assertEqual("last/first", self.manager.GetSortOrder()) @timeout(os.environ.get('TESTPIM_TEST_SYNC_TESTCASES', False) and 300000 or 300) @property("snapshot", "simple-sort") def testSync(self): '''TestContacts.testSync - test caching of a dummy peer which uses a real phone or a local directory as fallback''' sources = self.currentSources() expected = sources.copy() peers = {} logdir = xdg_root + '/cache/syncevolution' # If set, then test importing all these contacts, # matching them, and removing them. Prints timing information. testcases = os.environ.get('TESTPIM_TEST_SYNC_TESTCASES', None) if testcases: def progress(step, duration): print edslogs = [x for x in os.listdir(logdir) if x.startswith('eds@')] edslogs.sort() print '%s: %fs, see %s' % (step, duration, os.path.join(logdir, edslogs[-1])) else: def progress(*args1, **args2): pass syncProgress = [] signal = bus.add_signal_receiver(lambda uid, event, data: (logging.printf('received SyncProgress: %s, %s, %s', uid, event, data), syncProgress.append((uid, event, data)), logging.printf('progress %s' % syncProgress)), 'SyncProgress', 'org._01.pim.contacts.Manager', None, #'org._01.pim.contacts', '/org/01/pim/contacts', byte_arrays=True, utf8_strings=True) def checkSync(expectedResult, result, intermediateResult=None): self.assertEqual(expectedResult, result) while not (uid, 'done', {}) in syncProgress: self.loopIteration('added signal') progress = [ (uid, 'started', {}) ] if intermediateResult: progress.append((uid, 'modified', intermediateResult)) progress.append((uid, 'modified', expectedResult)) progress.append((uid, 'done', {})) self.assertEqual(progress, syncProgress) # Must be the Bluetooth MAC address (like A0:4E:04:1E:AD:30) # of a phone which is paired, currently connected, and # supports both PBAP and SyncML. SyncML is needed for putting # data onto the phone. Nokia phones like the N97 Mini are # known to work and easily available, therefore the test # hard-codes the Nokia SyncML settings (could be changed). # # If set, that phone will be used instead of local sync with # the file backend. phone = os.environ.get('TEST_DBUS_PBAP_PHONE', None) # dummy peer directory contacts = os.path.abspath(os.path.join(xdg_root, 'contacts')) os.makedirs(contacts) # add foo uid = self.uidPrefix + 'foo' if phone: peers[uid] = {'protocol': 'PBAP', 'address': phone} else: peers[uid] = {'protocol': 'files', 'address': contacts} self.manager.SetPeer(uid, peers[uid]) expected.add(self.managerPrefix + uid) self.assertEqual(peers, self.manager.GetAllPeers()) self.assertEqual(expected, self.currentSources()) # Clear data in the phone. self.configurePhone(phone, uid, contacts) self.syncPhone(phone, uid) def listall(dirs, exclude=[]): result = {} def append(dirname, entry): fullname = os.path.join(dirname, entry) for pattern in exclude: if re.match(pattern, fullname): return result[fullname] = os.stat(fullname).st_mtime for dir in dirs: for dirname, dirnames, filenames in os.walk(dir): for subdirname in dirnames: append(dirname, subdirname) for filename in filenames: append(dirname, filename) return result def listsyncevo(exclude=[]): '''find all files owned by SyncEvolution, excluding the logs for syncing with a real phone''' return listall([os.path.join(xdg_root, x) for x in ['config/syncevolution', 'cache/syncevolution']], exclude + ['.*/phone@.*', '.*/peers/phone.*']) # Remember current list of files and modification time stamp. files = listsyncevo() # Remove all data locally. There may or may not have been data # locally, because the database of the peer might have existed # from previous tests. duration, res = timeFunction(self.manager.SyncPeer, uid) progress('clear', duration) # TODO: check that syncPhone() really used PBAP - but how? # Should not have written files, except for specific exceptions: exclude = [] # - directories in which we need to create files exclude.extend([xdg_root + '/cache/syncevolution/eds@[^/]*$', xdg_root + '/cache/syncevolution/target_.config@[^/]*$']) # - some files which are allowed to be written exclude.extend([xdg_root + '/cache/syncevolution/[^/]*/(status.ini|syncevolution-log.html)$']) # - synthesis client files (should not be written at all, but that's harder - redirect into cache for now) exclude.extend([xdg_root + '/cache/syncevolution/[^/]*/synthesis(/|$)']) # Now compare files and their modification time stamp. self.assertEqual(files, listsyncevo(exclude=exclude)) # Export data from local database into a file via the --export # operation in the syncevo-dbus-server. Depends on (and tests) # that the SyncEvolution configuration was created as # expected. It does not actually check that EDS is used - the # test would also pass for any other storage. export = os.path.join(xdg_root, 'local.vcf') self.exportCache(uid, export) # Must be empty now. self.compareDBs(contacts, export) # Add a contact. john = '''BEGIN:VCARD VERSION:3.0 FN:John Doe N:Doe;John PHOTO;ENCODING=b;TYPE=JPEG:/9j/4AAQSkZJRgABAQEASABIAAD/4QAWRXhpZgAATU0AKgAA AAgAAAAAAAD//gAXQ3JlYXRlZCB3aXRoIFRoZSBHSU1Q/9sAQwAFAwQEBAMFBAQEBQUFBgcM CAcHBwcPCwsJDBEPEhIRDxERExYcFxMUGhURERghGBodHR8fHxMXIiQiHiQcHh8e/9sAQwEF BQUHBgcOCAgOHhQRFB4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4e Hh4eHh4eHh4e/8AAEQgAFwAkAwEiAAIRAQMRAf/EABkAAQADAQEAAAAAAAAAAAAAAAAGBwgE Bf/EADIQAAECBQMCAwQLAAAAAAAAAAECBAADBQYRBxIhEzEUFSIIFjNBGCRHUVZ3lqXD0+P/ xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMR AD8AuX6UehP45/aXv9MTPTLVKxNSvMPcqu+a+XdLxf1SfJ6fU37PioTnOxfbOMc/KIZ7U/2V fmTR/wCaKlu6+blu/Ui72zxWtUmmUOrTaWwkWDT09FPR4K587OVrUfVsIwElPPPAbAjxr2um hWXbDu5rmfeApLPZ4hx0lzNm9aUJ9KAVHKlJHAPf7ozPLqWt9y6Z0EPGmoLNjTq48a1iaybJ YV52yEtCms5KJmAT61JXtJyUdyQTEc1WlMql7N1/oZ6jagVZVFfUyZPpFy5lvWcxU7Z03BUk GZLWJqVhPYLkIIPBEBtSEUyNAsjI1q1m/VP+UICwL/sqlXp7v+aOHsnyGttq218MtKd8+Ru2 JXuScoO45Awe2CIi96aKW1cVyubkYVy6rTqz0J8a5t2qqZl0UjAMwYKScfPAJ+cIQHHP0Dth VFaMWt0XwxetnM50Ks2rsxL6ZMnJlJmb5hBBBEiVxjA28dznqo+hdksbQuS3Hs6tVtNzdM1Z /VH5nO3Bl/CJmYHKDynjv3zCEB5rLQNo0bIbydWNWxKljbLQLoWkISOAkBKAABCEID//2Q== END:VCARD''' # Test all fields that PIM Manager supports in its D-Bus API # when using the file backend, because (in contrast to a phone) # we know that it supports them, too. if not phone: john = r'''BEGIN:VCARD VERSION:3.0 URL:http://john.doe.com TITLE:Senior Tester ORG:Test Inc.;Testing;test#1 ROLE:professional test case X-EVOLUTION-MANAGER:John Doe Senior X-EVOLUTION-ASSISTANT:John Doe Junior NICKNAME:user1 BDAY:2006-01-08 X-EVOLUTION-ANNIVERSARY:2006-01-09 X-EVOLUTION-SPOUSE:Joan Doe NOTE:This is a test case which uses almost all Evolution fields. FN:John Doe N:Doe;John;;; X-EVOLUTION-FILE-AS:Doe\, John CATEGORIES:TEST X-EVOLUTION-BLOG-URL:web log GEO:30.12;-130.34 CALURI:calender FBURL:free/busy X-EVOLUTION-VIDEO-URL:chat X-MOZILLA-HTML:TRUE ADR;TYPE=WORK:Test Box #2;;Test Drive 2;Test Town;Upper Test County;12346;O ld Testovia LABEL;TYPE=WORK:Test Drive 2\nTest Town\, Upper Test County\n12346\nTest Bo x #2\nOld Testovia ADR;TYPE=HOME:Test Box #1;;Test Drive 1;Test Village;Lower Test County;1234 5;Testovia LABEL;TYPE=HOME:Test Drive 1\nTest Village\, Lower Test County\n12345\nTest Box #1\nTestovia ADR:Test Box #3;;Test Drive 3;Test Megacity;Test County;12347;New Testonia LABEL;TYPE=OTHER:Test Drive 3\nTest Megacity\, Test County\n12347\nTest Box #3\nNew Testonia UID:pas-id-43C0ED3900000001 EMAIL;TYPE=WORK;X-EVOLUTION-UI-SLOT=1:john.doe@work.com EMAIL;TYPE=HOME;X-EVOLUTION-UI-SLOT=2:john.doe@home.priv EMAIL;TYPE=OTHER;X-EVOLUTION-UI-SLOT=3:john.doe@other.world EMAIL;TYPE=OTHER;X-EVOLUTION-UI-SLOT=4:john.doe@yet.another.world TEL;TYPE=work;TYPE=Voice;X-EVOLUTION-UI-SLOT=1:business 1 TEL;TYPE=homE;TYPE=VOICE;X-EVOLUTION-UI-SLOT=2:home 2 TEL;TYPE=CELL;X-EVOLUTION-UI-SLOT=3:mobile 3 TEL;TYPE=WORK;TYPE=FAX;X-EVOLUTION-UI-SLOT=4:businessfax 4 TEL;TYPE=HOME;TYPE=FAX;X-EVOLUTION-UI-SLOT=5:homefax 5 TEL;TYPE=PAGER;X-EVOLUTION-UI-SLOT=6:pager 6 TEL;TYPE=CAR;X-EVOLUTION-UI-SLOT=7:car 7 TEL;TYPE=PREF;X-EVOLUTION-UI-SLOT=8:primary 8 END:VCARD ''' if testcases: # Split test case file into files. numPhotos = 0 hasPhoto = re.compile('^PHOTO[;:].+\r?$', re.MULTILINE) for i, data in enumerate(open(testcases).read().split('\n\n')): item = os.path.join(contacts, '%d.vcf' % i) output = open(item, "w") output.write(data) numPhotos = numPhotos + 1 output.close() numItems = i + 1 else: item = os.path.join(contacts, 'john.vcf') output = open(item, "w") output.write(john) output.close() numItems = 1 numPhotos = 1 self.syncPhone(phone, uid) syncProgress = [] duration, result = timeFunction(self.manager.SyncPeer, uid) progress('import', duration) incrementalSync = phone and os.environ.get('SYNCEVOLUTION_PBAP_SYNC', 'incremental') == 'incremental' if incrementalSync: # Single contact gets added, then updated to add the photo. finalUpdated = numPhotos intermediate = {'modified': True, 'added': numItems, 'updated': 0, 'removed': 0} else: finalUpdated = 0 intermediate = None checkSync({'modified': True, 'added': numItems, 'updated': finalUpdated, 'removed': 0}, result, intermediate) # Also exclude modified database files. self.assertEqual(files, listsyncevo(exclude=exclude)) # Testcase data does not necessarily import/export without changes. if not testcases: self.exportCache(uid, export) self.compareDBs(contacts, export) # Skip logdir tests when focusing on syncing data. if not testcases: # Keep one session directory in a non-default location. logdir = xdg_root + '/pim-logdir' peers[uid]['logdir'] = logdir peers[uid]['maxsessions'] = '1' self.manager.SetPeer(uid, peers[uid]) files = listsyncevo(exclude=exclude) syncProgress = [] result = self.manager.SyncPeer(uid) expectedResult = {'modified': False, 'added': 0, 'updated': 0, 'removed': 0} checkSync(expectedResult, result, incrementalSync and expectedResult) exclude.append(logdir + '(/$)') self.assertEqual(files, listsyncevo(exclude=exclude)) self.assertEqual(2, len(os.listdir(logdir))) # No changes. syncProgress = [] duration, result = timeFunction(self.manager.SyncPeer, uid) progress('match', duration) expectedResult = {'modified': False, 'added': 0, 'updated': 0, 'removed': 0} checkSync(expectedResult, result, incrementalSync and expectedResult) self.assertEqual(files, listsyncevo(exclude=exclude)) if not phone: self.assertEqual(testcases and 6 or 2, len(os.listdir(logdir))) if not testcases: # And now prune none. peers[uid]['maxsessions'] = '0' self.manager.SetPeer(uid, peers[uid]) files = listsyncevo(exclude=exclude) syncProgress = [] result = self.manager.SyncPeer(uid) expectedResult = {'modified': False, 'added': 0, 'updated': 0, 'removed': 0} checkSync(expectedResult, result, incrementalSync and expectedResult) exclude.append(logdir + '(/$)') self.assertEqual(files, listsyncevo(exclude=exclude)) self.assertEqual(4, len(os.listdir(logdir))) # Cannot update data when using pre-defined test cases. if not testcases: # Update contact, removing extra properties. john = '''BEGIN:VCARD VERSION:3.0 FN:John Doe N:Doe;John END:VCARD''' output = open(item, "w") output.write(john) output.close() self.syncPhone(phone, uid) syncProgress = [] result = self.manager.SyncPeer(uid) checkSync({'modified': True, 'added': 0, 'updated': 1, 'removed': 0}, result, incrementalSync and {'modified': False, 'added': 0, 'updated': 0, 'removed': 0}) # Remove contact(s). for file in os.listdir(contacts): os.remove(os.path.join(contacts, file)) self.syncPhone(phone, uid) syncProgress = [] duration, result = timeFunction(self.manager.SyncPeer, uid) progress('remove', duration) expectedResult = {'modified': True, 'added': 0, 'updated': 0, 'removed': numItems} checkSync(expectedResult, result, incrementalSync and expectedResult) # Test invalid maxsession values. if not testcases: with self.assertRaisesRegexp(dbus.DBusException, "negative 'maxsessions' not allowed: -1"): self.manager.SetPeer(uid, {'protocol': 'PBAP', 'address': 'foo', 'maxsessions': '-1'}) self.assertEqual(files, listsyncevo(exclude=exclude)) with self.assertRaisesRegexp(dbus.DBusException, 'bad lexical cast: source type value could not be interpreted as target'): self.manager.SetPeer(uid, {'protocol': 'PBAP', 'address': 'foo', 'maxsessions': '1000000000000000000000000000000000000000000000'}) self.assertEqual(files, listsyncevo(exclude=exclude)) @timeout(100) @property("ENV", "SYNCEVOLUTION_SYNC_DELAY=200") @property("snapshot", "simple-sort") def testSyncAbort(self): '''TestContacts.testSyncAbort - test StopSync()''' self.setUpServer() sources = self.currentSources() expected = sources.copy() peers = {} # Disable the default checking because # we trigger one ERROR message. self.runTestDBusCheck = None self.runTestOutputCheck = None # dummy peer directory contacts = os.path.abspath(os.path.join(xdg_root, 'contacts')) os.makedirs(contacts) # add foo uid = self.uidPrefix + 'foo' peers[uid] = {'protocol': 'files', 'address': contacts} self.manager.SetPeer(uid, peers[uid]) expected.add(self.managerPrefix + uid) self.assertEqual(peers, self.manager.GetAllPeers()) self.assertEqual(expected, self.currentSources()) # Start a sync. Because of SYNCEVOLUTION_SYNC_DELAY, this will block until # we kill it. syncCompleted = [ False, False ] self.aborted = False def result(index, res): syncCompleted[index] = res def output(path, level, text, procname): if self.running and not self.aborted and text == 'ready to sync': logging.printf('aborting sync') self.manager.StopSync(uid) self.aborted = True receiver = bus.add_signal_receiver(output, 'LogOutput', 'org.syncevolution.Server', self.server.bus_name, byte_arrays=True, utf8_strings=True) try: self.manager.SyncPeer(uid, reply_handler=lambda: result(0, True), error_handler=lambda x: result(0, x)) self.manager.SyncPeer(uid, reply_handler=lambda: result(1, True), error_handler=lambda x: result(1, x)) self.runUntil('both syncs done', check=lambda: True, until=lambda: not False in syncCompleted) finally: receiver.remove() # Check for specified error. self.assertIsInstance(syncCompleted[0], dbus.DBusException) self.assertEqual('org._01.pim.contacts.Manager.Aborted', syncCompleted[0].get_dbus_name()) self.assertIsInstance(syncCompleted[1], dbus.DBusException) self.assertEqual('org._01.pim.contacts.Manager.Aborted', syncCompleted[1].get_dbus_name()) @timeout(60) @property("snapshot", "simple-sort") def testView(self): '''TestContacts.testView - test making changes to the unified address book''' self.setUpView() # Insert new contact. self.view.quiescentCount = 0 john = '''BEGIN:VCARD VERSION:3.0 FN:John Doe N:Doe;John END:VCARD''' item = os.path.join(self.contacts, 'john.vcf') output = open(item, "w") output.write(john) output.close() logging.log('inserting John') out, err, returncode = self.runCmdline(['--import', item, '@' + self.managerPrefix + self.uid, 'local']) luid = self.extractLUIDs(out)[0] # Run until the view has adapted. self.runUntil('view with one contact', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.quiescentCount > 0) # Check for the one expected event. self.assertEqual([('added', 0, 1), ('quiescent',)], self.view.events) self.view.events = [] self.view.quiescentCount = 0 # Read contact. logging.log('reading contact') self.view.read(0) self.runUntil('contact', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.haveData(0)) self.assertEqual('John Doe', self.view.contacts[0]['full-name']) # Update contacts. johnBDay = '''BEGIN:VCARD VERSION:3.0 FN:John Doe N:Doe;John BDAY:20120924 END:VCARD''' output = open(item, "w") output.write(johnBDay) output.close() logging.log('updating John') self.runCmdline(['--update', item, '@' + self.managerPrefix + self.uid, 'local', luid]) self.runUntil('view with changed contact', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.quiescentCount > 0) self.assertEqual([('modified', 0, 1), ('quiescent',)], self.view.events) self.view.events = [] self.view.quiescentCount = 0 # Remove contact. self.runCmdline(['--delete-items', '@' + self.managerPrefix + self.uid, 'local', '*']) self.runUntil('view without contacts', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.quiescentCount > 0) self.assertEqual([('removed', 0, 1), ('quiescent',)], self.view.events) self.view.events = [] self.view.quiescentCount = 0 @timeout(60) @property("snapshot", "simple-sort") def testViewSorting(self): '''TestContacts.testViewSorting - check that sorting works when changing contacts''' self.setUpView() # Insert new contacts. # # The names are chosen so that sorting by first name and sorting by last name needs to # reverse the list. The default sort method is active, which is not # locale-aware. Therefore the test data only uses ASCII characters. for i, contact in enumerate(['''BEGIN:VCARD VERSION:3.0 FN:Abraham Zoo N:Zoo;Abraham END:VCARD''', '''BEGIN:VCARD VERSION:3.0 FN:Benjamin Yeah N:Yeah;Benjamin END:VCARD''', '''BEGIN:VCARD VERSION:3.0 FN:Charly Xing N:Xing;Charly END:VCARD''']): item = os.path.join(self.contacts, 'contact%d.vcf' % i) output = open(item, "w") output.write(contact) output.close() logging.log('inserting contacts') out, err, returncode = self.runCmdline(['--import', self.contacts, '@' + self.managerPrefix + self.uid, 'local']) # Relies on importing contacts sorted ascending by file name. luids = self.extractLUIDs(out) logging.printf('created contacts with luids: %s' % luids) # Run until the view has adapted. self.runUntil('view with three contacts', check=lambda: self.assertEqual([], self.view.errors), until=lambda: len(self.view.contacts) == 3) # Check for the one expected event. # TODO: self.assertEqual([('added', 0, 3)], view.events) self.view.events = [] # Read contacts. logging.log('reading contacts') self.view.read(0, 3) self.runUntil('contacts', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.haveData(0)) self.assertEqual('Charly Xing', self.view.contacts[0]['full-name']) self.assertEqual('Benjamin Yeah', self.view.contacts[1]['full-name']) self.assertEqual('Abraham Zoo', self.view.contacts[2]['full-name']) # We should have seen all events for the changes above now, # start with a clean slate. self.view.events = [] self.view.quiescentCount = 0 # No re-ordering necessary: criteria doesn't change. item = os.path.join(self.contacts, 'contact0.vcf') output = open(item, "w") output.write('''BEGIN:VCARD VERSION:3.0 FN:Abraham Zoo N:Zoo;Abraham BDAY:20120924 END:VCARD''') output.close() self.runCmdline(['--update', item, '@' + self.managerPrefix + self.uid, 'local', luids[0]]) self.runUntil('view with changed contact #2', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.quiescentCount > 0) self.assertEqual([('modified', 2, 1), ('quiescent',)], self.view.events) self.view.events = [] self.view.quiescentCount = 0 logging.log('reading updated contact') self.view.read(2, 1) self.runUntil('updated contact', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.haveData(2)) self.assertEqual('Charly Xing', self.view.contacts[0]['full-name']) self.assertEqual('Benjamin Yeah', self.view.contacts[1]['full-name']) self.assertEqual('Abraham Zoo', self.view.contacts[2]['full-name']) # No re-ordering necessary: criteria changes, but not in a relevant way, at end. item = os.path.join(self.contacts, 'contact0.vcf') output = open(item, "w") output.write('''BEGIN:VCARD VERSION:3.0 FN:Abraham Zoo N:Zoo;Abraham;Middle BDAY:20120924 END:VCARD''') output.close() self.runCmdline(['--update', item, '@' + self.managerPrefix + self.uid, 'local', luids[0]]) self.runUntil('view with changed contact #2', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.quiescentCount > 0) self.assertEqual([('modified', 2, 1), ('quiescent',)], self.view.events) self.view.events = [] self.view.quiescentCount = 0 logging.log('reading updated contact') self.view.read(2, 1) self.runUntil('updated contact', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.haveData(2)) self.assertEqual('Charly Xing', self.view.contacts[0]['full-name']) self.assertEqual('Benjamin Yeah', self.view.contacts[1]['full-name']) self.assertEqual('Abraham Zoo', self.view.contacts[2]['full-name']) # No re-ordering necessary: criteria changes, but not in a relevant way, in the middle. item = os.path.join(self.contacts, 'contact1.vcf') output = open(item, "w") output.write('''BEGIN:VCARD VERSION:3.0 FN:Benjamin Yeah N:Yeah;Benjamin;Middle END:VCARD''') output.close() self.runCmdline(['--update', item, '@' + self.managerPrefix + self.uid, 'local', luids[1]]) self.runUntil('view with changed contact #1', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.quiescentCount > 0) self.assertEqual([('modified', 1, 1), ('quiescent',)], self.view.events) self.view.events = [] self.view.quiescentCount = 0 logging.log('reading updated contact') self.view.read(1, 1) self.runUntil('updated contact', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.haveData(1)) self.assertEqual('Charly Xing', self.view.contacts[0]['full-name']) self.assertEqual('Benjamin Yeah', self.view.contacts[1]['full-name']) self.assertEqual('Abraham Zoo', self.view.contacts[2]['full-name']) # No re-ordering necessary: criteria changes, but not in a relevant way, in the middle. item = os.path.join(self.contacts, 'contact2.vcf') output = open(item, "w") output.write('''BEGIN:VCARD VERSION:3.0 FN:Charly Xing N:Xing;Charly;Middle END:VCARD''') output.close() self.runCmdline(['--update', item, '@' + self.managerPrefix + self.uid, 'local', luids[2]]) self.runUntil('view with changed contact #0', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.quiescentCount > 0) self.assertEqual([('modified', 0, 1), ('quiescent',)], self.view.events) self.view.events = [] self.view.quiescentCount = 0 logging.log('reading updated contact') self.view.read(0, 1) self.runUntil('updated contact', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.haveData(0)) self.assertEqual('Charly Xing', self.view.contacts[0]['full-name']) self.assertEqual('Benjamin Yeah', self.view.contacts[1]['full-name']) self.assertEqual('Abraham Zoo', self.view.contacts[2]['full-name']) # Re-ordering necessary: last item (Zoo;Abraham) becomes first (Ace;Abraham). item = os.path.join(self.contacts, 'contact0.vcf') output = open(item, "w") output.write('''BEGIN:VCARD VERSION:3.0 FN:Abraham Ace N:Ace;Abraham;Middle BDAY:20120924 END:VCARD''') output.close() self.runCmdline(['--update', item, '@' + self.managerPrefix + self.uid, 'local', luids[0]]) self.runUntil('view with changed contact #0', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.quiescentCount > 0) self.assertEqual([('removed', 2, 1), ('added', 0, 1), ('quiescent',)], self.view.events) self.view.events = [] self.view.quiescentCount = 0 logging.log('reading updated contact') self.view.read(0, 1) self.runUntil('updated contact', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.haveData(0)) self.assertEqual('Abraham Ace', self.view.contacts[0]['full-name']) self.assertEqual('Charly Xing', self.view.contacts[1]['full-name']) self.assertEqual('Benjamin Yeah', self.view.contacts[2]['full-name']) # Re-ordering necessary: first item (Ace;Abraham) becomes last (Zoo;Abraham). item = os.path.join(self.contacts, 'contact0.vcf') output = open(item, "w") output.write('''BEGIN:VCARD VERSION:3.0 FN:Abraham Zoo N:Zoo;Abraham;Middle BDAY:20120924 END:VCARD''') output.close() self.runCmdline(['--update', item, '@' + self.managerPrefix + self.uid, 'local', luids[0]]) self.runUntil('view with changed contact #2', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.quiescentCount > 0) self.assertEqual([('removed', 0, 1), ('added', 2, 1), ('quiescent',)], self.view.events) self.view.events = [] self.view.quiescentCount = 0 logging.log('reading updated contact') self.view.read(2, 1) self.runUntil('updated contact', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.haveData(2)) self.assertEqual('Charly Xing', self.view.contacts[0]['full-name']) self.assertEqual('Benjamin Yeah', self.view.contacts[1]['full-name']) self.assertEqual('Abraham Zoo', self.view.contacts[2]['full-name']) @timeout(60) # boost::locale checks LC_TYPE first, then LC_ALL, LANG. Set all, just # to be sure. @property("ENV", "LC_TYPE=de_DE.UTF-8 LC_ALL=de_DE.UTF-8 LANG=de_DE.UTF-8") @property("snapshot", "first-last-sort") def testSortOrder(self): '''TestContacts.testSortOrder - check that sorting works when changing the comparison''' self.setUpView() # Locale-aware "first/last" sorting from "first-last-sort" config. self.assertEqual("first/last", self.manager.GetSortOrder()) # Expect an error, no change to sort order. with self.assertRaisesRegexp(dbus.DBusException, 'sort order.*not supported'): self.manager.SetSortOrder('no-such-order') self.assertEqual("first/last", self.manager.GetSortOrder()) # Insert new contacts. # # The names are chosen so that sorting by first name and sorting by last name needs to # reverse the list. for i, contact in enumerate([u'''BEGIN:VCARD VERSION:3.0 N:Zoo;Äbraham END:VCARD''', u'''BEGIN:VCARD VERSION:3.0 N:Yeah;Bénjamin END:VCARD''', u'''BEGIN:VCARD VERSION:3.0 FN:Chàrly Xing N:Xing;Chàrly END:VCARD''']): item = os.path.join(self.contacts, 'contact%d.vcf' % i) output = codecs.open(item, "w", "utf-8") output.write(contact) output.close() logging.log('inserting contacts') out, err, returncode = self.runCmdline(['--import', self.contacts, '@' + self.managerPrefix + self.uid, 'local']) # Relies on importing contacts sorted ascending by file name. luids = self.extractLUIDs(out) logging.printf('created contacts with luids: %s' % luids) # Run until the view has adapted. self.runUntil('view with three contacts', check=lambda: self.assertEqual([], self.view.errors), until=lambda: len(self.view.contacts) == 3) # Check for the one expected event. # TODO: self.assertEqual([('added', 0, 3)], view.events) self.view.events = [] # Read contacts. logging.log('reading contacts') self.view.read(0, 3) self.runUntil('contacts', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.haveData(0)) self.assertEqual(u'Äbraham', self.view.contacts[0]['structured-name']['given']) self.assertEqual(u'Bénjamin', self.view.contacts[1]['structured-name']['given']) self.assertEqual(u'Chàrly', self.view.contacts[2]['structured-name']['given']) # Invert sort order. self.manager.SetSortOrder("last/first") # Check that order was adapted and stored permanently. self.assertEqual("last/first", self.manager.GetSortOrder()) self.assertIn("sort = last/first\n", self.readManagerIni()) # Contact in the middle may or may not become invalidated. self.runUntil('reordered', check=lambda: self.assertEqual([], self.view.errors), until=lambda: len(self.view.contacts) == 3 and \ self.view.haveNoData(0) and \ self.view.haveNoData(2)) # Read contacts. logging.log('reading contacts') self.view.read(0, 3) self.runUntil('contacts', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.haveData(0)) self.assertEqual('Xing', self.view.contacts[0]['structured-name']['family']) self.assertEqual('Yeah', self.view.contacts[1]['structured-name']['family']) self.assertEqual('Zoo', self.view.contacts[2]['structured-name']['family']) # Sort by FN or as fallback. self.manager.SetSortOrder("fullname") self.runUntil('reordered', check=lambda: self.assertEqual([], self.view.errors), until=lambda: len(self.view.contacts) == 3 and \ self.view.haveNoData(0) and \ self.view.haveNoData(2)) logging.log('reading contacts') self.view.read(0, 3) self.runUntil('contacts', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.haveData(0)) self.assertEqual(u'Äbraham', self.view.contacts[0]['structured-name']['given']) self.assertEqual(u'Bénjamin', self.view.contacts[1]['structured-name']['given']) self.assertEqual(u'Chàrly', self.view.contacts[2]['structured-name']['given']) @timeout(60) @property("snapshot", "simple-sort") def testRead(self): '''TestContacts.testRead - check that folks and PIM Manager deliver EDS data correctly''' self.setUpView() # Insert new contacts. # # Not all of the vCard properties need to be available via PIM Manager. testcases = [r'''BEGIN:VCARD VERSION:3.0 URL:http://john.doe.com TITLE:Senior Tester ORG:Test Inc.;Testing;test#1 ROLE:professional test case X-EVOLUTION-MANAGER:John Doe Senior X-EVOLUTION-ASSISTANT:John Doe Junior NICKNAME:user1 BDAY:2006-01-08 X-FOOBAR-EXTENSION;X-FOOBAR-PARAMETER=foobar:has to be stored internally by engine and preserved in testExtensions test\; never sent to a peer X-TEST;PARAMETER1=nonquoted;PARAMETER2="quoted because of spaces":Content with\nMultiple\nText lines\nand national chars: äöü X-EVOLUTION-ANNIVERSARY:2006-01-09 X-EVOLUTION-SPOUSE:Joan Doe NOTE:This is a test case which uses almost all Evolution fields. FN:John Doe N:Doe;John;;; X-EVOLUTION-FILE-AS:Doe\, John CATEGORIES:TEST1,TEST2 GEO:30.12;-130.34 X-EVOLUTION-BLOG-URL:web log CALURI:calender FBURL:free/busy X-EVOLUTION-VIDEO-URL:chat X-MOZILLA-HTML:TRUE ADR;TYPE=WORK:Test Box #2;;Test Drive 2;Test Town;Upper Test County;12346;O ld Testovia LABEL;TYPE=WORK:Test Drive 2\nTest Town\, Upper Test County\n12346\nTest Bo x #2\nOld Testovia ADR;TYPE=HOME:Test Box #1;;Test Drive 1;Test Village;Lower Test County;1234 5;Testovia LABEL;TYPE=HOME:Test Drive 1\nTest Village\, Lower Test County\n12345\nTest Box #1\nTestovia ADR:Test Box #3;;Test Drive 3;Test Megacity;Test County;12347;New Testonia LABEL;TYPE=OTHER:Test Drive 3\nTest Megacity\, Test County\n12347\nTest Box #3\nNew Testonia UID:pas-id-43C0ED3900000001 EMAIL;TYPE=WORK;X-EVOLUTION-UI-SLOT=1:john.doe@work.com EMAIL;TYPE=HOME;X-EVOLUTION-UI-SLOT=2:john.doe@home.priv EMAIL;TYPE=OTHER;X-EVOLUTION-UI-SLOT=3:john.doe@other.world EMAIL;TYPE=OTHER;X-EVOLUTION-UI-SLOT=4:john.doe@yet.another.world TEL;TYPE=work;TYPE=Voice;X-EVOLUTION-UI-SLOT=1:business 1 TEL;TYPE=homE;TYPE=VOICE;X-EVOLUTION-UI-SLOT=2:home 2 TEL;TYPE=CELL;X-EVOLUTION-UI-SLOT=3:mobile 3 TEL;TYPE=WORK;TYPE=FAX;X-EVOLUTION-UI-SLOT=4:businessfax 4 TEL;TYPE=HOME;TYPE=FAX;X-EVOLUTION-UI-SLOT=5:homefax 5 TEL;TYPE=PAGER;X-EVOLUTION-UI-SLOT=6:pager 6 TEL;TYPE=CAR;X-EVOLUTION-UI-SLOT=7:car 7 TEL;TYPE=PREF;X-EVOLUTION-UI-SLOT=8:primary 8 X-AIM;X-EVOLUTION-UI-SLOT=1:AIM JOHN X-YAHOO;X-EVOLUTION-UI-SLOT=2:YAHOO JDOE X-ICQ;X-EVOLUTION-UI-SLOT=3:ICQ JD X-GROUPWISE;X-EVOLUTION-UI-SLOT=4:GROUPWISE DOE X-GADUGADU:GADUGADU DOE X-JABBER:JABBER DOE X-MSN:MSN DOE X-SKYPE:SKYPE DOE X-SIP:SIP DOE PHOTO;ENCODING=b;TYPE=JPEG:/9j/4AAQSkZJRgABAQEASABIAAD/4QAWRXhpZgAATU0AKgAA AAgAAAAAAAD//gAXQ3JlYXRlZCB3aXRoIFRoZSBHSU1Q/9sAQwAFAwQEBAMFBAQEBQUFBgcM CAcHBwcPCwsJDBEPEhIRDxERExYcFxMUGhURERghGBodHR8fHxMXIiQiHiQcHh8e/9sAQwEF BQUHBgcOCAgOHhQRFB4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4e Hh4eHh4eHh4e/8AAEQgAFwAkAwEiAAIRAQMRAf/EABkAAQADAQEAAAAAAAAAAAAAAAAGBwgE Bf/EADIQAAECBQMCAwQLAAAAAAAAAAECBAADBQYRBxIhEzEUFSIIFjNBGCRHUVZ3lqXD0+P/ xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMR AD8AuX6UehP45/aXv9MTPTLVKxNSvMPcqu+a+XdLxf1SfJ6fU37PioTnOxfbOMc/KIZ7U/2V fmTR/wCaKlu6+blu/Ui72zxWtUmmUOrTaWwkWDT09FPR4K587OVrUfVsIwElPPPAbAjxr2um hWXbDu5rmfeApLPZ4hx0lzNm9aUJ9KAVHKlJHAPf7ozPLqWt9y6Z0EPGmoLNjTq48a1iaybJ YV52yEtCms5KJmAT61JXtJyUdyQTEc1WlMql7N1/oZ6jagVZVFfUyZPpFy5lvWcxU7Z03BUk GZLWJqVhPYLkIIPBEBtSEUyNAsjI1q1m/VP+UICwL/sqlXp7v+aOHsnyGttq218MtKd8+Ru2 JXuScoO45Awe2CIi96aKW1cVyubkYVy6rTqz0J8a5t2qqZl0UjAMwYKScfPAJ+cIQHHP0Dth VFaMWt0XwxetnM50Ks2rsxL6ZMnJlJmb5hBBBEiVxjA28dznqo+hdksbQuS3Hs6tVtNzdM1Z /VH5nO3Bl/CJmYHKDynjv3zCEB5rLQNo0bIbydWNWxKljbLQLoWkISOAkBKAABCEID//2Q== END:VCARD '''] for i, contact in enumerate(testcases): item = os.path.join(self.contacts, 'contact%d.vcf' % i) output = open(item, "w") output.write(contact) output.close() logging.log('inserting contacts') out, err, returncode = self.runCmdline(['--import', self.contacts, '@' + self.managerPrefix + self.uid, 'local']) # Relies on importing contacts sorted ascending by file name. luids = self.extractLUIDs(out) logging.printf('created contacts with luids: %s' % luids) # Run until the view has adapted. self.runUntil('view with contacts', check=lambda: self.assertEqual([], self.view.errors), until=lambda: len(self.view.contacts) == len(testcases)) # Check for the one expected event. # TODO: self.assertEqual([('added', 0, len(testcases))], view.events) self.view.events = [] # Read contacts. logging.log('reading contacts') self.view.read(0, len(testcases)) self.runUntil('contacts', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.haveData(0)) contact = copy.deepcopy(self.view.contacts[0]) # Simplify the photo URI, if there was one. Avoid any assumptions # about the filename, except that it is a file:/// uri. if contact.has_key('photo'): contact['photo'] = re.sub('^file:///.*', 'file:///', contact['photo']) if contact.has_key('id'): contact['id'] = '' self.assertEqual({'full-name': 'John Doe', 'groups': ['TEST1', 'TEST2'], 'location': (30.12, -130.34), 'nickname': 'user1', 'structured-name': {'given': 'John', 'family': 'Doe'}, 'birthday': (2006, 1, 8), 'photo': 'file:///', 'roles': [ { 'organisation': 'Test Inc.', 'role': 'professional test case', 'title': 'Senior Tester', }, ], 'source': [ (self.uidPrefix + 'foo', luids[0]) ], 'id': '', 'notes': [ 'This is a test case which uses almost all Evolution fields.', ], 'emails': [ ('john.doe@home.priv', ['home']), ('john.doe@other.world', ['other']), ('john.doe@work.com', ['work']), ('john.doe@yet.another.world', ['other']), ], 'phones': [ ('business 1', ['voice', 'work']), ('businessfax 4', ['fax', 'work']), ('car 7', ['car']), ('home 2', ['home', 'voice']), ('homefax 5', ['fax', 'home']), ('mobile 3', ['cell']), ('pager 6', ['pager']), ('primary 8', ['pref']), ], 'addresses': [ ({'country': 'New Testonia', 'locality': 'Test Megacity', 'po-box': 'Test Box #3', 'postal-code': '12347', 'region': 'Test County', 'street': 'Test Drive 3'}, []), ({'country': 'Old Testovia', 'locality': 'Test Town', 'po-box': 'Test Box #2', 'postal-code': '12346', 'region': 'Upper Test County', 'street': 'Test Drive 2'}, ['work']), ({'country': 'Testovia', 'locality': 'Test Village', 'po-box': 'Test Box #1', 'postal-code': '12345', 'region': 'Lower Test County', 'street': 'Test Drive 1'}, ['home']), ], 'urls': [ ('chat', ['x-video']), ('free/busy', ['x-free-busy']), ('http://john.doe.com', ['x-home-page']), ('web log', ['x-blog']), ], }, # Order of list entries in the result is not specified. # Must sort before comparing. contact, sortLists=True) def addressbooks(self): entries = os.listdir(os.path.join(os.environ["XDG_DATA_HOME"], "evolution", "addressbook")) entries.sort(); # Ignore trash folder and system DB, because they may or may not be present. for db in ('trash', 'system'): try: entries.remove(db) except ValueError: pass return entries @timeout(60) @property("snapshot", "simple-sort") def testRemove(self): '''TestContacts.testRemove - check that EDS database is created and removed''' self.setUpView(search=None) # Force sqlite DB files to exist by inserting a contact. testcases = [r'''BEGIN:VCARD VERSION:3.0 FN:John Doe N:Doe;John TEL:1234-5678 EMAIL:john.doe@example.com URL:http://john.doe.com X-JABBER:jd@example.com END:VCARD '''] for i, contact in enumerate(testcases): item = os.path.join(self.contacts, 'contact%d.vcf' % i) output = open(item, "w") output.write(contact) output.close() logging.log('inserting contacts') out, err, returncode = self.runCmdline(['--import', self.contacts, '@' + self.managerPrefix + self.uid, 'local']) self.assertEqual([self.managerPrefix + self.uid], self.addressbooks()) self.manager.RemovePeer(self.uid) self.assertEqual([], self.addressbooks()) @timeout(60) @property("snapshot", "simple-sort") def testRemoveLive(self): '''TestContacts.testRemove - check that EDS database is created and removed while it is open in a view''' self.setUpView() # Force sqlite DB files to exist by inserting a contact. testcases = [r'''BEGIN:VCARD VERSION:3.0 FN:John Doe N:Doe;John TEL:1234-5678 EMAIL:john.doe@example.com URL:http://john.doe.com X-JABBER:jd@example.com END:VCARD '''] for i, contact in enumerate(testcases): item = os.path.join(self.contacts, 'contact%d.vcf' % i) output = open(item, "w") output.write(contact) output.close() logging.log('inserting contacts') out, err, returncode = self.runCmdline(['--import', self.contacts, '@' + self.managerPrefix + self.uid, 'local']) # Run until the view has adapted. self.runUntil('view with one contact', check=lambda: self.assertEqual([], self.view.errors), until=lambda: len(self.view.contacts) > 0) # Don't wait for more contacts here. They shouldn't come, and if # they do, we'll notice it below. self.assertEqual(1, len(self.view.contacts)) self.assertEqual([self.managerPrefix + self.uid], self.addressbooks()) self.manager.RemovePeer(self.uid) self.assertEqual([], self.addressbooks()) @timeout(60) @property("snapshot", "simple-sort") def testStop(self): '''TestContacts.testStop - stop a started server''' # Auto-start. self.assertEqual(False, self.manager.IsRunning()) self.setUpView(peers=['foo']) self.assertEqual(True, self.manager.IsRunning()) # Must not stop now. self.manager.Stop() self.view.read(0, 0) # It may stop after closing the view. self.view.view.Close() self.manager.Stop() self.assertEqual(False, self.manager.IsRunning()) @timeout(60) @property("snapshot", "simple-sort") def testEmpty(self): '''TestContacts.testEmpty - start with empty view without databases''' self.setUpView(peers=[]) # Let it run for a bit longer, to catch further unintentional changes. now = time.time() self.runUntil('delay', check=lambda: (self.assertEqual([], self.view.errors), self.assertEqual([], self.view.contacts)), until=lambda: time.time() - now > 10) @timeout(60) @property("snapshot", "simple-sort") def testMerge(self): '''TestContacts.testMerge - merge identical contacts from two stores''' self.setUpView(peers=['foo', 'bar']) # folks merges this because a) X-JABBER (always) b) EMAIL (only # with patch for https://bugzilla.gnome.org/show_bug.cgi?id=685401). john = '''BEGIN:VCARD VERSION:3.0 FN:John Doe N:Doe;John TEL:1234-5678 EMAIL:john.doe@example.com URL:http://john.doe.com X-JABBER:jd@example.com END:VCARD''' item = os.path.join(self.contacts, 'john.vcf') output = open(item, "w") output.write(john) output.close() check = self.view.setCheck(lambda: self.assertGreater(2, len(self.view.contacts))) luids = {} for uid in self.uids: logging.log('inserting John into ' + uid) out, err, returncode = self.runCmdline(['--import', item, '@' + self.managerPrefix + uid, 'local']) luids[uid] = self.extractLUIDs(out) # Run until the view has adapted. We accept Added + Removed + Added # (happens when folks switches IDs, which happens when the "primary" store # changes) and Added + Updated. # # Let it run for a bit longer, to catch further unintentional changes # and ensure that changes from both stores where processed. now = time.time() self.runUntil('delay', check=check, until=lambda: time.time() - now > 10) self.assertEqual(1, len(self.view.contacts)) self.view.setCheck(None) # Read contact. logging.log('reading contact') self.view.read(0, 1) self.runUntil('contact', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.haveData(0)) contact = copy.deepcopy(self.view.contacts[0]) self.assertEqual({'full-name': 'John Doe', 'structured-name': {'given': 'John', 'family': 'Doe'}, 'emails': [('john.doe@example.com', [])], 'phones': [('1234-5678', [])], 'urls': [('http://john.doe.com', ['x-home-page'])], 'id': contact.get('id', ''), 'source': [ (self.uidPrefix + 'bar', luids[self.uidPrefix + 'bar'][0]), (self.uidPrefix + 'foo', luids[self.uidPrefix + 'foo'][0]) ], }, # Order of list entries in the result is not specified. # Must sort before comparing. contact, sortLists=True) @timeout(int(os.environ.get('TESTPIM_TEST_ACTIVE_NUM', 10)) * (usingValgrind() and 15 or 6)) @property("snapshot", "db-active") def testActive(self): '''TestContacts.testActive - reconfigure active address books several times''' contactsPerPeer = int(os.environ.get('TESTPIM_TEST_ACTIVE_NUM', 10)) self.assertEqual(['', 'peer-' + self.uidPrefix + 'a', 'peer-' + self.uidPrefix + 'c'], self.manager.GetActiveAddressBooks(), sortLists=True) withLogging = (contactsPerPeer <= 10) checkPerformance = os.environ.get('TESTPIM_TEST_ACTIVE_RESPONSE', None) threshold = 0 if checkPerformance: threshold = float(checkPerformance) # When starting the search right away and then add more # contact data later, we test the situation where new data # comes in because of a sync. # # When adding data first and then searching, we cover the # startup situation with already populated caches. # # Both are relevant scenarios. The first one stresses folks # more, because SyncEvolution adds contacts one at a time, # which then leads to many D-Bus messages containing a single # "contact added" notification that need to be processed by # folks. Testing this is the default. if os.environ.get('TESTPIM_TEST_ACTIVE_LOAD', False): # Delay starting the PIM Manager until data is ready to be read. # More realistic that way; otherwise folks must process new # contacts one-by-one. search=None else: # Start folks right away. search='' peers = ['a', 'b', 'c'] self.setUpView(peers=peers, withSystemAddressBook=True, search=search, withLogging=withLogging) active = [''] + peers # Check that active databases were adapted and stored permanently. self.assertEqual(['', 'peer-' + self.uidPrefix + 'a', 'peer-' + self.uidPrefix + 'b', 'peer-' + self.uidPrefix + 'c'], self.manager.GetActiveAddressBooks(), sortLists=True) # Order mirrors the one of SetActiveAddressBooks() in setUpView(), # assuming that the PIM Manager preserves that order (not really guaranteed # by the API, but is how it is implemented). self.assertIn('active = pim-manager-' + self.uidPrefix + 'a pim-manager-' + self.uidPrefix + 'b pim-manager-' + self.uidPrefix + 'c system-address-book\n', self.readManagerIni()) for peer in active: for index in range(0, contactsPerPeer): item = os.path.join(self.contacts, 'john%d.vcf' % index) output = open(item, "w") output.write('''BEGIN:VCARD VERSION:3.0 FN:John_%(peer)s%(index)04d Doe N:Doe;John_%(peer)s%(index)04d END:VCARD''' % {'peer': peer, 'index': index}) output.close() uid = self.uidPrefix + peer logging.log('inserting data into ' + uid) if peer != '': out, err, returncode = self.runCmdline(['--import', self.contacts, '@' + self.managerPrefix + uid, 'local']) else: out, err, returncode = self.runCmdline(['--import', self.contacts, 'database=', 'backend=evolution-contacts']) # Ping server regularly and check that it remains responsive. # Depends on processing all D-Bus replies with minimum # delay, because delays caused by us would lead to false negatives. w = Watchdog(self, self.manager, threshold=threshold) if checkPerformance: w.start() self.cleanup.append(w.stop) # Start the view if not done yet and run until the view has adapted. if search == None: self.view.search('') self.runUntil('view with contacts', check=lambda: self.assertEqual([], self.view.errors), until=lambda: len(self.view.contacts) == contactsPerPeer * len(active), may_block=checkPerformance) w.checkpoint('full view') def checkContacts(): contacts = copy.deepcopy(self.view.contacts) for contact in contacts: del contact['id'] del contact['source'] expected = [{'full-name': first + ' Doe', 'structured-name': {'given': first, 'family': 'Doe'}} for \ first in ['John_%(peer)s%(index)04d' % {'peer': peer, 'index': index} \ for peer in active \ for index in range(0, contactsPerPeer)] ] return (expected, contacts) def assertHaveContacts(): expected, contacts = checkContacts() self.assertEqual(expected, contacts) # Read contacts. logging.log('reading contacts') self.view.read(0, contactsPerPeer * len(active)) self.runUntil('contact', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.haveData(0, contactsPerPeer * len(active)), may_block=not withLogging) assertHaveContacts() w.checkpoint('contact data') def haveExpectedView(): current = time.time() logNow = withLogging or current - haveExpectedView.last > 1 if len(self.view.contacts) == contactsPerPeer * len(active): if self.view.haveData(0, contactsPerPeer * len(active)): expected, contacts = checkContacts() # TODO: avoid calling this if expected == contacts: logging.log('got expected data') return True else: if withLogging: logging.printf('data mismatch, keep waiting; currently have: %s', contacts) elif logNow: logging.printf('data mismatch, keep waiting') else: self.view.read(0, len(self.view.contacts)) if logNow: logging.log('still waiting for all data') elif logNow: logging.printf('wrong contact count, keep waiting: have %d, want %d', len(self.view.contacts), contactsPerPeer * len(active)) haveExpectedView.last = current return False haveExpectedView.last = time.time() # Now test all subsets until we are back at 'all active'. current = ['', 'a', 'b', 'c'] for active in [filter(lambda x: x != None, [s, a, b, c]) for s in [None, ''] for a in [None, 'a'] for b in [None, 'b'] for c in [None, 'c']]: logging.printf('changing address books %s -> %s', current, active) self.manager.SetActiveAddressBooks([x != '' and 'peer-' + self.uidPrefix + x or x for x in active]) self.runUntil('contacts %s' % str(active), check=lambda: self.assertEqual([], self.view.errors), until=haveExpectedView, may_block=not withLogging) w.checkpoint('%s -> %s' % (current, active)) current = active @timeout(60) @property("ENV", "LC_TYPE=de_DE.UTF-8 LC_ALL=de_DE.UTF-8 LANG=de_DE.UTF-8") def testFilterExisting(self): '''TestContacts.testFilterExisting - check that filtering works when applied to static contacts''' self.setUpView() # Can refine full view. Doesn't change anything here. self.view.view.RefineSearch([]) # Override default sorting. self.assertEqual("last/first", self.manager.GetSortOrder()) self.manager.SetSortOrder("first/last") # Insert new contacts. # # The names are chosen so that sorting by first name and sorting by last name needs to # reverse the list. for i, contact in enumerate([u'''BEGIN:VCARD VERSION:3.0 N:Zoo;Abraham NICKNAME:Ace TEL:1234 TEL:56/78 TEL:+1-800-FOOBAR TEL:089/7888-99 EMAIL:az@example.com END:VCARD''', u'''BEGIN:VCARD VERSION:3.0 N:Yeah;Benjamin TEL:+1-89-7888-99 END:VCARD''', # Chárleß has chárless as representation after folding the case. # This is different from lower case. # See http://www.boost.org/doc/libs/1_51_0/libs/locale/doc/html/glossary.html#term_case_folding u'''BEGIN:VCARD VERSION:3.0 FN:Charly 'Chárleß' Xing N:Xing;Charly END:VCARD''']): item = os.path.join(self.contacts, 'contact%d.vcf' % i) output = codecs.open(item, "w", "utf-8") output.write(contact) output.close() logging.log('inserting contacts') out, err, returncode = self.runCmdline(['--import', self.contacts, '@' + self.managerPrefix + self.uid, 'local']) # Relies on importing contacts sorted ascending by file name. luids = self.extractLUIDs(out) logging.printf('created contacts with luids: %s' % luids) # Run until the view has adapted. self.runUntil('view with three contacts', check=lambda: self.assertEqual([], self.view.errors), until=lambda: len(self.view.contacts) == 3) # Check for the one expected event. # TODO: self.assertEqual([('added', 0, 3)], view.events) self.view.events = [] # Read contacts. logging.log('reading contacts') self.view.read(0, 3) self.runUntil('contacts', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.haveData(0, 3)) self.assertEqual(u'Abraham', self.view.contacts[0]['structured-name']['given']) self.assertEqual(u'Benjamin', self.view.contacts[1]['structured-name']['given']) self.assertEqual(u'Charly', self.view.contacts[2]['structured-name']['given']) # Find Charly by his FN (case insensitive by default). view = ContactsView(self.manager) view.search([['any-contains', 'chÁrles']]) self.runUntil('charles search results', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(1, len(view.contacts)) view.read(0, 1) self.runUntil('charles', check=lambda: self.assertEqual([], view.errors), until=lambda: view.haveData(0)) self.assertEqual(u'Charly', view.contacts[0]['structured-name']['given']) # We can expand the search with ReplaceSearch(). view.view.ReplaceSearch([], False) self.runUntil('expanded view with three contacts', check=lambda: self.assertEqual([], self.view.errors), until=lambda: len(self.view.contacts) == 3) self.view.read(0, 3) self.runUntil('expanded contacts', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.haveData(0, 3)) self.assertEqual(u'Abraham', self.view.contacts[0]['structured-name']['given']) self.assertEqual(u'Benjamin', self.view.contacts[1]['structured-name']['given']) self.assertEqual(u'Charly', self.view.contacts[2]['structured-name']['given']) # Find Charly by his FN (case insensitive explicitly). view = ContactsView(self.manager) view.search([['any-contains', 'chÁrless', 'case-insensitive']]) self.runUntil('charles search results', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(1, len(view.contacts)) view.read(0, 1) self.runUntil('charles', check=lambda: self.assertEqual([], view.errors), until=lambda: view.haveData(0)) self.assertEqual(u'Charly', view.contacts[0]['structured-name']['given']) # Find Charly by his FN (case sensitive explicitly). view = ContactsView(self.manager) view.search([['any-contains', 'Chárleß', 'case-sensitive']]) self.runUntil('charles search results', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(1, len(view.contacts)) view.read(0, 1) self.runUntil('charles', check=lambda: self.assertEqual([], view.errors), until=lambda: view.haveData(0)) self.assertEqual(u'Charly', view.contacts[0]['structured-name']['given']) # Do not find Charly by his FN (case sensitive explicitly). view = ContactsView(self.manager) view.search([['any-contains', 'charles', 'case-sensitive']]) self.runUntil('charles search results', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(0, len(view.contacts)) # Find Abraham and Benjamin. view = ContactsView(self.manager) view.search([['any-contains', 'am']]) self.runUntil('"am" search results', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(2, len(view.contacts)) view.read(0, 2) self.runUntil('two contacts', check=lambda: self.assertEqual([], view.errors), until=lambda: view.haveData(0)) self.assertEqual(u'Abraham', view.contacts[0]['structured-name']['given']) self.assertEqual(u'Benjamin', view.contacts[1]['structured-name']['given']) # Refine search without actually changing the result. for refine in [True, False]: view.quiescentCount = 0 view.view.ReplaceSearch([['any-contains', 'am']], refine) self.runUntil('end of search refinement', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(u'Abraham', view.contacts[0]['structured-name']['given']) self.assertEqual(u'Benjamin', view.contacts[1]['structured-name']['given']) # Restrict search to Benjamin. The result is a view # which has different indices than the full view. view.quiescentCount = 0 view.view.RefineSearch([['any-contains', 'Benjamin']]) self.runUntil('end of search refinement', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(1, len(view.contacts)) self.assertEqual(u'Benjamin', view.contacts[0]['structured-name']['given']) # Refine again, without changes. view.quiescentCount = 0 view.view.RefineSearch([['any-contains', 'Benjamin']]) self.runUntil('end of search refinement', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(1, len(view.contacts)) self.assertEqual(u'Benjamin', view.contacts[0]['structured-name']['given']) # Refine to empty view. view.quiescentCount = 0 view.view.RefineSearch([['any-contains', 'XXXBenjamin']]) self.runUntil('end of search refinement', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(0, len(view.contacts)) # Find Abraham by his nickname. view = ContactsView(self.manager) view.search([['any-contains', 'ace']]) self.runUntil('"ace" search results', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(1, len(view.contacts)) view.read(0, 1) self.runUntil('two contacts', check=lambda: self.assertEqual([], view.errors), until=lambda: view.haveData(0)) self.assertEqual(u'Abraham', view.contacts[0]['structured-name']['given']) # Find Abraham by his email. view = ContactsView(self.manager) view.search([['any-contains', 'az@']]) self.runUntil('"az@" search results', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(1, len(view.contacts)) view.read(0, 1) self.runUntil('two contacts', check=lambda: self.assertEqual([], view.errors), until=lambda: view.haveData(0)) self.assertEqual(u'Abraham', view.contacts[0]['structured-name']['given']) # Find Abraham by his 1234 telephone number. view = ContactsView(self.manager) view.search([['any-contains', '1234']]) self.runUntil('"1234" search results', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(1, len(view.contacts)) view.read(0, 1) self.runUntil('1234 data', check=lambda: self.assertEqual([], view.errors), until=lambda: view.haveData(0)) self.assertEqual(u'Abraham', view.contacts[0]['structured-name']['given']) # Find Abraham by his 1234 telephone number, as sub-string. view = ContactsView(self.manager) view.search([['any-contains', '23']]) self.runUntil('"23" search results', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(1, len(view.contacts)) view.read(0, 1) self.runUntil('23 data', check=lambda: self.assertEqual([], view.errors), until=lambda: view.haveData(0)) self.assertEqual(u'Abraham', view.contacts[0]['structured-name']['given']) # Find Abraham by his 1234 telephone number, ignoring # formatting. view = ContactsView(self.manager) view.search([['any-contains', '12/34']]) self.runUntil('"12/34" search results', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(1, len(view.contacts)) view.read(0, 1) self.runUntil('12/34 data', check=lambda: self.assertEqual([], view.errors), until=lambda: view.haveData(0)) self.assertEqual(u'Abraham', view.contacts[0]['structured-name']['given']) # Find Abraham by his 56/78 telephone number, ignoring # slash in contact. view = ContactsView(self.manager) view.search([['any-contains', '5678']]) self.runUntil('"5678" search results', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(1, len(view.contacts)) view.read(0, 1) self.runUntil('5678 data', check=lambda: self.assertEqual([], view.errors), until=lambda: view.haveData(0)) self.assertEqual(u'Abraham', view.contacts[0]['structured-name']['given']) # Find Abraham via the +1-800-FOOBAR vanity number. view = ContactsView(self.manager) view.search([['any-contains', '+1-800-foobar']]) self.runUntil('"+1-800-foobar" search results', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(1, len(view.contacts)) view.read(0, 1) self.runUntil('+1-800-foobar data', check=lambda: self.assertEqual([], view.errors), until=lambda: view.haveData(0)) self.assertEqual(u'Abraham', view.contacts[0]['structured-name']['given']) # Find Abraham via the +1-800-FOOBAR vanity number, with digits # instead of alpha characters. view = ContactsView(self.manager) view.search([['any-contains', '366227']]) self.runUntil('"366227" search results', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(1, len(view.contacts)) view.read(0, 1) self.runUntil('366227 data', check=lambda: self.assertEqual([], view.errors), until=lambda: view.haveData(0)) self.assertEqual(u'Abraham', view.contacts[0]['structured-name']['given']) # Find Abraham via caller ID for +1-800-FOOBAR. view = ContactsView(self.manager) view.search([['phone', '+1800366227']]) self.runUntil('"+1800366227" search results', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(1, len(view.contacts)) view.read(0, 1) self.runUntil('+1800366227 data', check=lambda: self.assertEqual([], view.errors), until=lambda: view.haveData(0)) self.assertEqual(u'Abraham', view.contacts[0]['structured-name']['given']) # Find Abraham via caller ID for 089/7888-99 (country is Germany). view = ContactsView(self.manager) view.search([['phone', '+4989788899']]) self.runUntil('"+4989788899" search results', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(1, len(view.contacts)) view.read(0, 1) self.runUntil('+4989788899 data', check=lambda: self.assertEqual([], view.errors), until=lambda: view.haveData(0)) self.assertEqual(u'Abraham', view.contacts[0]['structured-name']['given']) # Find Abraham via caller ID for +44 89 7888-99 (Abraham has no country code # set and matches, whereas Benjamin has +1 as country code and does not match). view = ContactsView(self.manager) view.search([['phone', '+4489788899']]) self.runUntil('"+4489788899" search results', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(1, len(view.contacts)) view.read(0, 1) self.runUntil('+4489788899 data', check=lambda: self.assertEqual([], view.errors), until=lambda: view.haveData(0)) self.assertEqual(u'Abraham', view.contacts[0]['structured-name']['given']) # Find Abraham via 089/7888-99 (not a full caller ID, but at least a valid phone number). view = ContactsView(self.manager) view.search([['phone', '089788899']]) self.runUntil('"089788899" search results', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(1, len(view.contacts)) view.read(0, 1) self.runUntil('089788899 data', check=lambda: self.assertEqual([], view.errors), until=lambda: view.haveData(0)) self.assertEqual(u'Abraham', view.contacts[0]['structured-name']['given']) # Don't find anyone. view = ContactsView(self.manager) view.search([['phone', '+49897888000']]) self.runUntil('"+49897888000" search results', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(0, len(view.contacts)) def doFilter(self, testdata, searches): self.setUpView() msg = None view = None try: # Insert new contacts and calculate their family names. names = [] numtestcases = len(testdata) for i, contact in enumerate(testdata): item = os.path.join(self.contacts, 'contact%d.vcf' % i) output = codecs.open(item, "w", "utf-8") if isinstance(contact, tuple): # name + vcard output.write(contact[1]) names.append(contact[0]) else: # just the name output.write(u'''BEGIN:VCARD VERSION:3.0 FN:%(name)s N:%(name)s;;;; END:VCARD ''' % { 'name': contact }) names.append(contact) output.close() logging.log('inserting contacts') out, err, returncode = self.runCmdline(['--import', self.contacts, '@' + self.managerPrefix + self.uid, 'local']) # Relies on importing contacts sorted ascending by file name. luids = self.extractLUIDs(out) logging.printf('created contacts with luids: %s' % luids) # Run until the view has adapted. self.runUntil('view with three contacts', check=lambda: self.assertEqual([], self.view.errors), until=lambda: len(self.view.contacts) == numtestcases) # Check for the one expected event. # TODO: self.assertEqual([('added', 0, 3)], view.events) self.view.events = [] # Read contacts. logging.log('reading contacts') self.view.read(0, numtestcases) self.runUntil('contacts', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.haveData(0, numtestcases)) for i, name in enumerate(names): msg = u'contact #%d with name %s in\n%s' % (i, name, pprint.pformat(self.stripDBus(self.view.contacts, sortLists=False))) self.assertEqual(name, self.view.contacts[i]['full-name']) # Run searches and compare results. for i, (query, names) in enumerate(searches): msg = u'query %s, names %s' % (query, names) view = ContactsView(self.manager) view.search(query) self.runUntil('search %d: %s' % (i, query), check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) msg = u'query %s, names %s in\n%s' % (query, names, pprint.pformat(self.stripDBus(view.contacts, sortLists=False))) self.assertEqual(len(names), len(view.contacts)) view.read(0, len(names)) self.runUntil('data %d: %s' % (i, query), check=lambda: self.assertEqual([], view.errors), until=lambda: view.haveData(0, len(names))) for e, name in enumerate(names): msg = u'query %s, names %s, name #%d %s in\n%s' % (query, names, e, name, pprint.pformat(self.stripDBus(view.contacts, sortLists=False))) self.assertEqual(name, view.contacts[e]['full-name']) except Exception, ex: if msg: info = sys.exc_info() raise Exception('%s:\n%s' % (msg, repr(ex))), None, info[2] else: raise return view @timeout(60) @property("ENV", "LC_TYPE=ja_JP.UTF-8 LC_ALL=ja_JP.UTF-8 LANG=ja_JP.UTF-8") def testFilterJapanese(self): self.doFilter(# Names of all contacts, sorted as expected. ('111', u'1月', 'Bad'), # Query + expected results. (([], ('111', u'1月', 'Bad')), ([['any-contains', '1']], ('111', u'1月')), ([['any-contains', u'1月']], (u'1月',))) ) @timeout(60) @property("ENV", "LC_TYPE=zh_CN.UTF-8 LC_ALL=zh_CN.UTF-8 LANG=zh_CN.UTF-8") def testFilterChinesePinyin(self): self.doFilter(# Names of all contacts, sorted as expected. # 江 = jiāng = Jiang when using Pinyin and thus after Jeffries and before Meadows. # 鳥 = niǎo before 女性 = nǚ xìng (see FDO #66618) ('Adams', 'Jeffries', u'江', 'jiang', 'Meadows', u'鳥', u'女性' ), # 'J' may or may not match Jiang; by default, it matches. (([['any-contains', 'J']], ('Jeffries', u'江', 'jiang')), ([['any-contains', 'J', 'no-transliteration']], ('Jeffries', 'jiang')), ([['any-contains', 'J', 'no-transliteration', 'case-sensitive']], ('Jeffries',)), ([['any-contains', u'江']], (u'江', 'jiang')), ([['any-contains', u'jiang']], (u'江', 'jiang')), ([['any-contains', u'jiāng']], (u'江', 'jiang')), ([['any-contains', u'jiāng', 'no-transliteration']], ('jiang',)), ([['any-contains', u'jiāng', 'accent-sensitive']], (u'江',)), ([['any-contains', u'jiāng', 'accent-sensitive', 'case-sensitive']], (u'江',)), ([['any-contains', u'Jiāng', 'accent-sensitive', 'case-sensitive']], ()), ([['any-contains', u'Jiang']], (u'江', 'jiang')), ), ) @timeout(60) @property("ENV", "LC_TYPE=de_DE.UTF-8 LC_ALL=de_DE.UTF-8 LANG=de_DE.UTF-8") def testFilterGermany(self): self.doFilter(# Names of all contacts, sorted as expected. # DIN 5007 Variant 2 defines phone book sorting in # Germany. It does not apply to Austria. # Example from http://de.wikipedia.org/wiki/Alphabetische_Sortierung (u'Göbel', u'Goethe', u'Göthe', u'Götz', u'Goldmann'), (), ) @timeout(60) @property("ENV", "LC_TYPE=zh_CN.UTF-8 LANG=zh_CN.UTF-8") def testLocaled(self): # Use mixed Chinese/Western names, because then the locale really matters. namespinyin = ('Adams', 'Jeffries', u'江', 'Meadows', u'鳥', u'女性' ) namesgerman = ('Adams', 'Jeffries', 'Meadows', u'女性', u'江', u'鳥' ) numtestcases = len(namespinyin) self.doFilter(namespinyin, ()) daemon = localed.Localed() msg = None try: # Broadcast Locale value together with PropertiesChanged signal. self.view.quiescentCount = 0 daemon.SetLocale(['LC_TYPE=de_DE.UTF-8', 'LANG=de_DE.UTF-8'], False) logging.log('reading contacts, German') self.runUntil('German sorting', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.quiescentCount > 1) self.view.read(0, numtestcases) self.runUntil('German contacts', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.haveData(0, numtestcases)) for i, name in enumerate(namesgerman): msg = u'contact #%d with name %s in\n%s' % (i, name, pprint.pformat(self.stripDBus(self.view.contacts, sortLists=False))) self.assertEqual(name, self.view.contacts[i]['full-name']) # Switch back to Pinyin without including the new value. self.view.quiescentCount = 0 daemon.SetLocale(['LC_TYPE=zh_CN.UTF-8', 'LANG=zh_CN.UTF-8'], True) logging.log('reading contacts, Pinyin') self.runUntil('Pinyin sorting', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.quiescentCount > 1) self.view.read(0, numtestcases) self.runUntil('Pinyin contacts', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.haveData(0, numtestcases)) for i, name in enumerate(namespinyin): msg = u'contact #%d with name %s in\n%s' % (i, name, pprint.pformat(self.stripDBus(self.view.contacts, sortLists=False))) self.assertEqual(name, self.view.contacts[i]['full-name']) except Exception, ex: if msg: info = sys.exc_info() raise Exception('%s:\n%s' % (msg, repr(ex))), None, info[2] else: raise finally: daemon.remove_from_connection() @timeout(60) # Must disable usage of pre-computed phone numbers from EDS, because although we can # tell EDS about locale changes, it currently crashes when we do that (https://bugs.freedesktop.org/show_bug.cgi?id=59571#c20). # # Remove the SYNCEVOLUTION_PIM_EDS_NO_E164=1 part from ENV to test and use EDS. @property("ENV", "LANG=en_US.UTF-8 SYNCEVOLUTION_PIM_EDS_NO_E164=1") def testLocaledPhone(self): # Parsing of 1234-5 depends on locale: US drops the 1 from 1234 # Germany (and other countries) don't. Use that to match (or not match) # a contact. usingEDS = not 'SYNCEVOLUTION_PIM_EDS_NO_E164=1' in self.getTestProperty("ENV", "") if usingEDS: daemon = localed.Localed() daemon.SetLocale(['LANG=en_US.UTF-8'], True) # Give EDS some time to notice the new daemon and it's en_US setting. Timeout.addTimeout(5, loop.quit) loop.run() testcases = (('Doe', '''BEGIN:VCARD VERSION:3.0 FN:Doe N:Doe;;;; TEL:12 34-5 END:VCARD '''),) names = ('Doe') numtestcases = len(testcases) view = self.doFilter(testcases, (([['phone', '+12345']], ('Doe',)),)) msg = None if not usingEDS: # Don't do that too early, otherwise EDS also sees the daemon # and crashes (https://bugs.freedesktop.org/show_bug.cgi?id=59571#c20). daemon = localed.Localed() try: # Contact no longer matched because it's phone number normalization # becomes different. view.quiescentCount = 0 daemon.SetLocale(['LANG=de_DE.UTF-8'], True) self.runUntil('German locale', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 1) self.assertEqual(len(view.contacts), 0) # Switch back to US. view.quiescentCount = 0 daemon.SetLocale(['LANG=en_US.UTF-8'], True) self.runUntil('US locale', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 1) self.assertEqual(len(view.contacts), 1) except Exception, ex: if msg: info = sys.exc_info() raise Exception('%s:\n%s' % (msg, repr(ex))), None, info[2] else: raise finally: daemon.remove_from_connection() # Not supported correctly by ICU? # See icu-support "Subject: Austrian phone book sorting" # @timeout(60) # @property("ENV", "LC_TYPE=de_AT.UTF-8 LC_ALL=de_AT.UTF-8 LANG=de_AT.UTF-8") # def testFilterAustria(self): # self.doFilter(# Names of all contacts, sorted as expected. # # Austrian phone book sorting. # # Example from http://de.wikipedia.org/wiki/Alphabetische_Sortierung # (u'Goethe', u'Goldmann', u'Göbel', u'Göthe', u'Götz'), # (), # ) @timeout(60) def testFilterLogic(self): '''TestContacts.testFilterLogic - check logic operators''' self.doFilter(('Xing', 'Yeah', 'Zooh'), ((['or', ['any-contains', 'X'], ['any-contains', 'Z']], ('Xing', 'Zooh')), (['or', ['any-contains', 'X']], ('Xing',)), (['or', ['any-contains', 'Z']], ('Zooh',)), (['or'], ()), (['and', ['any-contains', 'h'], ['any-contains', 'Z']], ('Zooh',)), (['and', ['any-contains', 'h']], ('Yeah', 'Zooh')), (['and', ['any-contains', 'Z']], ('Zooh',)), (['and', ['any-contains', 'h'], ['any-contains', 'Z'], ['any-contains', 'A']], ()), (['and'], ()), # Python D-Bus does not like mixing elements of different types in a list. # In a tuple that's fine, and also works with the PIM Manager. (('or', ('and', ('any-contains', 'h'), ('any-contains', 'Z')), ('any-contains', 'X')), ('Xing', 'Zooh')), (('and', ('or', ('any-contains', 'X'), ('any-contains', 'Z')), ('any-contains', 'h')), ('Zooh',)))) @timeout(60) def testFilterFields(self): '''TestContacts.testFilterFields - check filter operations on fields''' self.doFilter([('John Doe', r'''BEGIN:VCARD VERSION:3.0 URL:http://john.doe.com TITLE:Senior Tester ORG:Test Inc.;Testing;test#1 ROLE:professional test case X-EVOLUTION-MANAGER:John Doe Senior X-EVOLUTION-ASSISTANT:John Doe Junior NICKNAME:user1 BDAY:2006-01-08 X-EVOLUTION-ANNIVERSARY:2006-01-09 X-EVOLUTION-SPOUSE:Joan Doe NOTE:This is a test case which uses almost all Evolution fields. FN:John Doe N:Doe;John;Johnny;; X-EVOLUTION-FILE-AS:Doe\, John CATEGORIES:TEST X-EVOLUTION-BLOG-URL:web log GEO:30.12;-130.34 CALURI:calender FBURL:free/busy X-EVOLUTION-VIDEO-URL:chat X-MOZILLA-HTML:TRUE ADR;TYPE=WORK:Test Box #2;;Test Drive 2;Test Town;Upper Test County;12346;O ld Testovia LABEL;TYPE=WORK:Test Drive 2\nTest Town\, Upper Test County\n12346\nTest Bo x #2\nOld Testovia ADR;TYPE=HOME:Test Box #1;;Test Drive 1;Test Village;Lower Test County;1234 5;Testovia LABEL;TYPE=HOME:Test Drive 1\nTest Village\, Lower Test County\n12345\nTest Box #1\nTestovia ADR:Test Box #3;Test Extension;Test Drive 3;Test Megacity;Test County;12347;New Testonia LABEL;TYPE=OTHER:Test Drive 3\nTest Megacity\, Test County\n12347\nTest Box #3\nNew Testonia UID:pas-id-43C0ED3900000001 EMAIL;TYPE=WORK;X-EVOLUTION-UI-SLOT=1:john.doe@work.com EMAIL;TYPE=HOME;X-EVOLUTION-UI-SLOT=2:john.doe@home.priv EMAIL;TYPE=OTHER;X-EVOLUTION-UI-SLOT=3:john.doe@other.world EMAIL;TYPE=OTHER;X-EVOLUTION-UI-SLOT=4:john.doe@yet.another.world TEL;TYPE=work;TYPE=Voice;X-EVOLUTION-UI-SLOT=1:business 1 TEL;TYPE=homE;TYPE=VOICE;X-EVOLUTION-UI-SLOT=2:home 2 TEL;TYPE=CELL;X-EVOLUTION-UI-SLOT=3:mobile 3 TEL;TYPE=WORK;TYPE=FAX;X-EVOLUTION-UI-SLOT=4:businessfax 4 TEL;TYPE=HOME;TYPE=FAX;X-EVOLUTION-UI-SLOT=5:homefax 5 TEL;TYPE=PAGER;X-EVOLUTION-UI-SLOT=6:pager 6 TEL;TYPE=CAR;X-EVOLUTION-UI-SLOT=7:car 7 TEL;TYPE=PREF;X-EVOLUTION-UI-SLOT=8:primary 8 TEL:12 34-5 END:VCARD ''')], ((['is', 'full-name', 'john doe'], ('John Doe',)), (['is', 'full-name', 'John Doe', 'case-sensitive'], ('John Doe',)), (['is', 'full-name', 'john doe', 'case-sensitive'], ()), (['is', 'full-name', 'John Doe', 'case-insensitive'], ('John Doe',)), (['is', 'full-name', 'john'], ()), (['contains', 'full-name', 'ohn d'], ('John Doe',)), (['contains', 'full-name', 'ohn D', 'case-sensitive'], ('John Doe',)), (['contains', 'full-name', 'ohn d', 'case-sensitive'], ()), (['contains', 'full-name', 'ohn d', 'case-insensitive'], ('John Doe',)), (['contains', 'full-name', 'foobar'], ()), (['begins-with', 'full-name', 'john'], ('John Doe',)), (['begins-with', 'full-name', 'John', 'case-sensitive'], ('John Doe',)), (['begins-with', 'full-name', 'john', 'case-sensitive'], ()), (['begins-with', 'full-name', 'John', 'case-insensitive'], ('John Doe',)), (['begins-with', 'full-name', 'doe'], ()), (['ends-with', 'full-name', 'doe'], ('John Doe',)), (['ends-with', 'full-name', 'Doe', 'case-sensitive'], ('John Doe',)), (['ends-with', 'full-name', 'doe', 'case-sensitive'], ()), (['ends-with', 'full-name', 'Doe', 'case-insensitive'], ('John Doe',)), (['ends-with', 'full-name', 'john'], ()), (['is', 'nickname', 'user1'], ('John Doe',)), (['is', 'nickname', 'Johnny'], ()), (['is', 'structured-name/family', 'Doe'], ('John Doe',)), (['is', 'structured-name/family', 'John'], ()), (['is', 'structured-name/given', 'John'], ('John Doe',)), (['is', 'structured-name/given', 'Doe'], ()), (['is', 'structured-name/additional', 'Johnny'], ('John Doe',)), (['is', 'structured-name/additional', 'John'], ()), (['is', 'emails/value', 'john.doe@work.com'], ('John Doe',)), (['is', 'emails/value', 'foo@abc.com'], ()), (['is', 'addresses/po-box', 'Test Box #3'], ('John Doe',)), (['is', 'addresses/po-box', 'Foo Box'], ()), (['is', 'addresses/extension', 'Test Extension'], ('John Doe',)), (['is', 'addresses/extension', 'Foo Extension'], ()), (['is', 'addresses/street', 'Test Drive 3'], ('John Doe',)), (['is', 'addresses/street', 'Rodeo Drive'], ()), (['is', 'addresses/locality', 'Test Megacity'], ('John Doe',)), (['is', 'addresses/locality', 'New York'], ()), (['is', 'addresses/region', 'Test County'], ('John Doe',)), (['is', 'addresses/region', 'Testovia'], ()), (['is', 'addresses/postal-code', '54321'], ()), (['is', 'addresses/country', 'New Testonia'], ('John Doe',)), (['is', 'addresses/country', 'America'], ()), (['is', 'phones/value', 'business 1'], ('John Doe',)), (['is', 'phones/value', 'business 123'], ()), (['is', 'phones/value', '12345'], ('John Doe',)), (['is', 'phones/value', '123456'], ()), )) @timeout(60) @property("ENV", "LC_TYPE=de_DE.UTF-8 LC_ALL=de_DE.UTF-8 LANG=de_DE.UTF-8") def testFilterQuiescence(self): '''TestContacts.testFilterQuiescence - check that starting server via filter leads to quiescence signal''' self.setUpView(peers=[], withSystemAddressBook=True, search=[('any-contains', 'foo')]) self.assertEqual(1, self.view.quiescentCount) @timeout(60) @property("ENV", "LC_TYPE=de_DE.UTF-8 LC_ALL=de_DE.UTF-8 LANG=de_DE.UTF-8") def testFullQuiescence(self): '''TestContacts.testFullQuiescence - check that starting server via filter leads to quiescence signal''' self.setUpView(peers=[], withSystemAddressBook=True, search=[]) self.assertEqual(1, self.view.quiescentCount) @timeout(60) @property("ENV", "LC_TYPE=de_DE.UTF-8 LC_ALL=de_DE.UTF-8 LANG=de_DE.UTF-8") def testFilterLive(self): '''TestContacts.testFilterLive - check that filtering works while adding contacts''' self.setUpView() self.manager.SetSortOrder("first/last") # Find Charly by his FN (case insensitive by default). view = ContactsView(self.manager) view.search([['any-contains', 'chÁrles']]) self.runUntil('charles search results', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(0, len(view.contacts)) # Insert new contacts. # # The names are chosen so that sorting by first name and sorting by last name needs to # reverse the list. for i, contact in enumerate([u'''BEGIN:VCARD VERSION:3.0 N:Zoo;Abraham NICKNAME:Ace TEL:1234 EMAIL:az@example.com END:VCARD''', u'''BEGIN:VCARD VERSION:3.0 N:Yeah;Benjamin END:VCARD''', # Chárleß has chárless as representation after folding the case. # This is different from lower case. # See http://www.boost.org/doc/libs/1_51_0/libs/locale/doc/html/glossary.html#term_case_folding u'''BEGIN:VCARD VERSION:3.0 FN:Charly 'Chárleß' Xing N:Xing;Charly END:VCARD''']): item = os.path.join(self.contacts, 'contact%d.vcf' % i) output = codecs.open(item, "w", "utf-8") output.write(contact) output.close() logging.log('inserting contacts') out, err, returncode = self.runCmdline(['--import', self.contacts, '@' + self.managerPrefix + self.uid, 'local']) # Relies on importing contacts sorted ascending by file name. luids = self.extractLUIDs(out) logging.printf('created contacts with luids: %s' % luids) # Run until the view has adapted. self.runUntil('view with one contact', check=lambda: self.assertEqual([], view.errors), until=lambda: len(view.contacts) > 0) # Don't wait for more contacts here. They shouldn't come, and if # they do, we'll notice it below. self.assertEqual(1, len(view.contacts)) # Search for telephone number. phone = ContactsView(self.manager) phone.search([['phone', '1234']]) self.runUntil('phone results', check=lambda: self.assertEqual([], phone.errors), until=lambda: phone.quiescentCount > 0) self.assertEqual(1, len(phone.contacts)) # Read contacts. logging.log('reading contacts') view.read(0, 1) self.view.read(0, 3) self.runUntil('contacts', check=lambda: (self.assertEqual([], view.errors), self.assertEqual([], self.view.errors)), until=lambda: view.haveData(0) and \ self.view.haveData(0, 3)) self.assertEqual(u'Charly', view.contacts[0]['structured-name']['given']) self.assertEqual(u'Abraham', self.view.contacts[0]['structured-name']['given']) self.assertEqual(u'Benjamin', self.view.contacts[1]['structured-name']['given']) self.assertEqual(u'Charly', self.view.contacts[2]['structured-name']['given']) # Unmatched contact remains unmatched. # Modified phone number no longer matched. item = os.path.join(self.contacts, 'contact%d.vcf' % 0) output = codecs.open(item, "w", "utf-8") output.write(u'''BEGIN:VCARD VERSION:3.0 N:Zoo;Abraham NICKNAME:King TEL:123 EMAIL:az@example.com END:VCARD''') output.close() logging.log('change nick of Abraham') # Check as part of event processing: # - view has one unmodified contact check1 = view.setCheck(lambda: (self.assertEqual(1, len(view.contacts)), self.assertIsInstance(view.contacts[0], dict))) # - self.view changes, must not encounter errors check2 = self.view.setCheck(None) check = lambda: (check1(), check2()) out, err, returncode = self.runCmdline(['--update', item, '@' + self.managerPrefix + self.uid, 'local', luids[0]]) self.runUntil('Abraham nickname changed', check=check, until=lambda: self.view.haveNoData(0)) self.view.read(0, 1) self.runUntil('Abraham nickname read', check=check, until=lambda: self.view.haveData(0)) self.assertEqual(u'Charly', view.contacts[0]['structured-name']['given']) self.assertEqual(u'Abraham', self.view.contacts[0]['structured-name']['given']) # No longer part of the telephone search view. self.runUntil('phone results', check=lambda: self.assertEqual([], phone.errors), until=lambda: len(phone.contacts) == 0) phone.close() # Matched contact remains matched, but we loose the data. check1 = view.setCheck(lambda: self.assertEqual(1, len(view.contacts))) item = os.path.join(self.contacts, 'contact%d.vcf' % 2) output = codecs.open(item, "w", "utf-8") output.write(u'''BEGIN:VCARD VERSION:3.0 FN:Charly 'Chárleß' Xing NICKNAME:Angel N:Xing;Charly END:VCARD''') output.close() logging.log('change nick of Charly') out, err, returncode = self.runCmdline(['--update', item, '@' + self.managerPrefix + self.uid, 'local', luids[2]]) self.runUntil('Charly nickname changed', check=check, until=lambda: self.view.haveNoData(2) and \ view.haveNoData(0)) view.read(0, 1) self.view.read(2, 1) self.runUntil('Charly nickname read', check=check, until=lambda: self.view.haveData(2) and \ view.haveData(0)) self.assertEqual(u'Charly', view.contacts[0]['structured-name']['given']) self.assertEqual(u'Charly', self.view.contacts[2]['structured-name']['given']) # Unmatched contact gets matched. check1 = view.setCheck(lambda: self.assertLess(0, len(view.contacts))) item = os.path.join(self.contacts, 'contact%d.vcf' % 0) output = codecs.open(item, "w", "utf-8") output.write(u'''BEGIN:VCARD VERSION:3.0 N:Zoo;Abraham NICKNAME:Chárleß alter ego TEL:1234 EMAIL:az@example.com END:VCARD''') output.close() logging.log('change nick of Abraham, II') out, err, returncode = self.runCmdline(['--update', item, '@' + self.managerPrefix + self.uid, 'local', luids[0]]) self.runUntil('Abraham nickname changed', check=check, until=lambda: self.view.haveNoData(0) and \ len(view.contacts) == 2) check1 = view.setCheck(lambda: self.assertEqual(2, len(view.contacts))) self.assertNotIsInstance(view.contacts[0], dict) self.assertIsInstance(view.contacts[1], dict) view.read(0, 1) self.view.read(0, 1) self.runUntil('Abraham nickname read, II', check=check, until=lambda: self.view.haveData(0) and \ view.haveData(0)) self.assertEqual(u'Abraham', view.contacts[0]['structured-name']['given']) self.assertEqual(u'Charly', view.contacts[1]['structured-name']['given']) self.assertEqual(u'Abraham', self.view.contacts[0]['structured-name']['given']) # Invert sort order. check1 = view.setCheck(None) self.manager.SetSortOrder("last/first") self.runUntil('reordering', check=check, until=lambda: self.view.haveNoData(0) and \ self.view.haveNoData(2) and \ view.haveNoData(0, 2)) view.read(0, 2) self.view.read(0, 3) self.runUntil('read reordered contacts', check=check, until=lambda: self.view.haveData(0, 3) and \ view.haveData(0, 2)) self.assertEqual(2, len(view.contacts)) self.assertEqual(u'Charly', view.contacts[0]['structured-name']['given']) self.assertEqual(u'Abraham', view.contacts[1]['structured-name']['given']) self.assertEqual(3, len(self.view.contacts)) self.assertEqual(u'Charly', self.view.contacts[0]['structured-name']['given']) self.assertEqual(u'Benjamin', self.view.contacts[1]['structured-name']['given']) self.assertEqual(u'Abraham', self.view.contacts[2]['structured-name']['given']) # And back again. self.manager.SetSortOrder("first/last") self.runUntil('reordering, II', check=check, until=lambda: self.view.haveNoData(0) and \ self.view.haveNoData(2) and \ view.haveNoData(0, 2)) view.read(0, 2) self.view.read(0, 3) self.runUntil('read reordered contacts, II', check=check, until=lambda: self.view.haveData(0, 3) and \ view.haveData(0, 2)) self.assertEqual(2, len(view.contacts)) self.assertEqual(u'Abraham', view.contacts[0]['structured-name']['given']) self.assertEqual(u'Charly', view.contacts[1]['structured-name']['given']) self.assertEqual(3, len(self.view.contacts)) self.assertEqual(u'Abraham', self.view.contacts[0]['structured-name']['given']) self.assertEqual(u'Benjamin', self.view.contacts[1]['structured-name']['given']) self.assertEqual(u'Charly', self.view.contacts[2]['structured-name']['given']) # Matched contact gets unmatched. check1 = view.setCheck(lambda: self.assertLess(0, len(view.contacts))) item = os.path.join(self.contacts, 'contact%d.vcf' % 0) output = codecs.open(item, "w", "utf-8") output.write(u'''BEGIN:VCARD VERSION:3.0 N:Zoo;Abraham NICKNAME:None TEL:1234 EMAIL:az@example.com END:VCARD''') output.close() logging.log('change nick of Abraham, II') out, err, returncode = self.runCmdline(['--update', item, '@' + self.managerPrefix + self.uid, 'local', luids[0]]) self.runUntil('Abraham nickname changed to None', check=check, until=lambda: self.view.haveNoData(0) and \ len(view.contacts) == 1) self.assertIsInstance(view.contacts[0], dict) self.view.read(0, 1) check1 = view.setCheck(lambda: (self.assertEqual(1, len(view.contacts)), self.assertIsInstance(view.contacts[0], dict))) self.runUntil('Abraham nickname read, None', check=check, until=lambda: self.view.haveData(0)) self.assertEqual(u'Charly', view.contacts[0]['structured-name']['given']) self.assertEqual(u'Abraham', self.view.contacts[0]['structured-name']['given']) # Finally, remove everything. logging.log('remove contacts') check1 = view.setCheck(None) out, err, returncode = self.runCmdline(['--delete-items', '@' + self.managerPrefix + self.uid, 'local', '*']) self.runUntil('all contacts removed', check=lambda: (self.assertEqual([], view.errors), self.assertEqual([], self.view.errors)), until=lambda: len(self.view.contacts) == 0 and \ len(view.contacts) == 0) @timeout(60) @property("ENV", "LC_TYPE=de_DE.UTF-8 LC_ALL=de_DE.UTF-8 LANG=de_DE.UTF-8") def testFilterExistingLimit(self): '''TestContacts.testFilterExistingLimit - check that filtering works when applied to static contacts, with maximum number of results''' self.setUpView() # Override default sorting. self.assertEqual("last/first", self.manager.GetSortOrder()) self.manager.SetSortOrder("first/last") # Insert new contacts. # # The names are chosen so that sorting by first name and sorting by last name needs to # reverse the list. for i, contact in enumerate([u'''BEGIN:VCARD VERSION:3.0 N:Zoo;Abraham NICKNAME:Ace TEL:1234 TEL:56/78 TEL:+1-800-FOOBAR TEL:089/7888-99 EMAIL:az@example.com END:VCARD''', u'''BEGIN:VCARD VERSION:3.0 N:Yeah;Benjamin TEL:+1-89-7888-99 END:VCARD''', # Chárleß has chárless as representation after folding the case. # This is different from lower case. # See http://www.boost.org/doc/libs/1_51_0/libs/locale/doc/html/glossary.html#term_case_folding u'''BEGIN:VCARD VERSION:3.0 FN:Charly 'Chárleß' Xing N:Xing;Charly END:VCARD''']): item = os.path.join(self.contacts, 'contact%d.vcf' % i) output = codecs.open(item, "w", "utf-8") output.write(contact) output.close() logging.log('inserting contacts') out, err, returncode = self.runCmdline(['--import', self.contacts, '@' + self.managerPrefix + self.uid, 'local']) # Relies on importing contacts sorted ascending by file name. luids = self.extractLUIDs(out) logging.printf('created contacts with luids: %s' % luids) # Run until the view has adapted. self.runUntil('view with three contacts', check=lambda: self.assertEqual([], self.view.errors), until=lambda: len(self.view.contacts) == 3) # Check for the one expected event. # TODO: self.assertEqual([('added', 0, 3)], view.events) self.view.events = [] # Read contacts. logging.log('reading contacts') self.view.read(0, 3) self.runUntil('contacts', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.haveData(0, 3)) self.assertEqual(u'Abraham', self.view.contacts[0]['structured-name']['given']) self.assertEqual(u'Benjamin', self.view.contacts[1]['structured-name']['given']) self.assertEqual(u'Charly', self.view.contacts[2]['structured-name']['given']) # Browse initial two contacts (= uses MatchAll filter with limit). view = ContactsView(self.manager) view.search([['limit', '2']]) self.runUntil('browse results', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(2, len(view.contacts)) view.read(0, 2) self.runUntil('browse data', check=lambda: self.assertEqual([], view.errors), until=lambda: view.haveData(0, 2)) self.assertEqual(u'Abraham', self.view.contacts[0]['structured-name']['given']) self.assertEqual(u'Benjamin', self.view.contacts[1]['structured-name']['given']) view.close() # Find Abraham and Benjamin but stop at first contact. view = ContactsView(self.manager) view.search([['any-contains', 'am'], ['limit', '1']]) self.runUntil('"am" search results', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(1, len(view.contacts)) view.read(0, 1) self.runUntil('one contact', check=lambda: self.assertEqual([], view.errors), until=lambda: view.haveData(0)) self.assertEqual(u'Abraham', view.contacts[0]['structured-name']['given']) # Changing the limit is not supported. with self.assertRaisesRegexp(dbus.DBusException, r'.*: refining the search must not change the maximum number of results$'): view.view.RefineSearch([['limit', '3'], ['any-contains', 'foo']]) # Refine search without actually changing the result. view.quiescentCount = 0 view.view.RefineSearch([['any-contains', 'am'], ['limit', '1']]) self.runUntil('end of search refinement', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(1, len(view.contacts)) self.assertEqual(u'Abraham', view.contacts[0]['structured-name']['given']) # Restrict search to Benjamin. We can leave out the limit, the old # stays active automatically. Abraham drops out of the view # and Benjamin enters the result subset. view.quiescentCount = 0 view.view.RefineSearch([['any-contains', 'Benjamin']]) self.runUntil('end of search refinement', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(1, len(view.contacts)) self.assertFalse(view.haveData(0)) view.read(0, 1) self.runUntil('Benjamin', check=lambda: self.assertEqual([], view.errors), until=lambda: view.haveData(0)) self.assertEqual(u'Benjamin', view.contacts[0]['structured-name']['given']) # Refine again, without changes. view.quiescentCount = 0 view.view.RefineSearch([['any-contains', 'Benjamin']]) self.runUntil('end of search refinement', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(1, len(view.contacts)) self.assertEqual(u'Benjamin', view.contacts[0]['structured-name']['given']) # Refine to empty view. view.quiescentCount = 0 view.view.RefineSearch([['any-contains', 'XXXBenjamin']]) self.runUntil('end of search refinement', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(0, len(view.contacts)) # Expand back to view with Benjamin. view.quiescentCount = 0 view.view.ReplaceSearch([['any-contains', 'Benjamin']], False) self.runUntil('end of search replacement', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(1, len(view.contacts)) view.read(0, 1) self.runUntil('Benjamin', check=lambda: self.assertEqual([], view.errors), until=lambda: view.haveData(0)) self.assertEqual(u'Benjamin', view.contacts[0]['structured-name']['given']) @timeout(60) @property("ENV", "LC_TYPE=de_DE.UTF-8 LC_ALL=de_DE.UTF-8 LANG=de_DE.UTF-8") def testFilterLiveLimit(self): '''TestContacts.testFilterLiveLimit - check that filtering works while modifying contacts, with a maximum number of results''' self.setUpView() self.manager.SetSortOrder("first/last") # Find Abraham and Benjamin, but limit results to first one. view = ContactsView(self.manager) view.search([['any-contains', 'am'], ['limit', '1']]) self.runUntil('am search results', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(0, len(view.contacts)) # Insert new contacts. # # The names are chosen so that sorting by first name and sorting by last name needs to # reverse the list. for i, contact in enumerate([u'''BEGIN:VCARD VERSION:3.0 N:Zoo;Abraham FN:Abraham Zoo NICKNAME:Ace END:VCARD''', u'''BEGIN:VCARD VERSION:3.0 N:Yeah;Benjamin END:VCARD''', # Chárleß has chárless as representation after folding the case. # This is different from lower case. # See http://www.boost.org/doc/libs/1_51_0/libs/locale/doc/html/glossary.html#term_case_folding u'''BEGIN:VCARD VERSION:3.0 FN:Charly 'Chárleß' Xing N:Xing;Charly END:VCARD''']): item = os.path.join(self.contacts, 'contact%d.vcf' % i) output = codecs.open(item, "w", "utf-8") output.write(contact) output.close() logging.log('inserting contacts') out, err, returncode = self.runCmdline(['--import', self.contacts, '@' + self.managerPrefix + self.uid, 'local']) # Relies on importing contacts sorted ascending by file name. luids = self.extractLUIDs(out) logging.printf('created contacts with luids: %s' % luids) # Run until the view has adapted. self.runUntil('view with one contact', check=lambda: self.assertEqual([], view.errors), until=lambda: len(view.contacts) > 0 and len(self.view.contacts) == 3) # Don't wait for more contacts here. They shouldn't come, and if # they do, we'll notice it below. self.assertEqual(1, len(view.contacts)) # Check conditions as part of view updates. check1 = view.setCheck(None) check2 = self.view.setCheck(None) check = lambda: (check1(), check2()) # Read contacts. logging.log('reading contacts') view.read(0, 1) self.view.read(0, 3) self.runUntil('contacts', check=check, until=lambda: view.haveData(0) and \ self.view.haveData(0, 3)) self.assertEqual(u'Abraham', view.contacts[0]['structured-name']['given']) self.assertEqual(u'Abraham', self.view.contacts[0]['structured-name']['given']) self.assertEqual(u'Benjamin', self.view.contacts[1]['structured-name']['given']) self.assertEqual(u'Charly', self.view.contacts[2]['structured-name']['given']) # Matched contact remains matched. item = os.path.join(self.contacts, 'contact%d.vcf' % 0) output = codecs.open(item, "w", "utf-8") output.write(u'''BEGIN:VCARD VERSION:3.0 N:Zoo;Abraham FN:Abraham Zoo NICKNAME:King END:VCARD''') output.close() logging.log('change nick of Abraham') check1 = view.setCheck(lambda: self.assertEqual(1, len(view.contacts))) out, err, returncode = self.runCmdline(['--update', item, '@' + self.managerPrefix + self.uid, 'local', luids[0]]) self.runUntil('Abraham nickname changed', check=check, until=lambda: self.view.haveNoData(0) and view.haveNoData(0)) view.read(0, 1) self.view.read(0, 1) self.runUntil('Abraham nickname read', check=check, until=lambda: self.view.haveData(0) and view.haveData(0)) self.assertEqual(u'Abraham', view.contacts[0]['structured-name']['given']) self.assertEqual(u'Abraham', self.view.contacts[0]['structured-name']['given']) # Unmatched contact gets matched, but stays out of view. item = os.path.join(self.contacts, 'contact%d.vcf' % 0) output = codecs.open(item, "w", "utf-8") output.write(u'''BEGIN:VCARD VERSION:3.0 VERSION:3.0 FN:Charly 'Chárleß' Xing N:Xing;Charly NICKNAME:mamam END:VCARD''') output.close() logging.log('change nick of Charly') check1 = view.setCheck(lambda: self.assertLess(0, len(view.contacts))) out, err, returncode = self.runCmdline(['--update', item, '@' + self.managerPrefix + self.uid, 'local', luids[2]]) self.runUntil('Charly nickname changed', check=check, until=lambda: self.view.haveNoData(2)) check1 = view.setCheck(lambda: self.assertEqual(1, len(view.contacts))) self.assertEqual(1, len(view.contacts)) self.assertTrue(view.haveData(0)) self.assertEqual(u'Abraham', view.contacts[0]['structured-name']['given']) self.assertFalse(self.view.haveData(2)) self.view.read(2, 1) self.runUntil('Abraham nickname read, II', check=check, until=lambda: self.view.haveData(2) and \ view.haveData(0)) self.assertEqual(u'Abraham', view.contacts[0]['structured-name']['given']) self.assertEqual(u'Abraham', self.view.contacts[0]['structured-name']['given']) self.assertEqual(u'Benjamin', self.view.contacts[1]['structured-name']['given']) self.assertEqual(u'Charly', self.view.contacts[2]['structured-name']['given']) # Invert sort order. check1 = view.setCheck(None) self.manager.SetSortOrder("last/first") self.runUntil('reordering', check=check, until=lambda: self.view.haveNoData(0) and \ self.view.haveNoData(2) and \ view.haveNoData(0, 1)) view.read(0, 1) self.view.read(0, 3) self.runUntil('read reordered contacts', check=check, until=lambda: self.view.haveData(0, 3) and \ view.haveData(0, 1)) self.assertEqual(1, len(view.contacts)) self.assertEqual(u'Charly', view.contacts[0]['structured-name']['given']) self.assertEqual(3, len(self.view.contacts)) self.assertEqual(u'Charly', self.view.contacts[0]['structured-name']['given']) self.assertEqual(u'Benjamin', self.view.contacts[1]['structured-name']['given']) self.assertEqual(u'Abraham', self.view.contacts[2]['structured-name']['given']) # And back again. self.manager.SetSortOrder("first/last") self.runUntil('reordering, II', check=check, until=lambda: self.view.haveNoData(0) and \ self.view.haveNoData(2) and \ view.haveNoData(0, 1)) view.read(0, 1) self.view.read(0, 3) self.runUntil('read reordered contacts, II', check=check, until=lambda: self.view.haveData(0, 3) and \ view.haveData(0, 1)) self.assertEqual(1, len(view.contacts)) self.assertEqual(u'Abraham', view.contacts[0]['structured-name']['given']) self.assertEqual(3, len(self.view.contacts)) self.assertEqual(u'Abraham', self.view.contacts[0]['structured-name']['given']) self.assertEqual(u'Benjamin', self.view.contacts[1]['structured-name']['given']) self.assertEqual(u'Charly', self.view.contacts[2]['structured-name']['given']) # Matched contact gets unmatched. item = os.path.join(self.contacts, 'contact%d.vcf' % 0) output = codecs.open(item, "w", "utf-8") output.write(u'''BEGIN:VCARD VERSION:3.0 N:Zoo;Abrahan FN:Abrahan Zoo NICKNAME:None END:VCARD''') output.close() logging.log('change name of Abraham') out, err, returncode = self.runCmdline(['--update', item, '@' + self.managerPrefix + self.uid, 'local', luids[0]]) self.runUntil('Abraham -> Abrahan', check=check, until=lambda: self.view.haveNoData(0) and \ len(view.contacts) == 1 and \ view.haveNoData(0)) check1 = view.setCheck(lambda: self.assertEqual(1, len(view.contacts))) view.read(0, 1) self.view.read(0, 1) self.runUntil('Abrahan read', check=check, until=lambda: self.view.haveData(0) and view.haveData(0)) self.assertEqual(u'Benjamin', view.contacts[0]['structured-name']['given']) self.assertEqual(u'Abrahan', self.view.contacts[0]['structured-name']['given']) # Finally, remove everything. logging.log('remove contacts') check1 = view.setCheck(None) out, err, returncode = self.runCmdline(['--delete-items', '@' + self.managerPrefix + self.uid, 'local', '*']) self.runUntil('all contacts removed', check=check, until=lambda: len(self.view.contacts) == 0 and \ len(view.contacts) == 0) @timeout(60) @property("ENV", "LC_TYPE=de_DE.UTF-8 LC_ALL=de_DE.UTF-8 LANG=de_DE.UTF-8") def testFilterLiveLimitInverted(self): '''TestContacts.testFilterLiveLimitInverted - check that filtering works while modifying contacts, with a maximum number of results, and inverted sort order''' self.setUpView() self.manager.SetSortOrder("last/first") # Find Benjamin and Abraham, but limit results to first one. view = ContactsView(self.manager) view.search([['any-contains', 'am'], ['limit', '1']]) self.runUntil('am search results', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(0, len(view.contacts)) # Insert new contacts. # # The names are chosen so that sorting by first name and sorting by last name needs to # reverse the list. for i, contact in enumerate([u'''BEGIN:VCARD VERSION:3.0 N:Zoo;Abraham FN:Abraham Zoo NICKNAME:Ace END:VCARD''', u'''BEGIN:VCARD VERSION:3.0 N:Yeah;Benjamin FN:Benjamin Yeah END:VCARD''', # Chárleß has chárless as representation after folding the case. # This is different from lower case. # See http://www.boost.org/doc/libs/1_51_0/libs/locale/doc/html/glossary.html#term_case_folding u'''BEGIN:VCARD VERSION:3.0 FN:Charly 'Chárleß' Xing N:Xing;Charly END:VCARD''']): item = os.path.join(self.contacts, 'contact%d.vcf' % i) output = codecs.open(item, "w", "utf-8") output.write(contact) output.close() logging.log('inserting contacts') out, err, returncode = self.runCmdline(['--import', self.contacts, '@' + self.managerPrefix + self.uid, 'local']) # Relies on importing contacts sorted ascending by file name. luids = self.extractLUIDs(out) logging.printf('created contacts with luids: %s' % luids) # Run until the view has adapted. self.runUntil('view with one contact', check=lambda: self.assertEqual([], view.errors), until=lambda: len(view.contacts) > 0 and len(self.view.contacts) == 3) # Don't wait for more contacts here. They shouldn't come, and if # they do, we'll notice it below. self.assertEqual(1, len(view.contacts)) # Read contacts. logging.log('reading contacts') view.read(0, 1) self.view.read(0, 3) self.runUntil('contacts', check=lambda: (self.assertEqual([], view.errors), self.assertEqual([], self.view.errors)), until=lambda: view.haveData(0) and \ self.view.haveData(0, 3)) self.assertEqual(u'Benjamin', view.contacts[0]['structured-name']['given']) self.assertEqual(u'Charly', self.view.contacts[0]['structured-name']['given']) self.assertEqual(u'Benjamin', self.view.contacts[1]['structured-name']['given']) self.assertEqual(u'Abraham', self.view.contacts[2]['structured-name']['given']) @timeout(60) @property("ENV", "LC_TYPE=de_DE.UTF-8 LC_ALL=de_DE.UTF-8 LANG=de_DE.UTF-8") def testFilterLiveLimitRemove(self): '''TestContacts.testFilterLiveLimitRemove - check that filtering works while removing a contact, with a maximum number of results''' self.setUpView() self.manager.SetSortOrder("first/last") # Find Abraham and Benjamin, but limit results to first one. view = ContactsView(self.manager) view.search([['any-contains', 'am'], ['limit', '1']]) self.runUntil('am search results', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(0, len(view.contacts)) # Insert new contacts. # # The names are chosen so that sorting by first name and sorting by last name needs to # reverse the list. for i, contact in enumerate([u'''BEGIN:VCARD VERSION:3.0 N:Zoo;Abraham FN:Abraham Zoo NICKNAME:Ace END:VCARD''', u'''BEGIN:VCARD VERSION:3.0 N:Yeah;Benjamin END:VCARD''', # Chárleß has chárless as representation after folding the case. # This is different from lower case. # See http://www.boost.org/doc/libs/1_51_0/libs/locale/doc/html/glossary.html#term_case_folding u'''BEGIN:VCARD VERSION:3.0 FN:Charly 'Chárleß' Xing N:Xing;Charly END:VCARD''']): item = os.path.join(self.contacts, 'contact%d.vcf' % i) output = codecs.open(item, "w", "utf-8") output.write(contact) output.close() logging.log('inserting contacts') out, err, returncode = self.runCmdline(['--import', self.contacts, '@' + self.managerPrefix + self.uid, 'local']) # Relies on importing contacts sorted ascending by file name. luids = self.extractLUIDs(out) logging.printf('created contacts with luids: %s' % luids) # Run until the view has adapted. self.runUntil('view with one contact', check=lambda: self.assertEqual([], view.errors), until=lambda: len(view.contacts) > 0 and len(self.view.contacts) == 3) # Don't wait for more contacts here. They shouldn't come, and if # they do, we'll notice it below. self.assertEqual(1, len(view.contacts)) # Read contacts. logging.log('reading contacts') view.read(0, 1) self.view.read(0, 3) self.runUntil('contacts', check=lambda: (self.assertEqual([], view.errors), self.assertEqual([], self.view.errors)), until=lambda: view.haveData(0) and \ self.view.haveData(0, 3)) self.assertEqual(u'Abraham', view.contacts[0]['structured-name']['given']) self.assertEqual(u'Abraham', self.view.contacts[0]['structured-name']['given']) self.assertEqual(u'Benjamin', self.view.contacts[1]['structured-name']['given']) self.assertEqual(u'Charly', self.view.contacts[2]['structured-name']['given']) # Remove Abraham. Gets replaced by Benjamin in the view. self.runCmdline(['--delete-items', '@' + self.managerPrefix + self.uid, 'local', luids[0]]) self.runUntil('view with Benjamin', check=lambda: self.assertEqual([], view.errors), until=lambda: len(view.contacts) == 1 and view.haveNoData(0) and len(self.view.contacts) == 2) view.read(0, 1) self.runUntil('Benjamin', check=lambda: (self.assertEqual([], view.errors), self.assertEqual([], self.view.errors)), until=lambda: view.haveData(0) and \ self.view.haveData(0, 2)) self.assertEqual(u'Benjamin', view.contacts[0]['structured-name']['given']) self.assertEqual(u'Benjamin', self.view.contacts[0]['structured-name']['given']) self.assertEqual(u'Charly', self.view.contacts[1]['structured-name']['given']) # Remove Benjamin. self.runCmdline(['--delete-items', '@' + self.managerPrefix + self.uid, 'local', luids[1]]) self.runUntil('view without Benjamin', check=lambda: self.assertEqual([], view.errors), until=lambda: len(view.contacts) == 0 and len(self.view.contacts) == 1) self.assertEqual(u'Charly', self.view.contacts[0]['structured-name']['given']) @timeout(60) @property("snapshot", "simple-sort") @property("ENV", "LC_TYPE=de_DE.UTF-8 LC_ALL=de_DE.UTF-8 LANG=de_DE.UTF-8") def testContactWrite(self): '''TestContacts.testContactWrite - add, update and remove contact''' self.setUpView(peers=[], withSystemAddressBook=True) # Use unicode strings to make the assertEqual output nicer in case # of a mismatch. It's not necessary otherwise. # # This covers all fields which can be written by the folks EDS # backend. john = { 'full-name': 'John Doe', 'groups': ['Foo', 'Bar'], 'location': (30.12, -130.34), 'structured-name': { 'family': 'Doe', 'given': 'John', 'additional': 'D.', 'prefixes': 'Mr.', 'suffixes': 'Sr.' }, # 'nickname': 'Johnny', TODO: should be stored by folks, currently not supported 'birthday': (2011, 12, 1), # 'photo': 'file:///tmp/photo.png', TODO: test with real file, folks will store the content of it # 'gender', 'male', not exposed via D-Bus # 'im': ... # 'is-favorite': ... 'emails': [ ( 'john.doe@work', [ 'work' ] ), ( 'john@home', [ 'home' ] ), ], 'phones': [ ( '1234', ['fax']), ( '5678', ['cell', 'work'] ), ( 'foobar', dbus.Array(signature="s")), # empty string list ], 'addresses': [ ( { 'country': 'United States of America', 'extension': 'ext', 'locality': 'New York', 'po-box': 'box', 'region': 'NY', 'street': 'Lower East Side', }, ['work'] ), ( { 'locality': 'Boston', 'street': 'Main Street', }, dbus.Array(signature="s") # empty string list ), ], # 'web-services' 'roles': [ { 'organisation': 'ACME', 'title': 'president', 'role': 'decision maker', }, { 'organisation': 'BAR', 'title': 'CEO', 'role': 'spokesperson', }, ], 'notes': [ 'note\n\ntext', # TODO: notes -> note (EDS only supports one NOTE) ], 'urls': [ ('chat', ['x-video']), ('free/busy', ['x-free-busy']), ('http://john.doe.com', ['x-home-page']), ('web log', ['x-blog']), ], } with self.assertRaisesRegexp(dbus.DBusException, r'.*: only the system address book is writable'): self.manager.AddContact('no-such-address-book', john) # Add new contact. localID = self.manager.AddContact('', john) john['source'] = [('', unicode(localID))] self.runUntil('view with one contact', check=lambda: self.assertEqual([], self.view.errors), until=lambda: len(self.view.contacts) > 0) self.assertEqual(1, len(self.view.contacts)) self.view.read(0, 1) self.runUntil('contact data', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.haveData(0)) contact = self.view.contacts[0] john['id'] = contact.get('id', '') self.assertEqual(john, contact, sortLists=True) with self.assertRaisesRegexp(dbus.DBusException, r'''.*: contact with local ID 'no-such-local-id' not found in system address book'''): self.manager.ModifyContact('', 'no-such-local-id', john) # Update the contact. john = { 'source': [('', localID)], 'id': contact.get('id', ''), 'full-name': 'John A. Doe', 'groups': ['Foo', 'Bar'], 'location': (30.12, -130.34), 'structured-name': { 'family': 'Doe', 'given': 'John', 'additional': 'A.', 'prefixes': 'Mr.', 'suffixes': 'Sr.' }, # 'nickname': 'Johnny', TODO: should be stored by folks, currently not supported - https://bugzilla.gnome.org/show_bug.cgi?id=686695 'birthday': (2011, 12, 24), # 'photo': 'file:///tmp/photo.png', TODO: test with real file, folks will store the content of it # 'gender', 'male', not exposed via D-Bus # 'im': ... # 'is-favorite': ... 'emails': [ ( 'john2@home', [ 'home' ] ), ], 'phones': [ ( '1234', ['fax']), ( '56789', ['work'] ), ], 'addresses': [ ( { 'country': 'United States of America', 'extension': 'ext', 'locality': 'New York', 'po-box': 'box', 'region': 'NY', 'street': 'Upper East Side', }, ['work'] ), ( { 'country': 'United States of America', 'locality': 'Boston', 'street': 'Main Street', }, dbus.Array(signature="s") # empty string list ), ], # 'web-services' 'roles': [ { 'organisation': 'ACME', 'title': 'senior president', 'role': 'scapegoat', }, ], 'notes': [ 'note\n\ntext modified', # TODO: notes -> note (EDS only supports one NOTE) ], 'urls': [ ('http://john.A.doe.com', ['x-home-page']), ('web log 2', ['x-blog']), ], } self.manager.ModifyContact('', localID, john) self.runUntil('modified contact', check=lambda: self.assertEqual([], self.view.errors), until=lambda: len(self.view.contacts) == 1 and self.view.haveNoData(0)) # Keep asking for data for a while: we may get "modified" signals multiple times, # which invalidates data that we just read until the unified address book # is stable again. start = time.time() self.runUntil('modified contact data', check=lambda: (self.assertEqual([], self.view.errors), self.view.haveData(0) or self.view.read(0, 1) or True), until=lambda: self.view.haveData(0) and time.time() - start > 5) self.assertEqual(john, self.view.contacts[0], sortLists=True) # Search for modified telephone number. # Depends on having a valid country set via env variables. view = ContactsView(self.manager) view.search([['phone', '56789']]) self.runUntil('phone results', check=lambda: self.assertEqual([], view.errors), until=lambda: view.quiescentCount > 0) self.assertEqual(1, len(view.contacts)) # Remove all properties, except for a minimal name. # Entirely emtpy contacts make no sense. john = { 'source': [('', localID)], 'id': contact.get('id', ''), 'full-name': 'nobody', 'structured-name': { 'given': 'nobody', }, } self.manager.ModifyContact('', localID, john) self.runUntil('modified contact', check=lambda: self.assertEqual([], self.view.errors), until=lambda: len(self.view.contacts) == 1 and self.view.haveNoData(0)) start = time.time() self.runUntil('modified contact data', check=lambda: (self.assertEqual([], self.view.errors), self.view.haveData(0) or self.view.read(0, 1) or True), until=lambda: self.view.haveData(0) and time.time() - start > 5) self.assertEqual(john, self.view.contacts[0], sortLists=True) # No longer part of the telephone search view. self.runUntil('phone results', check=lambda: self.assertEqual([], view.errors), until=lambda: len(view.contacts) == 0) # Remove the contact. self.manager.RemoveContact('', localID) self.runUntil('empty view', check=lambda: self.assertEqual([], self.view.errors), until=lambda: len(self.view.contacts) == 0) # TODO: check that deleting or modifying a contact works directly # after starting the PIM manager. The problem is that FolksPersonaStore # might still be loading the contacts, in which case looking up the # contact would fail. @timeout(60) @property("ENV", "LC_TYPE=de_DE.UTF-8 LC_ALL=de_DE.UTF-8 LANG=de_DE.UTF-8 SYNCEVOLUTION_PIM_DELAY_FOLKS=5") def testFilterStartup(self): '''TestContacts.testFilterStartup - phone number lookup while folks still loads''' self.setUpView(search=None) # Override default sorting. self.assertEqual("last/first", self.manager.GetSortOrder()) self.manager.SetSortOrder("first/last") # Insert new contacts. # # The names are chosen so that sorting by first name and sorting by last name needs to # reverse the list. for i, contact in enumerate([u'''BEGIN:VCARD VERSION:3.0 N:Zoo;Abraham NICKNAME:Ace TEL:1234 TEL:56/78 TEL:+1-800-FOOBAR TEL:089/788899 EMAIL:az@example.com END:VCARD''', u'''BEGIN:VCARD VERSION:3.0 N:Yeah;Benjamin TEL:+49-89-788899 END:VCARD''', u'''BEGIN:VCARD VERSION:3.0 FN:Charly 'Chárleß' Xing N:Xing;Charly END:VCARD''']): item = os.path.join(self.contacts, 'contact%d.vcf' % i) output = codecs.open(item, "w", "utf-8") output.write(contact) output.close() logging.log('inserting contacts') out, err, returncode = self.runCmdline(['--import', self.contacts, '@' + self.managerPrefix + self.uid, 'local']) # Now start with a phone number search which must look # directly in EDS because the unified address book is not # ready (delayed via env variable). self.view.search([['phone', '089/788899']]) self.runUntil('phone results', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.quiescentCount > 0) self.assertEqual(2, len(self.view.contacts)) self.view.read(0, 2) self.runUntil('phone results', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.haveData(0, 2)) self.assertEqual(u'Abraham', self.view.contacts[0]['structured-name']['given']) self.assertEqual(u'Benjamin', self.view.contacts[1]['structured-name']['given']) # Wait for final results from folks. The same in this case. self.runUntil('phone results', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.quiescentCount > 1) self.assertEqual(2, len(self.view.contacts)) self.view.read(0, 2) self.runUntil('phone results', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.haveData(0, 2)) self.assertEqual(u'Abraham', self.view.contacts[0]['structured-name']['given']) self.assertEqual(u'Benjamin', self.view.contacts[1]['structured-name']['given']) # Nothing changed when folks became active. self.assertEqual([ ('added', 0, 2), ('quiescent',), ('quiescent',), ], self.view.events) def doFilterStartupRefine(self, simpleSearch=True): '''TestContacts.testFilterStartupRefine - phone number lookup while folks still loads, with folks finding more contacts (simple search in EDS) or the same contacts (intelligent search)''' self.setUpView(search=None) # Override default sorting. self.assertEqual("last/first", self.manager.GetSortOrder()) self.manager.SetSortOrder("first/last") # Insert new contacts. # # The names are chosen so that sorting by first name and sorting by last name needs to # reverse the list. for i, contact in enumerate([u'''BEGIN:VCARD VERSION:3.0 N:Zoo;Abraham NICKNAME:Ace TEL:1234 TEL:56/78 TEL:+1-800-FOOBAR TEL:089/788899 EMAIL:az@example.com END:VCARD''', # Extra space, breaks suffix match in EDS. # A more intelligent phone number search in EDS # will find this again. u'''BEGIN:VCARD VERSION:3.0 N:Yeah;Benjamin TEL:+49-89-7888 99 END:VCARD''', u'''BEGIN:VCARD VERSION:3.0 FN:Charly 'Chárleß' Xing N:Xing;Charly END:VCARD''']): item = os.path.join(self.contacts, 'contact%d.vcf' % i) output = codecs.open(item, "w", "utf-8") output.write(contact) output.close() logging.log('inserting contacts') out, err, returncode = self.runCmdline(['--import', self.contacts, '@' + self.managerPrefix + self.uid, 'local']) # Now start with a phone number search which must look # directly in EDS because the unified address book is not # ready (delayed via env variable). self.view.search([['phone', '089/788899']]) self.runUntil('phone results', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.quiescentCount > 0) if simpleSearch: self.assertEqual(1, len(self.view.contacts)) self.view.read(0, 1) else: self.assertEqual(2, len(self.view.contacts)) self.view.read(0, 2) self.runUntil('phone results', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.haveData(0, simpleSearch and 1 or 2)) self.assertEqual(u'Abraham', self.view.contacts[0]['structured-name']['given']) if not simpleSearch: self.assertEqual(u'Benjamin', self.view.contacts[1]['structured-name']['given']) # Wait for final results from folks. Also finds Benjamin. self.runUntil('phone results', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.quiescentCount > 1) self.assertEqual(2, len(self.view.contacts)) self.view.read(0, 2) self.runUntil('phone results', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.haveData(0, 2)) self.assertEqual(u'Abraham', self.view.contacts[0]['structured-name']['given']) self.assertEqual(u'Benjamin', self.view.contacts[1]['structured-name']['given']) if simpleSearch: # One contact added by folks. self.assertEqual([ ('added', 0, 1), ('quiescent',), ('added', 1, 1), ('quiescent',), ], self.view.events) else: # Two contacts added initially, not updated by folks. self.assertEqual([ ('added', 0, 2), ('quiescent',), ('quiescent',), ], self.view.events) @timeout(60) @property("ENV", "LC_TYPE=de_DE.UTF-8 LC_ALL=de_DE.UTF-8 LANG=de_DE.UTF-8 SYNCEVOLUTION_PIM_DELAY_FOLKS=5 SYNCEVOLUTION_PIM_EDS_SUBSTRING=1") def testFilterStartupRefine(self): '''TestContacts.testFilterStartupRefine - phone number lookup while folks still loads, with folks finding more contacts because we use substring search in EDS''' self.doFilterStartupRefine() @timeout(60) @property("ENV", "LC_TYPE=de_DE.UTF-8 LC_ALL=de_DE.UTF-8 LANG=de_DE.UTF-8 SYNCEVOLUTION_PIM_DELAY_FOLKS=5") def testFilterStartupRefineSmart(self): '''TestContacts.testFilterStartupRefine - phone number lookup while folks still loads, with folks finding the same contacts because we use smart search in EDS''' # This test depends on libphonenumber support in EDS! self.doFilterStartupRefine(simpleSearch=False) @timeout(60) @property("ENV", "LC_TYPE=de_DE.UTF-8 LC_ALL=de_DE.UTF-8 LANG=de_DE.UTF-8 SYNCEVOLUTION_PIM_DELAY_FOLKS=5") def testFilterStartupMany(self): '''TestContacts.testFilterStartupMany - phone number lookup in many address books''' self.setUpView(search=None, peers=['0', '1', '2']) # Override default sorting. self.assertEqual("last/first", self.manager.GetSortOrder()) self.manager.SetSortOrder("first/last") # Insert new contacts. # # The names are chosen so that sorting by first name and sorting by last name needs to # reverse the list. for i, contact in enumerate([u'''BEGIN:VCARD VERSION:3.0 N:Zoo;Abraham NICKNAME:Ace TEL:1234 TEL:56/78 TEL:+1-800-FOOBAR TEL:089/788899 EMAIL:az@example.com END:VCARD''', u'''BEGIN:VCARD VERSION:3.0 N:Yeah;Benjamin TEL:+49-89-788899 END:VCARD''', u'''BEGIN:VCARD VERSION:3.0 FN:Charly 'Chárleß' Xing N:Xing;Charly END:VCARD''']): item = os.path.join(self.contacts, 'contact.vcf') output = codecs.open(item, "w", "utf-8") output.write(contact) output.close() logging.printf('inserting contact %d', i) uid = self.uidPrefix + str(i) out, err, returncode = self.runCmdline(['--import', self.contacts, '@' + self.managerPrefix + uid, 'local']) # Now start with a phone number search which must look # directly in EDS because the unified address book is not # ready (delayed via env variable). self.view.search([['phone', '089/788899']]) self.runUntil('phone results', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.quiescentCount > 0) self.assertEqual(2, len(self.view.contacts)) self.view.read(0, 2) self.runUntil('phone results', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.haveData(0, 2)) self.assertEqual(u'Abraham', self.view.contacts[0]['structured-name']['given']) self.assertEqual(u'Benjamin', self.view.contacts[1]['structured-name']['given']) # Wait for final results from folks. The same in this case. self.runUntil('phone results', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.quiescentCount > 1) self.assertEqual(2, len(self.view.contacts)) self.view.read(0, 2) self.runUntil('phone results', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.haveData(0, 2)) self.assertEqual(u'Abraham', self.view.contacts[0]['structured-name']['given']) self.assertEqual(u'Benjamin', self.view.contacts[1]['structured-name']['given']) # Nothing changed when folks became active. self.assertEqual([ ('added', 0, 2), ('quiescent',), ('quiescent',), ], self.view.events) @timeout(60) def testDeadAgent(self): '''TestContacts.testDeadAgent - an error from the agent kills the view''' self.setUpView(search=None, peers=[], withSystemAddressBook=True) # Insert new contact. for i, contact in enumerate([u'''BEGIN:VCARD VERSION:3.0 FN:John Doe N:Doe;John END:VCARD''', ]): item = os.path.join(self.contacts, 'contact.vcf') output = codecs.open(item, "w", "utf-8") output.write(contact) output.close() logging.printf('inserting contact %d', i) out, err, returncode = self.runCmdline(['--import', self.contacts, 'backend=evolution-contacts']) # Plug into processEvent() method so that it throws an error # when receiving the ContactsAdded method call. The same cannot be # done for Quiescent, because that call is optional and thus allowed # to fail. original = self.view.processEvent def intercept(message, event): if event[0] == 'quiescent': # Sometimes the aggregator was seen as idle before # it loaded the item above, leading to one # additional 'quiescent' before 'added'. Not sure # why. Anyway, that belongs into a different test, # so ignore 'quiescent' here. return # Record it. original(message, event) # Raise error? if event[0] == 'added': logging.printf('raising "fake error" for event %s' % event) raise Exception('fake error') self.view.processEvent = intercept self.view.search([]) self.runUntil('phone results', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.events) self.assertEqual([('added', 0, 1)], self.view.events) # Expect an error, view should have been closed already. with self.assertRaisesRegexp(dbus.DBusException, "org.freedesktop.DBus.Error.UnknownMethod: .*"): self.view.close() @timeout(60) def testQuiescentOptional(self): '''TestContacts.testQuiescentOptional - the Quiescent() method is allowed to fail''' self.setUpView(search=None, peers=[], withSystemAddressBook=True) # Plug into "Quiescent" method so that it throws an error. original = self.view.processEvent def intercept(message, event): original(message, event) if event[0] == 'quiescent': raise Exception('fake error') self.view.processEvent = intercept self.view.search([]) self.runUntil('phone results', check=lambda: self.assertEqual([], self.view.errors), until=lambda: self.view.quiescentCount > 0) self.assertEqual([('quiescent',)], self.view.events) self.view.close() class TestSlowSync(TestPIMUtil, unittest.TestCase): """Test PIM Manager Sync""" def run(self, result): TestPIMUtil.run(self, result, serverArgs=['-d', '10']) @timeout(usingValgrind() and 600 or 60) @property("ENV", "SYNCEVOLUTION_SYNC_DELAY=20") def testSlowSync(self): '''TestSlowSync.testSlowSync - run a sync which takes longer than the 10 second inactivity duration''' # dummy peer directory contacts = os.path.abspath(os.path.join(xdg_root, 'contacts')) os.makedirs(contacts) # add foo peers = {} uid = self.uidPrefix + 'foo' peers[uid] = {'protocol': 'files', 'address': contacts} self.manager.SetPeer(uid, peers[uid]) self.manager.SyncPeer(uid) if __name__ == '__main__': error = '' paths = [ (os.path.dirname(x), os.path.basename(x)) for x in \ [ os.environ.get(y, '') for y in ['XDG_CONFIG_HOME', 'XDG_DATA_HOME', 'XDG_CACHE_HOME'] ] ] xdg_root = paths[0][0] if not xdg_root or xdg_root != paths[1][0] or xdg_root != paths[2][0] or \ paths[0][1] != 'config' or paths[1][1] != 'data' or paths[2][1] != 'cache': # Don't allow user of the script to erase his normal EDS data and enforce # common basedir with well-known names for each xdg home. error = error + 'testpim.py must be started in a D-Bus session with XDG_CONFIG_HOME, XDG_DATA_HOME, XDG_CACHE_HOME set to temporary directories /config, /data, /cache because it will modify system EDS databases there and relies on a known, flat layout underneath a common directory.\n' else: # Tell test-dbus.py about the temporary directory that we expect # to use. It'll wipe it clean for us because we run with own_xdg=true. testdbus.xdg_root = xdg_root if os.environ.get('LANG', '') != 'de_DE.utf-8': error = error + 'EDS daemon must use the same LANG=de_DE.utf-8 as tests to get phone number normalization right.\n' if error: sys.exit(error) unittest.main() syncevolution_1.4/src/dbus/server/pim/view.cpp000066400000000000000000000051471230021373600216620ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "view.h" #include SE_BEGIN_CXX void View::start() { SE_LOG_DEBUG(NULL, "%s: start() %s", getName(), m_started ? "already done" : "doing it now"); if (!m_started) { m_started = true; doStart(); } } bool View::isRunning() const { return m_started; } void IndividualView::findContact(const std::string &id, int hint, int &index, FolksIndividualCXX &individual) { int i; int count = size(); // Start searching at the hint. for (i = hint; i < count; i++) { individual = getContact(i)->m_individual; if (id == folks_individual_get_id(individual.get())) { index = i; return; } } // Finish search before the hint. for (i = 0; i < hint; i++) { individual = getContact(i)->m_individual; if (id == folks_individual_get_id(individual.get())) { index = i; return; } } // Nothing found. index = -1; individual.reset(); } void IndividualView::readContacts(const std::vector &ids, Contacts &contacts) { contacts.clear(); contacts.reserve(ids.size()); // The search is optimized for the case where many consecutive // contacts in increasing order are requested. For that case, a // linear search is needed for the first contact and then the // following ones are found in constant time. // // Randomly requesting contacts performs poorly, due to the O(n) // lookup complexity. int hint = 0; BOOST_FOREACH (const std::string &id, ids) { int index; FolksIndividualCXX individual; findContact(id, hint, index, individual); contacts.push_back(std::make_pair(index, individual)); if (index >= 0) { hint = index; } } } SE_END_CXX syncevolution_1.4/src/dbus/server/pim/view.h000066400000000000000000000104271230021373600213240ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ /** * Base classes for reading data, in particular individuals. */ #ifndef INCL_SYNCEVO_DBUS_SERVER_PIM_VIEW #define INCL_SYNCEVO_DBUS_SERVER_PIM_VIEW #include "folks.h" #include SE_BEGIN_CXX /** * Accesses data once started. Derived classes report that data * differently. */ class View { Bool m_started; std::string m_name; public: typedef boost::signals2::signal QuiescenceSignal_t; /** * Triggered each time the view reaches a quiescence state, meaning * that its current content is stable, at least for now. */ QuiescenceSignal_t m_quiescenceSignal; /** * False when more changes are known to come. */ virtual bool isQuiescent() const = 0; /** * A name for the view, for debugging. */ const char *getName() const { return m_name.c_str(); } void setName(const std::string &name) { m_name = name; } /** * Start filling the view. Gives the user a chance to connect * to the signals first. May be called multiple times. */ void start(); /** * start() was called. */ bool isRunning() const; protected: /** * Start filling the view. Will only be called once by start(). */ virtual void doStart() = 0; }; /** * Reports individuals once as they come in, unsorted. */ class StreamingView : public View { public: typedef boost::signals2::signal AddedSignal_t; /** * A new FolksIndividual was added. */ AddedSignal_t m_addedSignal; }; /** * A view on a sorted list of individuals. Entries are numbered from * #0 to #n - 1, where n is the number of entries. Change * notifications are based upon those numbers and will be triggered * immediately. */ class IndividualView : public View { public: typedef boost::signals2::signal ChangeSignal_t; /** * A new FolksIndividual was added at a specific index. This * increased the index of all individuals it was inserted in front * off by one. */ ChangeSignal_t m_addedSignal; /** * A FolksIndividual was removed at a specific index. This * increased the index of all individuals after it by one. */ ChangeSignal_t m_removedSignal; /** * A FolksIndividual was modified at a specific index, without * affecting its position in the view. If changing a FolksIndividual * affects its position, m_removedSignal followed by m_addedSignal * will be emitted. */ ChangeSignal_t m_modifiedSignal; /** * Replace filter with more specific one (refine = true) or redo * search without limitations. */ virtual void replaceFilter(const boost::shared_ptr &individualFilter, bool refine) { SE_THROW("adding a search not supported by this view"); } /** current number of entries */ virtual int size() const = 0; typedef std::vector< std::pair > Contacts; /** read a set of contacts - see org.01.pim.contacts.ViewControl.ReadContacts() */ virtual void readContacts(const std::vector &ids, Contacts &contacts); /** returns access to one individual or an empty pointer if outside of the current range */ virtual const IndividualData *getContact(int index) = 0; protected: void findContact(const std::string &id, int hint, int &index, FolksIndividualCXX &individual); }; SE_END_CXX #endif // INCL_SYNCEVO_DBUS_SERVER_PIM_VIEW syncevolution_1.4/src/dbus/server/presence-status.cpp000066400000000000000000000140061230021373600232420ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "presence-status.h" #include "server.h" SE_BEGIN_CXX void PresenceStatus::init(){ //initialize the configured peer list if (!m_initiated) { SyncConfig::ConfigList list = SyncConfig::getConfigs(); BOOST_FOREACH(const SyncConfig::ConfigList::value_type &server, list) { SyncConfig config (server.first); vector urls = config.getSyncURL(); m_peers[server.first].clear(); BOOST_FOREACH (const string &url, urls) { // take current status into account, // PresenceStatus::checkPresence() calls init() and // expects up-to-date information PeerStatus status; if ((boost::starts_with(url, "obex-bt") && m_btPresence) || (boost::starts_with (url, "http") && m_httpPresence) || boost::starts_with (url, "local")) { status = MIGHTWORK; } else { status = NOTRANSPORT; } m_peers[server.first].push_back(make_pair(url, status)); } } m_initiated = true; } } /* Implement PresenceStatus::checkPresence*/ void PresenceStatus::checkPresence (const string &peer, string& status, std::vector &transport) { if (!m_initiated) { //might triggered by updateConfigPeers init(); } string peerName = SyncConfig::normalizeConfigString (peer); vector< pair > mytransports = m_peers[peerName]; if (mytransports.empty()) { //wrong config name? status = status2string(NOTRANSPORT); transport.clear(); return; } PeerStatus mystatus = MIGHTWORK; transport.clear(); //only if all transports are unavailable can we declare the peer //status as unavailable BOOST_FOREACH (PeerStatusPair &mytransport, mytransports) { if (mytransport.second == MIGHTWORK) { transport.push_back (mytransport.first); } } if (transport.empty()) { mystatus = NOTRANSPORT; } status = status2string(mystatus); } void PresenceStatus::updateConfigPeers (const std::string &peer, const ReadOperations::Config_t &config) { ReadOperations::Config_t::const_iterator iter = config.find (""); if (iter != config.end()) { //As a simple approach, just reinitialize the whole STATUSMAP //it will cause later updatePresenceStatus resend all signals //and a reload in checkPresence m_initiated = false; } } void PresenceStatus::updatePresenceStatus (bool newStatus, PresenceStatus::TransportType type) { if (type == PresenceStatus::HTTP_TRANSPORT) { updatePresenceStatus (newStatus, m_btPresence); } else if (type == PresenceStatus::BT_TRANSPORT) { updatePresenceStatus (m_httpPresence, newStatus); }else { } } void PresenceStatus::updatePresenceStatus (bool httpPresence, bool btPresence) { bool httpChanged = (m_httpPresence != httpPresence); bool btChanged = (m_btPresence != btPresence); if (m_initiated && !httpChanged && !btChanged) { //nothing changed return; } //initialize the configured peer list using old presence status bool initiated = m_initiated; if (!m_initiated) { init(); } // switch to new status m_httpPresence = httpPresence; m_btPresence = btPresence; if (httpChanged) { m_httpPresenceSignal(httpPresence); } if (btChanged) { m_btPresenceSignal(btPresence); } //iterate all configured peers and fire singals BOOST_FOREACH (StatusPair &peer, m_peers) { //iterate all possible transports //TODO One peer might got more than one signals, avoid this std::vector > &transports = peer.second; BOOST_FOREACH (PeerStatusPair &entry, transports) { string url = entry.first; if (boost::starts_with (url, "http") && (httpChanged || !initiated)) { entry.second = m_httpPresence ? MIGHTWORK: NOTRANSPORT; m_server.emitPresence (peer.first, status2string (entry.second), entry.first); SE_LOG_DEBUG(NULL, "http presence signal %s,%s,%s", peer.first.c_str(), status2string (entry.second).c_str(), entry.first.c_str()); } else if (boost::starts_with (url, "obex-bt") && (btChanged || !initiated)) { entry.second = m_btPresence ? MIGHTWORK: NOTRANSPORT; m_server.emitPresence (peer.first, status2string (entry.second), entry.first); SE_LOG_DEBUG(NULL, "bluetooth presence signal %s,%s,%s", peer.first.c_str(), status2string (entry.second).c_str(), entry.first.c_str()); } else if (boost::starts_with (url, "local") && !initiated) { m_server.emitPresence (peer.first, status2string (MIGHTWORK), entry.first); SE_LOG_DEBUG(NULL, "local presence signal %s,%s,%s", peer.first.c_str(), status2string (MIGHTWORK).c_str(), entry.first.c_str()); } } } } SE_END_CXX syncevolution_1.4/src/dbus/server/presence-status.h000066400000000000000000000061201230021373600227050ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef PRESENCE_STATUS_H #define PRESENCE_STATUS_H #include "read-operations.h" #include SE_BEGIN_CXX class Server; class PresenceStatus { bool m_httpPresence; bool m_btPresence; bool m_initiated; Server &m_server; enum PeerStatus { /* The transport is not available (local problem) */ NOTRANSPORT, /* The peer is not contactable (remote problem) */ UNREACHABLE, /* Not for sure whether the peer is presence but likely*/ MIGHTWORK, INVALID }; typedef std::map > > StatusMap; typedef std::pair > > StatusPair; typedef std::pair PeerStatusPair; StatusMap m_peers; static std::string status2string (PeerStatus status) { switch (status) { case NOTRANSPORT: return "no transport"; break; case UNREACHABLE: return "not present"; break; case MIGHTWORK: return ""; break; case INVALID: return "invalid transport status"; } // not reached, keep compiler happy return ""; } public: PresenceStatus (Server &server) :m_httpPresence (false), m_btPresence (false), m_initiated (false), m_server (server) { } enum TransportType{ HTTP_TRANSPORT, BT_TRANSPORT, INVALID_TRANSPORT }; void init(); /* Implement Server::checkPresence*/ void checkPresence (const std::string &peer, std::string& status, std::vector &transport); void updateConfigPeers (const std::string &peer, const ReadOperations::Config_t &config); void updatePresenceStatus (bool newStatus, TransportType type); bool getHttpPresence() { return m_httpPresence; } bool getBtPresence() { return m_btPresence; } /** emitted on changes of the current value */ typedef boost::signals2::signal PresenceSignal_t; PresenceSignal_t m_httpPresenceSignal; PresenceSignal_t m_btPresenceSignal; private: void updatePresenceStatus (bool httpPresence, bool btPresence); }; SE_END_CXX #endif // PRESENCE_STATUS_H syncevolution_1.4/src/dbus/server/progress-data.cpp000066400000000000000000000156271230021373600227020ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "progress-data.h" #include #include SE_BEGIN_CXX const float ProgressData::PRO_SYNC_PREPARE_RATIO = 0.2; const float ProgressData::DATA_PREPARE_RATIO = 0.10; const float ProgressData::ONEITEM_SEND_RATIO = 0.05; const float ProgressData::ONEITEM_RECEIVE_RATIO = 0.05; const float ProgressData::CONN_SETUP_RATIO = 0.5; ProgressData::ProgressData() : m_progress(0), m_step(PRO_SYNC_INVALID), m_sendCounts(0), m_internalMode(INTERNAL_NONE) { /** * init default units of each step */ float totalUnits = 0.0; for(int i = 0; i < PRO_SYNC_TOTAL; i++) { float units = getDefaultUnits((ProgressStep)i); m_syncUnits[i] = units; totalUnits += units; } m_propOfUnit = 1.0 / totalUnits; /** * init default sync step proportions. each step stores proportions of * its previous steps and itself. */ m_syncProp[0] = 0; for(int i = 1; i < PRO_SYNC_TOTAL - 1; i++) { m_syncProp[i] = m_syncProp[i - 1] + m_syncUnits[i] / totalUnits; } m_syncProp[PRO_SYNC_TOTAL - 1] = 1.0; } void ProgressData::setProgress(int progress) { m_progress = progress > 100 ? 100 : progress < 0 ? 0 : progress; } void ProgressData::setStep(ProgressStep step) { if(m_step != step) { /** if state is changed, progress is set as the end of current step*/ setProgress(100.0 * m_syncProp[(int)m_step]); m_step = step; ///< change to new state m_sendCounts = 0; ///< clear send/receive counts m_source = ""; ///< clear source } } void ProgressData::sendStart() { checkInternalMode(); m_sendCounts++; /* self adapts. If a new send and not default, we need re-calculate proportions */ if(m_sendCounts > MSG_SEND_RECEIVE_TIMES) { m_syncUnits[(int)m_step] += 1; recalc(); } /** * If in the send operation of PRO_SYNC_UNINIT, it often takes extra time * to send message due to items handling */ if(m_step == PRO_SYNC_UNINIT && m_syncUnits[(int)m_step] != MSG_SEND_RECEIVE_TIMES) { updateProg(DATA_PREPARE_RATIO); } } void ProgressData::receiveEnd() { /** * often receiveEnd is the last operation of each step by default. * If more send/receive, then we need expand proportion of current * step and re-calc them */ updateProg(m_syncUnits[(int)m_step]); } void ProgressData::addSyncMode(SyncMode mode) { switch(mode) { case SYNC_TWO_WAY: case SYNC_SLOW: m_internalMode |= INTERNAL_TWO_WAY; break; case SYNC_ONE_WAY_FROM_CLIENT: case SYNC_REFRESH_FROM_CLIENT: m_internalMode |= INTERNAL_ONLY_TO_CLIENT; break; case SYNC_ONE_WAY_FROM_SERVER: case SYNC_REFRESH_FROM_SERVER: m_internalMode |= INTERNAL_ONLY_TO_SERVER; break; default: ; }; } void ProgressData::itemPrepare() { checkInternalMode(); /** * only the first PEV_ITEMPREPARE event takes some time * due to data access, other events don't according to * profiling data */ if(m_source.empty()) { m_source = "source"; ///< use this to check whether itemPrepare occurs updateProg(DATA_PREPARE_RATIO); } } void ProgressData::itemReceive(const std::string &source, int count, int total) { /** * source is used to check whether a new source is received * If the first source, we compare its total number and default number * then re-calc sync units */ if(m_source.empty()) { m_source = source; if(total != 0) { m_syncUnits[PRO_SYNC_UNINIT] += ONEITEM_RECEIVE_RATIO * (total - DEFAULT_ITEMS); recalc(); } /** if another new source, add them into sync units */ } else if(m_source != source){ m_source = source; if(total != 0) { m_syncUnits[PRO_SYNC_UNINIT] += ONEITEM_RECEIVE_RATIO * total; recalc(); } } updateProg(ONEITEM_RECEIVE_RATIO); } void ProgressData::updateProg(float ratio) { setProgress(m_progress + m_propOfUnit * 100 * ratio); m_syncUnits[(int)m_step] -= ratio; } /** dynamically adapt the proportion of each step by their current units */ void ProgressData::recalc() { float units = getRemainTotalUnits(); if(std::abs(units) < std::numeric_limits::epsilon()) { m_propOfUnit = 0.0; } else { m_propOfUnit = ( 100.0 - m_progress ) / (100.0 * units); } if(m_step != PRO_SYNC_TOTAL -1 ) { m_syncProp[(int)m_step] = m_progress / 100.0 + m_syncUnits[(int)m_step] * m_propOfUnit; for(int i = ((int)m_step) + 1; i < PRO_SYNC_TOTAL - 1; i++) { m_syncProp[i] = m_syncProp[i - 1] + m_syncUnits[i] * m_propOfUnit; } } } void ProgressData::checkInternalMode() { if(!m_internalMode) { return; } else if(m_internalMode & INTERNAL_TWO_WAY) { // don't adjust } else if(m_internalMode & INTERNAL_ONLY_TO_CLIENT) { // only to client, remove units of prepare and send m_syncUnits[PRO_SYNC_DATA] -= (ONEITEM_RECEIVE_RATIO * DEFAULT_ITEMS + DATA_PREPARE_RATIO); recalc(); } else if(m_internalMode & INTERNAL_ONLY_TO_SERVER) { // only to server, remove units of receive m_syncUnits[PRO_SYNC_UNINIT] -= (ONEITEM_RECEIVE_RATIO * DEFAULT_ITEMS + DATA_PREPARE_RATIO); recalc(); } m_internalMode = INTERNAL_NONE; } float ProgressData::getRemainTotalUnits() { float total = 0.0; for(int i = (int)m_step; i < PRO_SYNC_TOTAL; i++) { total += m_syncUnits[i]; } return total; } float ProgressData::getDefaultUnits(ProgressStep step) { switch(step) { case PRO_SYNC_PREPARE: return PRO_SYNC_PREPARE_RATIO; case PRO_SYNC_INIT: return CONN_SETUP_RATIO + MSG_SEND_RECEIVE_TIMES; case PRO_SYNC_DATA: return ONEITEM_SEND_RATIO * DEFAULT_ITEMS + DATA_PREPARE_RATIO + MSG_SEND_RECEIVE_TIMES; case PRO_SYNC_UNINIT: return ONEITEM_RECEIVE_RATIO * DEFAULT_ITEMS + DATA_PREPARE_RATIO + MSG_SEND_RECEIVE_TIMES; default: return 0; }; } SE_END_CXX syncevolution_1.4/src/dbus/server/progress-data.h000066400000000000000000000132551230021373600223420ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef PROGRESS_DATA_H #define PROGRESS_DATA_H #include #include SE_BEGIN_CXX /** * Hold progress info and try to estimate current progress */ class ProgressData { public: /** * big steps, each step contains many operations, such as * data prepare, message send/receive. * The partitions of these steps are based on profiling data * for many usage scenarios and different sync modes */ enum ProgressStep { /** an invalid step */ PRO_SYNC_INVALID = 0, /** * sync prepare step: do some preparations and checkings, * such as source preparation, engine preparation */ PRO_SYNC_PREPARE, /** * session init step: transport connection set up, * start a session, authentication and dev info generation * normally it needs one time syncML messages send-receive. * Sometimes it may need messages send/receive many times to * handle authentication */ PRO_SYNC_INIT, /** * prepare sync data and send data, also receive data from server. * Also may need send/receive messages more than one time if too * much data. * assume 5 items to be sent by default */ PRO_SYNC_DATA, /** * item receive handling, send client's status to server and * close the session * assume 5 items to be received by default */ PRO_SYNC_UNINIT, /** number of sync steps */ PRO_SYNC_TOTAL }; /** * internal mode to represent whether it is possible that data is sent to * server or received from server. This could help remove some incorrect * hypothesis. For example, if only to client, then it is no data item * sending to server. */ enum InternalMode { INTERNAL_NONE = 0, INTERNAL_ONLY_TO_CLIENT = 1, INTERNAL_ONLY_TO_SERVER = 1 << 1, INTERNAL_TWO_WAY = 1 + (1 << 1) }; /** * treat a one-time send-receive without data items * as an internal standard unit. * below are ratios of other operations compared to one * standard unit. * These ratios might be dynamicall changed in the future. */ /** PRO_SYNC_PREPARE step ratio to standard unit */ static const float PRO_SYNC_PREPARE_RATIO; /** data prepare for data items to standard unit. All are combined by profiling data */ static const float DATA_PREPARE_RATIO; /** one data item send's ratio to standard unit */ static const float ONEITEM_SEND_RATIO; /** one data item receive&parse's ratio to standard unit */ static const float ONEITEM_RECEIVE_RATIO; /** connection setup to standard unit */ static const float CONN_SETUP_RATIO; /** assume the number of data items */ static const int DEFAULT_ITEMS = 5; /** default times of message send/receive in each step */ static const int MSG_SEND_RECEIVE_TIMES = 1; ProgressData(); int32_t getProgress() const { return m_progress; } /** set percentage, including clipping to the 0-100 range */ void setProgress(int32_t progress); /** * change the big step */ void setStep(ProgressStep step); /** * calc progress when a message is sent */ void sendStart(); /** * calc progress when a message is received from server */ void receiveEnd(); /** * re-calc progress proportions according to syncmode hint * typically, if only refresh-from-client, then * client won't receive data items. */ void addSyncMode(SyncMode mode); /** * calc progress when data prepare for sending */ void itemPrepare(); /** * calc progress when a data item is received */ void itemReceive(const std::string &source, int count, int total); private: /** update progress data */ void updateProg(float ratio); /** dynamically adapt the proportion of each step by their current units */ void recalc(); /** internally check sync mode */ void checkInternalMode(); /** get total units of current step and remaining steps */ float getRemainTotalUnits(); /** get default units of given step */ static float getDefaultUnits(ProgressStep step); private: /** progress percentage */ int32_t m_progress; /** current big step */ ProgressStep m_step; /** count of message send/receive in current step. Cleared in the start of a new step */ int m_sendCounts; /** internal sync mode combinations */ int m_internalMode; /** proportions when each step is end */ float m_syncProp[PRO_SYNC_TOTAL]; /** remaining units of each step according to current step */ float m_syncUnits[PRO_SYNC_TOTAL]; /** proportion of a standard unit, may changes dynamically */ float m_propOfUnit; /** current sync source */ std::string m_source; }; SE_END_CXX #endif // PROGRESS_DATA_H syncevolution_1.4/src/dbus/server/read-operations.cpp000066400000000000000000000335401230021373600232150ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "read-operations.h" #include "dbus-user-interface.h" #include "server.h" #include "dbus-sync.h" #include SE_BEGIN_CXX ReadOperations::ReadOperations(const std::string &config_name, Server &server) : m_configName(config_name), m_server(server) {} void ReadOperations::getConfigs(bool getTemplates, std::vector &configNames) { if (getTemplates) { SyncConfig::DeviceList devices; // get device list from dbus server, currently only bluetooth devices m_server.getDeviceList(devices); // also include server templates in search devices.push_back(SyncConfig::DeviceDescription("", "", SyncConfig::MATCH_FOR_CLIENT_MODE)); //clear existing templates in dbus server m_server.clearPeerTempls(); SyncConfig::TemplateList list = SyncConfig::getPeerTemplates(devices); std::map numbers; BOOST_FOREACH(const boost::shared_ptr peer, list) { //if it is not a template for device if(peer->m_deviceName.empty()) { configNames.push_back(peer->m_templateId); } else { string templName = "Bluetooth_"; templName += peer->m_deviceId; templName += "_"; std::map::iterator it = numbers.find(peer->m_deviceId); if(it == numbers.end()) { numbers.insert(std::make_pair(peer->m_deviceId, 1)); templName += "1"; } else { it->second++; stringstream seq; seq << it->second; templName += seq.str(); } configNames.push_back(templName); m_server.addPeerTempl(templName, peer); } } } else { SyncConfig::ConfigList list = SyncConfig::getConfigs(); BOOST_FOREACH(const SyncConfig::ConfigList::value_type &server, list) { configNames.push_back(server.first); } } } boost::shared_ptr ReadOperations::getLocalConfig(const string &configName, bool mustExist) { string peer, context; SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(configName), peer, context); boost::shared_ptr syncConfig(new SyncConfig(configName)); /** if config was not set temporarily */ if (!setFilters(*syncConfig)) { // the default configuration can always be opened for reading, // everything else must exist if ((context != "default" || peer != "") && mustExist && !syncConfig->exists()) { SE_THROW_EXCEPTION(NoSuchConfig, "No configuration '" + configName + "' found"); } } return syncConfig; } void ReadOperations::getConfig(bool getTemplate, Config_t &config) { getNamedConfig(m_configName, getTemplate, config); } void ReadOperations::getNamedConfig(const std::string &configName, bool getTemplate, Config_t &config) { map localConfigs; boost::shared_ptr dbusConfig; SyncConfig *syncConfig; string syncURL; /** get server template */ if(getTemplate) { string peer, context; boost::shared_ptr peerTemplate = m_server.getPeerTempl(configName); if(peerTemplate) { SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(peerTemplate->m_templateId), peer, context); dbusConfig = SyncConfig::createPeerTemplate(peerTemplate->m_path); // if we have cached template information, add match information for it localConfigs.insert(pair("description", peerTemplate->m_description)); stringstream score; score << peerTemplate->m_rank; localConfigs.insert(pair("score", score.str())); // Actually this fingerprint is transferred by getConfigs, which refers to device name localConfigs.insert(pair("deviceName", peerTemplate->m_deviceName)); // This is the reliable device info obtained from the bluetooth // device id profile (DIP) or emtpy if DIP not supported. if (!peerTemplate->m_hardwareName.empty()) { localConfigs.insert(pair("hardwareName", peerTemplate->m_hardwareName)); } // This is the fingerprint of the template localConfigs.insert(pair("fingerPrint", peerTemplate->m_matchedModel)); // This is the template name presented to UI (or device class) if (!peerTemplate->m_templateName.empty()) { localConfigs.insert(pair("templateName", peerTemplate->m_templateName)); } // if the peer is client, then replace syncURL with bluetooth // MAC address syncURL = "obex-bt://"; syncURL += peerTemplate->m_deviceId; } else { SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(configName), peer, context); dbusConfig = SyncConfig::createPeerTemplate(peer); } if(!dbusConfig.get()) { SE_THROW_EXCEPTION(NoSuchConfig, "No template '" + configName + "' found"); } // use the shared properties from the right context as filter // so that the returned template preserves existing properties boost::shared_ptr shared = getLocalConfig(string("@") + context, false); ConfigProps props; shared->getProperties()->readProperties(props); dbusConfig->setConfigFilter(true, "", props); BOOST_FOREACH(std::string source, shared->getSyncSources()) { SyncSourceNodes nodes = shared->getSyncSourceNodes(source, ""); props.clear(); nodes.getProperties()->readProperties(props); // Special case "type" property: the value in the context // is not preserved. Every new peer must ensure that // its own value is compatible (= same backend) with // the other peers. props.erase("type"); dbusConfig->setConfigFilter(false, source, props); } syncConfig = dbusConfig.get(); } else { dbusConfig = getLocalConfig(configName); DBusUserInterface ui(dbusConfig->getKeyring()); //try to check password and read password from gnome keyring if possible PasswordConfigProperty::checkPasswords(ui, *dbusConfig, // Keep usernames as they are, but retrieve passwords for the D-Bus client. PasswordConfigProperty::CHECK_PASSWORD_ALL & ~PasswordConfigProperty::CHECK_PASSWORD_RESOLVE_USERNAME, dbusConfig->getSyncSources()); syncConfig = dbusConfig.get(); } /** get sync properties and their values */ ConfigPropertyRegistry &syncRegistry = SyncConfig::getRegistry(); BOOST_FOREACH(const ConfigProperty *prop, syncRegistry) { InitStateString value = prop->getProperty(*syncConfig->getProperties()); if (boost::iequals(prop->getMainName(), "syncURL") && !syncURL.empty() ) { localConfigs.insert(pair(prop->getMainName(), syncURL)); } else if (value.wasSet()) { localConfigs.insert(pair(prop->getMainName(), value)); } } // Set ConsumerReady for existing SyncEvolution < 1.2 configs // if not set explicitly, // because in older releases all existing configurations where // shown. SyncEvolution 1.2 is more strict and assumes that // ConsumerReady must be set explicitly. The sync-ui always has // set the flag for configs created or modified with it, but the // command line did not. Matches similar code in the Cmdline.cpp // migration code. // // This does not apply to templates which always have ConsumerReady // set explicitly (to on or off) or not set (same as off). if (!getTemplate && syncConfig->getConfigVersion(CONFIG_LEVEL_PEER, CONFIG_CUR_VERSION) == 0 /* SyncEvolution < 1.2 */) { localConfigs.insert(make_pair("ConsumerReady", "1")); } // insert 'configName' of the chosen config (configName is not normalized) localConfigs.insert(pair("configName", syncConfig->getConfigName())); config.insert(pair >("", localConfigs)); /* get configurations from sources */ list sources = syncConfig->getSyncSources(); BOOST_FOREACH(const string &name, sources) { localConfigs.clear(); SyncSourceNodes sourceNodes = syncConfig->getSyncSourceNodes(name); ConfigPropertyRegistry &sourceRegistry = SyncSourceConfig::getRegistry(); BOOST_FOREACH(const ConfigProperty *prop, sourceRegistry) { InitStateString value = prop->getProperty(*sourceNodes.getProperties()); if (value.wasSet()) { localConfigs.insert(pair(prop->getMainName(), value)); } } config.insert(pair >( "source/" + name, localConfigs)); } } void ReadOperations::getReports(uint32_t start, uint32_t count, Reports_t &reports) { SyncContext client(m_configName, false); std::vector dirs; client.getSessions(dirs); uint32_t index = 0; // newest report firstly for( int i = dirs.size() - 1; i >= 0; --i) { /** if start plus count is bigger than actual size, then return actual - size reports */ if(index >= start && index - start < count) { const string &dir = dirs[i]; std::map aReport; // insert a 'dir' as an ID for the current report aReport.insert(pair("dir", dir)); SyncReport report; // peerName is also extracted from the dir string peerName = client.readSessionInfo(dir,report); boost::shared_ptr config(new SyncConfig(m_configName)); string storedPeerName = config->getPeerName(); //if can't find peer name, use the peer name from the log dir if(!storedPeerName.empty()) { peerName = storedPeerName; } /** serialize report to ConfigProps and then copy them to reports */ IniHashConfigNode node("/dev/null","",true); node << report; ConfigProps props; node.readProperties(props); BOOST_FOREACH(const ConfigProps::value_type &entry, props) { aReport.insert(entry); } // a new key-value pair <"peer", [peer name]> is transferred aReport.insert(pair("peer", peerName)); reports.push_back(aReport); } index++; } } void ReadOperations::checkSource(const std::string &sourceName) { boost::shared_ptr config(new SyncConfig(m_configName)); setFilters(*config); list sourceNames = config->getSyncSources(); list::iterator it; for(it = sourceNames.begin(); it != sourceNames.end(); ++it) { if(*it == sourceName) { break; } } if(it == sourceNames.end()) { SE_THROW_EXCEPTION(NoSuchSource, "'" + m_configName + "' has no '" + sourceName + "' source"); } bool checked = false; try { // this can already throw exceptions when the config is invalid SyncSourceParams params(sourceName, config->getSyncSourceNodes(sourceName), config); auto_ptr syncSource(SyncSource::createSource(params, false, config.get())); if (syncSource.get()) { syncSource->open(); // success! checked = true; } } catch (...) { Exception::handle(); } if (!checked) { SE_THROW_EXCEPTION(SourceUnusable, "The source '" + sourceName + "' is not usable"); } } void ReadOperations::getDatabases(const string &sourceName, SourceDatabases_t &databases) { boost::shared_ptr config(new SyncConfig(m_configName)); setFilters(*config); SyncSourceParams params(sourceName, config->getSyncSourceNodes(sourceName), config); const SourceRegistry ®istry(SyncSource::getSourceRegistry()); BOOST_FOREACH(const RegisterSyncSource *sourceInfo, registry) { auto_ptr source(sourceInfo->m_create(params)); if (!source.get()) { continue; } else if (source->isInactive()) { SE_THROW_EXCEPTION(NoSuchSource, "'" + m_configName + "' backend of source '" + sourceName + "' is not supported"); } else { databases = source->getDatabases(); return; } } SE_THROW_EXCEPTION(NoSuchSource, "'" + m_configName + "' has no '" + sourceName + "' source"); } SE_END_CXX syncevolution_1.4/src/dbus/server/read-operations.h000066400000000000000000000077421230021373600226670ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef READ_OPERATIONS_H #define READ_OPERATIONS_H #include #include #include "gdbus-cxx-bridge.h" SE_BEGIN_CXX class Server; /** * Implements the read-only methods in a Session and the Server. * Only data is the server configuration name, everything else * is created and destroyed inside the methods. */ class ReadOperations { public: const std::string m_configName; Server &m_server; ReadOperations(const std::string &config_name, Server &server); /** the double dictionary used to represent configurations */ typedef std::map< std::string, StringMap > Config_t; /** the array of reports filled by getReports() */ typedef std::vector< StringMap > Reports_t; /** the array of databases used by getDatabases() */ typedef SyncSource::Database SourceDatabase; typedef SyncSource::Databases SourceDatabases_t; /** implementation of D-Bus GetConfigs() */ void getConfigs(bool getTemplates, std::vector &configNames); /** implementation of D-Bus GetConfig() for m_configName as server configuration */ void getConfig(bool getTemplate, Config_t &config); /** implementation of D-Bus GetNamedConfig() for configuration named in parameter */ void getNamedConfig(const std::string &configName, bool getTemplate, Config_t &config); /** implementation of D-Bus GetReports() for m_configName as server configuration */ void getReports(uint32_t start, uint32_t count, Reports_t &reports); /** Session.CheckSource() */ void checkSource(const string &sourceName); /** Session.GetDatabases() */ void getDatabases(const string &sourceName, SourceDatabases_t &databases); private: /** * This virtual function is used to let subclass set * filters to config. Only used internally. * Return true if filters exists and have been set. * Otherwise, nothing is set to config */ virtual bool setFilters(SyncConfig &config) { return false; } /** * utility method which constructs a SyncConfig which references a local configuration (never a template) * * In general, the config must exist, except in two cases: * - configName = @default (considered always available) * - mustExist = false (used when reading a templates for a context which might not exist yet) */ boost::shared_ptr getLocalConfig(const std::string &configName, bool mustExist = true); }; SE_END_CXX namespace GDBusCXX { using namespace SyncEvo; /** * dbus_traits for SourceDatabase. Put it here for * avoiding polluting gxx-dbus-bridge.h */ template<> struct dbus_traits : public dbus_struct_traits > > >{}; } #endif // READ_OPERATIONS_H syncevolution_1.4/src/dbus/server/resource.h000066400000000000000000000017641230021373600214200ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef RESOURCE_H #define RESOURCE_H SE_BEGIN_CXX /** * Anything that can be owned by a client, like a connection * or session. */ class Resource { public: virtual ~Resource() {} }; SE_END_CXX #endif // RESOURCE_H syncevolution_1.4/src/dbus/server/restart.h000066400000000000000000000042721230021373600212520ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef RESTART_H #define RESTART_H #include #include #include #include #include #include SE_BEGIN_CXX /** * Encapsulates startup environment from main() and can do execve() * with it later on. Assumes that argv[0] is the executable to run. */ class Restart { std::vector m_argv; std::vector m_env; void saveArray(std::vector &array, char **p) { while(*p) { array.push_back(*p); p++; } } const char **createArray(const std::vector &array) { const char **res = new const char *[(array.size() + 1)]; size_t i; for (i = 0; i < array.size(); i++) { res[i] = array[i].c_str(); } res[i] = NULL; return res; } public: Restart(char **argv, char **env) { saveArray(m_argv, argv); saveArray(m_env, env); } void restart() { boost::scoped_array argv(createArray(m_argv)); boost::scoped_array env(createArray(m_env)); LogRedirect::reset(); if (execve(argv[0], (char *const *)argv.get(), (char *const *)env.get())) { SE_THROW(StringPrintf("restarting syncevo-dbus-server failed: %s", strerror(errno))); } } }; SE_END_CXX #endif // RESTART_H syncevolution_1.4/src/dbus/server/server.am000066400000000000000000000174041230021373600212430ustar00rootroot00000000000000EXTRA_DIST += \ src/dbus/server/pim/README \ src/dbus/server/pim/pim-manager-api.txt \ src/dbus/server/pim/examples/search.py \ src/dbus/server/pim/examples/sync.py \ src/dbus/server/pim/testpim.py \ src/dbus/server/pim/test-dbus/db-active/config/syncevolution/pim-manager.ini \ src/dbus/server/pim/test-dbus/simple-sort/config/syncevolution/pim-manager.ini \ src/dbus/server/pim/test-dbus/first-last-sort/config/syncevolution/pim-manager.ini if COND_DBUS noinst_LTLIBRARIES += src/dbus/server/libsyncevodbusserver.la src_dbus_server_server_cpp_files = \ src/dbus/server/notification-backend-noop.cpp \ src/dbus/server/notification-backend-libnotify.cpp \ src/dbus/server/notification-backend-mlite.cpp \ src/dbus/server/notification-manager-factory.cpp \ src/dbus/server/auto-sync-manager.cpp \ src/dbus/server/bluez-manager.cpp \ src/dbus/server/client.cpp \ src/dbus/server/connection.cpp \ src/dbus/server/connman-client.cpp \ src/dbus/server/dbus-callbacks.cpp \ src/dbus/server/dbus-user-interface.cpp \ src/dbus/server/exceptions.cpp \ src/dbus/server/localed-listener.cpp \ src/dbus/server/info-req.cpp \ src/dbus/server/network-manager-client.cpp \ src/dbus/server/presence-status.cpp \ src/dbus/server/progress-data.cpp \ src/dbus/server/read-operations.cpp \ src/dbus/server/server.cpp \ src/dbus/server/session.cpp src_dbus_server_server_h_files = \ $(src_dbus_server_server_cpp_files:.cpp=.h) \ src/dbus/server/notification-backend-base.h \ src/dbus/server/notification-manager.h \ src/dbus/server/notification-manager-base.h \ src/dbus/server/auto-term.h \ src/dbus/server/cmdline-wrapper.h \ src/dbus/server/resource.h \ src/dbus/server/restart.h \ src/dbus/server/session-common.h \ src/dbus/server/source-progress.h \ src/dbus/server/source-status.h \ src/dbus/server/timeout.h \ src/dbus/server/timer.h src_dbus_server_libsyncevodbusserver_la_SOURCES = \ $(src_dbus_server_server_h_files) \ $(src_dbus_server_server_cpp_files) \ src/dbus/server/main.cpp nodist_src_dbus_server_libsyncevodbusserver_la_SOURCES = dist_pkgdata_DATA += src/dbus/server/bluetooth_products.ini src_dbus_server_libsyncevodbusserver_la_LDFLAGS = src_dbus_server_libsyncevodbusserver_la_LIBADD = $(LIBNOTIFY_LIBS) $(MLITE_LIBS) $(DBUS_LIBS) $(PCRECPP_LIBS) $(ICU_LIBS) src_dbus_server_libsyncevodbusserver_la_CPPFLAGS = -DHAVE_CONFIG_H -DSYNCEVOLUTION_LOCALEDIR=\"${SYNCEVOLUTION_LOCALEDIR}\" -I$(top_srcdir)/src -I$(top_srcdir)/test -I$(top_srcdir) -I$(gdbus_dir) $(BACKEND_CPPFLAGS) src_dbus_server_libsyncevodbusserver_la_CXXFLAGS = $(SYNCEVOLUTION_CXXFLAGS) $(CORE_CXXFLAGS) $(SYNTHESIS_CFLAGS) $(GLIB_CFLAGS) $(DBUS_CFLAGS) $(LIBNOTIFY_CFLAGS) $(MLITE_CFLAGS) $(SYNCEVO_WFLAGS) $(ICU_CFLAGS) if COND_DBUS_PIM src_dbus_server_server_cpp_files += \ src/dbus/server/pim/view.cpp \ src/dbus/server/pim/full-view.cpp \ src/dbus/server/pim/filtered-view.cpp \ src/dbus/server/pim/edsf-view.cpp \ src/dbus/server/pim/locale-factory.cpp \ src/dbus/server/pim/merge-view.cpp \ src/dbus/server/pim/individual-traits.cpp \ src/dbus/server/pim/folks.cpp \ src/dbus/server/pim/manager.cpp src_dbus_server_server_h_files += \ src/dbus/server/pim/persona-details.h nodist_src_dbus_server_libsyncevodbusserver_la_SOURCES += \ src/dbus/server/pim/locale-factory-@DBUS_PIM_PLUGIN@.cpp src_dbus_server_libsyncevodbusserver_la_LDFLAGS += $(DBUS_PIM_PLUGIN_LDFLAGS) src_dbus_server_libsyncevodbusserver_la_LIBADD += $(FOLKS_LIBS) $(DBUS_PIM_PLUGIN_LIBS) $(PHONENUMBERS_LIBS) src_dbus_server_libsyncevodbusserver_la_CXXFLAGS += $(FOLKS_CFLAGS) $(DBUS_PIM_PLUGIN_CFLAGS) $(PHONENUMBERS_CFLAGS) $(DLT_CFLAGS) endif # Need to list all plugins here and not include the active one in the regular # source list above, because "make dist" would only include the configured one. EXTRA_DIST += \ src/dbus/server/pim/locale-factory-boost.cpp # Session helper: syncevo-dbus-helper noinst_LTLIBRARIES += src/dbus/server/libsyncevodbushelper.la src_dbus_server_dbus_helper_cpp_files = \ src/dbus/server/dbus-callbacks.cpp \ src/dbus/server/dbus-sync.cpp \ src/dbus/server/dbus-transport-agent.cpp \ src/dbus/server/session-helper.cpp src_dbus_server_dbus_helper_h_files = \ $(src_dbus_server_dbus_helper_cpp_files:.cpp=.h) \ src/dbus/server/cmdline-wrapper.h src_dbus_server_libsyncevodbushelper_la_SOURCES = \ $(src_dbus_server_dbus_helper_h_files) \ $(src_dbus_server_dbus_helper_cpp_files) \ src/dbus/server/session-common.h \ src/dbus/server/sync-helper.cpp src_dbus_server_libsyncevodbushelper_la_LIBADD = $(DBUS_LIBS) src_dbus_server_libsyncevodbushelper_la_CPPFLAGS = -DHAVE_CONFIG_H -DSYNCEVOLUTION_LOCALEDIR=\"${SYNCEVOLUTION_LOCALEDIR}\" -I$(top_srcdir)/src -I$(top_srcdir)/test -I$(top_srcdir) -I$(gdbus_dir) $(BACKEND_CPPFLAGS) src_dbus_server_libsyncevodbushelper_la_CXXFLAGS = $(SYNCEVOLUTION_CXXFLAGS) $(CORE_CXXFLAGS) $(SYNTHESIS_CFLAGS) $(GLIB_CFLAGS) $(DBUS_CFLAGS) $(SYNCEVO_WFLAGS) # Deal with .service, .desktop and startup script files. CLEANFILES += \ $(src_dbus_server_service_files) \ $(src_dbus_server_desktop) \ $(src_dbus_server_script) src_dbus_server_script_in = src/dbus/server/syncevo-dbus-server-startup.sh.in src_dbus_server_script = $(src_dbus_server_script_in:.sh.in=.sh) src_dbus_server_desktop_in = src/dbus/server/syncevo-dbus-server.desktop.in src_dbus_server_desktop = $(src_dbus_server_desktop_in:.desktop.in=.desktop) src_dbus_server_autostartdir = $(sysconfdir)/xdg/autostart src_dbus_server_autostart_DATA = $(src_dbus_server_desktop) libexec_SCRIPTS += $(src_dbus_server_script) $(src_dbus_server_script): $(src_dbus_server_script_in) @sed -e "s|\@libexecdir\@|$(libexecdir)|" -e "s|\@SYNCEVO_DBUS_SERVER_ARGS\@|$(SYNCEVO_DBUS_SERVER_ARGS)|" $< >$@ $(src_dbus_server_desktop): $(src_dbus_server_desktop_in) @sed -e "s|\@libexecdir\@|$(libexecdir)|" $< >$@ src_dbus_server_service_files_in = src/dbus/server/org.syncevolution.service.in src_dbus_server_service_files = $(src_dbus_server_service_files_in:.service.in=.service) src_dbus_server_servicedir = $(DBUS_SERVICES_DIR) src_dbus_server_service_DATA = $(src_dbus_server_service_files) src/dbus/server/%.service: src/dbus/server/%.service.in @sed -e "s|\@libexecdir\@|$(libexecdir)|" -e "s|\@SYNCEVO_DBUS_SERVER_ARGS\@|$(SYNCEVO_DBUS_SERVER_ARGS)|" $< >$@ if COND_DBUS_PIM src_dbus_server_service_files_in += \ src/dbus/server/pim/org._01.pim.contacts.service.in if ENABLE_TESTING test_SCRIPTS += \ src/dbus/server/pim/testpim.py \ $(NOP) # It would be nice if we could use the nobase_ prefix here, but we # can't because we want to replace parts of the path. Instead we have # to spell out the desired install dir completely for each file. src_dbus_server_testdbusdir = $(testdir)/test-dbus src_dbus_server_testdbus_broken_configdir = $(src_dbus_server_testdbusdir)/broken-config/config/syncevolution src_dbus_server_testdbus_broken_config_DATA = src/dbus/server/pim/test-dbus/broken-config/config/syncevolution/pim-manager.ini src_dbus_server_testdbus_db_activedir = $(src_dbus_server_testdbusdir)/db-active/config/syncevolution src_dbus_server_testdbus_db_active_DATA = src/dbus/server/pim/test-dbus/db-active/config/syncevolution/pim-manager.ini src_dbus_server_testdbus_first_last_sortdir = $(src_dbus_server_testdbusdir)/first-last-sort/config/syncevolution src_dbus_server_testdbus_first_last_sort_DATA = src/dbus/server/pim/test-dbus/first-last-sort/config/syncevolution/pim-manager.ini src_dbus_server_testdbus_simple_sortdir = $(src_dbus_server_testdbusdir)/simple-sort/config/syncevolution src_dbus_server_testdbus_simple_sort_DATA = src/dbus/server/pim/test-dbus/simple-sort/config/syncevolution/pim-manager.ini endif # ENABLE_TESTING endif # COND_DBUS_PIM dist_noinst_DATA += \ $(src_dbus_server_service_files_in) \ $(src_dbus_server_script_in) \ $(src_dbus_server_desktop_in) endif syncevolution_1.4/src/dbus/server/server.cpp000066400000000000000000001045721230021373600214330ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * Copyright (C) 2011 Symbio, Ville Nummela * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include "server.h" #include "info-req.h" #include "connection.h" #include "bluez-manager.h" #include "session.h" #include "timeout.h" #include "restart.h" #include "client.h" #include "auto-sync-manager.h" #include "connman-client.h" #include "network-manager-client.h" #include "presence-status.h" #include using namespace GDBusCXX; SE_BEGIN_CXX void Server::onIdleChange(bool idle) { SE_LOG_DEBUG(NULL, "server is %s", idle ? "idle" : "not idle"); if (idle) { autoTermUnref(); } else { autoTermRef(); } } class ServerLogger : public Logger { Logger::Handle m_parentLogger; // Currently a strong reference. Would be a weak reference // if we had proper reference counting for Server. boost::shared_ptr m_server; public: ServerLogger(const boost::shared_ptr &server) : m_parentLogger(Logger::instance()), m_server(server) { } virtual void remove() throw () { // Hold the Logger mutex while cutting our connection to the // server. The code using m_server below does the same and // holds the mutex while logging. That way we prevent threads // from holding onto the server while it tries to shut down. // // This is important because the server's live time is not // really controlled via the boost::shared_ptr, it may // destruct while there are still references. See // Server::m_logger instantiation below. RecMutex::Guard guard = lock(); m_server.reset(); } virtual void messagev(const MessageOptions &options, const char *format, va_list args) { // Ensure that remove() cannot proceed while we have the // server in use. RecMutex::Guard guard = lock(); Server *server = m_server.get(); message2DBus(server, options, format, args, server ? server->getPath() : "", getProcessName()); } /** * @param server may be NULL, in which case logging only goes to parent */ void message2DBus(Server *server, const MessageOptions &options, const char *format, va_list args, const std::string &dbusPath, const std::string &procname) { // Keeps logging consistent: otherwise thread A might log to // parent, thread B to parent and D-Bus, then thread A // finishes its logging via D-Bus. The order of log messages // would then not be the same in the parent and D-Bus. RecMutex::Guard guard = lock(); // iterating over args in messagev() is destructive, must make a copy first va_list argsCopy; va_copy(argsCopy, args); m_parentLogger.messagev(options, format, args); if (server) { try { if (options.m_level <= server->getDBusLogLevel()) { string log = StringPrintfV(format, argsCopy); server->logOutput(dbusPath, options.m_level, log, procname); } } catch (...) { remove(); // Give up on server logging silently. } } va_end(argsCopy); } }; void Server::clientGone(Client *c) { for (Clients_t::iterator it = m_clients.begin(); it != m_clients.end(); ++it) { if (it->second.get() == c) { SE_LOG_DEBUG(NULL, "D-Bus client %s has disconnected", c->m_ID.c_str()); autoTermUnref(it->second->getAttachCount()); m_clients.erase(it); return; } } SE_LOG_DEBUG(NULL, "unknown client has disconnected?!"); } std::string Server::getNextSession() { // Make the session ID somewhat random. This protects to // some extend against injecting unwanted messages into the // communication. m_lastSession++; if (!m_lastSession) { m_lastSession++; } return StringPrintf("%u%u", rand(), m_lastSession); } vector Server::getCapabilities() { // Note that this is tested by test-dbus.py in // TestServer.testCapabilities, update the test when adding // capabilities. vector capabilities; capabilities.push_back("ConfigChanged"); capabilities.push_back("GetConfigName"); capabilities.push_back("NamedConfig"); capabilities.push_back("Notifications"); capabilities.push_back("Version"); capabilities.push_back("SessionFlags"); capabilities.push_back("SessionAttach"); capabilities.push_back("DatabaseProperties"); return capabilities; } StringMap Server::getVersions() { StringMap versions; versions["version"] = VERSION; versions["system"] = EDSAbiWrapperInfo(); versions["backends"] = SyncSource::backendsInfo(); return versions; } void Server::attachClient(const Caller_t &caller, const boost::shared_ptr &watch) { boost::shared_ptr client = addClient(caller, watch); autoTermRef(); client->increaseAttachCount(); } void Server::detachClient(const Caller_t &caller) { boost::shared_ptr client = findClient(caller); if (client) { autoTermUnref(); client->decreaseAttachCount(); } } void Server::setNotifications(bool enabled, const Caller_t &caller, const string & /* notifications */) { boost::shared_ptr client = findClient(caller); if (client && client->getAttachCount()) { client->setNotificationsEnabled(enabled); } else { SE_THROW("client not attached, not allowed to change notifications"); } } bool Server::notificationsEnabled() { for (Clients_t::iterator it = m_clients.begin(); it != m_clients.end(); ++it) { if (!it->second->getNotificationsEnabled()) { return false; } } return true; } void Server::connect(const Caller_t &caller, const boost::shared_ptr &watch, const StringMap &peer, bool must_authenticate, const std::string &session, DBusObject_t &object) { if (m_shutdownRequested) { // don't allow new connections, we cannot activate them SE_THROW("server shutting down"); } if (!session.empty()) { // reconnecting to old connection is not implemented yet throw std::runtime_error("not implemented"); } std::string new_session = getNextSession(); boost::shared_ptr c(Connection::createConnection(*this, getConnection(), new_session, peer, must_authenticate)); SE_LOG_DEBUG(NULL, "connecting D-Bus client %s with connection %s '%s'", caller.c_str(), c->getPath(), c->m_description.c_str()); boost::shared_ptr client = addClient(caller, watch); client->attach(c); c->activate(); object = c->getPath(); } void Server::startSessionWithFlags(const Caller_t &caller, const boost::shared_ptr &watch, const std::string &server, const std::vector &flags, DBusObject_t &object) { if (m_shutdownRequested) { // don't allow new sessions, we cannot activate them SE_THROW("server shutting down"); } boost::shared_ptr client = addClient(caller, watch); std::string new_session = getNextSession(); boost::shared_ptr session = Session::createSession(*this, "is this a client or server session?", server, new_session, flags); client->attach(session); session->activate(); enqueue(session); object = session->getPath(); } boost::shared_ptr Server::startInternalSession(const std::string &server, SessionFlags flags, const boost::function &session)> &callback) { if (m_shutdownRequested) { // don't allow new sessions, we cannot activate them SE_THROW("server shutting down"); } std::vector dbusFlags; if (flags & SESSION_FLAG_NO_SYNC) { dbusFlags.push_back("no-sync"); } if (flags & SESSION_FLAG_ALL_CONFIGS) { dbusFlags.push_back("all-configs"); } std::string new_session = getNextSession(); boost::shared_ptr session = Session::createSession(*this, "is this a client or server session?", server, new_session, dbusFlags); session->m_sessionActiveSignal.connect(boost::bind(callback, boost::weak_ptr(session))); session->activate(); enqueue(session); return session; } void Server::checkPresence(const std::string &server, std::string &status, std::vector &transports) { return getPresenceStatus().checkPresence(server, status, transports); } void Server::getSessions(std::vector &sessions) { sessions.reserve(m_workQueue.size() + 1); if (m_activeSession) { sessions.push_back(m_activeSession->getPath()); } BOOST_FOREACH(boost::weak_ptr &session, m_workQueue) { boost::shared_ptr s = session.lock(); if (s) { sessions.push_back(s->getPath()); } } } Server::Server(GMainLoop *loop, bool &shutdownRequested, boost::shared_ptr &restart, const DBusConnectionPtr &conn, int duration) : DBusObjectHelper(conn, SessionCommon::SERVER_PATH, SessionCommon::SERVER_IFACE, boost::bind(&Server::autoTermCallback, this)), m_loop(loop), m_shutdownRequested(shutdownRequested), m_restart(restart), m_lastSession(time(NULL)), m_activeSession(NULL), m_lastInfoReq(0), m_bluezManager(new BluezManager(*this)), sessionChanged(*this, "SessionChanged"), presence(*this, "Presence"), templatesChanged(*this, "TemplatesChanged"), configChanged(*this, "ConfigChanged"), infoRequest(*this, "InfoRequest"), m_logOutputSignal(*this, "LogOutput"), m_autoTerm(m_loop, m_shutdownRequested, duration), m_dbusLogLevel(Logger::INFO), // TODO (?): turn Server into a proper reference counted instance. // This would help with dangling references to it when other threads // use it for logging, see ServerLogger. However, with mutex locking // in ServerLogger that shouldn't be a problem. m_logger(new ServerLogger(boost::shared_ptr(this, NopDestructor()))) { struct timeval tv; gettimeofday(&tv, NULL); srand(tv.tv_usec); add(this, &Server::getCapabilities, "GetCapabilities"); add(this, &Server::getVersions, "GetVersions"); add(this, &Server::attachClient, "Attach"); add(this, &Server::detachClient, "Detach"); add(this, &Server::enableNotifications, "EnableNotifications"); add(this, &Server::disableNotifications, "DisableNotifications"); add(this, &Server::notificationAction, "NotificationAction"); add(this, &Server::connect, "Connect"); add(this, &Server::startSession, "StartSession"); add(this, &Server::startSessionWithFlags, "StartSessionWithFlags"); add(this, &Server::getConfigs, "GetConfigs"); add(this, &Server::getConfig, "GetConfig"); add(this, &Server::getReports, "GetReports"); add(this, &Server::checkSource, "CheckSource"); add(this, &Server::getDatabases, "GetDatabases"); add(this, &Server::checkPresence, "CheckPresence"); add(this, &Server::getSessions, "GetSessions"); add(this, &Server::infoResponse, "InfoResponse"); add(sessionChanged); add(templatesChanged); add(configChanged); add(presence); add(infoRequest); add(m_logOutputSignal); // Log entering and leaving idle state and // allow/prevent auto-termination. m_idleSignal.connect(boost::bind(&Server::onIdleChange, this, _1)); // connect ConfigChanged signal to source for that information m_configChangedSignal.connect(boost::bind(boost::ref(configChanged))); } void Server::activate() { // Activate our D-Bus object *before* interacting with D-Bus // any further. Otherwise GIO D-Bus will start processing // messages for us while we start up and reject them because // out object isn't visible to it yet. GDBusCXX::DBusObjectHelper::activate(); // Push ourselves as logger for the time being. m_logger->setLevel(Logger::DEBUG); m_pushLogger.reset(m_logger); m_presence.reset(new PresenceStatus(*this)); // Assume that Bluetooth is available. Neither ConnMan nor Network // manager can tell us about that. The "Bluetooth" ConnMan technology // is about IP connection via Bluetooth - not what we need. getPresenceStatus().updatePresenceStatus(true, PresenceStatus::BT_TRANSPORT); m_connman.reset(new ConnmanClient(*this)); m_networkManager.reset(new NetworkManagerClient(*this)); if ((!m_connman || !m_connman->isAvailable()) && (!m_networkManager || !m_networkManager->isAvailable())) { // assume that we are online if no network manager was found at all getPresenceStatus().updatePresenceStatus(true, PresenceStatus::HTTP_TRANSPORT); } // create auto sync manager, now that server is ready m_autoSync = AutoSyncManager::createAutoSyncManager(*this); } Server::~Server() { // make sure all other objects are gone before destructing ourselves m_syncSession.reset(); m_workQueue.clear(); m_clients.clear(); m_autoSync.reset(); m_infoReqMap.clear(); m_timeouts.clear(); m_delayDeletion.clear(); m_connman.reset(); m_networkManager.reset(); m_presence.reset(); m_pushLogger.reset(); m_logger.reset(); } bool Server::shutdown() { Timespec now = Timespec::monotonic(); bool autosync = m_autoSync && m_autoSync->preventTerm(); SE_LOG_DEBUG(NULL, "shut down or restart server at %lu.%09lu because of file modifications, auto sync %s", now.tv_sec, now.tv_nsec, autosync ? "on" : "off"); if (autosync) { // suitable exec() call which restarts the server using the same environment it was in // when it was started SE_LOG_INFO(NULL, "server restarting because files loaded into memory were modified on disk"); m_restart->restart(); } else { // leave server now g_main_loop_quit(m_loop); SE_LOG_INFO(NULL, "server shutting down because files loaded into memory were modified on disk"); } return false; } void Server::fileModified() { SE_LOG_DEBUG(NULL, "file modified, %s shutdown: %s, %s", m_shutdownRequested ? "continuing" : "initiating", m_shutdownTimer ? "timer already active" : "timer not yet active", m_activeSession ? "waiting for active session to finish" : "setting timer"); m_lastFileMod = Timespec::monotonic(); if (!m_activeSession) { m_shutdownTimer.activate(SHUTDOWN_QUIESENCE_SECONDS, boost::bind(&Server::shutdown, this)); } m_shutdownRequested = true; } void Server::run() { // This has the intended side effect that it loads everything into // memory which might be dynamically loadable, like backend // plugins. StringMap map = getVersions(); SE_LOG_DEBUG(NULL, "D-Bus server ready to run, versions:"); BOOST_FOREACH(const StringPair &entry, map) { SE_LOG_DEBUG(NULL, "%s: %s", entry.first.c_str(), entry.second.c_str()); } // Now that everything is loaded, check memory map for files which we have to monitor. set files; std::ifstream in("/proc/self/maps"); while (!in.eof()) { string line; getline(in, line); size_t off = line.find('/'); if (off != line.npos && line.find(" r-xp ") != line.npos) { files.insert(line.substr(off)); } } in.close(); BOOST_FOREACH(const string &file, files) { try { SE_LOG_DEBUG(NULL, "watching: %s", file.c_str()); boost::shared_ptr notify(new GLibNotify(file.c_str(), boost::bind(&Server::fileModified, this))); m_files.push_back(notify); } catch (...) { // ignore errors for indidividual files Exception::handle(); } } SE_LOG_INFO(NULL, "ready to run"); if (!m_shutdownRequested) { g_main_loop_run(m_loop); } SE_LOG_DEBUG(NULL, "%s", "Exiting Server::run"); } /** * look up client by its ID */ boost::shared_ptr Server::findClient(const Caller_t &ID) { for (Clients_t::iterator it = m_clients.begin(); it != m_clients.end(); ++it) { if (it->second->m_ID == ID) { return it->second; } } return boost::shared_ptr(); } boost::shared_ptr Server::addClient(const Caller_t &ID, const boost::shared_ptr &watch) { boost::shared_ptr client(findClient(ID)); if (client) { return client; } client.reset(new Client(*this, ID)); // add to our list *before* checking that peer exists, so // that clientGone() can remove it if the check fails m_clients.push_back(std::make_pair(watch, client)); watch->setCallback(boost::bind(&Server::clientGone, this, client.get())); return client; } void Server::detach(Resource *resource) { BOOST_FOREACH(const Clients_t::value_type &client_entry, m_clients) { client_entry.second->detachAll(resource); } } void Server::enqueue(const boost::shared_ptr &session) { bool idle = isIdle(); WorkQueue_t::iterator it = m_workQueue.end(); while (it != m_workQueue.begin()) { --it; // skip over dead sessions, they will get cleaned up elsewhere boost::shared_ptr session = it->lock(); if (session && session->getPriority() <= session->getPriority()) { ++it; break; } } m_workQueue.insert(it, session); checkQueue(); if (idle) { m_idleSignal(false); } } void Server::killSessionsAsync(const std::string &peerDeviceID, const SimpleResult &onResult) { WorkQueue_t::iterator it = m_workQueue.begin(); while (it != m_workQueue.end()) { boost::shared_ptr session = it->lock(); if (session && session->getPeerDeviceID() == peerDeviceID) { SE_LOG_DEBUG(NULL, "removing pending session %s because it matches deviceID %s", session->getSessionID().c_str(), peerDeviceID.c_str()); // remove session and its corresponding connection boost::shared_ptr c = session->getStubConnection().lock(); if (c) { c->shutdown(); } it = m_workQueue.erase(it); } else { ++it; } } // Check active session. We need to wait for it to shut down cleanly. boost::shared_ptr active = m_activeSessionRef.lock(); if (active && active->getPeerDeviceID() == peerDeviceID) { SE_LOG_DEBUG(NULL, "aborting active session %s because it matches deviceID %s", active->getSessionID().c_str(), peerDeviceID.c_str()); // hand over work to session active->abortAsync(onResult); } else { onResult.done(); } } void Server::dequeue(Session *session) { bool idle = isIdle(); if (m_syncSession.get() == session) { // This is the running sync session. // It's not in the work queue and we have to // keep it active, so nothing to do. return; } for (WorkQueue_t::iterator it = m_workQueue.begin(); it != m_workQueue.end(); ++it) { if (it->lock().get() == session) { // remove from queue m_workQueue.erase(it); break; } } if (m_activeSession == session) { // The session is releasing the lock, so someone else might // run now. sessionChanged(session->getPath(), false); m_activeSession = NULL; m_activeSessionRef.reset(); checkQueue(); } if (!idle && isIdle()) { m_idleSignal(true); } } void Server::addSyncSession(Session *session) { // Only one session can run a sync, and only the active session // can make itself the sync session. if (m_syncSession) { if (m_syncSession.get() != session) { SE_THROW("already have a sync session"); } else { return; } } m_syncSession = m_activeSessionRef.lock(); m_newSyncSessionSignal(m_syncSession); if (!m_syncSession) { SE_THROW("session should not start a sync, all clients already detached"); } if (m_syncSession.get() != session) { m_syncSession.reset(); SE_THROW("inactive session asked to become sync session"); } } void Server::removeSyncSession(Session *session) { if (session == m_syncSession.get()) { // Normally the owner calls this, but if it is already gone, // then do it again and thus effectively start counting from // now. delaySessionDestruction(m_syncSession); m_syncSession.reset(); } else { SE_LOG_DEBUG(NULL, "ignoring removeSyncSession() for session %s, it is not the sync session", session->getSessionID().c_str()); } } static void quitLoop(GMainLoop *loop) { SE_LOG_DEBUG(NULL, "stopping server's event loop"); g_main_loop_quit(loop); } void Server::checkQueue() { if (m_activeSession) { // still busy return; } if (m_shutdownRequested) { // Don't schedule new sessions. Instead return to Server::run(). // But don't do it immediately: when done inside the Session.Detach() // call, the D-Bus response was not delivered reliably to the client // which caused the shutdown. SE_LOG_DEBUG(NULL, "shutting down in checkQueue(), idle and shutdown was requested"); addTimeout(boost::bind(quitLoop, m_loop), 0); return; } while (!m_workQueue.empty()) { boost::shared_ptr session = m_workQueue.front().lock(); m_workQueue.pop_front(); if (session) { // activate the session m_activeSession = session.get(); m_activeSessionRef = session; SE_LOG_DEBUG(NULL, "activating session %p", m_activeSession); session->activateSession(); sessionChanged(session->getPath(), true); return; } } } void Server::sessionExpired(const boost::shared_ptr &session) { SE_LOG_DEBUG(NULL, "session %s expired", session->getSessionID().c_str()); } void Server::delaySessionDestruction(const boost::shared_ptr &session) { if (!session) { return; } SE_LOG_DEBUG(NULL, "delaying destruction of session %s by one minute", session->getSessionID().c_str()); addTimeout(boost::bind(&Server::sessionExpired, session), 60 /* 1 minute */); } inline void insertPair(std::map ¶ms, const string &key, const string &value) { if(!value.empty()) { params.insert(pair(key, value)); } } boost::shared_ptr Server::passwordRequest(const string &descr, const ConfigPasswordKey &key, const boost::weak_ptr &s) { boost::shared_ptr session = s.lock(); if (!session) { // already gone, ignore request return boost::shared_ptr(); } std::map params; insertPair(params, "description", descr); insertPair(params, "user", key.user); insertPair(params, "SyncML server", key.server); insertPair(params, "domain", key.domain); insertPair(params, "object", key.object); insertPair(params, "protocol", key.protocol); insertPair(params, "authtype", key.authtype); insertPair(params, "port", key.port ? StringPrintf("%u",key.port) : ""); boost::shared_ptr req = createInfoReq("password", params, *session); // Return password or failure to Session and thus the session helper. req->m_responseSignal.connect(boost::bind(&Server::passwordResponse, this, _1, s)); // Tell session about timeout. req->m_timeoutSignal.connect(InfoReq::TimeoutSignal_t::slot_type(&Session::passwordResponse, session.get(), true, false, "").track(s)); // Request becomes obsolete when session is done. session->m_doneSignal.connect(boost::bind(&Server::removeInfoReq, this, req->getId())); return req; } void Server::passwordResponse(const InfoReq::InfoMap &response, const boost::weak_ptr &s) { boost::shared_ptr session = s.lock(); if (!session) { // already gone, ignore request return; } InfoReq::InfoMap::const_iterator it = response.find("password"); if (it == response.end()) { // no password provided, user wants to abort session->passwordResponse(false, true, ""); } else { // password provided, might be empty session->passwordResponse(false, false, it->second); } } bool Server::callTimeout(const boost::shared_ptr &timeout, const boost::function &callback) { callback(); // We are executing the timeout, don't invalidate the instance // until later when our caller is no longer using the instance to // call us. delayDeletion(timeout); m_timeouts.remove(timeout); return false; } void Server::addTimeout(const boost::function &callback, int seconds) { boost::shared_ptr timeout(new Timeout); m_timeouts.push_back(timeout); timeout->activate(seconds, boost::bind(&Server::callTimeout, this, // avoid copying the shared pointer here, // otherwise the Timeout will never be deleted boost::ref(m_timeouts.back()), callback)); } void Server::infoResponse(const Caller_t &caller, const std::string &id, const std::string &state, const std::map &response) { InfoReqMap::iterator it = m_infoReqMap.find(id); // if not found, ignore if (it != m_infoReqMap.end()) { const boost::shared_ptr infoReq = it->second.lock(); if (infoReq) { infoReq->setResponse(caller, state, response); } } } boost::shared_ptr Server::createInfoReq(const string &type, const std::map ¶meters, const Session &session) { boost::shared_ptr infoReq(new InfoReq(*this, type, parameters, session.getPath())); m_infoReqMap.insert(std::make_pair(infoReq->getId(), infoReq)); // will be removed automatically infoReq->m_responseSignal.connect(boost::bind(&Server::removeInfoReq, this, infoReq->getId())); infoReq->m_timeoutSignal.connect(boost::bind(&Server::removeInfoReq, this, infoReq->getId())); return infoReq; } std::string Server::getNextInfoReq() { return StringPrintf("%u", ++m_lastInfoReq); } void Server::emitInfoReq(const InfoReq &req) { infoRequest(req.getId(), req.getSessionPath(), req.getInfoStateStr(), req.getHandler(), req.getType(), req.getParam()); } void Server::removeInfoReq(const std::string &id) { // remove InfoRequest from hash map m_infoReqMap.erase(id); } PresenceStatus &Server::getPresenceStatus() { if (!m_presence) { SE_THROW("internal error: Server::getPresenceStatus() called while server has no instance"); } return *m_presence; } void Server::getDeviceList(SyncConfig::DeviceList &devices) { //wait bluez or other device managers // TODO: make this asynchronous?! while(!m_bluezManager->isDone()) { g_main_loop_run(m_loop); } devices.clear(); devices = m_syncDevices; } void Server::addPeerTempl(const string &templName, const boost::shared_ptr peerTempl) { std::string lower = templName; boost::to_lower(lower); m_matchedTempls.insert(MatchedTemplates::value_type(lower, peerTempl)); } boost::shared_ptr Server::getPeerTempl(const string &peer) { std::string lower = peer; boost::to_lower(lower); MatchedTemplates::iterator it = m_matchedTempls.find(lower); if(it != m_matchedTempls.end()) { return it->second; } else { return boost::shared_ptr(); } } bool Server::getDevice(const string &deviceId, SyncConfig::DeviceDescription &device) { SyncConfig::DeviceList::iterator syncDevIt; for (syncDevIt = m_syncDevices.begin(); syncDevIt != m_syncDevices.end(); ++syncDevIt) { if (boost::equals(syncDevIt->m_deviceId, deviceId)) { device = *syncDevIt; if (syncDevIt->m_pnpInformation) { device.m_pnpInformation = boost::shared_ptr( new SyncConfig::PnpInformation(syncDevIt->m_pnpInformation->m_vendor, syncDevIt->m_pnpInformation->m_product)); } return true; } } return false; } void Server::addDevice(const SyncConfig::DeviceDescription &device) { SyncConfig::DeviceList::iterator it; for (it = m_syncDevices.begin(); it != m_syncDevices.end(); ++it) { if (boost::iequals(it->m_deviceId, device.m_deviceId)) { break; } } if (it == m_syncDevices.end()) { m_syncDevices.push_back(device); templatesChanged(); } } void Server::removeDevice(const string &deviceId) { SyncConfig::DeviceList::iterator syncDevIt; for (syncDevIt = m_syncDevices.begin(); syncDevIt != m_syncDevices.end(); ++syncDevIt) { if (boost::equals(syncDevIt->m_deviceId, deviceId)) { m_syncDevices.erase(syncDevIt); templatesChanged(); break; } } } void Server::updateDevice(const string &deviceId, const SyncConfig::DeviceDescription &device) { SyncConfig::DeviceList::iterator it; for (it = m_syncDevices.begin(); it != m_syncDevices.end(); ++it) { if (boost::iequals(it->m_deviceId, deviceId)) { (*it) = device; templatesChanged(); break; } } } void Server::message2DBus(const Logger::MessageOptions &options, const char *format, va_list args, const std::string &dbusPath, const std::string &procname) { // prefix is used to set session path // for general server output, the object path field is dbus server // the object path can't be empty for object paths prevent using empty string. m_logger->message2DBus(this, options, format, args, dbusPath, procname); } void Server::logOutput(const GDBusCXX::DBusObject_t &path, Logger::Level level, const std::string &explanation, const std::string &procname) { if (level <= m_dbusLogLevel) { string strLevel = Logger::levelToStr(level); m_logOutputSignal(path, strLevel, explanation, procname); } } SE_END_CXX syncevolution_1.4/src/dbus/server/server.h000066400000000000000000000575541230021373600211070ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef SYNCEVO_DBUS_SERVER_H #define SYNCEVO_DBUS_SERVER_H #include #include #include #include #include #include "exceptions.h" #include "auto-term.h" #include "timeout.h" #include "dbus-callbacks.h" #include "read-operations.h" #include SE_BEGIN_CXX class Resource; class Session; class Server; class InfoReq; class BluezManager; class Restart; class Client; class GLibNotify; class AutoSyncManager; class PresenceStatus; class ConnmanClient; class NetworkManagerClient; // TODO: avoid polluting namespace using namespace std; class ServerLogger; /** * Implements the main org.syncevolution.Server interface. * * The Server class is responsible for listening to clients and * spinning of sync sessions as requested by clients. */ class Server : public GDBusCXX::DBusObjectHelper { GMainLoop *m_loop; bool &m_shutdownRequested; Timespec m_lastFileMod; boost::shared_ptr &m_restart; uint32_t m_lastSession; typedef std::list< std::pair< boost::shared_ptr, boost::shared_ptr > > Clients_t; Clients_t m_clients; /** * Functor will never be called, important are the shared pointers * bound to it. m_delayDeletion will be cleared in idle and when * server terminates, thus unrefing anything encapsulated inside * it. */ std::list< boost::function > m_delayDeletion; /** * Watch all files mapped into our address space. When * modifications are seen (as during a package upgrade), sets * m_shutdownRequested. This prevents adding new sessions and * prevents running already queued ones, because future sessions * might not be able to execute correctly without a restart. For example, a * sync with libsynthesis from 1.1 does not work with * SyncEvolution XML files from 1.2. The daemon then waits * for the changes to settle (see SHUTDOWN_QUIESENCE_SECONDS) and either shuts * down or restarts. The latter is necessary if the daemon has * automatic syncing enabled in a config. */ list< boost::shared_ptr > m_files; void fileModified(); bool shutdown(); /** * timer which counts seconds until server is meant to shut down */ Timeout m_shutdownTimer; /** * The session which currently holds the main lock on the server. * To avoid issues with concurrent modification of data or configs, * only one session may make such modifications at a time. A * plain pointer which is reset by the session's deconstructor. * * The server doesn't hold a shared pointer to the session so * that it can be deleted when the last client detaches from it. * * A weak pointer alone did not work because it does not provide access * to the underlying pointer after the last corresponding shared * pointer is gone (which triggers the deconstructing of the session). */ Session *m_activeSession; /** * The weak pointer that corresponds to m_activeSession. */ boost::weak_ptr m_activeSessionRef; /** * The running sync session. Having a separate reference to it * ensures that the object won't go away prematurely, even if all * clients disconnect. * * The session itself needs to request this special treatment with * addSyncSession() and remove itself with removeSyncSession() when * done. */ boost::shared_ptr m_syncSession; typedef std::list< boost::weak_ptr > WorkQueue_t; /** * A queue of pending, idle Sessions. Sorted by priority, most * important one first. Currently this is used to give client * requests a boost over remote connections and (in the future) * automatic syncs. * * Active sessions are removed from this list and then continue * to exist as long as a client in m_clients references it or * it is the currently running sync session (m_syncSession). */ WorkQueue_t m_workQueue; /** * a hash of pending InfoRequest */ typedef std::map > InfoReqMap; // hash map of pending info requests InfoReqMap m_infoReqMap; // the index of last info request uint32_t m_lastInfoReq; // a hash to represent matched templates for devices, the key is // the peer name typedef std::map > MatchedTemplates; MatchedTemplates m_matchedTempls; boost::shared_ptr m_bluezManager; /** devices which have sync services */ SyncConfig::DeviceList m_syncDevices; /** * Watch callback for a specific client or connection. */ void clientGone(Client *c); public: // D-Bus API, also usable directly /** Server.GetCapabilities() */ vector getCapabilities(); /** Server.GetVersions() */ StringMap getVersions(); /** Server.Attach() */ void attachClient(const GDBusCXX::Caller_t &caller, const boost::shared_ptr &watch); /** Server.Detach() */ void detachClient(const GDBusCXX::Caller_t &caller); /** Server.DisableNotifications() */ void disableNotifications(const GDBusCXX::Caller_t &caller, const string ¬ifications) { setNotifications(false, caller, notifications); } /** Server.EnableNotifications() */ void enableNotifications(const GDBusCXX::Caller_t &caller, const string ¬ifications) { setNotifications(true, caller, notifications); } /** Server.NotificationAction() */ void notificationAction(const GDBusCXX::Caller_t &caller) { pid_t pid; if((pid = fork()) == 0) { // search sync-ui from $PATH execlp("sync-ui", "sync-ui", (const char*)0); // Failing that, try meego-ux-settings/Sync execlp("meego-qml-launcher", "meego-qml-launcher", "--opengl", "--fullscreen", "--app", "meego-ux-settings", "--cmd", "showPage", "--cdata", "Sync", (const char*)0); // Failing that, simply exit exit(0); } } /** actual implementation of enable and disable */ void setNotifications(bool enable, const GDBusCXX::Caller_t &caller, const string ¬ifications); /** Server.Connect() */ void connect(const GDBusCXX::Caller_t &caller, const boost::shared_ptr &watch, const StringMap &peer, bool must_authenticate, const std::string &session, GDBusCXX::DBusObject_t &object); /** Server.StartSession() */ void startSession(const GDBusCXX::Caller_t &caller, const boost::shared_ptr &watch, const std::string &server, GDBusCXX::DBusObject_t &object) { startSessionWithFlags(caller, watch, server, std::vector(), object); } /** Server.StartSessionWithFlags() */ void startSessionWithFlags(const GDBusCXX::Caller_t &caller, const boost::shared_ptr &watch, const std::string &server, const std::vector &flags, GDBusCXX::DBusObject_t &object); /** internal representation of D-Bus API Server.StartSessionWithFlags() */ enum SessionFlags { SESSION_FLAG_NONE = 0, SESSION_FLAG_NO_SYNC = 1<<0, SESSION_FLAG_ALL_CONFIGS = 1<<1 }; /** * Creates a session, queues it, then invokes the callback * once the session is active. The caller is responsible * for holding a reference to the session. If it drops * that reference, the session gets deleted and the callback * will not be called. */ boost::shared_ptr startInternalSession(const std::string &server, SessionFlags flags, const boost::function &session)> &callback); /** Server.GetConfig() */ void getConfig(const std::string &config_name, bool getTemplate, ReadOperations::Config_t &config) { ReadOperations ops(config_name, *this); ops.getConfig(getTemplate , config); } /** Server.GetReports() */ void getReports(const std::string &config_name, uint32_t start, uint32_t count, ReadOperations::Reports_t &reports) { ReadOperations ops(config_name, *this); ops.getReports(start, count, reports); } /** Server.CheckSource() */ void checkSource(const std::string &configName, const std::string &sourceName) { ReadOperations ops(configName, *this); ops.checkSource(sourceName); } /** Server.GetDatabases() */ void getDatabases(const std::string &configName, const string &sourceName, ReadOperations::SourceDatabases_t &databases) { ReadOperations ops(configName, *this); ops.getDatabases(sourceName, databases); } /** Server.GetConfigs() */ void getConfigs(bool getTemplates, std::vector &configNames) { ReadOperations ops("", *this); ops.getConfigs(getTemplates, configNames); } /** Server.CheckPresence() */ void checkPresence(const std::string &server, std::string &status, std::vector &transports); /** Server.GetSessions() */ void getSessions(std::vector &sessions); /** Server.InfoResponse() */ void infoResponse(const GDBusCXX::Caller_t &caller, const std::string &id, const std::string &state, const std::map &response); /** Server.SessionChanged */ GDBusCXX::EmitSignal2 sessionChanged; /** Server.PresenceChanged */ GDBusCXX::EmitSignal3 presence; /** * Server.TemplatesChanged, triggered each time m_syncDevices, the * input for the templates, is changed */ GDBusCXX::EmitSignal0 templatesChanged; /** * Server.ConfigChanged, triggered each time a session ends * which modified its configuration */ GDBusCXX::EmitSignal0 configChanged; /** Server.InfoRequest */ GDBusCXX::EmitSignal6 &> infoRequest; /** wrapper around Server.LogOutput, filters by DBusLogLevel */ void logOutput(const GDBusCXX::DBusObject_t &path, Logger::Level level, const std::string &explanation, const std::string &procname); void setDBusLogLevel(Logger::Level level) { m_dbusLogLevel = level; } Logger::Level getDBusLogLevel() const { return m_dbusLogLevel; } private: /** Server.LogOutput */ GDBusCXX::EmitSignal4 m_logOutputSignal; friend class InfoReq; /** emit InfoRequest */ void emitInfoReq(const InfoReq &); /** get the next id of InfoRequest */ std::string getNextInfoReq(); /** remove InfoReq from hash map */ void removeInfoReq(const std::string &infoReqId); boost::scoped_ptr m_presence; boost::scoped_ptr m_connman; boost::scoped_ptr m_networkManager; /** Manager to automatic sync */ boost::shared_ptr m_autoSync; //automatic termination AutoTerm m_autoTerm; // The level of detail for D-Bus logging signals. Logger::Level m_dbusLogLevel; // Created in constructor and captures parent logger there, // then pushed as default logger in activate(). boost::shared_ptr m_logger; PushLogger m_pushLogger; /** * All active timeouts created by addTimeout(). * Each timeout which requests to be not called * again will be removed from this list. */ list< boost::shared_ptr > m_timeouts; /** * called each time a timeout triggers, * removes those which are done */ bool callTimeout(const boost::shared_ptr &timeout, const boost::function &callback); /** called 1 minute after last client detached from a session */ static void sessionExpired(const boost::shared_ptr &session); /** hooked into m_idleSignal, controls auto-termination */ void onIdleChange(bool idle); public: Server(GMainLoop *loop, bool &shutdownRequested, boost::shared_ptr &restart, const GDBusCXX::DBusConnectionPtr &conn, int duration); void activate(); ~Server(); /** access to the GMainLoop reference used by this Server instance */ GMainLoop *getLoop() { return m_loop; } /** process D-Bus calls until the server is ready to quit */ void run(); /** currently running operation */ boost::shared_ptr getSyncSession() const { return m_syncSession; } /** true iff no work is pending */ bool isIdle() const { return !m_activeSession && m_workQueue.empty(); } /** isIdle() has changed its value, current value included */ typedef boost::signals2::signal IdleSignal_t; IdleSignal_t m_idleSignal; /** * More specific "config changed signal", called with normalized * config name as parameter. Config name is empty if all configs * were affected. */ typedef boost::signals2::signal ConfigChangedSignal_t; ConfigChangedSignal_t m_configChangedSignal; /** * Called when a session starts its real work (= calls addSyncSession()). */ typedef boost::signals2::signal &)> NewSyncSessionSignal_t; NewSyncSessionSignal_t m_newSyncSessionSignal; /** * look up client by its ID */ boost::shared_ptr findClient(const GDBusCXX::Caller_t &ID); /** * find client by its ID or create one anew */ boost::shared_ptr addClient(const GDBusCXX::Caller_t &ID, const boost::shared_ptr &watch); /** detach this resource from all clients which own it */ void detach(Resource *resource); /** * Enqueue a session. Might also make it ready immediately, * if nothing else is first in the queue. To be called * by the creator of the session, *after* the session is * ready to run. */ void enqueue(const boost::shared_ptr &session); /** * Remove all sessions with this device ID from the * queue. If the active session also has this ID, * the session will be aborted and/or deactivated. * * Has to be asynchronous because it might involve ensuring that * there is no running helper for this device ID, which requires * communicating with the helper. */ void killSessionsAsync(const std::string &peerDeviceID, const SimpleResult &result); /** * Remove a session from the work queue. If it is running a sync, * it will keep running and nothing will change. Otherwise, if it * is "ready" (= holds a lock on its configuration), then release * that lock. */ void dequeue(Session *session); /** * Remember that the session is running a sync (or some other * important operation) and keeps a pointer to it, to prevent * deleting it. Currently can only called by the active sync * session. Will fail if all clients have detached already. * * If successful, it triggers m_newSyncSessionSignal. */ void addSyncSession(Session *session); /** * Session is done, ready to be deleted again. */ void removeSyncSession(Session *session); /** * Checks whether the server is ready to run another session * and if so, activates the first one in the queue. */ void checkQueue(); /** * Special behavior for sessions: keep them around for another * minute after the are no longer needed. Must be called by the * creator of the session right before it would normally cause the * destruction of the session. * * This allows another client to attach and/or get information * about the session. * * This is implemented as a timeout which holds a reference to the * session. Once the timeout fires, it is called and then removed, * which removes the reference. */ void delaySessionDestruction(const boost::shared_ptr &session); /** * Works for any kind of object: keep shared pointer until the * event loop is idle, then unref it inside. Useful for instances * which need to delete themselves. */ template void delayDeletion(const boost::shared_ptr &t) { // The functor will never be called, important here is only // that it contains a copy of the shared pointer. m_delayDeletion.push_back(boost::bind(delayDeletionDummy, t)); g_idle_add(&Server::delayDeletionCb, this); } template static void delayDeletionDummy(const boost::shared_ptr &t) throw () {} static gboolean delayDeletionCb(gpointer userData) throw () { Server *me = static_cast(userData); try { me->m_delayDeletion.clear(); } catch (...) { // Something unexpected went wrong, can only shut down. Exception::handle(HANDLE_EXCEPTION_FATAL); } return false; } /** * Handle the password request from a specific session. Ask our * clients, relay answer to session if it is still around at the * time when we get the response. * * Server does not keep a strong reference to info request, * caller must do that or the request will automatically be * deleted. */ boost::shared_ptr passwordRequest(const std::string &descr, const ConfigPasswordKey &key, const boost::weak_ptr &session); /** got response for earlier request, need to extract password and tell session */ void passwordResponse(const StringMap &response, const boost::weak_ptr &session); /** * Invokes the given callback once in the given amount of seconds. * Keeps a copy of the callback. If the Server is destructed * before that time, then the callback will be deleted without * being called. */ void addTimeout(const boost::function &callback, int seconds); /** * InfoReq will be added to map automatically and removed again * when it completes or times out. Caller is responsible for * calling removeInfoReq() when the request becomes obsolete * sooner than that. */ boost::shared_ptr createInfoReq(const string &type, const std::map ¶meters, const Session &session); void autoTermRef(int counts = 1) { m_autoTerm.ref(counts); } void autoTermUnref(int counts = 1) { m_autoTerm.unref(counts); } /** callback to reset for auto termination checking */ void autoTermCallback() { m_autoTerm.reset(); } /** poll_nm callback for connman, used for presence detection*/ void connmanCallback(const std::map > >& props, const string &error); PresenceStatus& getPresenceStatus(); void clearPeerTempls() { m_matchedTempls.clear(); } void addPeerTempl(const string &templName, const boost::shared_ptr peerTempl); boost::shared_ptr getPeerTempl(const string &peer); /** * methods to operate device list. See DeviceList definition. * The device id here is the identifier of device, the same as definition in DeviceList. * In bluetooth devices, it refers to actually the mac address of the bluetooth. * The finger print and match mode is used to match templates. */ /** get sync devices */ void getDeviceList(SyncConfig::DeviceList &devices); /** get a device according to device id. If not found, return false. */ bool getDevice(const string &deviceId, SyncConfig::DeviceDescription &device); /** add a device */ void addDevice(const SyncConfig::DeviceDescription &device); /** remove a device by device id. If not found, do nothing */ void removeDevice(const string &deviceId); /** update a device with the given device information. If not found, do nothing */ void updateDevice(const string &deviceId, const SyncConfig::DeviceDescription &device); /** emit a presence signal */ void emitPresence(const string &server, const string &status, const string &transport) { presence(server, status, transport); } /** * Returns new unique session ID. Implemented with a running * counter. Checks for overflow, but not currently for active * sessions. */ std::string getNextSession(); /** * Number of seconds to wait after file modifications are observed * before shutting down or restarting. Shutting down could be done * immediately, but restarting might not work right away. 10 * seconds was chosen because every single package is expected to * be upgraded on disk in that interval. If a long-running system * upgrade replaces additional packages later, then the server * might restart multiple times during a system upgrade. Because it * never runs operations directly after starting, that shouldn't * be a problem. */ static const int SHUTDOWN_QUIESENCE_SECONDS = 10; /** * false if any client requested suppression of notifications */ bool notificationsEnabled(); void message2DBus(const Logger::MessageOptions &options, const char *format, va_list args, const std::string &dbusPath, const std::string &procname); }; // extensions to the D-Bus server, created dynamically by main() #ifdef ENABLE_DBUS_PIM boost::shared_ptr CreateContactManager(const boost::shared_ptr &server, bool start); #endif SE_END_CXX #endif // SYNCEVO_DBUS_SERVER_H syncevolution_1.4/src/dbus/server/session-common.h000066400000000000000000000230401230021373600225310ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef SESSION_COMMON_H #define SESSION_COMMON_H #include "source-status.h" #include "source-progress.h" #include #include #include #include #include SE_BEGIN_CXX /** * This namespace holds constants and defines for Sessions and its * consumers. */ namespace SessionCommon { const char * const SERVICE_NAME = "org.syncevolution"; const char * const CONNECTION_PATH = "/org/syncevolution/Connection"; const char * const CONNECTION_IFACE = "org.syncevolution.Connection"; const char * const SESSION_PATH = "/org/syncevolution/Session"; const char * const SESSION_IFACE = "org.syncevolution.Session"; const char * const SERVER_PATH = "/org/syncevolution/Server"; const char * const SERVER_IFACE = "org.syncevolution.Server"; const char * const HELPER_PATH = "/dbushelper"; const char * const HELPER_IFACE = "org.syncevolution.Helper"; const char * const HELPER_DESTINATION = "direct.peer"; // doesn't matter, routing is off /** * The operation running inside the session. */ enum RunOperation { OP_SYNC, /**< running a sync */ OP_RESTORE, /**< restoring data */ OP_CMDLINE, /**< executing command line */ OP_NULL /**< idle, accepting commands via D-Bus */ }; inline std::string runOpToString(RunOperation op) { static const char * const strings[] = { "sync", "restore", "cmdline" }; return op >= OP_SYNC && op <= OP_CMDLINE ? strings[op] : ""; } /** * Used by both Connection class (inside server) and * DBusTransportAgent (inside helper). */ enum ConnectionState { SETUP, /**< ready for first message */ PROCESSING, /**< received message, waiting for engine's reply */ WAITING, /**< waiting for next follow-up message */ FINAL, /**< engine has sent final reply, wait for ACK by peer */ DONE, /**< peer has closed normally after the final reply */ FAILED /**< in a failed state, no further operation possible */ }; /** maps to names for debugging */ inline std::string ConnectionStateToString(ConnectionState state) { static const char * const strings[] = { "SETUP", "PROCESSING", "WAITING", "FINAL", "DONE", "FAILED" }; return state >= SETUP && state <= FAILED ? strings[state] : "???"; } typedef StringMap SourceModes_t; typedef std::map SourceFilters_t; /** * all the information that syncevo-dbus-server needs to * send to syncevo-dbus-helper before the latter can * run a sync */ struct SyncParams { SyncParams() : m_serverMode(false), m_serverAlerted(false), m_remoteInitiated(false) {} std::string m_config; std::string m_mode; SourceModes_t m_sourceModes; bool m_serverMode; bool m_serverAlerted; bool m_remoteInitiated; std::string m_sessionID; SharedBuffer m_initialMessage; std::string m_initialMessageType; SyncEvo::FilterConfigNode::ConfigFilter m_syncFilter; SyncEvo::FilterConfigNode::ConfigFilter m_sourceFilter; SourceFilters_t m_sourceFilters; }; } SE_END_CXX namespace GDBusCXX { using namespace SyncEvo::SessionCommon; using namespace SyncEvo; template<> struct dbus_traits : public dbus_struct_traits > > > > > > > > > > > > {}; /** * Similar to DBusArray, but with different native * types. Uses encoding/decoding from the base class, copies * to/from SharedBuffer as needed. * * DBusArray is more efficient because it avoids * copying the bytes from the D-Bus message when decoding, * but it is harder to use natively (cannot be copied). * SharedBuffer does ref counting for the memory chunk, * so once initialized, copying it is cheap. */ template <> struct dbus_traits : public dbus_traits< DBusArray > { typedef dbus_traits< DBusArray > base; typedef SharedBuffer host_type; typedef const SharedBuffer &arg_type; #ifdef GDBUS_CXX_GIO static void get(GDBusCXX::ExtractArgs &context, GDBusCXX::reader_type &iter, host_type &buffer) { base::host_type array; base::get(context, iter, array); buffer = SharedBuffer(reinterpret_cast(array.second), array.first); } #else static void get(GDBusCXX::connection_type *conn, GDBusCXX::message_type *msg, GDBusCXX::reader_type &iter, host_type &buffer) { base::host_type array; base::get(conn, msg, iter, array); buffer = SharedBuffer(reinterpret_cast(array.second), array.first); } #endif static void append(GDBusCXX::builder_type &builder, arg_type buffer) { base::host_type array(buffer.size(), reinterpret_cast(buffer.get())); base::append(builder, array); } }; template <> struct dbus_traits : public dbus_traits< std::string > { typedef dbus_traits< std::string > base; typedef SyncReport host_type; typedef const SyncReport &arg_type; #ifdef GDBUS_CXX_GIO static void get(GDBusCXX::ExtractArgs &context, GDBusCXX::reader_type &iter, host_type &report) { std::string dump; base::get(context, iter, dump); report = SyncReport(dump); } #else static void get(GDBusCXX::connection_type *conn, GDBusCXX::message_type *msg, GDBusCXX::reader_type &iter, host_type &report) { std::string dump; base::get(conn, msg, iter, dump); report = SyncReport(dump); } #endif static void append(GDBusCXX::builder_type &builder, arg_type report) { base::append(builder, report.toString()); } }; template <> struct dbus_traits : public dbus_traits< std::string > { typedef dbus_traits< std::string > base; typedef SyncSourceReport host_type; typedef const SyncSourceReport &arg_type; #ifdef GDBUS_CXX_GIO static void get(GDBusCXX::ExtractArgs &context, GDBusCXX::reader_type &iter, host_type &source) { std::string dump; base::get(context, iter, dump); SyncReport report = SyncReport(dump); const SyncSourceReport *foo = report.findSyncSourceReport("foo"); if (!foo) { SE_THROW("incomplete SyncReport"); } source = *foo; } #else static void get(GDBusCXX::connection_type *conn, GDBusCXX::message_type *msg, GDBusCXX::reader_type &iter, host_type &source) { std::string dump; base::get(conn, msg, iter, dump); SyncReport report = SyncReport(dump); const SyncSourceReport *foo = report.findSyncSourceReport("foo"); if (!foo) { SE_THROW("incomplete SyncReport"); } source = *foo; } #endif static void append(GDBusCXX::builder_type &builder, arg_type source) { SyncReport report; report.addSyncSourceReport("foo", source); base::append(builder, report.toString()); } }; } #endif // SESSION_COMMON_H syncevolution_1.4/src/dbus/server/session-helper.cpp000066400000000000000000000262141230021373600230610ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include "session-helper.h" #include "dbus-callbacks.h" #include "cmdline-wrapper.h" #include #include #include SE_BEGIN_CXX static void dumpString(const std::string &output) { fputs(output.c_str(), stdout); } /** * Same logging approach as in Server class: pretend that we * have reference counting for the SessionHelper class and * use mutex locking to prevent dangling pointers. */ class SessionHelperLogger : public Logger { Handle m_parentLogger; boost::shared_ptr m_helper; Level m_dbusLogLevel; public: SessionHelperLogger(const boost::shared_ptr &helper): m_parentLogger(Logger::instance()), m_helper(helper), m_dbusLogLevel(DEBUG) { } void setDBusLogLevel(Level level) { m_dbusLogLevel = level; } Level getDBusLogLevel() const { return m_dbusLogLevel; } virtual void remove() throw () { RecMutex::Guard guard = lock(); m_helper.reset(); } virtual void messagev(const MessageOptions &options, const char *format, va_list args) { RecMutex::Guard guard = lock(); static bool dbg = getenv("SYNCEVOLUTION_DEBUG"); if (dbg) { // Let parent LogRedirect or utility function handle the // output *in addition* to logging via D-Bus. That way // it'll be visible via our stdout/stderr (= console) // right away. // // In non-debug mode, nothing should get written to // stdout/stder, which got redirected into a pipe // read by our parend. In that mode, the parent will include // our output in its own output streams. va_list argsCopy; va_copy(argsCopy, args); if (m_parentLogger) { m_parentLogger.messagev(options, format, argsCopy); } else { formatLines(options.m_level, DEBUG, options.m_processName, options.m_prefix, format, argsCopy, boost::bind(dumpString, _1)); } va_end(argsCopy); } else if (m_parentLogger) { // Pass through to parent, but marked in such a way that // only the DLT and syslog logger will react to it, but // not LogStdout. That's necessary because // syncevo-dbus-server handles stdout for us. va_list argsCopy; va_copy(argsCopy, args); MessageOptions buffer(options); buffer.m_flags |= MessageOptions::ONLY_GLOBAL_LOG; m_parentLogger.messagev(buffer, format, argsCopy); va_end(argsCopy); } if (m_helper && options.m_level <= m_dbusLogLevel) { // send to parent string log = StringPrintfV(format, args); string strLevel = Logger::levelToStr(options.m_level); try { m_helper->emitLogOutput(strLevel, log, options.m_processName ? *options.m_processName : getProcessName()); } catch (...) { // Give up forwarding output. m_helper.reset(); } } } }; SessionHelper::SessionHelper(GMainLoop *loop, const GDBusCXX::DBusConnectionPtr &conn, const boost::shared_ptr &forkexec) : GDBusCXX::DBusObjectHelper(conn, std::string(SessionCommon::HELPER_PATH) + "/" + forkexec->getInstance(), SessionCommon::HELPER_IFACE, GDBusCXX::DBusObjectHelper::Callback_t(), // we don't care about a callback per message true), // direct connection, close it when done m_loop(loop), m_conn(conn), m_forkexec(forkexec), m_logger(new SessionHelperLogger(boost::shared_ptr(this, NopDestructor()))), emitLogOutput(*this, "LogOutput"), emitSyncProgress(*this, "SyncProgress"), emitSourceProgress(*this, "SourceProgress"), emitSourceSynced(*this, "SourceSynced"), emitWaiting(*this, "Waiting"), emitSyncSuccessStart(*this, "SyncSuccessStart"), emitConfigChanged(*this, "ConfigChanged"), emitPasswordRequest(*this, "PasswordRequest"), emitMessage(*this, "Message"), emitShutdown(*this, "Shutdown") { add(this, &SessionHelper::sync, "Sync"); add(this, &SessionHelper::restore, "Restore"); add(this, &SessionHelper::execute, "Execute"); add(this, &SessionHelper::passwordResponse, "PasswordResponse"); add(this, &SessionHelper::storeMessage, "StoreMessage"); add(this, &SessionHelper::connectionState, "ConnectionState"); add(emitLogOutput); add(emitSyncProgress); add(emitSourceProgress); add(emitSourceSynced); add(emitWaiting); add(emitSyncSuccessStart); add(emitConfigChanged); add(emitPasswordRequest); add(emitMessage); add(emitShutdown); } void SessionHelper::setDBusLogLevel(Logger::Level level) { m_logger->setDBusLogLevel(level); } Logger::Level SessionHelper::getDBusLogLevel() const { return m_logger->getDBusLogLevel(); } void SessionHelper::activate() { GDBusCXX::DBusObjectHelper::activate(); m_pushLogger.reset(m_logger); } SessionHelper::~SessionHelper() { m_pushLogger.reset(); m_logger.reset(); } void SessionHelper::run() { SuspendFlags &s = SuspendFlags::getSuspendFlags(); while (true) { if (s.getState() != SuspendFlags::NORMAL) { SE_LOG_DEBUG(NULL, "terminating because of suspend or abort signal"); break; } if (m_operation && m_operation()) { SE_LOG_DEBUG(NULL, "terminating as requested by operation"); break; } g_main_loop_run(m_loop); } } bool SessionHelper::connected() { return m_forkexec && m_forkexec->getState() == ForkExecChild::CONNECTED; } void SessionHelper::sync(const SessionCommon::SyncParams ¶ms, const boost::shared_ptr< GDBusCXX::Result2 > &result) { m_operation = boost::bind(&SessionHelper::doSync, this, params, result); g_main_loop_quit(m_loop); } bool SessionHelper::doSync(const SessionCommon::SyncParams ¶ms, const boost::shared_ptr< GDBusCXX::Result2 > &result) { try { m_sync.reset(new DBusSync(params, *this)); SyncReport report; SyncMLStatus status = m_sync->sync(&report); if (status) { // Clear the abort signal, to allow the process to send // out the D-Bus response. Our parent will signal us again // after it received the response. // TODO SE_THROW_EXCEPTION_STATUS(StatusException, "sync failed", status); } result->done(true, report); } catch (...) { dbusErrorCallback(result); } m_sync.reset(); // quit helper return true; } void SessionHelper::restore(const std::string &configName, const string &dir, bool before, const std::vector &sources, const boost::shared_ptr< GDBusCXX::Result1 > &result) { m_operation = boost::bind(&SessionHelper::doRestore, this, configName, dir, before, sources, result); g_main_loop_quit(m_loop); } bool SessionHelper::doRestore(const std::string &configName, const string &dir, bool before, const std::vector &sources, const boost::shared_ptr< GDBusCXX::Result1 > &result) { try { SessionCommon::SyncParams params; params.m_config = configName; DBusSync sync(params, *this); if (!sources.empty()) { BOOST_FOREACH(const std::string &source, sources) { FilterConfigNode::ConfigFilter filter; filter["sync"] = InitStateString("two-way", true); sync.setConfigFilter(false, source, filter); } // disable other sources FilterConfigNode::ConfigFilter disabled; disabled["sync"] = InitStateString("disabled", true); sync.setConfigFilter(false, "", disabled); } sync.restore(dir, before ? SyncContext::DATABASE_BEFORE_SYNC : SyncContext::DATABASE_AFTER_SYNC); result->done(true); } catch (...) { dbusErrorCallback(result); } // quit helper return true; } void SessionHelper::execute(const vector &args, const map &vars, const boost::shared_ptr< GDBusCXX::Result1 > &result) { m_operation = boost::bind(&SessionHelper::doExecute, this, args, vars, result); g_main_loop_quit(m_loop); } bool SessionHelper::doExecute(const vector &args, const map &vars, const boost::shared_ptr< GDBusCXX::Result1 > &result) { try { CmdlineWrapper cmdline(*this, args, vars); if (!cmdline.parse()) { SE_THROW_EXCEPTION(DBusSyncException, "arguments parsing error"); } bool success = false; // a command line operation can be many things, tell parent SessionCommon::RunOperation op; op = cmdline.isSync() ? SessionCommon::OP_SYNC : cmdline.isRestore() ? SessionCommon::OP_RESTORE : SessionCommon::OP_CMDLINE; emitSyncProgress(sysync::PEV_CUSTOM_START, op, 0, 0); try { success = cmdline.run(); } catch (...) { if (cmdline.configWasModified()) { emitConfigChanged(); } throw; } if (cmdline.configWasModified()) { emitConfigChanged(); } result->done(success); } catch (...) { dbusErrorCallback(result); } // quit helper return true; } void SessionHelper::passwordResponse(bool timedOut, bool aborted, const std::string &password) { if (m_sync) { m_sync->passwordResponse(timedOut, aborted, password); } else { SE_LOG_DEBUG(NULL, "discarding obsolete password response"); } } SE_END_CXX syncevolution_1.4/src/dbus/server/session-helper.h000066400000000000000000000134041230021373600225230ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef SESSION_HELPER_H #define SESSION_HELPER_H #include "session-common.h" #include "dbus-sync.h" #include #include #include #include #include SE_BEGIN_CXX class LogRedirect; class ForkExecChild; class SessionHelperLogger; /** * Waits for requests via the internal D-Bus connection in run(), sent * by the Session class in syncevo-dbus-server. Then for each request * it remembers what to do in m_operation and returns from the event * loop and executes the requested operation, pretty much like the * traditional syncevo-dbus-server did. */ class SessionHelper : public GDBusCXX::DBusObjectHelper, private boost::noncopyable { GMainLoop *m_loop; GDBusCXX::DBusConnectionPtr m_conn; boost::shared_ptr m_forkexec; boost::function m_operation; boost::shared_ptr m_logger; PushLogger m_pushLogger; /** valid during doSync() */ boost::scoped_ptr m_sync; /** called by main event loop: initiate a sync operation */ void sync(const SessionCommon::SyncParams ¶ms, const boost::shared_ptr< GDBusCXX::Result2 > &result); /** * called by run(): do the sync operation * @return true if the helper is meant to terminate */ bool doSync(const SessionCommon::SyncParams ¶ms, const boost::shared_ptr< GDBusCXX::Result2 > &result); void restore(const std::string &configName, const string &dir, bool before, const std::vector &sources, const boost::shared_ptr< GDBusCXX::Result1 > &result); bool doRestore(const std::string &configName, const string &dir, bool before, const std::vector &sources, const boost::shared_ptr< GDBusCXX::Result1 > &result); void execute(const vector &args, const map &vars, const boost::shared_ptr< GDBusCXX::Result1 > &result); bool doExecute(const vector &args, const map &vars, const boost::shared_ptr< GDBusCXX::Result1 > &result); /** SessionHelper.PasswordResponse */ void passwordResponse(bool timedOut, bool aborted, const std::string &password); public: SessionHelper(GMainLoop *loop, const GDBusCXX::DBusConnectionPtr &conn, const boost::shared_ptr &forkexec); ~SessionHelper(); void setDBusLogLevel(Logger::Level level); Logger::Level getDBusLogLevel() const; void activate(); void run(); GMainLoop *getLoop() const { return m_loop; } /** Still have connection to parent. Shortcut which asks the ForkExecChild class. */ bool connected(); boost::shared_ptr getForkExecChild() { return m_forkexec; } /** Server.LogOutput for the session D-Bus object */ GDBusCXX::EmitSignal3 emitLogOutput; /** SyncContext::displaySyncProgress */ GDBusCXX::EmitSignal4 emitSyncProgress; /** SyncContext::displaySourceProgress */ GDBusCXX::EmitSignal6 emitSourceProgress; /** SyncContext::m_sourceSyncedSignal */ GDBusCXX::EmitSignal2 emitSourceSynced; /** SyncContext::reportStepCmd -> true/false for "waiting on IO" */ GDBusCXX::EmitSignal1 emitWaiting; /** SyncContext::syncSuccessStart */ GDBusCXX::EmitSignal0Template emitSyncSuccessStart; /** Cmdline::configWasModified() */ GDBusCXX::EmitSignal0Template emitConfigChanged; /** SyncContext::askPassword */ GDBusCXX::EmitSignal2 emitPasswordRequest; /** send message to parent's connection (buffer, type, url) */ GDBusCXX::EmitSignal3, std::string, std::string> emitMessage; /** tell parent's connection to shut down */ GDBusCXX::EmitSignal0Template emitShutdown; /** store the next message received by the session's connection */ void storeMessage(const GDBusCXX::DBusArray &message, const std::string &type) { m_messageSignal(message, type); } typedef boost::signals2::signal &, const std::string &)> MessageSignal_t; MessageSignal_t m_messageSignal; /** store the latest connection state information */ void connectionState(const std::string &error) { m_connectionStateSignal(error); } typedef boost::signals2::signal ConnectionStateSignal_t; ConnectionStateSignal_t m_connectionStateSignal; }; SE_END_CXX #endif // SESSION_HELPER_H syncevolution_1.4/src/dbus/server/session.cpp000066400000000000000000001515721230021373600216120ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "session.h" #include "connection.h" #include "server.h" #include "client.h" #include "restart.h" #include "info-req.h" #include "session-common.h" #include "dbus-callbacks.h" #include "presence-status.h" #include #include #include #ifdef USE_DLT #include #endif #include #include using namespace GDBusCXX; SE_BEGIN_CXX /** A Proxy to the remote session. */ class SessionProxy : public GDBusCXX::DBusRemoteObject { public: SessionProxy(const GDBusCXX::DBusConnectionPtr &conn, const std::string &instance) : GDBusCXX::DBusRemoteObject(conn.get(), std::string(SessionCommon::HELPER_PATH) + "/" + instance, SessionCommon::HELPER_IFACE, SessionCommon::HELPER_DESTINATION, true), // This is a one-to-one connection. Close it. /* m_getNamedConfig (*this, "GetNamedConfig"), */ /* m_setNamedConfig (*this, "SetNamedConfig"), */ /* m_getReports (*this, "GetReports"), */ /* m_checkSource (*this, "CheckSource"), */ /* m_getDatabases (*this, "GetDatabases"), */ m_sync(*this, "Sync"), m_restore(*this, "Restore"), m_execute(*this, "Execute"), m_passwordResponse(*this, "PasswordResponse"), m_storeMessage(*this, "StoreMessage"), m_connectionState(*this, "ConnectionState"), /* m_abort (*this, "Abort"), */ /* m_suspend (*this, "Suspend"), */ /* m_getStatus (*this, "GetStatus"), */ /* m_getProgress (*this, "GetProgress"), */ /* m_restore (*this, "Restore"), */ /* m_execute (*this, "Execute"), */ /* m_serverShutdown (*this, "ServerShutdown"), */ /* m_passwordResponse (*this, "PasswordResponse"), */ /* m_setActive (*this, "SetActive"), */ /* m_statusChanged (*this, "StatusChanged", false), */ /* m_progressChanged (*this, "ProgressChanged", false), */ m_logOutput(*this, "LogOutput", false), m_syncProgress(*this, "SyncProgress", false), m_sourceProgress(*this, "SourceProgress", false), m_sourceSynced(*this, "SourceSynced", false), m_waiting(*this, "Waiting", false), m_syncSuccessStart(*this, "SyncSuccessStart", false), m_configChanged(*this, "ConfigChanged", false), m_passwordRequest(*this, "PasswordRequest", false), m_sendMessage(*this, "Message", false), m_shutdownConnection(*this, "Shutdown", false) {} /* GDBusCXX::DBusClientCall1 m_getNamedConfig; */ /* GDBusCXX::DBusClientCall1 m_setNamedConfig; */ /* GDBusCXX::DBusClientCall1 > m_getReports; */ /* GDBusCXX::DBusClientCall0 m_checkSource; */ /* GDBusCXX::DBusClientCall1 m_getDatabases; */ GDBusCXX::DBusClientCall2 m_sync; GDBusCXX::DBusClientCall1 m_restore; GDBusCXX::DBusClientCall1 m_execute; /* GDBusCXX::DBusClientCall0 m_serverShutdown; */ GDBusCXX::DBusClientCall0 m_passwordResponse; GDBusCXX::DBusClientCall0 m_storeMessage; GDBusCXX::DBusClientCall0 m_connectionState; /* GDBusCXX::DBusClientCall0 m_setActive; */ /* GDBusCXX::SignalWatch3 m_statusChanged; */ GDBusCXX::SignalWatch3 m_logOutput; GDBusCXX::SignalWatch4 m_syncProgress; GDBusCXX::SignalWatch6 m_sourceProgress; GDBusCXX::SignalWatch2 m_sourceSynced; GDBusCXX::SignalWatch1 m_waiting; GDBusCXX::SignalWatch0 m_syncSuccessStart; GDBusCXX::SignalWatch0 m_configChanged; GDBusCXX::SignalWatch2 m_passwordRequest; GDBusCXX::SignalWatch3, std::string, std::string> m_sendMessage; GDBusCXX::SignalWatch0 m_shutdownConnection; }; void Session::attach(const Caller_t &caller) { boost::shared_ptr client(m_server.findClient(caller)); if (!client) { throw runtime_error("unknown client"); } boost::shared_ptr me = m_me.lock(); if (!me) { throw runtime_error("session already deleted?!"); } client->attach(me); } void Session::detach(const Caller_t &caller) { boost::shared_ptr client(m_server.findClient(caller)); if (!client) { throw runtime_error("unknown client"); } client->detach(this); } /** * validate key/value property and copy it to the filter * if okay */ static void copyProperty(const StringPair &keyvalue, ConfigPropertyRegistry ®istry, FilterConfigNode::ConfigFilter &filter) { const std::string &name = keyvalue.first; const std::string &value = keyvalue.second; const ConfigProperty *prop = registry.find(name); if (!prop) { SE_THROW_EXCEPTION(InvalidCall, StringPrintf("unknown property '%s'", name.c_str())); } std::string error; if (!prop->checkValue(value, error)) { SE_THROW_EXCEPTION(InvalidCall, StringPrintf("invalid value '%s' for property '%s': '%s'", value.c_str(), name.c_str(), error.c_str())); } filter.insert(std::make_pair(keyvalue.first, InitStateString(keyvalue.second, true))); } static void setSyncFilters(const ReadOperations::Config_t &config,FilterConfigNode::ConfigFilter &syncFilter,std::map &sourceFilters) { ReadOperations::Config_t::const_iterator it; for (it = config.begin(); it != config.end(); ++it) { map::const_iterator sit; string name = it->first; if (name.empty()) { ConfigPropertyRegistry ®istry = SyncConfig::getRegistry(); for (sit = it->second.begin(); sit != it->second.end(); ++sit) { // read-only properties can (and have to be) ignored static const char *init[] = { "configName", "description", "score", "deviceName", "hardwareName", "templateName", "fingerprint" }; static const set< std::string, Nocase > special(init, init + (sizeof(init) / sizeof(*init))); if (special.find(sit->first) == special.end()) { copyProperty(*sit, registry, syncFilter); } } } else if (boost::starts_with(name, "source/")) { name = name.substr(strlen("source/")); FilterConfigNode::ConfigFilter &sourceFilter = sourceFilters[name]; ConfigPropertyRegistry ®istry = SyncSourceConfig::getRegistry(); for (sit = it->second.begin(); sit != it->second.end(); ++sit) { copyProperty(*sit, registry, sourceFilter); } } else { SE_THROW_EXCEPTION(InvalidCall, StringPrintf("invalid config entry '%s'", name.c_str())); } } } void Session::setConfig(bool update, bool temporary, const ReadOperations::Config_t &config) { setNamedConfig(m_configName, update, temporary, config); } void Session::setNamedConfig(const std::string &configName, bool update, bool temporary, const ReadOperations::Config_t &config) { PushLogger guard(m_me); if (m_runOperation != SessionCommon::OP_NULL) { string msg = StringPrintf("%s started, cannot change configuration at this time", runOpToString(m_runOperation).c_str()); SE_THROW_EXCEPTION(InvalidCall, msg); } if (m_status != SESSION_ACTIVE) { SE_THROW_EXCEPTION(InvalidCall, "session is not active, call not allowed at this time"); } // avoid the check if effect is the same as setConfig() if (m_configName != configName) { bool found = false; BOOST_FOREACH(const std::string &flag, m_flags) { if (boost::iequals(flag, "all-configs")) { found = true; break; } } if (!found) { SE_THROW_EXCEPTION(InvalidCall, "SetNameConfig() only allowed in 'all-configs' sessions"); } if (temporary) { SE_THROW_EXCEPTION(InvalidCall, "SetNameConfig() with temporary config change only supported for config named when starting the session"); } } m_server.getPresenceStatus().updateConfigPeers (configName, config); /** check whether we need remove the entire configuration */ if(!update && !temporary && config.empty()) { boost::shared_ptr syncConfig(new SyncConfig(configName)); if(syncConfig.get()) { syncConfig->remove(); m_setConfig = true; } return; } /* * validate input config and convert to filters; * if validation fails, no harm was done at this point yet */ FilterConfigNode::ConfigFilter syncFilter; SourceFilters_t sourceFilters; setSyncFilters(config, syncFilter, sourceFilters); if (temporary) { /* save temporary configs in session filters, either erasing old temporary settings or adding to them */ if (update) { m_syncFilter.insert(syncFilter.begin(), syncFilter.end()); BOOST_FOREACH(SourceFilters_t::value_type &source, sourceFilters) { SourceFilters_t::iterator it = m_sourceFilters.find(source.first); if (it != m_sourceFilters.end()) { // add to existing source filter it->second.insert(source.second.begin(), source.second.end()); } else { // add source filter m_sourceFilters.insert(source); } } } else { m_syncFilter = syncFilter; m_sourceFilters = sourceFilters; } m_tempConfig = true; } else { /* need to save configurations */ boost::shared_ptr from(new SyncConfig(configName)); /* if it is not clear mode and config does not exist, an error throws */ if(update && !from->exists()) { SE_THROW_EXCEPTION(NoSuchConfig, "The configuration '" + configName + "' doesn't exist" ); } if(!update) { list sources = from->getSyncSources(); list::iterator it; for(it = sources.begin(); it != sources.end(); ++it) { string source = "source/"; source += *it; ReadOperations::Config_t::const_iterator configIt = config.find(source); if(configIt == config.end()) { /** if no config for this source, we remove it */ from->removeSyncSource(*it); } else { /** just clear visiable properties, remove them and their values */ from->clearSyncSourceProperties(*it); } } from->clearSyncProperties(); } /** generate new sources in the config map */ for (ReadOperations::Config_t::const_iterator it = config.begin(); it != config.end(); ++it) { string sourceName = it->first; if(sourceName.find("source/") == 0) { sourceName = sourceName.substr(7); ///> 7 is the length of "source/" from->getSyncSourceNodes(sourceName); } } /* apply user settings */ from->setConfigFilter(true, "", syncFilter); map::iterator it; for (it = sourceFilters.begin(); it != sourceFilters.end(); ++it) { from->setConfigFilter(false, it->first, it->second); } // We need no interactive user interface, but we do need to handle // storing passwords in a keyring here. boost::shared_ptr syncConfig(new SyncContext(configName)); syncConfig->prepareConfigForWrite(); syncConfig->copy(*from, NULL); class KeyringUI : public UserInterface { InitStateString m_keyring; public: KeyringUI(const InitStateString &keyring) : m_keyring(keyring) {} // Implement UserInterface. virtual bool savePassword(const std::string &passwordName, const std::string &password, const ConfigPasswordKey &key) { return GetSavePasswordSignal()(m_keyring, passwordName, password, key); } virtual void readStdin(std::string &content) { SE_THROW("not implemented"); } virtual std::string askPassword(const std::string &passwordName, const std::string &descr, const ConfigPasswordKey &key) { SE_THROW("not implemented"); return ""; } } ui(syncConfig->getKeyring()); syncConfig->preFlush(ui); syncConfig->flush(); m_setConfig = true; } } void Session::initServer(SharedBuffer data, const std::string &messageType) { PushLogger guard(m_me); m_serverMode = true; m_initialMessage = data; m_initialMessageType = messageType; } void Session::sync(const std::string &mode, const SessionCommon::SourceModes_t &sourceModes) { PushLogger guard(m_me); if (m_runOperation == SessionCommon::OP_SYNC) { string msg = StringPrintf("%s started, cannot start again", runOpToString(m_runOperation).c_str()); SE_THROW_EXCEPTION(InvalidCall, msg); } else if (m_runOperation != SessionCommon::OP_NULL) { string msg = StringPrintf("%s started, cannot start sync", runOpToString(m_runOperation).c_str()); SE_THROW_EXCEPTION(InvalidCall, msg); } if (m_status != SESSION_ACTIVE) { SE_THROW_EXCEPTION(InvalidCall, "session is not active, call not allowed at this time"); } // Turn session into "running sync" now, before returning to // caller. Starting the helper (if needed) and making it // execute the sync is part of "running sync". runOperationAsync(SessionCommon::OP_SYNC, boost::bind(&Session::sync2, this, mode, sourceModes)); } void Session::sync2(const std::string &mode, const SessionCommon::SourceModes_t &sourceModes) { PushLogger guard(m_me); if (!m_forkExecParent || !m_helper) { SE_THROW("syncing cannot continue, helper died"); } // helper is ready, tell it what to do SyncParams params; params.m_config = m_configName; params.m_mode = mode; params.m_sourceModes = sourceModes; params.m_serverMode = m_serverMode; params.m_serverAlerted = m_serverAlerted; params.m_remoteInitiated = m_remoteInitiated; params.m_sessionID = m_sessionID; params.m_initialMessage = m_initialMessage; params.m_initialMessageType = m_initialMessageType; params.m_syncFilter = m_syncFilter; params.m_sourceFilter = m_sourceFilter; params.m_sourceFilters = m_sourceFilters; boost::shared_ptr c = m_connection.lock(); if (c && !c->mustAuthenticate()) { // unsetting username/password disables checking them params.m_syncFilter["password"] = InitStateString("", true); params.m_syncFilter["username"] = InitStateString("", true); } // Relay messages between connection and helper.If the // connection goes away, we need to tell the helper, because // otherwise it will never know that its message went into nirvana // and that it is waiting for a reply that will never come. // // We also need to send responses to the helper asynchronously // and ignore failures -> do it in our code instead of connection // signals directly. // // Session might quit before connection, so use instance // tracking. m_helper->m_sendMessage.activate(boost::bind(&Session::sendViaConnection, this, _1, _2, _3)); m_helper->m_shutdownConnection.activate(boost::bind(&Session::shutdownConnection, this)); boost::shared_ptr connection = m_connection.lock(); if (connection) { connection->m_messageSignal.connect(Connection::MessageSignal_t::slot_type(&Session::storeMessage, this, _1, _2).track(m_me)); connection->m_statusSignal.connect(Connection::StatusSignal_t::slot_type(&Session::connectionState, this, _1)); } // Helper implements Sync() asynchronously. If it completes // normally, dbusResultCb() will call doneCb() directly. Otherwise // the error is recorded before ending the session. Premature // exits by the helper are handled by D-Bus, which then will abort // the pending method call. m_helper->m_sync.start(params, boost::bind(&Session::dbusResultCb, m_me, "sync()", _1, _2, _3)); } void Session::abort() { PushLogger guard(m_me); if (m_runOperation != SessionCommon::OP_SYNC && m_runOperation != SessionCommon::OP_CMDLINE) { SE_THROW_EXCEPTION(InvalidCall, "sync not started, cannot abort at this time"); } if (m_forkExecParent) { // Tell helper to abort via SIGTERM. The signal might get // delivered so soon that the helper quits immediately. // Treat that as "aborted by user" instead of failure // in m_onQuit. m_wasAborted = true; m_forkExecParent->stop(SIGTERM); } if (m_syncStatus == SYNC_RUNNING || m_syncStatus == SYNC_SUSPEND) { m_syncStatus = SYNC_ABORT; fireStatus(true); } } void Session::suspend() { PushLogger guard(m_me); if (m_runOperation != SessionCommon::OP_SYNC && m_runOperation != SessionCommon::OP_CMDLINE) { SE_THROW_EXCEPTION(InvalidCall, "sync not started, cannot suspend at this time"); } if (m_forkExecParent) { // same as abort(), except that we use SIGINT m_wasAborted = true; m_forkExecParent->stop(SIGINT); } if (m_syncStatus == SYNC_RUNNING) { m_syncStatus = SYNC_SUSPEND; fireStatus(true); } } void Session::abortAsync(const SimpleResult &result) { PushLogger guard(m_me); if (!m_forkExecParent) { result.done(); } else { // Tell helper to quit, if necessary by aborting a running sync. // Once it is dead we know that the session no longer runs. // This must succeed; there is no timeout or failure mode. // TODO: kill helper after a certain amount of time?! m_forkExecParent->stop(SIGTERM); m_forkExecParent->m_onQuit.connect(boost::bind(&SimpleResult::done, result)); } } void Session::getStatus(std::string &status, uint32_t &error, SourceStatuses_t &sources) { PushLogger guard(m_me); status = syncStatusToString(m_syncStatus); if (m_stepIsWaiting) { status += ";waiting"; } error = m_error; sources = m_sourceStatus; } void Session::getProgress(int32_t &progress, SourceProgresses_t &sources) { PushLogger guard(m_me); progress = m_progData.getProgress(); sources = m_sourceProgress; } void Session::fireStatus(bool flush) { PushLogger guard(m_me); std::string status; uint32_t error; SourceStatuses_t sources; /** not force flushing and not timeout, return */ if(!flush && !m_statusTimer.timeout()) { return; } m_statusTimer.reset(); getStatus(status, error, sources); emitStatus(status, error, sources); } void Session::fireProgress(bool flush) { PushLogger guard(m_me); int32_t progress; SourceProgresses_t sources; /** not force flushing and not timeout, return */ if(!flush && !m_progressTimer.timeout()) { return; } m_progressTimer.reset(); getProgress(progress, sources); emitProgress(progress, sources); } boost::shared_ptr Session::createSession(Server &server, const std::string &peerDeviceID, const std::string &config_name, const std::string &session, const std::vector &flags) { boost::shared_ptr me(new Session(server, peerDeviceID, config_name, session, flags)); me->m_me = me; return me; } Session::Session(Server &server, const std::string &peerDeviceID, const std::string &config_name, const std::string &session, const std::vector &flags) : DBusObjectHelper(server.getConnection(), std::string("/org/syncevolution/Session/") + session, "org.syncevolution.Session", boost::bind(&Server::autoTermCallback, &server)), ReadOperations(config_name, server), m_server(server), m_flags(flags), m_sessionID(session), m_peerDeviceID(peerDeviceID), m_serverMode(false), m_serverAlerted(false), m_useConnection(false), m_tempConfig(false), m_setConfig(false), m_status(SESSION_IDLE), m_wasAborted(false), m_remoteInitiated(false), m_syncStatus(SYNC_QUEUEING), m_stepIsWaiting(false), m_priority(PRI_DEFAULT), m_error(0), m_statusTimer(100), m_progressTimer(50), m_restoreSrcTotal(0), m_restoreSrcEnd(0), m_runOperation(SessionCommon::OP_NULL), m_cmdlineOp(SessionCommon::OP_CMDLINE), emitStatus(*this, "StatusChanged"), emitProgress(*this, "ProgressChanged") { add(this, &Session::attach, "Attach"); add(this, &Session::detach, "Detach"); add(this, &Session::getFlags, "GetFlags"); add(this, &Session::getNormalConfigName, "GetConfigName"); add(static_cast(this), &ReadOperations::getConfigs, "GetConfigs"); add(static_cast(this), &ReadOperations::getConfig, "GetConfig"); add(static_cast(this), &ReadOperations::getNamedConfig, "GetNamedConfig"); add(this, &Session::setConfig, "SetConfig"); add(this, &Session::setNamedConfig, "SetNamedConfig"); add(static_cast(this), &ReadOperations::getReports, "GetReports"); add(static_cast(this), &ReadOperations::checkSource, "CheckSource"); add(static_cast(this), &ReadOperations::getDatabases, "GetDatabases"); add(this, &Session::sync, "Sync"); add(this, &Session::abort, "Abort"); add(this, &Session::suspend, "Suspend"); add(this, &Session::getStatus, "GetStatus"); add(this, &Session::getProgress, "GetProgress"); add(this, &Session::restore, "Restore"); add(this, &Session::checkPresence, "CheckPresence"); add(this, &Session::execute, "Execute"); add(emitStatus); add(emitProgress); SE_LOG_DEBUG(NULL, "session %s created", getPath()); } void Session::passwordRequest(const std::string &descr, const ConfigPasswordKey &key) { PushLogger guard(m_me); m_passwordRequest = m_server.passwordRequest(descr, key, m_me); } void Session::dbusResultCb(const std::string &operation, bool success, const SyncReport &report, const std::string &error) throw() { PushLogger guard(m_me); try { SE_LOG_DEBUG(NULL, "%s helper call completed, %s", operation.c_str(), !error.empty() ? error.c_str() : success ? "<>" : "<>"); if (error.empty()) { doneCb(success, report); } else { // Translate back into local exception, will be handled by // catch clause and (eventually) failureCb(). Exception::tryRethrowDBus(error); // generic fallback throw GDBusCXX::dbus_error("org.syncevolution.gdbuscxx.Exception", error); } } catch (...) { failureCb(); } } void Session::failureCb() throw() { PushLogger guard(m_me); try { if (m_status == SESSION_DONE) { // ignore errors that happen after session already closed, // only log them std::string explanation; Exception::handle(explanation, HANDLE_EXCEPTION_NO_ERROR); m_server.logOutput(getPath(), Logger::ERROR, explanation, ""); } else { // finish session with failure uint32_t error; try { throw; } catch (...) { // only record problem std::string explanation; error = Exception::handle(explanation, HANDLE_EXCEPTION_NO_ERROR); m_server.logOutput(getPath(), Logger::ERROR, explanation, ""); } // set error, but don't overwrite older one if (!m_error) { SE_LOG_DEBUG(NULL, "session failed: remember %d error", error); m_error = error; } // will fire status signal, including the error doneCb(false); } } catch (...) { // fatal problem, log it and terminate Exception::handle(HANDLE_EXCEPTION_FATAL); } } void Session::doneCb(bool success, const SyncReport &report) throw() { PushLogger guard(m_me); try { if (m_status == SESSION_DONE) { return; } m_status = SESSION_DONE; m_syncStatus = SYNC_DONE; if (!success && !m_error) { // some kind of local, internal problem m_error = STATUS_FATAL + sysync::LOCAL_STATUS_CODE; } fireStatus(true); boost::shared_ptr connection = m_connection.lock(); if (connection) { connection->shutdown(); } // tell everyone who is interested that our config changed (includes D-Bus signal) if (m_setConfig) { m_server.m_configChangedSignal(m_configName); } SE_LOG_DEBUG(NULL, "session %s done, config %s, %s, result %d", getPath(), m_configName.c_str(), m_setConfig ? "modified" : "not modified", m_error); m_doneSignal((SyncMLStatus)m_error, report); // now also kill helper m_helper.reset(); if (m_forkExecParent) { // Abort (just in case, helper should already be waiting // for SIGURG). m_forkExecParent->stop(SIGTERM); // Quit. m_forkExecParent->stop(SIGURG); } m_server.removeSyncSession(this); m_server.dequeue(this); } catch (...) { // fatal problem, log it and terminate (?!) Exception::handle(); } } Session::~Session() { SE_LOG_DEBUG(NULL, "session %s deconstructing", getPath()); // If we are not done yet, then something went wrong. doneCb(false); } /** child has quit before connecting, invoke result.failed() with suitable exception pending */ static void raiseChildTermError(int status, const SimpleResult &result) { try { SE_THROW(StringPrintf("helper died unexpectedly with return code %d before connecting", status)); } catch (...) { result.failed(); } } void Session::runOperationAsync(SessionCommon::RunOperation op, const SuccessCb_t &helperReady) { PushLogger guard(m_me); m_server.addSyncSession(this); m_runOperation = op; m_status = SESSION_RUNNING; m_syncStatus = SYNC_RUNNING; fireStatus(true); useHelperAsync(SimpleResult(helperReady, boost::bind(&Session::failureCb, this))); } void Session::useHelperAsync(const SimpleResult &result) { PushLogger guard(m_me); try { if (m_helper) { // exists already, invoke callback directly result.done(); } // Construct m_forkExecParent if it doesn't exist yet or not // currently starting. The only situation where the latter // might happen is when the helper is still starting when // a new request comes in. In that case we reuse the same // helper process for both operations. if (!m_forkExecParent || m_forkExecParent->getState() != ForkExecParent::STARTING) { std::vector args; args.push_back("--dbus-verbosity"); args.push_back(StringPrintf("%d", m_server.getDBusLogLevel())); m_forkExecParent = SyncEvo::ForkExecParent::create("syncevo-dbus-helper", args); #ifdef USE_DLT if (getenv("SYNCEVOLUTION_USE_DLT")) { m_forkExecParent->addEnvVar("SYNCEVOLUTION_USE_DLT", StringPrintf("%d", LoggerDLT::getCurrentDLTLogLevel())); } #endif // We own m_forkExecParent, so the "this" pointer for // onConnect will live longer than the signal in // m_forkExecParent -> no need for resource // tracking. onConnect sets up m_helper. The other two // only log the event. m_forkExecParent->m_onConnect.connect(bind(&Session::onConnect, this, _1)); m_forkExecParent->m_onQuit.connect(boost::bind(&Session::onQuit, this, _1)); m_forkExecParent->m_onFailure.connect(boost::bind(&Session::onFailure, this, _1, _2)); if (!getenv("SYNCEVOLUTION_DEBUG")) { // Any output from the helper is unexpected and will be // logged as error. The helper initializes stderr and // stdout redirection once it runs, so anything that // reaches us must have been problems during early process // startup or final shutdown. m_forkExecParent->m_onOutput.connect(bind(&Session::onOutput, this, _1, _2)); } } // Now also connect result with the right events. Will be // called after setting up m_helper (first come, first // serve). We copy the "result" instance with boost::bind, and // the creator of it must have made sure that we can invoke it // at any time without crashing. // // If the helper quits before connecting, the startup // failed. Need to remove that connection when successful. boost::signals2::connection c = m_forkExecParent->m_onQuit.connect(boost::bind(&raiseChildTermError, _1, result)); m_forkExecParent->m_onConnect.connect(boost::bind(&Session::useHelper2, this, result, c)); if (m_forkExecParent->getState() == ForkExecParent::IDLE) { m_forkExecParent->start(); } } catch (...) { // The assumption here is that any exception is related only // to the requested operation, and that the server itself is still // healthy. result.failed(); } } void Session::messagev(const MessageOptions &options, const char *format, va_list args) { // log with session path and empty process name, // just like the syncevo-dbus-helper does m_server.message2DBus(options, format, args, getPath(), ""); } static void Logging2ServerAndStdout(Server &server, const GDBusCXX::DBusObject_t &path, const Logger::MessageOptions &options, const char *format, ...) { va_list args; va_start(args, format); server.message2DBus(options, format, args, path, options.m_processName ? *options.m_processName : ""); va_end(args); } static void Logging2Server(Server &server, const GDBusCXX::DBusObject_t &path, const std::string &strLevel, const std::string &explanation, const std::string &procname) { static bool dbg = getenv("SYNCEVOLUTION_DEBUG"); if (dbg) { // Print to D-Bus directly. The helper handles its own // printing to the console. server.logOutput(path, Logger::strToLevel(strLevel.c_str()), explanation, procname); } else { // Print to D-Bus and console, because the helper // relies on us to do that. Its own stdout/stderr // was redirected into our pipe and any output // there is considered an error. Logger::MessageOptions options(Logger::strToLevel(strLevel.c_str())); options.m_processName = &procname; options.m_flags = Logger::MessageOptions::ALREADY_LOGGED; Logging2ServerAndStdout(server, path, options, "%s", explanation.c_str()); } } void Session::useHelper2(const SimpleResult &result, const boost::signals2::connection &c) { PushLogger guard(m_me); try { // helper is running, don't call result.failed() when it quits // sometime in the future c.disconnect(); // Verify that helper is really ready. Might not be the // case when something internally failed in onConnect. if (m_helper) { // Resend all output from helper via the server's own // LogOutput signal, with the session's object path as // first parameter. // // Any code in syncevo-dbus-server which might produce // output related to the session runs while a Session::LoggingGuard // captures output by pushing Session as logger onto the // logging stack. The Session::messagev implementation then // also calls m_server.logOutput, as if the syncevo-dbus-helper // had produced that output. // // The downside is that unrelated output (like // book-keeping messages about other clients) will also be // captured. m_helper->m_logOutput.activate(boost::bind(Logging2Server, boost::ref(m_server), getPath(), _1, _2, _3)); result.done(); } else { SE_THROW("internal error, helper not ready"); } } catch (...) { // Same assumption as above: let's hope the server is still // sane. result.failed(); } } void Session::onConnect(const GDBusCXX::DBusConnectionPtr &conn) throw () { PushLogger guard(m_me); try { std::string instance = m_forkExecParent->getInstance(); SE_LOG_DEBUG(NULL, "helper %s has connected", instance.c_str()); m_helper.reset(new SessionProxy(conn, instance)); // Activate signal watch on helper signals. m_helper->m_syncProgress.activate(boost::bind(&Session::syncProgress, this, _1, _2, _3, _4)); m_helper->m_sourceProgress.activate(boost::bind(&Session::sourceProgress, this, _1, _2, _3, _4, _5, _6)); m_helper->m_sourceSynced.activate(boost::bind(boost::ref(m_sourceSynced), _1, _2)); m_helper->m_waiting.activate(boost::bind(&Session::setWaiting, this, _1)); m_helper->m_syncSuccessStart.activate(boost::bind(boost::ref(Session::m_syncSuccessStartSignal))); m_helper->m_configChanged.activate(boost::bind(boost::ref(m_server.m_configChangedSignal), "")); m_helper->m_passwordRequest.activate(boost::bind(&Session::passwordRequest, this, _1, _2)); } catch (...) { Exception::handle(); } } void Session::onQuit(int status) throw () { PushLogger guard(m_me); try { SE_LOG_DEBUG(NULL, "helper quit with return code %d, was %s", status, m_wasAborted ? "aborted" : "not aborted"); if (m_status == SESSION_DONE) { // don't care anymore whether the helper goes down, not an error SE_LOG_DEBUG(NULL, "session already completed, ignore helper"); } else if (m_wasAborted && ((WIFEXITED(status) && WEXITSTATUS(status) == 0) || (WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM))) { SE_LOG_DEBUG(NULL, "helper terminated via SIGTERM, as expected"); if (!m_error) { m_error = sysync::LOCERR_USERABORT; SE_LOG_DEBUG(NULL, "helper was asked to quit -> error %d = LOCERR_USERABORT", m_error); } } else { // Premature exit from helper?! Not necessarily, it could // be that we get the "helper has quit" signal from // ForkExecParent before processing the helper's D-Bus // method reply. So instead of recording an error here, // wait for that reply. If the helper died without sending // it, then D-Bus will generate a "connection lost" error // for our pending method call. // // Except that libdbus does not deliver that error // reliably. As a workaround, schedule closing the // session as an idle callback, after that potential // future method return call was handled. The assumption // is that it is pending - it must be, because with the // helper gone, IO with it must be ready. Just to be sure // a small delay is used. } m_server.addTimeout(boost::bind(&Session::doneCb, m_me, false, SyncReport()), 0.1 /* seconds */); } catch (...) { Exception::handle(); } } void Session::onFailure(SyncMLStatus status, const std::string &explanation) throw () { PushLogger guard(m_me); try { SE_LOG_DEBUG(NULL, "helper failed, status code %d = %s, %s", status, Status2String(status).c_str(), explanation.c_str()); } catch (...) { Exception::handle(); } } void Session::onOutput(const char *buffer, size_t length) { PushLogger guard(m_me); // treat null-bytes inside the buffer like line breaks size_t off = 0; do { SE_LOG_ERROR("session-helper", "%s", buffer + off); off += strlen(buffer + off) + 1; } while (off < length); } void Session::activateSession() { PushLogger guard(m_me); if (m_status != SESSION_IDLE) { SE_THROW("internal error, session changing from non-idle to active"); } m_status = SESSION_ACTIVE; if (m_syncStatus == SYNC_QUEUEING) { m_syncStatus = SYNC_IDLE; fireStatus(true); } boost::shared_ptr c = m_connection.lock(); if (c) { c->ready(); } m_sessionActiveSignal(); } void Session::passwordResponse(bool timedOut, bool aborted, const std::string &password) { PushLogger guard(m_me); if (m_helper) { // Ignore communicaton failures with helper here, // we'll notice that elsewhere m_helper->m_passwordResponse.start(timedOut, aborted, password, boost::function()); } } void Session::syncProgress(sysync::TProgressEventEnum type, int32_t extra1, int32_t extra2, int32_t extra3) { PushLogger guard(m_me); switch(type) { case sysync::PEV_CUSTOM_START: m_cmdlineOp = (RunOperation)extra1; break; case sysync::PEV_SESSIONSTART: m_progData.setStep(ProgressData::PRO_SYNC_INIT); fireProgress(true); break; case sysync::PEV_SESSIONEND: // Ignore the error here. It was seen // (TestSessionAPIsDummy.testAutoSyncNetworkFailure) that the // engine reports 20017 = user abort when the real error is a // transport error encountered outside of the // engine. Recording the error as seen by the engine leads to // an incorrect final session result. Instead wait for the // result of the sync method invocation. // // if((uint32_t)extra1 != m_error) { // SE_LOG_DEBUG(NULL, "session sync progress: failed with code %d", extra1); // m_error = extra1; // fireStatus(true); // } m_progData.setStep(ProgressData::PRO_SYNC_INVALID); fireProgress(true); break; case sysync::PEV_SENDSTART: m_progData.sendStart(); break; case sysync::PEV_SENDEND: case sysync::PEV_RECVSTART: case sysync::PEV_RECVEND: m_progData.receiveEnd(); fireProgress(); break; case sysync::PEV_DISPLAY100: case sysync::PEV_SUSPENDCHECK: case sysync::PEV_DELETING: break; case sysync::PEV_SUSPENDING: m_syncStatus = SYNC_SUSPEND; fireStatus(true); break; default: ; } } void Session::sourceProgress(sysync::TProgressEventEnum type, const std::string &sourceName, SyncMode sourceSyncMode, int32_t extra1, int32_t extra2, int32_t extra3) { PushLogger guard(m_me); // a command line operation can be many things, helper must have told us SessionCommon::RunOperation op = m_runOperation == SessionCommon::OP_CMDLINE ? m_cmdlineOp : m_runOperation; switch(op) { case SessionCommon::OP_SYNC: { // Helper will create new source entries by sending a // sysync::PEV_PREPARING with SYNC_NONE. Must fire progress // and status events for such new sources. SourceProgresses_t::iterator pit = m_sourceProgress.find(sourceName); bool sourceProgressCreated = pit == m_sourceProgress.end(); SourceProgress &progress = sourceProgressCreated ? m_sourceProgress[sourceName] : pit->second; SourceStatuses_t::iterator sit = m_sourceStatus.find(sourceName); bool sourceStatusCreated = sit == m_sourceStatus.end(); SourceStatus &status = sourceStatusCreated ? m_sourceStatus[sourceName] : sit->second; switch(type) { case sysync::PEV_SYNCSTART: if (sourceSyncMode != SYNC_NONE) { m_progData.setStep(ProgressData::PRO_SYNC_UNINIT); fireProgress(); } break; case sysync::PEV_SYNCEND: if (sourceSyncMode != SYNC_NONE) { status.set(PrettyPrintSyncMode(sourceSyncMode), "done", extra1); fireStatus(true); } break; case sysync::PEV_PREPARING: if (sourceSyncMode != SYNC_NONE) { progress.m_phase = "preparing"; progress.m_prepareCount = extra1; progress.m_prepareTotal = extra2; m_progData.itemPrepare(); fireProgress(true); } else { // Check whether the sources where created. if (sourceProgressCreated) { fireProgress(); } if (sourceStatusCreated) { fireStatus(); } } break; case sysync::PEV_ITEMSENT: if (sourceSyncMode != SYNC_NONE) { progress.m_phase = "sending"; progress.m_sendCount = extra1; progress.m_sendTotal = extra2; fireProgress(true); } break; case sysync::PEV_ITEMRECEIVED: if (sourceSyncMode != SYNC_NONE) { progress.m_phase = "receiving"; progress.m_receiveCount = extra1; progress.m_receiveTotal = extra2; m_progData.itemReceive(sourceName, extra1, extra2); fireProgress(true); } break; case sysync::PEV_ALERTED: if (sourceSyncMode != SYNC_NONE) { status.set(PrettyPrintSyncMode(sourceSyncMode), "running", 0); fireStatus(true); m_progData.setStep(ProgressData::PRO_SYNC_DATA); m_progData.addSyncMode(sourceSyncMode); fireProgress(); } break; default: ; } break; } case SessionCommon::OP_RESTORE: { switch(type) { case sysync::PEV_ALERTED: // count the total number of sources to be restored m_restoreSrcTotal++; break; case sysync::PEV_SYNCSTART: { if (sourceSyncMode != SYNC_NONE) { SourceStatus &status = m_sourceStatus[sourceName]; // set statuses as 'restore-from-backup' status.set(PrettyPrintSyncMode(sourceSyncMode), "running", 0); fireStatus(true); } break; } case sysync::PEV_SYNCEND: { if (sourceSyncMode != SYNC_NONE) { m_restoreSrcEnd++; SourceStatus &status = m_sourceStatus[sourceName]; status.set(PrettyPrintSyncMode(sourceSyncMode), "done", 0); m_progData.setProgress(100 * m_restoreSrcEnd / m_restoreSrcTotal); fireStatus(true); fireProgress(true); } break; } default: break; } break; } default: break; } } bool Session::setFilters(SyncConfig &config) { PushLogger guard(m_me); /** apply temporary configs to config */ config.setConfigFilter(true, "", m_syncFilter); // set all sources in the filter to config BOOST_FOREACH(const SourceFilters_t::value_type &value, m_sourceFilters) { config.setConfigFilter(false, value.first, value.second); } return m_tempConfig; } void Session::setWaiting(bool isWaiting) { PushLogger guard(m_me); // if stepInfo doesn't change, then ignore it to avoid duplicate status info if(m_stepIsWaiting != isWaiting) { m_stepIsWaiting = isWaiting; fireStatus(true); } } void Session::restore(const string &dir, bool before, const std::vector &sources) { PushLogger guard(m_me); if (m_runOperation == SessionCommon::OP_RESTORE) { string msg = StringPrintf("restore started, cannot restore again"); SE_THROW_EXCEPTION(InvalidCall, msg); } else if (m_runOperation != SessionCommon::OP_NULL) { // actually this never happen currently, for during the real restore process, // it never poll the sources in default main context string msg = StringPrintf("%s started, cannot restore", runOpToString(m_runOperation).c_str()); SE_THROW_EXCEPTION(InvalidCall, msg); } if (m_status != SESSION_ACTIVE) { SE_THROW_EXCEPTION(InvalidCall, "session is not active, call not allowed at this time"); } runOperationAsync(SessionCommon::OP_RESTORE, boost::bind(&Session::restore2, this, dir, before, sources)); } void Session::restore2(const string &dir, bool before, const std::vector &sources) { PushLogger guard(m_me); if (!m_forkExecParent || !m_helper) { SE_THROW("syncing cannot continue, helper died"); } // helper is ready, tell it what to do m_helper->m_restore.start(m_configName, dir, before, sources, boost::bind(&Session::dbusResultCb, m_me, "restore()", _1, SyncReport(), _2)); } void Session::execute(const vector &args, const map &vars) { PushLogger guard(m_me); if (m_runOperation == SessionCommon::OP_CMDLINE) { SE_THROW_EXCEPTION(InvalidCall, "cmdline started, cannot start again"); } else if (m_runOperation != SessionCommon::OP_NULL) { string msg = StringPrintf("%s started, cannot start cmdline", runOpToString(m_runOperation).c_str()); SE_THROW_EXCEPTION(InvalidCall, msg); } if (m_status != SESSION_ACTIVE) { SE_THROW_EXCEPTION(InvalidCall, "session is not active, call not allowed at this time"); } runOperationAsync(SessionCommon::OP_CMDLINE, boost::bind(&Session::execute2, this, args, vars)); } void Session::execute2(const vector &args, const map &vars) { PushLogger guard(m_me); if (!m_forkExecParent || !m_helper) { SE_THROW("syncing cannot continue, helper died"); } // helper is ready, tell it what to do m_helper->m_execute.start(args, vars, boost::bind(&Session::dbusResultCb, m_me, "execute()", _1, SyncReport(), _2)); } /*Implementation of Session.CheckPresence */ void Session::checkPresence (string &status) { PushLogger guard(m_me); vector transport; m_server.checkPresence(m_configName, status, transport); } void Session::sendViaConnection(const DBusArray buffer, const std::string &type, const std::string &url) { PushLogger guard(m_me); try { boost::shared_ptr connection = m_connection.lock(); if (!connection) { SE_THROW_EXCEPTION(TransportException, "D-Bus peer has disconnected"); } connection->send(buffer, type, url); } catch (...) { std::string explanation; Exception::handle(explanation); connectionState(explanation); } } void Session::shutdownConnection() { PushLogger guard(m_me); try { boost::shared_ptr connection = m_connection.lock(); if (!connection) { SE_THROW_EXCEPTION(TransportException, "D-Bus peer has disconnected"); } connection->sendFinalMsg(); } catch (...) { std::string explanation; Exception::handle(explanation); connectionState(explanation); } } void Session::storeMessage(const DBusArray &message, const std::string &type) { PushLogger guard(m_me); // ignore errors if (m_helper) { m_helper->m_storeMessage.start(message, type, boost::function()); } } void Session::connectionState(const std::string &error) { PushLogger guard(m_me); // ignore errors if (m_helper) { m_helper->m_connectionState.start(error, boost::function()); } } SE_END_CXX syncevolution_1.4/src/dbus/server/session.h000066400000000000000000000434621230021373600212550ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef SESSION_H #define SESSION_H #include #include #include #include #include #include "session-common.h" #include "read-operations.h" #include "progress-data.h" #include "source-progress.h" #include "source-status.h" #include "timer.h" #include "timeout.h" #include "resource.h" #include "dbus-callbacks.h" SE_BEGIN_CXX class Connection; class Server; class ForkExecParent; class SessionProxy; class InfoReq; /** * Represents and implements the Session interface. Use * boost::shared_ptr to track it and ensure that there are references * to it as long as the connection is needed. * * The actual implementation is split into two parts: * - state as exposed via D-Bus is handled entirely in this class * - syncing and command line execution run inside * the forked syncevo-dbus-helper * * This allows creating and tracking a Session locally in * syncevo-dbus-server and minimizes asynchronous calls into the * helper. The helper is started on demand (which might be never, * for simple sessions). */ class Session : public GDBusCXX::DBusObjectHelper, public Logger, public Resource, private ReadOperations, private boost::noncopyable { public: /** * the sync status for session */ enum SyncStatus { SYNC_QUEUEING, ///< waiting to become ready for use SYNC_IDLE, ///< ready, session is initiated but sync not started SYNC_RUNNING, ///< sync is running SYNC_ABORT, ///< sync is aborting SYNC_SUSPEND, ///< sync is suspending SYNC_DONE, ///< sync is done SYNC_ILLEGAL }; typedef std::map SourceStatuses_t; typedef std::map SourceProgresses_t; private: Server &m_server; std::vector m_flags; const std::string m_sessionID; std::string m_peerDeviceID; /** Starts the helper, on demand (see useHelperAsync()). */ boost::shared_ptr m_forkExecParent; /** The D-Bus proxy for the helper. */ boost::shared_ptr m_helper; /** * Ensures that helper is running and that its D-Bus API is * available via m_helper, then invokes the success * callback. Startup errors are reported back via the error * callback. It is the responsibility of that error callback to * turn the session into the right failure state, usually via * Session::failed(). Likewise, any unexpected failures or helper * shutdowns need to be monitored by the caller of * useHelperAsync(). useHelperAsync() merely logs these events. * * useHelperAsync() and its helper function, useHelper2(), are the * ones called directly from the main event loop. They ensure that * any exceptions thrown inside them, including exceptions thrown * by the result.done(), are logged and turned into * result.failed() calls. * * In practice, the helper is started at most once per session, to * run the operation (see runOperation()). When it terminates, the * session is either considered "done" or "failed", depending on * whether the operation has completed already. */ void useHelperAsync(const SimpleResult &result); /** * Finish the work started by useHelperAsync once helper has * connected. The operation might still fail at this point. */ void useHelper2(const SimpleResult &result, const boost::signals2::connection &c); /** set up m_helper */ void onConnect(const GDBusCXX::DBusConnectionPtr &conn) throw (); /** unset m_helper but not m_forkExecParent (still processing signals) */ void onQuit(int result) throw (); /** set after abort() and suspend(), to turn "child died' into the LOCERR_USERABORT status code */ void expectChildTerm(int result) throw (); /** log failure */ void onFailure(SyncMLStatus status, const std::string &explanation) throw (); /** log error output from helper */ void onOutput(const char *buffer, size_t length); bool m_serverMode; bool m_serverAlerted; SharedBuffer m_initialMessage; string m_initialMessageType; boost::weak_ptr m_connection; std::string m_connectionError; bool m_useConnection; /** temporary config changes */ FilterConfigNode::ConfigFilter m_syncFilter; FilterConfigNode::ConfigFilter m_sourceFilter; SessionCommon::SourceFilters_t m_sourceFilters; /** whether dbus clients set temporary configs */ bool m_tempConfig; /** * whether the dbus clients updated, removed or cleared configs, * ignoring temporary configuration changes */ bool m_setConfig; /** Session life cycle */ enum SessionStatus { SESSION_IDLE, /**< not active yet, only Detach() allowed */ SESSION_ACTIVE, /**< active, config changes and Sync()/Execute() allowed */ SESSION_RUNNING, /**< one-time operation (Sync() or Execute()) in progress */ SESSION_DONE /**< operation completed, only Detach() still allowed */ }; SessionStatus m_status; /** * set when operation was aborted, enables special handling of "child quit" in onQuit(). */ bool m_wasAborted; /** * True iff we initiated the sync. */ bool m_remoteInitiated; SyncStatus m_syncStatus; /** maps to names as used in D-Bus API */ inline std::string static syncStatusToString(SyncStatus state) { static const char * const strings[SYNC_ILLEGAL] = { "queueing", "idle", "running", "aborting", "suspending", "done" }; return state >= SYNC_QUEUEING && state < SYNC_ILLEGAL ? strings[state] : ""; } /** step info: whether engine is waiting for something */ bool m_stepIsWaiting; /** * Priority which determines position in queue. * Lower is more important. PRI_DEFAULT is zero. */ int m_priority; /** progress data, holding progress calculation related info */ ProgressData m_progData; SourceStatuses_t m_sourceStatus; uint32_t m_error; SourceProgresses_t m_sourceProgress; // syncProgress() and sourceProgress() turn raw data from helper // into usable information on D-Bus server side void syncProgress(sysync::TProgressEventEnum type, int32_t extra1, int32_t extra2, int32_t extra3); void sourceProgress(sysync::TProgressEventEnum type, const std::string &sourceName, SyncMode sourceSyncMode, int32_t extra1, int32_t extra2, int32_t extra3); /** timer for fire status/progress usages */ Timer m_statusTimer; Timer m_progressTimer; /** the total number of sources to be restored */ int m_restoreSrcTotal; /** the number of sources that have been restored */ int m_restoreSrcEnd; /** * Wrapper around useHelperAsync() which sets up the session * to execute a specific operation (sync, command line, ...). */ void runOperationAsync(SessionCommon::RunOperation op, const SuccessCb_t &helperReady); /** * A Session can be used for exactly one of the operations. This * is the one. This gets set by the D-Bus method implementation * which triggers the operation. All other D-Bus method * implementations need to check it before allowing an operation * or method call which would conflict or be illegal. */ SessionCommon::RunOperation m_runOperation; /** * If m_runOperation == OP_CMDLINE, then we need further information * from the helper about the actual operation. We get that information * via a sync progress signal with event == PEV_CUSTOM_START. */ SessionCommon::RunOperation m_cmdlineOp; /** Session.Attach() */ void attach(const GDBusCXX::Caller_t &caller); /** Session.Detach() */ void detach(const GDBusCXX::Caller_t &caller); /** Session.GetStatus() */ void getStatus(std::string &status, uint32_t &error, SourceStatuses_t &sources); /** Session.GetProgress() */ void getProgress(int32_t &progress, SourceProgresses_t &sources); /** Session.Restore() */ void restore(const string &dir, bool before, const std::vector &sources); void restore2(const string &dir, bool before, const std::vector &sources); /** Session.checkPresence() */ void checkPresence (string &status); /** Session.Execute() */ void execute(const vector &args, const map &vars); void execute2(const vector &args, const map &vars); /** * Must be called each time that properties changing the * overall status are changed (m_syncStatus, m_error, m_sourceStatus). * Ensures that the corresponding D-Bus signal is sent. * * Doesn't always send the signal immediately, because often it is * likely that more status changes will follow shortly. To ensure * that the "final" status is sent, call with flush=true. * * @param flush force sending the current status */ void fireStatus(bool flush = false); /** like fireStatus() for progress information */ void fireProgress(bool flush = false); public: /** Session.StatusChanged */ GDBusCXX::EmitSignal3 emitStatus; /** Session.ProgressChanged */ GDBusCXX::EmitSignal2 emitProgress; /** * Sessions must always be held in a shared pointer * because some operations depend on that. This * constructor function here ensures that and * also adds a weak pointer to the instance itself, * so that it can create more shared pointers as * needed. */ static boost::shared_ptr createSession(Server &server, const std::string &peerDeviceID, const std::string &config_name, const std::string &session, const std::vector &flags = std::vector()); /** * automatically marks the session as completed before deleting it */ ~Session(); /** * explicitly mark an idle session as completed, even if it doesn't * get deleted yet (exceptions not expected by caller) */ void done(bool success) throw () { doneCb(success); } private: Session(Server &server, const std::string &peerDeviceID, const std::string &config_name, const std::string &session, const std::vector &flags = std::vector()); boost::weak_ptr m_me; boost::shared_ptr m_passwordRequest; void passwordRequest(const std::string &descr, const ConfigPasswordKey &key); void sendViaConnection(const GDBusCXX::DBusArray buffer, const std::string &type, const std::string &url); void shutdownConnection(); void storeMessage(const GDBusCXX::DBusArray &message, const std::string &type); void connectionState(const std::string &error); /** * Sends all messages via D-Bus, as if they came from the session * helper. To be activated only temporarily while executing code * in the server which is related to the session. */ virtual void messagev(const MessageOptions &options, const char *format, va_list args); public: enum { PRI_CMDLINE = -10, PRI_DEFAULT = 0, PRI_CONNECTION = 10, PRI_AUTOSYNC = 20 }; /** * Default priority is 0. Higher means less important. */ void setPriority(int priority) { m_priority = priority; } int getPriority() const { return m_priority; } bool isServerAlerted() const { return m_serverAlerted; } void setServerAlerted(bool serverAlerted) { m_serverAlerted = serverAlerted; } void initServer(SharedBuffer data, const std::string &messageType); void setStubConnection(const boost::shared_ptr c) { m_connection = c; m_useConnection = c; } boost::weak_ptr getStubConnection() { return m_connection; } bool useStubConnection() { return m_useConnection; } /** * After the connection closes, the Connection instance is * destructed immediately. This is necessary so that the * corresponding cleanup can remove all other classes * only referenced by the Connection. * * This leads to the problem that an active sync cannot * query the final error code of the connection. This * is solved by setting a generic error code here when * the sync starts and overwriting it when the connection * closes. */ void setStubConnectionError(const std::string &error) { m_connectionError = error; } std::string getStubConnectionError() { return m_connectionError; } Server &getServer() { return m_server; } std::string getConfigName() { return m_configName; } std::string getSessionID() const { return m_sessionID; } std::string getPeerDeviceID() const { return m_peerDeviceID; } /** Session.GetFlags() */ std::vector getFlags() { return m_flags; } /** Session.GetConfigName() */ std::string getNormalConfigName() { return SyncConfig::normalizeConfigString(m_configName); } /** Session.SetConfig() */ void setConfig(bool update, bool temporary, const ReadOperations::Config_t &config); /** Session.SetNamedConfig() */ void setNamedConfig(const std::string &configName, bool update, bool temporary, const ReadOperations::Config_t &config); /** Session.Sync() */ void sync(const std::string &mode, const SessionCommon::SourceModes_t &sourceModes); /** * finish the work started by sync once helper is ready (invoked * by useHelperAsync() and thus may throw exceptions) */ void sync2(const std::string &mode, const SessionCommon::SourceModes_t &sourceModes); /** Session.Abort() */ void abort(); /** abort active session, trigger result once done */ void abortAsync(const SimpleResult &result); /** Session.Suspend() */ void suspend(); /** * step info for engine: whether the engine is blocked by something * If yes, 'waiting' will be appended as specifiers in the status string. * see GetStatus documentation. */ void setWaiting(bool isWaiting); SyncStatus getSyncStatus() const { return m_syncStatus; } /** session was just activated */ typedef boost::signals2::signal SessionActiveSignal_t; SessionActiveSignal_t m_sessionActiveSignal; /** sync is successfully started */ typedef boost::signals2::signal SyncSuccessStartSignal_t; SyncSuccessStartSignal_t m_syncSuccessStartSignal; /** sync completed (may have failed) */ typedef boost::signals2::signal DoneSignal_t; DoneSignal_t m_doneSignal; /** a source was synced, emitted multiple times during a multi-cycle sync */ typedef boost::signals2::signal SourceSyncedSignal_t; SourceSyncedSignal_t m_sourceSynced; /** * Called by server when the session is ready to run. * Only the session itself can deactivate itself. */ void activateSession(); /** * Called by server when it has a password response for the * session. The session ensures that it only has one pending * request at a time, so these parameters are enough to identify * the request. */ void passwordResponse(bool timedOut, bool aborted, const std::string &password); void setRemoteInitiated (bool remote) { m_remoteInitiated = remote;} private: /** set m_syncFilter and m_sourceFilters to config */ virtual bool setFilters(SyncConfig &config); void dbusResultCb(const std::string &operation, bool success, const SyncReport &report, const std::string &error) throw(); /** * to be called inside a catch() clause: returns error for any * pending D-Bus method and then calls doneCb() */ void failureCb() throw(); /** * explicitly mark the session as completed, even if it doesn't * get deleted yet (invoked directly or indirectly from event * loop and thus must not throw exceptions) * * @param success if false, then ensure that m_error is set * before finalizing the session * @param report valid only in case of success */ void doneCb(bool success, const SyncReport &report = SyncReport()) throw(); }; SE_END_CXX #endif // SESSION_H syncevolution_1.4/src/dbus/server/source-progress.h000066400000000000000000000042101230021373600227200ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef SOURCE_PROGRESS_H #define SOURCE_PROGRESS_H #include "gdbus-cxx-bridge.h" #include SE_BEGIN_CXX struct SourceProgress { SourceProgress() : m_phase(""), m_prepareCount(-1), m_prepareTotal(-1), m_sendCount(-1), m_sendTotal(-1), m_receiveCount(-1), m_receiveTotal(-1) {} std::string m_phase; int32_t m_prepareCount, m_prepareTotal; int32_t m_sendCount, m_sendTotal; int32_t m_receiveCount, m_receiveTotal; }; SE_END_CXX namespace GDBusCXX { using namespace SyncEvo; template<> struct dbus_traits : public dbus_struct_traits > > > > > > > {}; } #endif // SOURCE_PROGRESS_H syncevolution_1.4/src/dbus/server/source-status.h000066400000000000000000000033231230021373600224030ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef SOURCE_STATUS_H #define SOURCE_STATUS_H #include "gdbus-cxx-bridge.h" #include SE_BEGIN_CXX struct SourceStatus { SourceStatus() : m_mode("none"), m_status("idle"), m_error(0) {} void set(const std::string &mode, const std::string &status, uint32_t error) { m_mode = mode; m_status = status; m_error = error; } std::string m_mode; std::string m_status; uint32_t m_error; }; SE_END_CXX namespace GDBusCXX { using namespace SyncEvo; template<> struct dbus_traits : public dbus_struct_traits > > > {}; } #endif // SOURCE_STATUS_H syncevolution_1.4/src/dbus/server/sync-helper.cpp000066400000000000000000000204271230021373600223520ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "session-helper.h" #include #include #include #include #include #include using namespace SyncEvo; using namespace GDBusCXX; namespace { GMainLoop *loop = NULL; int logLevelDBus = Logger::INFO; // that one is actually never called. probably a bug in ForkExec - it should // call m_onFailure instead of throwing an exception void onFailure(const std::string &error, bool &failed) throw () { SE_LOG_DEBUG(NULL, "failure, quitting now: %s", error.c_str()); failed = true; } void onConnect(const DBusConnectionPtr &conn, const boost::shared_ptr &forkexec, boost::shared_ptr &helper) { helper.reset(new SessionHelper(loop, conn, forkexec)); helper->activate(); helper->setDBusLogLevel(Logger::Level(logLevelDBus)); } void onAbort() { g_main_loop_quit(loop); } } // anonymous namespace /** * This program is a helper of syncevo-dbus-server which provides the * Connection and Session DBus interfaces and runs individual sync * sessions. It is only intended to be started by syncevo-dbus-server, */ int main(int argc, char **argv, char **envp) { // delay the client for debugging purposes const char *delay = getenv("SYNCEVOLUTION_LOCAL_CHILD_DELAY"); if (delay) { Sleep(atoi(delay)); } if (getenv("SYNCEVOLUTION_DBUS_HELPER_VGDB")) { // Trigger an error in valgrind. Use in combination with // --vgdb-error=1 --vgdb=yes (note the =1!) to attach when // the process is running. void *dummy = malloc(1); free(dummy); // cppcheck-suppress deallocDealloc // cppcheck-suppress doubleFree // cppcheck-suppress uninitvar free(dummy); } SyncContext::initMain("syncevo-dbus-helper"); loop = g_main_loop_new(NULL, FALSE); // Suspend and abort are signaled via SIGINT/SIGTERM // respectively. SuspendFlags handle that for us. // SIGURG is used as acknowledgement from parent to us that we // can quite. SuspendFlags &s = SuspendFlags::getSuspendFlags(); s.setLevel(Logger::DEV); boost::shared_ptr guard = s.activate((1< redirect; PushLogger pushRedirect; if (!debug) { redirect.reset(new LogRedirect(LogRedirect::STDERR_AND_STDOUT)); pushRedirect.reset(redirect); } #ifdef USE_DLT // Set by syncevo-dbus-server for us. bool useDLT = getenv("SYNCEVOLUTION_USE_DLT") != NULL; PushLogger loggerdlt; if (useDLT) { loggerdlt.reset(new LoggerDLT(DLT_SYNCEVO_DBUS_HELPER_ID, "SyncEvolution local sync helper")); } #endif setvbuf(stderr, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); try { static GOptionEntry entries[] = { { "dbus-verbosity", 'v', 0, G_OPTION_ARG_INT, &logLevelDBus, "Choose amount of output via D-Bus signals with Logger::Level; default is INFO = 3.", "level" }, { NULL } }; GErrorCXX gerror; static GOptionContext *context = g_option_context_new("- SyncEvolution D-Bus Helper"); g_option_context_add_main_entries(context, entries, GETTEXT_PACKAGE); bool success = g_option_context_parse(context, &argc, &argv, gerror); if (!success) { gerror.throwError("parsing command line options"); } if (debug) { Logger::instance().setLevel(Logger::DEBUG); Logger::setProcessName(StringPrintf("syncevo-dbus-helper-%ld", (long)getpid())); } // syncevo-dbus-helper produces the output which is of most // interest to users, and therefore it is allowed to print // [INFO/ERROR/DEBUG] without including a process name in // the brackets, like the other processes do. // Logger::setProcessName("syncevo-dbus-helper"); boost::shared_ptr forkexec = ForkExecChild::create(); boost::shared_ptr helper; bool failed = false; forkexec->m_onConnect.connect(boost::bind(onConnect, _1, boost::cref(forkexec), boost::ref(helper))); forkexec->m_onFailure.connect(boost::bind(onFailure, _2, boost::ref(failed))); forkexec->connect(); // Run until we are connected, failed or get interrupted. boost::signals2::connection c = s.m_stateChanged.connect(boost::bind(&onAbort)); SE_LOG_DEBUG(NULL, "helper (pid %d) finished setup, waiting for parent connection", getpid()); while (true) { if (s.getState() != SuspendFlags::NORMAL) { // not an error, someone wanted us to stop SE_LOG_DEBUG(NULL, "aborted via signal while starting, terminating"); // tell caller that we aborted by terminating via the SIGTERM signal return 0; } if (failed) { SE_THROW("parent connection failed"); } if (helper) { // done break; } // wait g_main_loop_run(loop); } // Now we no longer care whether the parent connection fails. // TODO: What if the parent fails to call us and instead closes his // side of the connection? Will we notice and abort? c.disconnect(); SE_LOG_DEBUG(NULL, "connected to parent, run helper"); helper->run(); SE_LOG_DEBUG(NULL, "helper operation done"); helper.reset(); SE_LOG_DEBUG(NULL, "helper destroyed"); // Wait for confirmation from parent that we are allowed to // quit. This is necessary because we might have pending IO // for the parent, like D-Bus method replies. while (true) { if (s.getReceivedSignals() & (1<getState() != ForkExecChild::CONNECTED) { // No point running any longer, parent is gone. // // This can occur during normal operations, so don't // treat it as an error: // - we send final method response // - parent signals us and closes the connection // - our event loop processes these two events such // that we see the "not connected" one first SE_LOG_DEBUG(NULL, "parent has quit, terminating"); return 0; } g_main_context_iteration(NULL, true); } } catch ( const std::exception &ex ) { SE_LOG_ERROR(NULL, "helper quitting with exception: %s", ex.what()); } catch (...) { SE_LOG_ERROR(NULL, "helper quitting: unknown error"); } return 1; } syncevolution_1.4/src/dbus/server/syncevo-dbus-server-startup.sh.in000077500000000000000000000001471230021373600260030ustar00rootroot00000000000000#! /bin/sh sleep 120 exec @libexecdir@/syncevo-dbus-server @SYNCEVO_DBUS_SERVER_ARGS@ 2>/dev/null 1>&1 syncevolution_1.4/src/dbus/server/syncevo-dbus-server.desktop.in000066400000000000000000000002341230021373600253340ustar00rootroot00000000000000[Desktop Entry] Type=Application Hidden=false Name=syncevo-dbus-server Comment=SyncEvolution D-Bus Server Exec=@libexecdir@/syncevo-dbus-server-startup.sh syncevolution_1.4/src/dbus/server/timeout.h000066400000000000000000000113401230021373600212460ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef TIMEOUT_H #define TIMEOUT_H #include #include #include #include #include SE_BEGIN_CXX /** * Utility class which makes it easier to work with g_timeout_add_seconds(). * Instantiate this class with a specific callback. Use boost::bind() * to attach specific parameters to that callback. Then activate * the timeout. Destructing this class will automatically remove * the timeout and thus ensure that it doesn't trigger without * valid parameters. * * This class is thread-safe. If called by a thread different from the * main thread, the callback will happen inside the main thread. Use * g_main_context_wakeup() to ensure that the main thread notices the * new callback right away. */ class Timeout : boost::noncopyable { guint m_tag; boost::function m_callback; public: enum { PRIORITY_HIGH = G_PRIORITY_HIGH, PRIORITY_DEFAULT = G_PRIORITY_DEFAULT, PRIORITY_HIGH_IDLE = G_PRIORITY_HIGH_IDLE, PRIORITY_DEFAULT_IDLE = G_PRIORITY_DEFAULT_IDLE, PRIORITY_LOW = G_PRIORITY_LOW }; Timeout() : m_tag(0) { } ~Timeout() { if (m_tag) { g_source_remove(m_tag); } } /** * call the callback at regular intervals until it returns false * * @param seconds a value < 0 runs the function as soon as the process is idle, * otherwise in the specified amount of time */ void activate(int seconds, const boost::function &callback, int priority = G_PRIORITY_DEFAULT) { deactivate(); m_callback = callback; m_tag = seconds < 0 ? g_idle_add(triggered, static_cast(this)) : g_timeout_add_seconds(seconds, triggered, static_cast(this)); if (!m_tag) { SE_THROW("g_timeout_add_seconds() or g_idle_add() failed"); } } void activate(const boost::function &idleCallback, int priority = G_PRIORITY_DEFAULT_IDLE) { activate(-1, idleCallback, priority); } /** * invoke the callback once */ void runOnce(int seconds, const boost::function &callback, int priority = G_PRIORITY_DEFAULT) { activate(seconds, boost::bind(&Timeout::once, callback), priority); } void runOnce(const boost::function &idleCallback, int priority = G_PRIORITY_DEFAULT) { runOnce(-1, idleCallback, priority); } /** * stop calling the callback, drop callback */ void deactivate() { if (m_tag) { g_source_remove(m_tag); m_tag = 0; } m_callback = 0; } /** true iff active */ operator bool () const { return m_tag != 0; } private: static gboolean triggered(gpointer data) throw () { Timeout *me = static_cast(data); bool runAgain = false; uint tag = me->m_tag; try { // Be extra careful and don't trigger a deactivated callback. if (me->m_callback) { runAgain = me->m_callback(); } } catch (...) { // Something unexpected went wrong, can only shut down. Exception::handle(HANDLE_EXCEPTION_FATAL); } if (!runAgain && // Returning false will automatically deactivate the source, remember that. me->m_tag == tag // Beware that the callback may have already reused the Timeout instance. // In that case, we must not reset the new tag and callback. ) { me->m_tag = 0; me->m_callback = 0; } return runAgain; } static bool once(const boost::function &callback) { callback(); return false; } }; SE_END_CXX #endif // TIMEOUT_H syncevolution_1.4/src/dbus/server/timer.h000066400000000000000000000046451230021373600207120ustar00rootroot00000000000000 /* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef TIMER_H #define TIMER_H #include #include #include SE_BEGIN_CXX /** * A timer helper to check whether now is timeout according to * user's setting. Timeout is calculated in milliseconds */ class Timer { timeval m_startTime; ///< start time unsigned long m_timeoutMs; ///< timeout in milliseconds, set by user /** * calculate duration between now and start time * return value is in milliseconds */ unsigned long duration(const timeval &minuend, const timeval &subtrahend) { unsigned long result = 0; if(minuend.tv_sec > subtrahend.tv_sec || (minuend.tv_sec == subtrahend.tv_sec && minuend.tv_usec > subtrahend.tv_usec)) { result = minuend.tv_sec - subtrahend.tv_sec; result *= 1000; result += (minuend.tv_usec - subtrahend.tv_usec) / 1000; } return result; } public: /** * constructor * @param timeoutMs timeout in milliseconds */ Timer(unsigned long timeoutMs = 0) : m_timeoutMs(timeoutMs) { reset(); } /** * reset the timer and mark start time as current time */ void reset() { gettimeofday(&m_startTime, NULL); } /** * check whether it is timeout */ bool timeout() { return timeout(m_timeoutMs); } /** * check whether the duration timer records is longer than the given duration */ bool timeout(unsigned long timeoutMs) { timeval now; gettimeofday(&now, NULL); return duration(now, m_startTime) >= timeoutMs; } }; SE_END_CXX #endif // TIMER_H syncevolution_1.4/src/gdbus/000077500000000000000000000000001230021373600162515ustar00rootroot00000000000000syncevolution_1.4/src/gdbus/README000066400000000000000000000021531230021373600171320ustar00rootroot00000000000000This is a copy of the libgdbus source code: http://git.kernel.org/?p=bluetooth/libgdbus.git;a=summary It is licensed under LGPL v2.1, see upstream COPYING. The source is included here because there is no stable upstream release. Patches added here need to be submitted upstream. Likewise, patches applied upstream must be imported. The build/import-gdbus.sh and build/export-gdbus.sh scripts automate that process. To import fixes from upstream: - checkout out libgdbus and syncevolution - enter syncevolution directory - if not done before, create local "gdbus" branch: git branch gdbus origin/gdbus - run build/import-gdbus.sh - "gdbus" branch is now checked out and updated - verify changes, merge into master, etc. - push into remote syncevolution repo To export fixes to upstream: - check out relevant branch in syncevolution which has our local changes (typically "master") - run build/export-gdbus.sh - send 0*.patch files to upstream Caveats: - only files explicitly mentioned in the two scripts are imported/exports - Makefile changes are only imported, but not exported (local changes not relevant upstream) syncevolution_1.4/src/gdbus/debug.c000066400000000000000000000020101230021373600174740ustar00rootroot00000000000000/* * * Library for simple D-Bus integration with GLib * * Copyright (C) 2007-2008 Intel Corporation. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License version 2.1 as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include "debug.h" static void __attribute__ ((constructor)) __init(void) { DBG(""); } static void __attribute__ ((destructor)) __cleanup(void) { DBG(""); } syncevolution_1.4/src/gdbus/debug.h000066400000000000000000000016501230021373600175120ustar00rootroot00000000000000/* * * Library for simple D-Bus integration with GLib * * Copyright (C) 2007-2008 Intel Corporation. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License version 2.1 as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #include //#define DBG(fmt, arg...) g_debug("%s: " fmt, __FUNCTION__ , ## arg) #define DBG(fmt, arg...) syncevolution_1.4/src/gdbus/gdbus-cxx-bridge.cpp000066400000000000000000000126131230021373600221160ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "gdbus-cxx-bridge.h" #include #include void intrusive_ptr_add_ref(DBusConnection *con) { dbus_connection_ref(con); } void intrusive_ptr_release(DBusConnection *con) { dbus_connection_unref(con); } void intrusive_ptr_add_ref(DBusMessage *msg) { dbus_message_ref(msg); } void intrusive_ptr_release(DBusMessage *msg) { dbus_message_unref(msg); } void intrusive_ptr_add_ref(DBusPendingCall *call) { dbus_pending_call_ref (call); } void intrusive_ptr_release(DBusPendingCall *call) { dbus_pending_call_unref (call); } namespace GDBusCXX { DBusConnectionPtr dbus_get_bus_connection(const char *busType, const char *name, bool unshared, DBusErrorCXX *err) { return DBusConnectionPtr(b_dbus_setup_bus(boost::iequals(busType, "SYSTEM") ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION, name, unshared, err), false); } static void ConnectionLost(DBusConnection *connection, void *user_data) { DBusConnectionPtr::Disconnect_t *cb = static_cast(user_data); (*cb)(); } static void DestroyDisconnect(void *user_data) { DBusConnectionPtr::Disconnect_t *cb = static_cast(user_data); delete cb; } void DBusConnectionPtr::setDisconnect(const Disconnect_t &func) { b_dbus_set_disconnect_function(get(), ConnectionLost, new Disconnect_t(func), DestroyDisconnect); } DBusConnectionPtr dbus_get_bus_connection(const std::string &address, DBusErrorCXX *err, bool /*delayed*/ /*= false*/) { DBusConnectionPtr conn(dbus_connection_open_private(address.c_str(), err), false); if (conn) { b_dbus_setup_connection(conn.get(), TRUE, NULL); dbus_connection_set_exit_on_disconnect(conn.get(), FALSE); } return conn; } void dbus_bus_connection_undelay(const DBusConnectionPtr &/*ptr*/) { // no op } boost::shared_ptr DBusServerCXX::listen(const std::string &address, DBusErrorCXX *err) { DBusServer *server = NULL; const char *realAddr = address.c_str(); char buffer[80]; if (address.empty()) { realAddr = buffer; buffer[0] = 0; for (int counter = 1; counter < 100 && !server; counter++) { if (*err) { g_debug("dbus_server_listen(%s) failed, trying next candidate: %s", buffer, err->message); dbus_error_init(err); } sprintf(buffer, "unix:abstract=gdbuscxx-%d", counter); server = dbus_server_listen(realAddr, err); } } else { server = dbus_server_listen(realAddr, err); } if (!server) { return boost::shared_ptr(); } b_dbus_setup_server(server); boost::shared_ptr res(new DBusServerCXX(server, realAddr)); dbus_server_set_new_connection_function(server, newConnection, res.get(), NULL); return res; } void DBusServerCXX::newConnection(DBusServer *server, DBusConnection *newConn, void *data) throw() { DBusServerCXX *me = static_cast(data); if (me->m_newConnection) { try { b_dbus_setup_connection(newConn, FALSE, NULL); dbus_connection_set_exit_on_disconnect(newConn, FALSE); DBusConnectionPtr conn(newConn); me->m_newConnection(*me, conn); } catch (...) { g_error("handling new D-Bus connection failed with C++ exception"); } } } DBusServerCXX::DBusServerCXX(DBusServer *server, const std::string &address) : m_server(server), m_address(address) { } DBusServerCXX::~DBusServerCXX() { if (m_server) { dbus_server_disconnect(m_server.get()); } } bool CheckError(const DBusMessagePtr &reply, std::string &buffer) { const char* errname = dbus_message_get_error_name (reply.get()); if (errname) { buffer = errname; DBusMessageIter iter; if (dbus_message_iter_init(reply.get(), &iter)) { if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING) { buffer += ": "; const char *str; dbus_message_iter_get_basic(&iter, &str); buffer += str; } } return true; } else { return false; } } } syncevolution_1.4/src/gdbus/gdbus-cxx-bridge.h000066400000000000000000005046441230021373600215750ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ /** * This file contains everything that a D-Bus server needs to * integrate a normal C++ class into D-Bus. Argument and result * marshaling is done in wrapper functions which convert directly * to normal C++ types (bool, integers, std::string, std::map<>, ...). * See dbus_traits for the full list of supported types. * * Before explaining the binding, some terminology first: * - A function has a return type and multiple parameters. * - Input parameters are read-only arguments of the function. * - The function can return values to the caller via the * return type and output parameters (retvals). * * The C++ binding roughly looks like this: * - Arguments can be passed as plain types or const references: void foo(int arg); void bar(const std::string &str); * - A single result can be returned as return value: * int foo(); * - Multiple results can be copied into instances provided by * the wrapper, passed by reference: void foo(std::string &res); * - A return value, arguments and retvals can be combined * arbitrarily. In the D-Bus reply the return code comes before * all return values. * * Asynchronous methods are possible by declaring one parameter as a * Result pointer and later calling the virtual function provided by * it. Parameter passing of results is less flexible than that of * method parameters: the later allows both std::string as well as * const std::string &, for results only the const reference is * supported. The Result instance is passed as pointer and then owned * by the called method. * * Reference counting via boost::intrusive_ptr ensures that all * D-Bus objects are handled automatically internally. */ #ifndef INCL_BDBUS_CXX_BRIDGE #define INCL_BDBUS_CXX_BRIDGE #include "gdbus.h" #include "gdbus-cxx.h" // Not defined by 1.4.x in Maemo Harmattan; INT_MAX has the same // value and effect there. In older libdbus, it is the same as // a very long timeout (2147483s), which is good enough. #include #ifndef DBUS_TIMEOUT_INFINITE # define DBUS_TIMEOUT_INFINITE INT_MAX #endif #include #include #include // Boost docs want this in the boost:: namespace, but // that fails with clang 2.9 depending on the inclusion order of // header files. Global namespace works in all cases. void intrusive_ptr_add_ref(DBusConnection *con); void intrusive_ptr_release(DBusConnection *con); void intrusive_ptr_add_ref(DBusMessage *msg); void intrusive_ptr_release(DBusMessage *msg); void intrusive_ptr_add_ref(DBusPendingCall *call); void intrusive_ptr_release(DBusPendingCall *call); static inline void intrusive_ptr_add_ref(DBusServer *server) { dbus_server_ref(server); } static inline void intrusive_ptr_release(DBusServer *server) { dbus_server_unref(server); } #include #include #include #include #include #include #include /* The connection is the only client-exposed type from the C API. To * keep changes to a minimum while supporting both dbus * implementations, this is made to be a define. The intention is to * remove the define once the in-tree gdbus is dropped. */ #define DBUS_NEW_ERROR_MSG b_dbus_create_error namespace GDBusCXX { // GDBusCXX aliases for the underlying types. // Useful for some external dbus_traits which // need to pass pointers to these types in their // append()/get() methods without depending on GIO or // libdbus types. typedef DBusConnection connection_type; typedef DBusMessage message_type; typedef DBusMessageIter builder_type; typedef DBusMessageIter reader_type; class DBusMessagePtr; class DBusConnectionPtr : public boost::intrusive_ptr { public: DBusConnectionPtr() {} // connections are typically created once, so increment the ref counter by default DBusConnectionPtr(DBusConnection *conn, bool add_ref = true) : boost::intrusive_ptr(conn, add_ref) {} DBusConnection *reference(void) throw() { DBusConnection *conn = get(); dbus_connection_ref(conn); return conn; } /** empty stub: flushing only necessary with GIO D-Bus */ void flush() {} /** GDBus GIO specific: disconnect callback */ typedef boost::function Disconnect_t; void setDisconnect(const Disconnect_t &func); #define GDBUS_CXX_HAVE_DISCONNECT 1 }; class DBusMessagePtr : public boost::intrusive_ptr { public: DBusMessagePtr() {} // expected to be used for messages created anew, // so use the reference already incremented for us // and don't increment by default DBusMessagePtr(DBusMessage *msg, bool add_ref = false) : boost::intrusive_ptr(msg, add_ref) {} DBusMessage *reference(void) throw() { DBusMessage *msg = get(); dbus_message_ref(msg); return msg; } }; class DBusPendingCallPtr : public boost::intrusive_ptr { public: DBusPendingCallPtr(DBusPendingCall *call, bool add_ref = false) : boost::intrusive_ptr(call, add_ref) {} DBusPendingCall *reference(void) throw() { DBusPendingCall *call = get(); dbus_pending_call_ref(call); return call; } }; /** * wrapper around DBusError which initializes * the struct automatically, then can be used to * throw an exception */ class DBusErrorCXX : public DBusError { public: DBusErrorCXX() { dbus_error_init(this); } void throwFailure(const std::string &operation, const std::string &explanation = " failed") { if (dbus_error_is_set(this)) { throw std::runtime_error(operation + ": " + message); } else { throw std::runtime_error(operation + explanation); } } operator bool () { return dbus_error_is_set(this); } }; DBusConnectionPtr dbus_get_bus_connection(const char *busType, const char *name, bool unshared, DBusErrorCXX *err); DBusConnectionPtr dbus_get_bus_connection(const std::string &address, DBusErrorCXX *err, bool delayed = false); void dbus_bus_connection_undelay(const DBusConnectionPtr &conn); /** * Wrapper around DBusServer. Does intentionally not expose * any of the underlying methods so that the public API * can be implemented differently for GIO GDBus. */ class DBusServerCXX : private boost::noncopyable { public: ~DBusServerCXX(); /** * Called for each new connection. Callback must store the DBusConnectionPtr, * otherwise it will be unref'ed after the callback returns. * If the new connection is not wanted, then it is good style to close it * explicitly in the callback. */ typedef boost::function NewConnection_t; void setNewConnectionCallback(const NewConnection_t &newConnection) { m_newConnection = newConnection; } NewConnection_t getNewConnectionCallback() const { return m_newConnection; } /** * Start listening for new connections on the given address, like unix:abstract=myaddr. * Address may be empty, in which case a new, unused address will chosen. */ static boost::shared_ptr listen(const std::string &address, DBusErrorCXX *err); /** * address used by the server */ std::string getAddress() const { return m_address; } private: DBusServerCXX(DBusServer *server, const std::string &address); static void newConnection(DBusServer *server, DBusConnection *newConn, void *data) throw(); NewConnection_t m_newConnection; boost::intrusive_ptr m_server; std::string m_address; }; /** * Special type for object paths. A string in practice. */ class DBusObject_t : public std::string { public: DBusObject_t() {} template DBusObject_t(T val) : std::string(val) {} template DBusObject_t &operator = (T val) { assign(val); return *this; } }; /** * specializations of this must defined methods for encoding and * decoding type C and declare its signature */ template struct dbus_traits {}; struct dbus_traits_base { /** * A C++ method or function can handle a call asynchronously by * asking to be passed a "boost::shared_ptr" parameter. * The dbus_traits for those parameters have "asynchronous" set to * true, which skips all processing after calling the method. */ static const bool asynchronous = false; }; /** * Append a varying number of parameters as result to the * message, using AppendRetvals(msg) << res1 << res2 << ...; * * Types can be anything that has a dbus_traits, including * types which are normally recognized as input parameters in D-Bus * method calls. */ class AppendRetvals { DBusMessageIter m_iter; public: AppendRetvals(DBusMessagePtr &msg) { dbus_message_iter_init_append(msg.get(), &m_iter); } template AppendRetvals & operator << (const A &a) { dbus_traits::append(m_iter, a); return *this; } }; /** * Append a varying number of method parameters as result to the reply * message, using AppendArgs(msg) << Set(res1) << Set(res2) << ...; */ struct AppendArgs { DBusMessageIter m_iter; AppendArgs(DBusMessage *msg) { dbus_message_iter_init_append(msg, &m_iter); } /** syntactic sugar: redirect << into Set instance */ template AppendArgs & operator << (const A &a) { return a.set(*this); } /** * Always append argument, including those types which * would be recognized by << as parameters and thus get * skipped. */ template AppendArgs & operator + (const A &a) { dbus_traits::append(m_iter, a); return *this; } }; /** default: skip it, not a result of the method */ template struct Set { Set(A &a) {} AppendArgs &set(AppendArgs &context) const { return context; } }; /** same for const reference */ template struct Set { Set(A &a) {} AppendArgs &set(AppendArgs &context) const { return context; } }; /** specialization for reference: marshal result */ template struct Set { A &m_a; Set(A &a) : m_a(a) {} AppendArgs &set(AppendArgs &context) const { dbus_traits::append(context.m_iter, m_a); return context; } }; /** * Extract values from a message, using ExtractArgs(conn, msg) >> Get(val1) >> Get(val2) >> ...; * * This complements AppendArgs: it skips over those method arguments * which are results of the method. Which values are skipped and * which are marshalled depends on the specialization of Get and thus * ultimately on the prototype of the method. */ struct ExtractArgs { DBusConnection *m_conn; DBusMessage *m_msg; DBusMessageIter m_iter; public: ExtractArgs(DBusConnection *conn, DBusMessage *msg) { m_conn = conn; m_msg = msg; dbus_message_iter_init(msg, &m_iter); } /** syntactic sugar: redirect >> into Get instance */ template ExtractArgs & operator >> (const A &a) { return a.get(*this); } }; /** default: extract data from message */ template struct Get { A &m_a; Get(A &a) : m_a(a) {} ExtractArgs &get(ExtractArgs &context) const { dbus_traits::get(context.m_conn, context.m_msg, context.m_iter, m_a); return context; } }; /** same for const reference */ template struct Get { A &m_a; Get(A &a) : m_a(a) {} ExtractArgs &get(ExtractArgs &context) const { dbus_traits::get(context.m_conn, context.m_msg, context.m_iter, m_a); return context; } }; /** specialization for reference: skip it, not an input parameter */ template struct Get { Get(A &a) {} ExtractArgs &get(ExtractArgs &context) const { return context; } }; /** * combines D-Bus connection, path and interface */ class DBusObject { DBusConnectionPtr m_conn; std::string m_path; std::string m_interface; bool m_closeConnection; public: /** * @param closeConnection set to true if the connection * is private and this instance of * DBusObject is meant to be the * last user of the connection; * when this DBusObject deconstructs, * it'll close the connection * (required by libdbus for private * connections; the mechanism in GDBus for * this didn't work) */ DBusObject(const DBusConnectionPtr &conn, const std::string &path, const std::string &interface, bool closeConnection = false) : m_conn(conn), m_path(path), m_interface(interface), m_closeConnection(closeConnection) {} virtual ~DBusObject() { if (m_closeConnection && m_conn) { dbus_connection_close(m_conn.get()); } } DBusConnection *getConnection() const { return m_conn.get(); } const char *getPath() const { return m_path.c_str(); } const char *getInterface() const { return m_interface.c_str(); } }; /** * adds destination to D-Bus connection, path and interface */ class DBusRemoteObject : public DBusObject { std::string m_destination; public: DBusRemoteObject(const DBusConnectionPtr &conn, const std::string &path, const std::string &interface, const std::string &destination, bool closeConnection = false) : DBusObject(conn, path, interface, closeConnection), m_destination(destination) {} const char *getDestination() const { return m_destination.c_str(); } }; template class EmitSignal0Template { const DBusObject &m_object; const std::string m_signal; public: EmitSignal0Template(const DBusObject &object, const std::string &signal) : m_object(object), m_signal(signal) {} typedef void result_type; void operator () () { DBusMessagePtr msg(dbus_message_new_signal(m_object.getPath(), m_object.getInterface(), m_signal.c_str())); if (!msg) { if (optional) { return; } throw std::runtime_error("dbus_message_new_signal() failed"); } if (!dbus_connection_send(m_object.getConnection(), msg.get(), NULL)) { if (optional) { return; } throw std::runtime_error("dbus_connection_send failed"); } } BDBusSignalTable makeSignalEntry(BDBusSignalFlags flags = G_DBUS_SIGNAL_FLAG_NONE) const { BDBusSignalTable entry; entry.name = m_signal.c_str(); std::string buffer; entry.signature = strdup(buffer.c_str()); entry.flags = flags; return entry; } }; typedef EmitSignal0Template EmitSignal0; template class EmitSignal1 { const DBusObject &m_object; const std::string m_signal; public: EmitSignal1(const DBusObject &object, const std::string &signal) : m_object(object), m_signal(signal) {} typedef void result_type; void operator () (A1 a1) { DBusMessagePtr msg(dbus_message_new_signal(m_object.getPath(), m_object.getInterface(), m_signal.c_str())); if (!msg) { if (optional) { return; } throw std::runtime_error("dbus_message_new_signal() failed"); } AppendRetvals(msg) << a1; if (!dbus_connection_send(m_object.getConnection(), msg.get(), NULL)) { if (optional) { return; } throw std::runtime_error("dbus_connection_send failed"); } } BDBusSignalTable makeSignalEntry(BDBusSignalFlags flags = G_DBUS_SIGNAL_FLAG_NONE) const { BDBusSignalTable entry; entry.name = m_signal.c_str(); std::string buffer; buffer += dbus_traits::getSignature(); entry.signature = strdup(buffer.c_str()); entry.flags = flags; return entry; } }; template class EmitSignal2 { const DBusObject &m_object; const std::string m_signal; public: EmitSignal2(const DBusObject &object, const std::string &signal) : m_object(object), m_signal(signal) {} typedef void result_type; void operator () (A1 a1, A2 a2) { DBusMessagePtr msg(dbus_message_new_signal(m_object.getPath(), m_object.getInterface(), m_signal.c_str())); if (!msg) { if (optional) { return; } throw std::runtime_error("dbus_message_new_signal() failed"); } AppendRetvals(msg) << a1 << a2; if (!dbus_connection_send(m_object.getConnection(), msg.get(), NULL)) { if (optional) { return; } throw std::runtime_error("dbus_connection_send failed"); } } BDBusSignalTable makeSignalEntry(BDBusSignalFlags flags = G_DBUS_SIGNAL_FLAG_NONE) const { BDBusSignalTable entry; entry.name = m_signal.c_str(); std::string buffer; buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); entry.signature = strdup(buffer.c_str()); entry.flags = flags; return entry; } }; template class EmitSignal3 { const DBusObject &m_object; const std::string m_signal; public: EmitSignal3(const DBusObject &object, const std::string &signal) : m_object(object), m_signal(signal) {} typedef void result_type; void operator () (A1 a1, A2 a2, A3 a3) { DBusMessagePtr msg(dbus_message_new_signal(m_object.getPath(), m_object.getInterface(), m_signal.c_str())); if (!msg) { if (optional) { return; } throw std::runtime_error("dbus_message_new_signal() failed"); } AppendRetvals(msg) << a1 << a2 << a3; if (!dbus_connection_send(m_object.getConnection(), msg.get(), NULL)) { if (optional) { return; } throw std::runtime_error("dbus_connection_send failed"); } } BDBusSignalTable makeSignalEntry(BDBusSignalFlags flags = G_DBUS_SIGNAL_FLAG_NONE) const { BDBusSignalTable entry; entry.name = m_signal.c_str(); std::string buffer; buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); entry.signature = strdup(buffer.c_str()); entry.flags = flags; return entry; } }; template class EmitSignal4 { const DBusObject &m_object; const std::string m_signal; public: EmitSignal4(const DBusObject &object, const std::string &signal) : m_object(object), m_signal(signal) {} typedef void result_type; void operator () (A1 a1, A2 a2, A3 a3, A4 a4) { DBusMessagePtr msg(dbus_message_new_signal(m_object.getPath(), m_object.getInterface(), m_signal.c_str())); if (!msg) { if (optional) { return; } throw std::runtime_error("dbus_message_new_signal() failed"); } AppendRetvals(msg) << a1 << a2 << a3 << a4; if (!dbus_connection_send(m_object.getConnection(), msg.get(), NULL)) { if (optional) { return; } throw std::runtime_error("dbus_connection_send failed"); } } BDBusSignalTable makeSignalEntry(BDBusSignalFlags flags = G_DBUS_SIGNAL_FLAG_NONE) const { BDBusSignalTable entry; entry.name = m_signal.c_str(); std::string buffer; buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); entry.signature = strdup(buffer.c_str()); entry.flags = flags; return entry; } }; template class EmitSignal5 { const DBusObject &m_object; const std::string m_signal; public: EmitSignal5(const DBusObject &object, const std::string &signal) : m_object(object), m_signal(signal) {} typedef void result_type; void operator () (A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) { DBusMessagePtr msg(dbus_message_new_signal(m_object.getPath(), m_object.getInterface(), m_signal.c_str())); if (!msg) { if (optional) { return; } throw std::runtime_error("dbus_message_new_signal() failed"); } AppendRetvals(msg) << a1 << a2 << a3 << a4 << a5; if (!dbus_connection_send(m_object.getConnection(), msg.get(), NULL)) { if (optional) { return; } throw std::runtime_error("dbus_connection_send failed"); } } BDBusSignalTable makeSignalEntry(BDBusSignalFlags flags = G_DBUS_SIGNAL_FLAG_NONE) const { BDBusSignalTable entry; entry.name = m_signal.c_str(); std::string buffer; buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); entry.signature = strdup(buffer.c_str()); entry.flags = flags; return entry; } }; template class EmitSignal6 { const DBusObject &m_object; const std::string m_signal; public: EmitSignal6(const DBusObject &object, const std::string &signal) : m_object(object), m_signal(signal) {} typedef void result_type; void operator () (A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) { DBusMessagePtr msg(dbus_message_new_signal(m_object.getPath(), m_object.getInterface(), m_signal.c_str())); if (!msg) { if (optional) { return; } throw std::runtime_error("dbus_message_new_signal() failed"); } AppendRetvals(msg) << a1 << a2 << a3 << a4 << a5 << a6; if (!dbus_connection_send(m_object.getConnection(), msg.get(), NULL)) { if (optional) { return; } throw std::runtime_error("dbus_connection_send failed"); } } BDBusSignalTable makeSignalEntry(BDBusSignalFlags flags = G_DBUS_SIGNAL_FLAG_NONE) const { BDBusSignalTable entry; entry.name = m_signal.c_str(); std::string buffer; buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); entry.signature = strdup(buffer.c_str()); entry.flags = flags; return entry; } }; template struct MakeMethodEntry { // There is no generic implementation of this method. // If you get an error about it missing, then write // a specialization for your type M (the method pointer). // // static BDBusMethodTable make(const char *name, // BDBusMethodFlags flags) }; /** * Storage for method/signal/property arrays. * Always contains at least one empty element * at the end or is NULL. */ template class DBusVector { size_t m_entries; size_t m_size; T *m_elements; static void destroy(BDBusMethodTable &entry) { free(const_cast(entry.name)); free(const_cast(entry.signature)); free(const_cast(entry.reply)); if (entry.destroy) { entry.destroy(&entry); } } static void destroy(BDBusSignalTable &entry) { free(const_cast(entry.signature)); // if (entry.destroy) { // entry.destroy(&entry); // } } public: DBusVector() : m_entries(0), m_size(0), m_elements(NULL) {} ~DBusVector() { if (m_elements) { for (size_t i = 0; i < m_entries; i++) { destroy(m_elements[i]); } free(m_elements); } } T *get() { return m_elements; } void push_back(const T &element) { if (m_entries + 1 >= m_size) { size_t newSize = m_size ? m_size * 2 : 16; T *elements = static_cast(realloc(m_elements, newSize * sizeof(T))); if (!elements) { throw std::bad_alloc(); } m_elements = elements; m_size = newSize; } m_elements[m_entries] = element; m_entries++; memset(m_elements + m_entries, 0, sizeof(T)); } }; /** * utility class for registering an interface */ class DBusObjectHelper : public DBusObject { boost::function m_callback; bool m_activated; DBusVector m_methods; DBusVector m_signals; public: typedef boost::function Callback_t; DBusObjectHelper(const DBusConnectionPtr &conn, const std::string &path, const std::string &interface, const Callback_t &callback = Callback_t(), bool closeConnection = false) : DBusObject(conn, path, interface, closeConnection), m_callback(callback), m_activated(false) { } ~DBusObjectHelper() { deactivate(); } /** * binds a member to the this pointer of its instance * and invokes it when the specified method is called */ template void add(A1 instance, M C::*method, const char *name, BDBusMethodFlags flags = G_DBUS_METHOD_FLAG_NONE) { typedef MakeMethodEntry< boost::function > entry_type; m_methods.push_back(entry_type::make(name, flags, entry_type::boostptr(method, instance))); } /** * binds a plain function pointer with no additional arguments and * invokes it when the specified method is called */ template void add(M *function, const char *name, BDBusMethodFlags flags = G_DBUS_METHOD_FLAG_NONE) { m_methods.push_back(MakeMethodEntry< boost::function >::make(name, flags, function)); } /** * add an existing signal entry */ template void add(const S &s) { m_signals.push_back(s.makeSignalEntry()); } void activate(BDBusMethodTable *methods, BDBusSignalTable *signals, BDBusPropertyTable *properties, const Callback_t &callback) { if (!b_dbus_register_interface_with_callback(getConnection(), getPath(), getInterface(), methods, signals, properties, this, NULL, interfaceCallback)) { throw std::runtime_error(std::string("b_dbus_register_interface() failed for ") + getPath() + " " + getInterface()); } m_callback = callback; m_activated = true; } void activate() { if (!b_dbus_register_interface_with_callback(getConnection(), getPath(), getInterface(), m_methods.get(), m_signals.get(), NULL, this, NULL, interfaceCallback)) { throw std::runtime_error(std::string("b_dbus_register_interface() failed for ") + getPath() + " " + getInterface()); } m_activated = true; } void deactivate() { if (m_activated) { if (!b_dbus_unregister_interface(getConnection(), getPath(), getInterface())) { throw std::runtime_error(std::string("b_dbus_unregister_interface() failed for ") + getPath() + " " + getInterface()); } m_activated = false; } } static void interfaceCallback(void *userData) { DBusObjectHelper* helper = static_cast(userData); if (helper->m_callback) { helper->m_callback(); } } }; /** * to be used for plain parameters like int32_t: * treat as arguments which have to be extracted * from the D-Bus message and can be skipped when * encoding the reply */ template struct basic_marshal : public dbus_traits_base { typedef host host_type; typedef host arg_type; static const int dbus_type = dbus; /** * copy value from D-Bus iterator into variable */ static void get(DBusConnection *conn, DBusMessage *msg, DBusMessageIter &iter, host &value) { if (dbus_message_iter_get_arg_type(&iter) != dbus) { throw std::runtime_error("invalid argument"); } dbus_message_iter_get_basic(&iter, &value); dbus_message_iter_next(&iter); } /** * copy value into D-Bus iterator */ static void append(DBusMessageIter &iter, arg_type value) { if (!dbus_message_iter_append_basic(&iter, dbus, &value)) { throw std::runtime_error("out of memory"); } } }; template<> struct dbus_traits : public basic_marshal< uint8_t, DBUS_TYPE_BYTE > { /** * plain type, regardless of whether used as * input or output parameter */ static std::string getType() { return "y"; } /** * plain type => input parameter => non-empty signature */ static std::string getSignature() {return getType(); } /** * plain type => not returned to caller */ static std::string getReply() { return ""; } }; /** if the app wants to use signed char, let it and treat it like a byte */ template<> struct dbus_traits : dbus_traits { typedef int8_t host_type; typedef int8_t arg_type; static void get(DBusConnection *conn, DBusMessage *msg, DBusMessageIter &iter, host_type &value) { dbus_traits::get(conn, msg, iter, reinterpret_cast(value)); } }; template<> struct dbus_traits : public basic_marshal< int16_t, DBUS_TYPE_INT16 > { static std::string getType() { return "n"; } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } }; template<> struct dbus_traits : public basic_marshal< uint16_t, DBUS_TYPE_UINT16 > { static std::string getType() { return "q"; } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } }; template<> struct dbus_traits : public basic_marshal< int32_t, DBUS_TYPE_INT32 > { static std::string getType() { return "i"; } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } }; template<> struct dbus_traits : public basic_marshal< uint32_t, DBUS_TYPE_UINT32 > { static std::string getType() { return "u"; } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } }; template<> struct dbus_traits : public dbus_traits_base { static std::string getType() { return "b"; } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } static const int dbus = DBUS_TYPE_BOOLEAN; static void get(DBusConnection *conn, DBusMessage *msg, DBusMessageIter &iter, bool &value) { if (dbus_message_iter_get_arg_type(&iter) != dbus) { throw std::runtime_error("invalid argument"); } dbus_bool_t dbus_value; dbus_message_iter_get_basic(&iter, &dbus_value); dbus_message_iter_next(&iter); value = dbus_value; } static void append(DBusMessageIter &iter, bool value) { dbus_bool_t dbus_value = value; if (!dbus_message_iter_append_basic(&iter, dbus, &dbus_value)) { throw std::runtime_error("out of memory"); } } typedef bool host_type; typedef bool arg_type; }; template<> struct dbus_traits : public dbus_traits_base { static std::string getType() { return "s"; } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } static const int dbus = DBUS_TYPE_STRING; static void get(DBusConnection *conn, DBusMessage *msg, DBusMessageIter &iter, std::string &value) { if (dbus_message_iter_get_arg_type(&iter) != dbus) { throw std::runtime_error("invalid argument"); } const char *str; dbus_message_iter_get_basic(&iter, &str); dbus_message_iter_next(&iter); value = str; } static void append(DBusMessageIter &iter, const std::string &value) { const char *str = value.c_str(); if (!dbus_message_iter_append_basic(&iter, dbus, &str)) { throw std::runtime_error("out of memory"); } } typedef std::string host_type; typedef const std::string &arg_type; }; template <> struct dbus_traits : public dbus_traits_base { static std::string getType() { return "o"; } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } static const int dbus = DBUS_TYPE_OBJECT_PATH; static void get(DBusConnection *conn, DBusMessage *msg, DBusMessageIter &iter, DBusObject_t &value) { if (dbus_message_iter_get_arg_type(&iter) != dbus) { throw std::runtime_error("invalid argument"); } const char *str; dbus_message_iter_get_basic(&iter, &str); dbus_message_iter_next(&iter); value = str; } static void append(DBusMessageIter &iter, const DBusObject_t &value) { const char *str = value.c_str(); if (!dbus_message_iter_append_basic(&iter, dbus, &str)) { throw std::runtime_error("out of memory"); } } typedef DBusObject_t host_type; typedef const DBusObject_t &arg_type; }; /** * pseudo-parameter: not part of D-Bus signature, * but rather extracted from message attributes */ template <> struct dbus_traits : public dbus_traits_base { static std::string getType() { return ""; } static std::string getSignature() { return ""; } static std::string getReply() { return ""; } static void get(DBusConnection *conn, DBusMessage *msg, DBusMessageIter &iter, Caller_t &value) { const char *peer = dbus_message_get_sender(msg); if (!peer) { throw std::runtime_error("D-Bus method call without sender?!"); } value = peer; } typedef Caller_t host_type; typedef const Caller_t &arg_type; }; /** * a std::pair - maps to D-Bus struct */ template struct dbus_traits< std::pair > : public dbus_traits_base { static std::string getContainedType() { return dbus_traits::getType() + dbus_traits::getType(); } static std::string getType() { return "(" + getContainedType() + ")"; } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } typedef std::pair host_type; typedef const std::pair &arg_type; static void get(DBusConnection *conn, DBusMessage *msg, DBusMessageIter &iter, host_type &pair) { if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRUCT) { throw std::runtime_error("invalid argument"); } DBusMessageIter sub; dbus_message_iter_recurse(&iter, &sub); if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { dbus_traits::get(conn, msg, sub, pair.first); } if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { dbus_traits::get(conn, msg, sub, pair.second); } dbus_message_iter_next(&iter); } static void append(DBusMessageIter &iter, arg_type pair) { DBusMessageIter sub; if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_STRUCT, NULL, &sub)) { throw std::runtime_error("out of memory"); } dbus_traits::append(sub, pair.first); dbus_traits::append(sub, pair.second); if (!dbus_message_iter_close_container(&iter, &sub)) { throw std::runtime_error("out of memory"); } } }; /** * dedicated type for chunk of data, to distinguish this case from * a normal std::pair of two values */ template class DBusArray : public std::pair { public: DBusArray() : std::pair(0, NULL) {} DBusArray(size_t len, const V *data) : std::pair(len, data) {} }; template class DBusArray makeDBusArray(size_t len, const V *data) { return DBusArray(len, data); } /** * Pass array of basic type plus its number of entries. * Can only be used in cases where the caller owns the * memory and can discard it when the call returns, in * other words, for method calls, asynchronous replys and * signals, but not for return values. */ template struct dbus_traits< DBusArray > : public dbus_traits_base { static std::string getContainedType() { return dbus_traits::getType(); } static std::string getType() { return std::string("a") + dbus_traits::getType(); } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } typedef DBusArray host_type; typedef const host_type &arg_type; static void get(DBusConnection *conn, DBusMessage *msg, DBusMessageIter &iter, host_type &array) { if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { throw std::runtime_error("invalid argument"); } DBusMessageIter sub; dbus_message_iter_recurse(&iter, &sub); int type = dbus_message_iter_get_arg_type(&sub); // type is zero for empty arrays?! if (type && type != dbus_traits::dbus_type) { throw std::runtime_error("invalid argument"); } int nelements; typename dbus_traits::host_type *data; dbus_message_iter_get_fixed_array(&sub, &data, &nelements); array.first = nelements; array.second = data; dbus_message_iter_next(&iter); if (!type && nelements) { // non-empty array of invalid type?! throw std::runtime_error("could not decode DBusArray: type is zero, but array isn't empty"); } } static void append(DBusMessageIter &iter, arg_type array) { DBusMessageIter sub; if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, getContainedType().c_str(), &sub) || !dbus_message_iter_append_fixed_array(&sub, dbus_traits::dbus_type, &array.second, array.first) || !dbus_message_iter_close_container(&iter, &sub)) { throw std::runtime_error("out of memory"); } } }; /** * a std::map - treat it like a D-Bus dict */ template struct dbus_traits< std::map > : public dbus_traits_base { static std::string getContainedType() { return std::string("{") + dbus_traits::getType() + dbus_traits::getType() + "}"; } static std::string getType() { return std::string("a") + getContainedType(); } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } typedef std::map host_type; typedef const host_type &arg_type; static void get(DBusConnection *conn, DBusMessage *msg, DBusMessageIter &iter, host_type &dict) { if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { throw std::runtime_error("invalid argument"); } DBusMessageIter sub; dbus_message_iter_recurse(&iter, &sub); int type; while ((type = dbus_message_iter_get_arg_type(&sub)) != DBUS_TYPE_INVALID) { if (type != DBUS_TYPE_DICT_ENTRY) { throw std::runtime_error("invalid argument"); } DBusMessageIter entry; dbus_message_iter_recurse(&sub, &entry); K key; V value; dbus_traits::get(conn, msg, entry, key); dbus_traits::get(conn, msg, entry, value); dict.insert(std::make_pair(key, value)); dbus_message_iter_next(&sub); } dbus_message_iter_next(&iter); } static void append(DBusMessageIter &iter, arg_type dict) { DBusMessageIter sub; if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, getContainedType().c_str(), &sub)) { throw std::runtime_error("out of memory"); } for(typename host_type::const_iterator it = dict.begin(); it != dict.end(); ++it) { DBusMessageIter entry; if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_DICT_ENTRY, NULL, &entry)) { throw std::runtime_error("out of memory"); } dbus_traits::append(entry, it->first); dbus_traits::append(entry, it->second); if (!dbus_message_iter_close_container(&sub, &entry)) { throw std::runtime_error("out of memory"); } } if (!dbus_message_iter_close_container(&iter, &sub)) { throw std::runtime_error("out of memory"); } } }; /** * a std::vector - maps to D-Bus array, but with inefficient marshaling * because we cannot get a base pointer for the whole array */ template struct dbus_traits< std::vector > : public dbus_traits_base { static std::string getContainedType() { return dbus_traits::getType(); } static std::string getType() { return std::string("a") + getContainedType(); } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } typedef std::vector host_type; typedef const std::vector &arg_type; static void get(DBusConnection *conn, DBusMessage *msg, DBusMessageIter &iter, host_type &array) { if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { throw std::runtime_error("invalid argument"); } DBusMessageIter sub; dbus_message_iter_recurse(&iter, &sub); while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { V value; dbus_traits::get(conn, msg, sub, value); array.push_back(value); } dbus_message_iter_next(&iter); } static void append(DBusMessageIter &iter, arg_type array) { DBusMessageIter sub; if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, getContainedType().c_str(), &sub)) { throw std::runtime_error("out of memory"); } for(typename host_type::const_iterator it = array.begin(); it != array.end(); ++it) { dbus_traits::append(sub, *it); } if (!dbus_message_iter_close_container(&iter, &sub)) { throw std::runtime_error("out of memory"); } } }; /** simple smart pointer which takes memory allocated by libdbus and frees it with dbus_free() */ template class DBusMem : private boost::noncopyable { T *m_pointer; public: DBusMem(T *pointer) : m_pointer(pointer) {} ~DBusMem() { if (m_pointer) dbus_free(m_pointer); } operator T * () { return m_pointer; } T * get() { return m_pointer; } operator bool () { return m_pointer != 0; } }; /** * Helper class to append variant values into an iterator */ class append_visitor_dummy_type {}; struct append_visitor : public boost::static_visitor<> { DBusMessageIter &iter; append_visitor(DBusMessageIter &i) : iter(i) {} template void operator()(const V &v) const { DBusMessageIter sub; if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, dbus_traits::getType().c_str(), &sub)) { throw std::runtime_error("out of memory"); } dbus_traits::append(sub, v); if (!dbus_message_iter_close_container(&iter, &sub)) { throw std::runtime_error("out of memory"); } } }; /** * A boost::variant maps to a dbus variant, only care about values of * type V but will not throw error if type is not matched, this is useful if * application is interested on only a sub set of possible value types * in variant. */ template struct dbus_traits > : public dbus_traits_base { static std::string getType() { return "v"; } static std::string getSignature() { return getType(); } static std::string getReply() { return ""; } static const int dbus = DBUS_TYPE_VARIANT; static void get(DBusConnection *conn, DBusMessage *msg, DBusMessageIter &iter, boost::variant &value) { if (dbus_message_iter_get_arg_type(&iter) != dbus) { throw std::runtime_error("invalid argument"); return; } DBusMessageIter sub; dbus_message_iter_recurse(&iter, &sub); DBusMem sig(dbus_message_iter_get_signature(&sub)); if (dbus_traits::getSignature() != sig.get()) { //ignore unrecognized sub type in variant return; } V val; dbus_traits::get (conn, msg, sub, val); value = val; } static void append(DBusMessageIter &iter, const boost::variant &value) { boost::apply_visitor(append_visitor(iter), value); } typedef boost::variant host_type; typedef const boost::variant &arg_type; }; /** * A boost::variant maps to a dbus variant, only care about values of * type V1, V2 but will not throw error if type is not matched, this is useful if * application is interested on only a sub set of possible value types * in variant. */ template struct dbus_traits > : public dbus_traits_base { static std::string getType() { return "v"; } static std::string getSignature() { return getType(); } static std::string getReply() { return ""; } static const int dbus = DBUS_TYPE_VARIANT; static void get(DBusConnection *conn, DBusMessage *msg, DBusMessageIter &iter, boost::variant &value) { if (dbus_message_iter_get_arg_type(&iter) != dbus) { throw std::runtime_error("invalid argument"); return; } DBusMessageIter sub; dbus_message_iter_recurse(&iter, &sub); DBusMem sig(dbus_message_iter_get_signature(&sub)); if (dbus_traits::getSignature() == sig.get()) { V1 val; dbus_traits::get (conn, msg, sub, val); value = val; } else if (dbus_traits::getSignature() == sig.get()) { V2 val; dbus_traits::get (conn, msg, sub, val); value = val; } else { //ignore unrecognized sub type in variant } } static void append(DBusMessageIter &iter, const boost::variant &value) { boost::apply_visitor(append_visitor(iter), value); } typedef boost::variant host_type; typedef const boost::variant &arg_type; }; /** * a single member m of type V in a struct K */ template struct dbus_member_single { static std::string getType() { return dbus_traits::getType(); } typedef V host_type; static void get(DBusConnection *conn, DBusMessage *msg, DBusMessageIter &iter, K &val) { dbus_traits::get(conn, msg, iter, val.*m); } static void append(DBusMessageIter &iter, const K &val) { dbus_traits::append(iter, val.*m); } }; /** * a member m of type V in a struct K, followed by another dbus_member * or dbus_member_single to end the chain */ template struct dbus_member { static std::string getType() { return dbus_traits::getType() + M::getType(); } typedef V host_type; static void get(DBusConnection *conn, DBusMessage *msg, DBusMessageIter &iter, K &val) { dbus_traits::get(conn, msg, iter, val.*m); M::get(conn, msg, iter, val); } static void append(DBusMessageIter &iter, const K &val) { dbus_traits::append(iter, val.*m); M::append(iter, val); } }; /** * a helper class which implements dbus_traits for * a class, use with: * struct foo { int a; std::string b; }; * template<> struct dbus_traits< foo > : dbus_struct_traits< foo, * dbus_member > > {}; */ template struct dbus_struct_traits : public dbus_traits_base { static std::string getContainedType() { return M::getType(); } static std::string getType() { return std::string("(") + getContainedType() + ")"; } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } typedef K host_type; typedef const K &arg_type; static void get(DBusConnection *conn, DBusMessage *msg, DBusMessageIter &iter, host_type &val) { if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRUCT) { throw std::runtime_error("invalid argument"); } DBusMessageIter sub; dbus_message_iter_recurse(&iter, &sub); M::get(conn, msg, sub, val); dbus_message_iter_next(&iter); } static void append(DBusMessageIter &iter, arg_type val) { DBusMessageIter sub; if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_STRUCT, NULL, &sub)) { throw std::runtime_error("out of memory"); } M::append(sub, val); if (!dbus_message_iter_close_container(&iter, &sub)) { throw std::runtime_error("out of memory"); } } }; /** * a helper class which implements dbus_traits for an enum, * parameterize it with the enum type and an integer type * large enough to hold all valid enum values */ template struct dbus_enum_traits : public dbus_traits { typedef E host_type; typedef E arg_type; // cast from enum to int in append() is implicit; in // get() we have to make it explicit static void get(DBusConnection *conn, DBusMessage *msg, DBusMessageIter &iter, host_type &val) { I ival; dbus_traits::get(conn, msg, iter, ival); val = static_cast(ival); } }; /** * special case const reference parameter: * treat like pass-by-value input argument * * Example: const std::string &arg */ template struct dbus_traits : public dbus_traits {}; /** * special case writeable reference parameter: * must be a return value * * Example: std::string &retval */ template struct dbus_traits : public dbus_traits { static std::string getSignature() { return ""; } static std::string getReply() { return dbus_traits::getType(); } }; /** * dbus-cxx base exception thrown in dbus server * org.syncevolution.gdbuscxx.Exception * This base class only contains interfaces, no data members */ class DBusCXXException { public: /** * get exception name, used to convert to dbus error name * subclasses should override it */ virtual std::string getName() const { return "org.syncevolution.gdbuscxx.Exception"; } /** * get error message */ virtual const char* getMessage() const { return "unknown"; } }; static DBusMessage *handleException(DBusMessage *msg) { try { #ifdef DBUS_CXX_EXCEPTION_HANDLER return DBUS_CXX_EXCEPTION_HANDLER(msg); #else throw; #endif } catch (const dbus_error &ex) { return b_dbus_create_error(msg, ex.dbusName().c_str(), "%s", ex.what()); } catch (const DBusCXXException &ex) { return b_dbus_create_error(msg, ex.getName().c_str(), "%s", ex.getMessage()); } catch (const std::runtime_error &ex) { return b_dbus_create_error(msg, "org.syncevolution.gdbuscxx.Exception", "%s", ex.what()); } catch (...) { return b_dbus_create_error(msg, "org.syncevolution.gdbuscxx.Exception", "unknown"); } } /** * Check presence of a certain D-Bus client. */ class Watch : private boost::noncopyable { DBusConnectionPtr m_conn; boost::function m_callback; bool m_called; guint m_watchID; static void disconnect(DBusConnection *connection, void *user_data) { Watch *watch = static_cast(user_data); if (!watch->m_called) { watch->m_called = true; if (watch->m_callback) { watch->m_callback(); } } } public: Watch(const DBusConnectionPtr &conn, const boost::function &callback = boost::function()) : m_conn(conn), m_callback(callback), m_called(false), m_watchID(0) { } void setCallback(const boost::function &callback) { m_callback = callback; if (m_called && m_callback) { m_callback(); } } void activate(const char *peer) { if (!peer) { throw std::runtime_error("Watch::activate(): no peer"); } // Install watch first ... m_watchID = b_dbus_add_disconnect_watch(m_conn.get(), peer, disconnect, this, NULL); if (!m_watchID) { throw std::runtime_error("b_dbus_add_disconnect_watch() failed"); } // ... then check that the peer really exists, // otherwise we'll never notice the disconnect. // If it disconnects while we are doing this, // then disconnect() will be called twice, // but it handles that. DBusErrorCXX error; if (!dbus_bus_name_has_owner(m_conn.get(), peer, &error)) { if (error) { error.throwFailure("dbus_bus_name_has_owner()"); } disconnect(m_conn.get(), this); } } ~Watch() { if (m_watchID) { if (!b_dbus_remove_watch(m_conn.get(), m_watchID)) { // this may happen because the watch is // removed automatically when it was triggered } m_watchID = 0; } } }; /** * pseudo-parameter: not part of D-Bus signature, * but rather extracted from message attributes */ template <> struct dbus_traits< boost::shared_ptr > : public dbus_traits_base { static std::string getType() { return ""; } static std::string getSignature() { return ""; } static std::string getReply() { return ""; } static void get(DBusConnection *conn, DBusMessage *msg, DBusMessageIter &iter, boost::shared_ptr &value) { boost::shared_ptr watch(new Watch(conn)); watch->activate(dbus_message_get_sender(msg)); value = watch; } static void append(DBusMessageIter &iter, const boost::shared_ptr &value) {} typedef boost::shared_ptr host_type; typedef const boost::shared_ptr &arg_type; }; /** * base class for D-Bus results, * keeps references to required objects and provides the * failed() method */ class DBusResult : virtual public Result { protected: DBusConnectionPtr m_conn; /**< connection via which the message was received */ DBusMessagePtr m_msg; /**< the method invocation message */ public: DBusResult(DBusConnection *conn, DBusMessage *msg) : m_conn(conn, true), m_msg(msg, true) {} virtual void failed(const dbus_error &error) { if (!b_dbus_send_error(m_conn.get(), m_msg.get(), error.dbusName().c_str(), "%s", error.what())) { throw std::runtime_error("b_dbus_send_error() failed"); } } virtual Watch *createWatch(const boost::function &callback) { std::auto_ptr watch(new Watch(m_conn, callback)); watch->activate(dbus_message_get_sender(m_msg.get())); return watch.release(); } }; class DBusResult0 : public Result0, public DBusResult { public: DBusResult0(DBusConnection *conn, DBusMessage *msg) : DBusResult(conn, msg) {} virtual void done() { DBusMessagePtr reply(b_dbus_create_reply(m_msg.get(), DBUS_TYPE_INVALID)); if (!reply) { throw std::runtime_error("no DBusMessage"); } if (!dbus_connection_send(m_conn.get(), reply.get(), NULL)) { throw std::runtime_error("dbus_connection_send failed"); } } static std::string getSignature() { return ""; } }; template class DBusResult1 : public Result1, public DBusResult { public: DBusResult1(DBusConnection *conn, DBusMessage *msg) : DBusResult(conn, msg) {} virtual void done(A1 a1) { DBusMessagePtr reply(b_dbus_create_reply(m_msg.get(), DBUS_TYPE_INVALID)); if (!reply) { throw std::runtime_error("no DBusMessage"); } AppendRetvals(reply) << a1; if (!dbus_connection_send(m_conn.get(), reply.get(), NULL)) { throw std::runtime_error("dbus_connection_send failed"); } } static std::string getSignature() { return dbus_traits::getSignature(); } static const bool asynchronous = dbus_traits::asynchronous; }; template class DBusResult2 : public Result2, public DBusResult { public: DBusResult2(DBusConnection *conn, DBusMessage *msg) : DBusResult(conn, msg) {} virtual void done(A1 a1, A2 a2) { DBusMessagePtr reply(b_dbus_create_reply(m_msg.get(), DBUS_TYPE_INVALID)); if (!reply) { throw std::runtime_error("no DBusMessage"); } AppendRetvals(reply) << a1 << a2; if (!dbus_connection_send(m_conn.get(), reply.get(), NULL)) { throw std::runtime_error("dbus_connection_send failed"); } } static std::string getSignature() { return dbus_traits::getSignature() + DBusResult1::getSignature(); } static const bool asynchronous = dbus_traits::asynchronous || DBusResult1::asynchronous; }; template class DBusResult3 : public Result3, public DBusResult { public: DBusResult3(DBusConnection *conn, DBusMessage *msg) : DBusResult(conn, msg) {} virtual void done(A1 a1, A2 a2, A3 a3) { DBusMessagePtr reply(b_dbus_create_reply(m_msg.get(), DBUS_TYPE_INVALID)); if (!reply) { throw std::runtime_error("no DBusMessage"); } AppendRetvals(reply) << a1 << a2 << a3; if (!dbus_connection_send(m_conn.get(), reply.get(), NULL)) { throw std::runtime_error("dbus_connection_send failed"); } } static std::string getSignature() { return dbus_traits::getSignature() + DBusResult2::getSignature(); } static const bool asynchronous = dbus_traits::asynchronous || DBusResult2::asynchronous; }; template class DBusResult4 : public Result4, public DBusResult { public: DBusResult4(DBusConnection *conn, DBusMessage *msg) : DBusResult(conn, msg) {} virtual void done(A1 a1, A2 a2, A3 a3, A4 a4) { DBusMessagePtr reply(b_dbus_create_reply(m_msg.get(), DBUS_TYPE_INVALID)); if (!reply) { throw std::runtime_error("no DBusMessage"); } AppendRetvals(reply) << a1 << a2 << a3 << a4; if (!dbus_connection_send(m_conn.get(), reply.get(), NULL)) { throw std::runtime_error("dbus_connection_send failed"); } } static std::string getSignature() { return dbus_traits::getSignature() + DBusResult3::getSignature(); } static const bool asynchronous = dbus_traits::asynchronous || DBusResult3::asynchronous; }; template class DBusResult5 : public Result5, public DBusResult { public: DBusResult5(DBusConnection *conn, DBusMessage *msg) : DBusResult(conn, msg) {} virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) { DBusMessagePtr reply(b_dbus_create_reply(m_msg.get(), DBUS_TYPE_INVALID)); if (!reply) { throw std::runtime_error("no DBusMessage"); } AppendRetvals(reply) << a1 << a2 << a3 << a4 << a5; if (!dbus_connection_send(m_conn.get(), reply.get(), NULL)) { throw std::runtime_error("dbus_connection_send failed"); } } static std::string getSignature() { return dbus_traits::getSignature() + DBusResult4::getSignature(); } static const bool asynchronous = dbus_traits::asynchronous || DBusResult4::asynchronous; }; template class DBusResult6 : public Result6, public DBusResult { public: DBusResult6(DBusConnection *conn, DBusMessage *msg) : DBusResult(conn, msg) {} virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) { DBusMessagePtr reply(b_dbus_create_reply(m_msg.get(), DBUS_TYPE_INVALID)); if (!reply) { throw std::runtime_error("no DBusMessage"); } AppendRetvals(reply) << a1 << a2 << a3 << a4 << a5 << a6; if (!dbus_connection_send(m_conn.get(), reply.get(), NULL)) { throw std::runtime_error("dbus_connection_send failed"); } } static std::string getSignature() { return dbus_traits::getSignature() + DBusResult5::getSignature(); } static const bool asynchronous = dbus_traits::asynchronous || DBusResult5::asynchronous; }; template class DBusResult7 : public Result7, public DBusResult { public: DBusResult7(DBusConnection *conn, DBusMessage *msg) : DBusResult(conn, msg) {} virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) { DBusMessagePtr reply(b_dbus_create_reply(m_msg.get(), DBUS_TYPE_INVALID)); if (!reply) { throw std::runtime_error("no DBusMessage"); } AppendRetvals(reply) << a1 << a2 << a3 << a4 << a5 << a6 << a7; if (!dbus_connection_send(m_conn.get(), reply.get(), NULL)) { throw std::runtime_error("dbus_connection_send failed"); } } static std::string getSignature() { return dbus_traits::getSignature() + DBusResult6::getSignature(); } static const bool asynchronous = dbus_traits::asynchronous || DBusResult6::asynchronous; }; template class DBusResult8 : public Result8, public DBusResult { public: DBusResult8(DBusConnection *conn, DBusMessage *msg) : DBusResult(conn, msg) {} virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) { DBusMessagePtr reply(b_dbus_create_reply(m_msg.get(), DBUS_TYPE_INVALID)); if (!reply) { throw std::runtime_error("no DBusMessage"); } AppendRetvals(reply) << a1 << a2 << a3 << a4 << a5 << a6 << a7 << a8; if (!dbus_connection_send(m_conn.get(), reply.get(), NULL)) { throw std::runtime_error("dbus_connection_send failed"); } } static std::string getSignature() { return dbus_traits::getSignature() + DBusResult7::getSignature(); } static const bool asynchronous = dbus_traits::asynchronous || DBusResult7::asynchronous; }; template class DBusResult9 : public Result9, public DBusResult { public: DBusResult9(DBusConnection *conn, DBusMessage *msg) : DBusResult(conn, msg) {} virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9) { DBusMessagePtr reply(b_dbus_create_reply(m_msg.get(), DBUS_TYPE_INVALID)); if (!reply) { throw std::runtime_error("no DBusMessage"); } AppendRetvals(reply) << a1 << a2 << a3 << a4 << a5 << a6 << a7 << a8 << a9; if (!dbus_connection_send(m_conn.get(), reply.get(), NULL)) { throw std::runtime_error("dbus_connection_send failed"); } } static std::string getSignature() { return dbus_traits::getSignature() + DBusResult8::getSignature(); } static const bool asynchronous = dbus_traits::asynchronous || DBusResult8::asynchronous; }; template class DBusResult10 : public Result10, public DBusResult { public: DBusResult10(DBusConnection *conn, DBusMessage *msg) : DBusResult(conn, msg) {} virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10) { DBusMessagePtr reply(b_dbus_create_reply(m_msg.get(), DBUS_TYPE_INVALID)); if (!reply) { throw std::runtime_error("no DBusMessage"); } AppendRetvals(reply) << a1 << a2 << a3 << a4 << a5 << a6 << a7 << a8 << a9 << a10; if (!dbus_connection_send(m_conn.get(), reply.get(), NULL)) { throw std::runtime_error("dbus_connection_send failed"); } } static std::string getSignature() { return dbus_traits::getSignature() + DBusResult9::getSignature(); } static const bool asynchronous = dbus_traits::asynchronous || DBusResult9::asynchronous; }; /** * A parameter which points towards one of our Result* structures. * All of the types contained in it count towards the Reply signature. * The requested Result type itself is constructed here. * * @param R Result0, Result1, ... * @param DBusR the class implementing R */ template struct dbus_traits_result { static std::string getType() { return DBusR::getSignature(); } static std::string getSignature() { return ""; } static std::string getReply() { return getType(); } typedef boost::shared_ptr host_type; typedef boost::shared_ptr &arg_type; static const bool asynchronous = true; static void get(DBusConnection *conn, DBusMessage *msg, DBusMessageIter &iter, host_type &value) { value.reset(new DBusR(conn, msg)); } }; template <> struct dbus_traits< boost::shared_ptr > : public dbus_traits_result {}; template struct dbus_traits< boost::shared_ptr< Result1 > >: public dbus_traits_result< Result1, DBusResult1 > {}; template struct dbus_traits< boost::shared_ptr< Result2 > >: public dbus_traits_result< Result2, DBusResult2 > {}; template struct dbus_traits< boost::shared_ptr< Result3 > >: public dbus_traits_result< Result3, DBusResult3 > {}; template struct dbus_traits< boost::shared_ptr< Result4 > >: public dbus_traits_result< Result4, DBusResult4 > {}; template struct dbus_traits< boost::shared_ptr< Result5 > >: public dbus_traits_result< Result5, DBusResult5 > {}; template struct dbus_traits< boost::shared_ptr< Result6 > >: public dbus_traits_result< Result6, DBusResult6 > {}; template struct dbus_traits< boost::shared_ptr< Result7 > >: public dbus_traits_result< Result7, DBusResult7 > {}; template struct dbus_traits< boost::shared_ptr< Result8 > >: public dbus_traits_result< Result8, DBusResult8 > {}; template struct dbus_traits< boost::shared_ptr< Result9 > >: public dbus_traits_result< Result9, DBusResult9 > {}; template struct dbus_traits< boost::shared_ptr< Result10 > >: public dbus_traits_result< Result10, DBusResult10 > {}; #if 0 /** * Call with two parameters and one return code. All other calls are * variations of this, so this one is fully documented to explain all * tricks used in these templates. The actual code without comments is * below. */ template struct MakeMethodEntry< boost::function > { typedef boost::function M; // Any type a Result parameter? This can be computed at compile time. static const bool asynchronous = dbus_traits< DBusResult2 >::asynchronous; static DBusMessage *methodFunction(DBusConnection *conn, DBusMessage *msg, void *data) { // all exceptions must be caught and translated into // a suitable D-Bus reply try { // Argument types might may be references or pointers. // To instantiate a variable we need the underlying // datatype, which is provided by the dbus_traits. // "typename" is necessary to tell the compiler // that host_type really is a type. typename dbus_traits::host_type r; typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; // Extract all parameters. Because we don't now // whether a parameter is an argument or a return // value, we call get() for each of them and let // the corresponding dbus_traits decide that. Traits // for types which are plain types or const references // have a non-empty get(), whereas references are treated // as return values and have an empty get(). DBusMessageIter iter; dbus_message_iter_init(msg, &iter); dbus_traits::get(conn, msg, iter, a1); dbus_traits::get(conn, msg, iter, a2); // The data pointer is a pointer to a boost function, // as set up for us by make(). The compiler knows the // exact method prototype and thus can handle // call-by-value and call-by-reference correctly. r = (*static_cast(data))(a1, a2); // No reply necessary? If any of the types is asking for // a Result handle, then the reply will be sent later. if (asynchronous) { return NULL; } // Now prepare the reply. As with extracting parameters, // append() is empty for those parameters where nothing // has to be done. DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) return NULL; dbus_message_iter_init_append(reply, &iter); // We know that the return value has to be appended, // even though the trait would not normally do that // because it is a plain type => call utility function // directly. dbus_traits::append(iter, r); dbus_traits::append(iter, a1); dbus_traits::append(iter, a2); return reply; } catch (...) { // let handleException rethrow the exception // to determine its type return handleException(msg); } } /** * The boost function doesn't have a virtual destructor. * Therefore we have to cast down to the right type M * before deleting it. The rest of the allocated data * is freed by BDBusVector. */ static void destroyFunction(void *user_data) { BDBusMethodTable *entry = static_cast(user_data); delete static_cast(entry->method_data); } /** * Creates a BDBusMethodTable entry. * The strings inside the entry are allocated * with strdup(), to be freed by BDBusVector::destroy(). */ BDBusMethodTable make(const char *name, BDBusMethodFlags flags, const M &m) { BDBusMethodTable entry; entry.name = strdup(name); // same trick as before: only argument types // are added to the signature std::string buffer; buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); entry.signature = strdup(buffer.c_str()); // now the same for reply types buffer.clear(); buffer += dbus_traits::getType(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); entry.reply = strdup(buffer.c_str()); // these are the function templates above entry.function = methodFunction; entry.destroy = destroyFunction; // make sure that methodFunction has access to the boost function entry.flags = BDBusMethodFlags(flags | G_DBUS_METHOD_FLAG_METHOD_DATA | (asynchronous ? G_DBUS_METHOD_FLAG_ASYNC : 0)); entry.method_data = new M(m); return entry; } }; #endif // 0 /** ===> 10 parameters */ template struct MakeMethodEntry< boost::function > { typedef void (Mptr)(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10); typedef boost::function M; template static M bind(Mptr C::*method, I instance) { // this fails because bind() only supports up to 9 parameters, including // the initial this pointer return boost::bind(method, instance, _1, _2, _3, _4, _5, _6, _7, _8, _9 /* _10 */); } static const bool asynchronous = dbus_traits< DBusResult10 >::asynchronous; static DBusMessage *methodFunction(DBusConnection *conn, DBusMessage *msg, void *data) { try { typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; typename dbus_traits::host_type a5; typename dbus_traits::host_type a6; typename dbus_traits::host_type a7; typename dbus_traits::host_type a8; typename dbus_traits::host_type a9; typename dbus_traits::host_type a10; ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3) >> Get(a4) >> Get(a5) >> Get(a6) >> Get(a7) >> Get(a8) >> Get(a9) >> Get(a10); (*static_cast(data))(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); if (asynchronous) { return NULL; } DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) return NULL; AppendArgs(reply) << Set(a1) << Set(a2) << Set(a3) << Set(a4) << Set(a5) << Set(a6) << Set(a7) << Set(a8) << Set(a9) << Set(a10); return reply; } catch (...) { return handleException(msg); } } static void destroyFunction(void *user_data) { BDBusMethodTable *entry = static_cast(user_data); delete static_cast(entry->method_data); } static BDBusMethodTable make(const char *name, BDBusMethodFlags flags, const M &m) { BDBusMethodTable entry; entry.name = strdup(name); std::string buffer; buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); entry.signature = strdup(buffer.c_str()); buffer.clear(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); entry.reply = strdup(buffer.c_str()); entry.function = methodFunction; entry.destroy = destroyFunction; entry.flags = BDBusMethodFlags(flags | G_DBUS_METHOD_FLAG_METHOD_DATA | (asynchronous ? G_DBUS_METHOD_FLAG_ASYNC : 0)); entry.method_data = new M(m); return entry; } }; /** 9 arguments, 1 return value */ template struct MakeMethodEntry< boost::function > { typedef R (Mptr)(A1, A2, A3, A4, A5, A6, A7, A8, A9); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { // this fails because bind() only supports up to 9 parameters, including // the initial this pointer return boost::bind(method, instance, _1, _2, _3, _4, _5, _6, _7, _8, _9); } static const bool asynchronous = DBusResult9::asynchronous; static DBusMessage *methodFunction(DBusConnection *conn, DBusMessage *msg, void *data) { try { typename dbus_traits::host_type r; typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; typename dbus_traits::host_type a5; typename dbus_traits::host_type a6; typename dbus_traits::host_type a7; typename dbus_traits::host_type a8; typename dbus_traits::host_type a9; ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3) >> Get(a4) >> Get(a5) >> Get(a6) >> Get(a7) >> Get(a8) >> Get(a9); r = (*static_cast(data))(a1, a2, a3, a4, a5, a6, a7, a8, a9); if (asynchronous) { return NULL; } DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) return NULL; AppendArgs(reply) + r << Set(a1) << Set(a2) << Set(a3) << Set(a4) << Set(a5) << Set(a6) << Set(a7) << Set(a8) << Set(a9); return reply; } catch (...) { return handleException(msg); } } static void destroyFunction(void *user_data) { BDBusMethodTable *entry = static_cast(user_data); delete static_cast(entry->method_data); } static BDBusMethodTable make(const char *name, BDBusMethodFlags flags, const M &m) { BDBusMethodTable entry; entry.name = strdup(name); std::string buffer; buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); entry.signature = strdup(buffer.c_str()); buffer.clear(); buffer += dbus_traits::getType(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); entry.reply = strdup(buffer.c_str()); entry.function = methodFunction; entry.destroy = destroyFunction; entry.flags = BDBusMethodFlags(flags | G_DBUS_METHOD_FLAG_METHOD_DATA | (asynchronous ? G_DBUS_METHOD_FLAG_ASYNC : 0)); entry.method_data = new M(m); return entry; } }; /** ===> 9 parameters */ template struct MakeMethodEntry< boost::function > { typedef void (Mptr)(A1, A2, A3, A4, A5, A6, A7, A8, A9); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2, _3, _4, _5, _6, _7, _8, _9); } static const bool asynchronous = DBusResult9::asynchronous; static DBusMessage *methodFunction(DBusConnection *conn, DBusMessage *msg, void *data) { try { typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; typename dbus_traits::host_type a5; typename dbus_traits::host_type a6; typename dbus_traits::host_type a7; typename dbus_traits::host_type a8; typename dbus_traits::host_type a9; ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3) >> Get(a4) >> Get(a5) >> Get(a6) >> Get(a7) >> Get(a8) >> Get(a9); (*static_cast(data))(a1, a2, a3, a4, a5, a6, a7, a8, a9); if (asynchronous) { return NULL; } DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) return NULL; AppendArgs(reply) << Set(a1) << Set(a2) << Set(a3) << Set(a4) << Set(a5) << Set(a6) << Set(a7) << Set(a8) << Set(a9); return reply; } catch (...) { return handleException(msg); } } static void destroyFunction(void *user_data) { BDBusMethodTable *entry = static_cast(user_data); delete static_cast(entry->method_data); } static BDBusMethodTable make(const char *name, BDBusMethodFlags flags, const M &m) { BDBusMethodTable entry; entry.name = strdup(name); std::string buffer; buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); entry.signature = strdup(buffer.c_str()); buffer.clear(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); entry.reply = strdup(buffer.c_str()); entry.function = methodFunction; entry.destroy = destroyFunction; entry.flags = BDBusMethodFlags(flags | G_DBUS_METHOD_FLAG_METHOD_DATA | (asynchronous ? G_DBUS_METHOD_FLAG_ASYNC : 0)); entry.method_data = new M(m); return entry; } }; /** 8 arguments, 1 return value */ template struct MakeMethodEntry< boost::function > { typedef R (Mptr)(A1, A2, A3, A4, A5, A6, A7, A8); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2, _3, _4, _5, _6, _7, _8); } static const bool asynchronous = DBusResult8::asynchronous; static DBusMessage *methodFunction(DBusConnection *conn, DBusMessage *msg, void *data) { try { typename dbus_traits::host_type r; typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; typename dbus_traits::host_type a5; typename dbus_traits::host_type a6; typename dbus_traits::host_type a7; typename dbus_traits::host_type a8; ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3) >> Get(a4) >> Get(a5) >> Get(a6) >> Get(a7) >> Get(a8); r = (*static_cast(data))(a1, a2, a3, a4, a5, a6, a7, a8); if (asynchronous) { return NULL; } DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) return NULL; AppendArgs(reply) + r << Set(a1) << Set(a2) << Set(a3) << Set(a4) << Set(a5) << Set(a6) << Set(a7) << Set(a8); return reply; } catch (...) { return handleException(msg); } } static void destroyFunction(void *user_data) { BDBusMethodTable *entry = static_cast(user_data); delete static_cast(entry->method_data); } static BDBusMethodTable make(const char *name, BDBusMethodFlags flags, const M &m) { BDBusMethodTable entry; entry.name = strdup(name); std::string buffer; buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); entry.signature = strdup(buffer.c_str()); buffer.clear(); buffer += dbus_traits::getType(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); entry.reply = strdup(buffer.c_str()); entry.function = methodFunction; entry.destroy = destroyFunction; entry.flags = BDBusMethodFlags(flags | G_DBUS_METHOD_FLAG_METHOD_DATA | (asynchronous ? G_DBUS_METHOD_FLAG_ASYNC : 0)); entry.method_data = new M(m); return entry; } }; /** ===> 8 parameters */ template struct MakeMethodEntry< boost::function > { typedef void (Mptr)(A1, A2, A3, A4, A5, A6, A7, A8); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2, _3, _4, _5, _6, _7, _8); } static const bool asynchronous = DBusResult8::asynchronous; static DBusMessage *methodFunction(DBusConnection *conn, DBusMessage *msg, void *data) { try { typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; typename dbus_traits::host_type a5; typename dbus_traits::host_type a6; typename dbus_traits::host_type a7; typename dbus_traits::host_type a8; ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3) >> Get(a4) >> Get(a5) >> Get(a6) >> Get(a7) >> Get(a8); (*static_cast(data))(a1, a2, a3, a4, a5, a6, a7, a8); if (asynchronous) { return NULL; } DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) return NULL; AppendArgs(reply) << Set(a1) << Set(a2) << Set(a3) << Set(a4) << Set(a5) << Set(a6) << Set(a7) << Set(a8); return reply; } catch (...) { return handleException(msg); } } static void destroyFunction(void *user_data) { BDBusMethodTable *entry = static_cast(user_data); delete static_cast(entry->method_data); } static BDBusMethodTable make(const char *name, BDBusMethodFlags flags, const M &m) { BDBusMethodTable entry; entry.name = strdup(name); std::string buffer; buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); entry.signature = strdup(buffer.c_str()); buffer.clear(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); entry.reply = strdup(buffer.c_str()); entry.function = methodFunction; entry.destroy = destroyFunction; entry.flags = BDBusMethodFlags(flags | G_DBUS_METHOD_FLAG_METHOD_DATA | (asynchronous ? G_DBUS_METHOD_FLAG_ASYNC : 0)); entry.method_data = new M(m); return entry; } }; /** 7 arguments, 1 return value */ template struct MakeMethodEntry< boost::function > { typedef R (Mptr)(A1, A2, A3, A4, A5, A6, A7); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2, _3, _4, _5, _6, _7); } static const bool asynchronous = DBusResult7::asynchronous; static DBusMessage *methodFunction(DBusConnection *conn, DBusMessage *msg, void *data) { try { typename dbus_traits::host_type r; typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; typename dbus_traits::host_type a5; typename dbus_traits::host_type a6; typename dbus_traits::host_type a7; ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3) >> Get(a4) >> Get(a5) >> Get(a6) >> Get(a7); r = (*static_cast(data))(a1, a2, a3, a4, a5, a6, a7); if (asynchronous) { return NULL; } DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) return NULL; AppendArgs(reply) + r << Set(a1) << Set(a2) << Set(a3) << Set(a4) << Set(a5) << Set(a6) << Set(a7); return reply; } catch (...) { return handleException(msg); } } static void destroyFunction(void *user_data) { BDBusMethodTable *entry = static_cast(user_data); delete static_cast(entry->method_data); } static BDBusMethodTable make(const char *name, BDBusMethodFlags flags, const M &m) { BDBusMethodTable entry; entry.name = strdup(name); std::string buffer; buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); entry.signature = strdup(buffer.c_str()); buffer.clear(); buffer += dbus_traits::getType(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); entry.reply = strdup(buffer.c_str()); entry.function = methodFunction; entry.destroy = destroyFunction; entry.flags = BDBusMethodFlags(flags | G_DBUS_METHOD_FLAG_METHOD_DATA | (asynchronous ? G_DBUS_METHOD_FLAG_ASYNC : 0)); entry.method_data = new M(m); return entry; } }; /** ===> 7 parameters */ template struct MakeMethodEntry< boost::function > { typedef void (Mptr)(A1, A2, A3, A4, A5, A6, A7); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2, _3, _4, _5, _6, _7); } static const bool asynchronous = DBusResult7::asynchronous; static DBusMessage *methodFunction(DBusConnection *conn, DBusMessage *msg, void *data) { try { typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; typename dbus_traits::host_type a5; typename dbus_traits::host_type a6; typename dbus_traits::host_type a7; ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3) >> Get(a4) >> Get(a5) >> Get(a6) >> Get(a7); (*static_cast(data))(a1, a2, a3, a4, a5, a6, a7); if (asynchronous) { return NULL; } DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) return NULL; AppendArgs(reply) << Set(a1) << Set(a2) << Set(a3) << Set(a4) << Set(a5) << Set(a6) << Set(a7); return reply; } catch (...) { return handleException(msg); } } static void destroyFunction(void *user_data) { BDBusMethodTable *entry = static_cast(user_data); delete static_cast(entry->method_data); } static BDBusMethodTable make(const char *name, BDBusMethodFlags flags, const M &m) { BDBusMethodTable entry; entry.name = strdup(name); std::string buffer; buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); entry.signature = strdup(buffer.c_str()); buffer.clear(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); entry.reply = strdup(buffer.c_str()); entry.function = methodFunction; entry.destroy = destroyFunction; entry.flags = BDBusMethodFlags(flags | G_DBUS_METHOD_FLAG_METHOD_DATA | (asynchronous ? G_DBUS_METHOD_FLAG_ASYNC : 0)); entry.method_data = new M(m); return entry; } }; /** 6 arguments, 1 return value */ template struct MakeMethodEntry< boost::function > { typedef R (Mptr)(A1, A2, A3, A4, A5, A6); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2, _3, _4, _5, _6); } static const bool asynchronous = DBusResult6::asynchronous; static DBusMessage *methodFunction(DBusConnection *conn, DBusMessage *msg, void *data) { try { typename dbus_traits::host_type r; typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; typename dbus_traits::host_type a5; typename dbus_traits::host_type a6; ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3) >> Get(a4) >> Get(a5) >> Get(a6); r = (*static_cast(data))(a1, a2, a3, a4, a5, a6); if (asynchronous) { return NULL; } DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) return NULL; AppendArgs(reply) + r << Set(a1) << Set(a2) << Set(a3) << Set(a4) << Set(a5) << Set(a6); return reply; } catch (...) { return handleException(msg); } } static void destroyFunction(void *user_data) { BDBusMethodTable *entry = static_cast(user_data); delete static_cast(entry->method_data); } static BDBusMethodTable make(const char *name, BDBusMethodFlags flags, const M &m) { BDBusMethodTable entry; entry.name = strdup(name); std::string buffer; buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); entry.signature = strdup(buffer.c_str()); buffer.clear(); buffer += dbus_traits::getType(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); entry.reply = strdup(buffer.c_str()); entry.function = methodFunction; entry.destroy = destroyFunction; entry.flags = BDBusMethodFlags(flags | G_DBUS_METHOD_FLAG_METHOD_DATA | (asynchronous ? G_DBUS_METHOD_FLAG_ASYNC : 0)); entry.method_data = new M(m); return entry; } }; /** ===> 6 parameters */ template struct MakeMethodEntry< boost::function > { typedef void (Mptr)(A1, A2, A3, A4, A5, A6); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2, _3, _4, _5, _6); } static const bool asynchronous = DBusResult6::asynchronous; static DBusMessage *methodFunction(DBusConnection *conn, DBusMessage *msg, void *data) { try { typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; typename dbus_traits::host_type a5; typename dbus_traits::host_type a6; ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3) >> Get(a4) >> Get(a5) >> Get(a6); (*static_cast(data))(a1, a2, a3, a4, a5, a6); if (asynchronous) { return NULL; } DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) return NULL; AppendArgs(reply) << Set(a1) << Set(a2) << Set(a3) << Set(a4) << Set(a5) << Set(a6); return reply; } catch (...) { return handleException(msg); } } static void destroyFunction(void *user_data) { BDBusMethodTable *entry = static_cast(user_data); delete static_cast(entry->method_data); } static BDBusMethodTable make(const char *name, BDBusMethodFlags flags, const M &m) { BDBusMethodTable entry; entry.name = strdup(name); std::string buffer; buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); entry.signature = strdup(buffer.c_str()); buffer.clear(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); entry.reply = strdup(buffer.c_str()); entry.function = methodFunction; entry.destroy = destroyFunction; entry.flags = BDBusMethodFlags(flags | G_DBUS_METHOD_FLAG_METHOD_DATA | (asynchronous ? G_DBUS_METHOD_FLAG_ASYNC : 0)); entry.method_data = new M(m); return entry; } }; /** 5 arguments, 1 return value */ template struct MakeMethodEntry< boost::function > { typedef R (Mptr)(A1, A2, A3, A4, A5); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2, _3, _4, _5); } static const bool asynchronous = DBusResult5::asynchronous; static DBusMessage *methodFunction(DBusConnection *conn, DBusMessage *msg, void *data) { try { typename dbus_traits::host_type r; typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; typename dbus_traits::host_type a5; ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3) >> Get(a4) >> Get(a5); r = (*static_cast(data))(a1, a2, a3, a4, a5); if (asynchronous) { return NULL; } DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) return NULL; AppendArgs(reply) + r << Set(a1) << Set(a2) << Set(a3) << Set(a4) << Set(a5); return reply; } catch (...) { return handleException(msg); } } static void destroyFunction(void *user_data) { BDBusMethodTable *entry = static_cast(user_data); delete static_cast(entry->method_data); } static BDBusMethodTable make(const char *name, BDBusMethodFlags flags, const M &m) { BDBusMethodTable entry; entry.name = strdup(name); std::string buffer; buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); entry.signature = strdup(buffer.c_str()); buffer.clear(); buffer += dbus_traits::getType(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); entry.reply = strdup(buffer.c_str()); entry.function = methodFunction; entry.destroy = destroyFunction; entry.flags = BDBusMethodFlags(flags | G_DBUS_METHOD_FLAG_METHOD_DATA | (asynchronous ? G_DBUS_METHOD_FLAG_ASYNC : 0)); entry.method_data = new M(m); return entry; } }; /** ===> 5 parameters */ template struct MakeMethodEntry< boost::function > { typedef void (Mptr)(A1, A2, A3, A4, A5); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2, _3, _4, _5); } static const bool asynchronous = DBusResult5::asynchronous; static DBusMessage *methodFunction(DBusConnection *conn, DBusMessage *msg, void *data) { try { typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; typename dbus_traits::host_type a5; ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3) >> Get(a4) >> Get(a5); (*static_cast(data))(a1, a2, a3, a4, a5); if (asynchronous) { return NULL; } DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) return NULL; AppendArgs(reply) << Set(a1) << Set(a2) << Set(a3) << Set(a4) << Set(a5); return reply; } catch (...) { return handleException(msg); } } static void destroyFunction(void *user_data) { BDBusMethodTable *entry = static_cast(user_data); delete static_cast(entry->method_data); } static BDBusMethodTable make(const char *name, BDBusMethodFlags flags, const M &m) { BDBusMethodTable entry; entry.name = strdup(name); std::string buffer; buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); entry.signature = strdup(buffer.c_str()); buffer.clear(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); entry.reply = strdup(buffer.c_str()); entry.function = methodFunction; entry.destroy = destroyFunction; entry.flags = BDBusMethodFlags(flags | G_DBUS_METHOD_FLAG_METHOD_DATA | (asynchronous ? G_DBUS_METHOD_FLAG_ASYNC : 0)); entry.method_data = new M(m); return entry; } }; /** 4 arguments, 1 return value */ template struct MakeMethodEntry< boost::function > { typedef R (Mptr)(A1, A2, A3, A4); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2, _3, _4); } static const bool asynchronous = DBusResult4::asynchronous; static DBusMessage *methodFunction(DBusConnection *conn, DBusMessage *msg, void *data) { try { typename dbus_traits::host_type r; typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3) >> Get(a4); r = (*static_cast(data))(a1, a2, a3, a4); if (asynchronous) { return NULL; } DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) return NULL; AppendArgs(reply) + r << Set(a1) << Set(a2) << Set(a3) << Set(a4); return reply; } catch (...) { return handleException(msg); } } static void destroyFunction(void *user_data) { BDBusMethodTable *entry = static_cast(user_data); delete static_cast(entry->method_data); } static BDBusMethodTable make(const char *name, BDBusMethodFlags flags, const M &m) { BDBusMethodTable entry; entry.name = strdup(name); std::string buffer; buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); entry.signature = strdup(buffer.c_str()); buffer.clear(); buffer += dbus_traits::getType(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); entry.reply = strdup(buffer.c_str()); entry.function = methodFunction; entry.destroy = destroyFunction; entry.flags = BDBusMethodFlags(flags | G_DBUS_METHOD_FLAG_METHOD_DATA | (asynchronous ? G_DBUS_METHOD_FLAG_ASYNC : 0)); entry.method_data = new M(m); return entry; } }; /** ===> 4 parameters */ template struct MakeMethodEntry< boost::function > { typedef void (Mptr)(A1, A2, A3, A4); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2, _3, _4); } static const bool asynchronous = DBusResult4::asynchronous; static DBusMessage *methodFunction(DBusConnection *conn, DBusMessage *msg, void *data) { try { typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3) >> Get(a4); (*static_cast(data))(a1, a2, a3, a4); if (asynchronous) { return NULL; } DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) return NULL; AppendArgs(reply) << Set(a1) << Set(a2) << Set(a3) << Set(a4); return reply; } catch (...) { return handleException(msg); } } static void destroyFunction(void *user_data) { BDBusMethodTable *entry = static_cast(user_data); delete static_cast(entry->method_data); } static BDBusMethodTable make(const char *name, BDBusMethodFlags flags, const M &m) { BDBusMethodTable entry; entry.name = strdup(name); std::string buffer; buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); entry.signature = strdup(buffer.c_str()); buffer.clear(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); entry.reply = strdup(buffer.c_str()); entry.function = methodFunction; entry.destroy = destroyFunction; entry.flags = BDBusMethodFlags(flags | G_DBUS_METHOD_FLAG_METHOD_DATA | (asynchronous ? G_DBUS_METHOD_FLAG_ASYNC : 0)); entry.method_data = new M(m); return entry; } }; /** 3 arguments, 1 return value */ template struct MakeMethodEntry< boost::function > { typedef R (Mptr)(A1, A2, A3); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2, _3); } static const bool asynchronous = DBusResult3::asynchronous; static DBusMessage *methodFunction(DBusConnection *conn, DBusMessage *msg, void *data) { try { typename dbus_traits::host_type r; typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3); r = (*static_cast(data))(a1, a2, a3); if (asynchronous) { return NULL; } DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) return NULL; AppendArgs(reply) + r << Set(a1) << Set(a2) << Set(a3); return reply; } catch (...) { return handleException(msg); } } static void destroyFunction(void *user_data) { BDBusMethodTable *entry = static_cast(user_data); delete static_cast(entry->method_data); } static BDBusMethodTable make(const char *name, BDBusMethodFlags flags, const M &m) { BDBusMethodTable entry; entry.name = strdup(name); std::string buffer; buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); entry.signature = strdup(buffer.c_str()); buffer.clear(); buffer += dbus_traits::getType(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); entry.reply = strdup(buffer.c_str()); entry.function = methodFunction; entry.destroy = destroyFunction; entry.flags = BDBusMethodFlags(flags | G_DBUS_METHOD_FLAG_METHOD_DATA | (asynchronous ? G_DBUS_METHOD_FLAG_ASYNC : 0)); entry.method_data = new M(m); return entry; } }; /** ===> 3 parameters */ template struct MakeMethodEntry< boost::function > { typedef void (Mptr)(A1, A2, A3); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2, _3); } static const bool asynchronous = DBusResult3::asynchronous; static DBusMessage *methodFunction(DBusConnection *conn, DBusMessage *msg, void *data) { try { typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3); (*static_cast(data))(a1, a2, a3); if (asynchronous) { return NULL; } DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) return NULL; AppendArgs(reply) << Set(a1) << Set(a2) << Set(a3); return reply; } catch (...) { return handleException(msg); } } static void destroyFunction(void *user_data) { BDBusMethodTable *entry = static_cast(user_data); delete static_cast(entry->method_data); } static BDBusMethodTable make(const char *name, BDBusMethodFlags flags, const M &m) { BDBusMethodTable entry; entry.name = strdup(name); std::string buffer; buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); entry.signature = strdup(buffer.c_str()); buffer.clear(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); entry.reply = strdup(buffer.c_str()); entry.function = methodFunction; entry.destroy = destroyFunction; entry.flags = BDBusMethodFlags(flags | G_DBUS_METHOD_FLAG_METHOD_DATA | (asynchronous ? G_DBUS_METHOD_FLAG_ASYNC : 0)); entry.method_data = new M(m); return entry; } }; /** 2 arguments, 1 return value */ template struct MakeMethodEntry< boost::function > { typedef R (Mptr)(A1, A2); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2); } static const bool asynchronous = DBusResult2::asynchronous; static DBusMessage *methodFunction(DBusConnection *conn, DBusMessage *msg, void *data) { try { typename dbus_traits::host_type r; typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; ExtractArgs(conn, msg) >> Get(a1) >> Get(a2); r = (*static_cast(data))(a1, a2); if (asynchronous) { return NULL; } DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) return NULL; AppendArgs(reply) + r << Set(a1) << Set(a2); return reply; } catch (...) { return handleException(msg); } } static void destroyFunction(void *user_data) { BDBusMethodTable *entry = static_cast(user_data); delete static_cast(entry->method_data); } static BDBusMethodTable make(const char *name, BDBusMethodFlags flags, const M &m) { BDBusMethodTable entry; entry.name = strdup(name); std::string buffer; buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); entry.signature = strdup(buffer.c_str()); buffer.clear(); buffer += dbus_traits::getType(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); entry.reply = strdup(buffer.c_str()); entry.function = methodFunction; entry.destroy = destroyFunction; entry.flags = BDBusMethodFlags(flags | G_DBUS_METHOD_FLAG_METHOD_DATA | (asynchronous ? G_DBUS_METHOD_FLAG_ASYNC : 0)); entry.method_data = new M(m); return entry; } }; /** ===> 2 parameters */ template struct MakeMethodEntry< boost::function > { typedef void (Mptr)(A1, A2); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2); } static const bool asynchronous = DBusResult2::asynchronous; static DBusMessage *methodFunction(DBusConnection *conn, DBusMessage *msg, void *data) { try { typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; ExtractArgs(conn, msg) >> Get(a1) >> Get(a2); (*static_cast(data))(a1, a2); if (asynchronous) { return NULL; } DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) return NULL; AppendArgs(reply) << Set(a1) << Set(a2); return reply; } catch (...) { return handleException(msg); } } static void destroyFunction(void *user_data) { BDBusMethodTable *entry = static_cast(user_data); delete static_cast(entry->method_data); } static BDBusMethodTable make(const char *name, BDBusMethodFlags flags, const M &m) { BDBusMethodTable entry; entry.name = strdup(name); std::string buffer; buffer += dbus_traits::getSignature(); buffer += dbus_traits::getSignature(); entry.signature = strdup(buffer.c_str()); buffer.clear(); buffer += dbus_traits::getReply(); buffer += dbus_traits::getReply(); entry.reply = strdup(buffer.c_str()); entry.function = methodFunction; entry.destroy = destroyFunction; entry.flags = BDBusMethodFlags(flags | G_DBUS_METHOD_FLAG_METHOD_DATA | (asynchronous ? G_DBUS_METHOD_FLAG_ASYNC : 0)); entry.method_data = new M(m); return entry; } }; /** 1 argument, 1 return value */ template struct MakeMethodEntry< boost::function > { typedef R (Mptr)(A1); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1); } static const bool asynchronous = DBusResult1::asynchronous; static DBusMessage *methodFunction(DBusConnection *conn, DBusMessage *msg, void *data) { try { typename dbus_traits::host_type r; typename dbus_traits::host_type a1; ExtractArgs(conn, msg) >> Get(a1); r = (*static_cast(data))(a1); if (asynchronous) { return NULL; } DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) return NULL; AppendArgs(reply) + r << Set(a1); return reply; } catch (...) { return handleException(msg); } } static void destroyFunction(void *user_data) { BDBusMethodTable *entry = static_cast(user_data); delete static_cast(entry->method_data); } static BDBusMethodTable make(const char *name, BDBusMethodFlags flags, const M &m) { BDBusMethodTable entry; entry.name = strdup(name); std::string buffer; buffer += dbus_traits::getSignature(); entry.signature = strdup(buffer.c_str()); buffer.clear(); buffer += dbus_traits::getType(); buffer += dbus_traits::getReply(); entry.reply = strdup(buffer.c_str()); entry.function = methodFunction; entry.destroy = destroyFunction; entry.flags = BDBusMethodFlags(flags | G_DBUS_METHOD_FLAG_METHOD_DATA | (asynchronous ? G_DBUS_METHOD_FLAG_ASYNC : 0)); entry.method_data = new M(m); return entry; } }; /** ===> 1 parameter */ template struct MakeMethodEntry< boost::function > { typedef void (Mptr)(A1); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1); } static const bool asynchronous = DBusResult1::asynchronous; static DBusMessage *methodFunction(DBusConnection *conn, DBusMessage *msg, void *data) { try { typename dbus_traits::host_type a1; ExtractArgs(conn, msg) >> Get(a1); (*static_cast(data))(a1); if (asynchronous) { return NULL; } DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) return NULL; AppendArgs(reply) << Set(a1); return reply; } catch (...) { return handleException(msg); } } static void destroyFunction(void *user_data) { BDBusMethodTable *entry = static_cast(user_data); delete static_cast(entry->method_data); } static BDBusMethodTable make(const char *name, BDBusMethodFlags flags, const M &m) { BDBusMethodTable entry; entry.name = strdup(name); std::string buffer; buffer += dbus_traits::getSignature(); entry.signature = strdup(buffer.c_str()); buffer.clear(); buffer += dbus_traits::getReply(); entry.reply = strdup(buffer.c_str()); entry.function = methodFunction; entry.destroy = destroyFunction; entry.flags = BDBusMethodFlags(flags | G_DBUS_METHOD_FLAG_METHOD_DATA | (asynchronous ? G_DBUS_METHOD_FLAG_ASYNC : 0)); entry.method_data = new M(m); return entry; } }; /** 0 arguments, 1 return value */ template struct MakeMethodEntry< boost::function > { typedef R (Mptr)(); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance); } static DBusMessage *methodFunction(DBusConnection *conn, DBusMessage *msg, void *data) { try { typename dbus_traits::host_type r; r = (*static_cast(data))(); DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) return NULL; AppendArgs(reply) + r; return reply; } catch (...) { return handleException(msg); } } static void destroyFunction(void *user_data) { BDBusMethodTable *entry = static_cast(user_data); delete static_cast(entry->method_data); } static BDBusMethodTable make(const char *name, BDBusMethodFlags flags, const M &m) { BDBusMethodTable entry; entry.name = strdup(name); std::string buffer; entry.signature = strdup(buffer.c_str()); buffer.clear(); buffer += dbus_traits::getType(); entry.reply = strdup(buffer.c_str()); entry.function = methodFunction; entry.destroy = destroyFunction; entry.flags = BDBusMethodFlags(flags | G_DBUS_METHOD_FLAG_METHOD_DATA); entry.method_data = new M(m); return entry; } }; /** ===> 0 parameter */ template <> struct MakeMethodEntry< boost::function > { typedef void (Mptr)(); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance); } static DBusMessage *methodFunction(DBusConnection *conn, DBusMessage *msg, void *data) { try { (*static_cast(data))(); DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) return NULL; return reply; } catch (...) { return handleException(msg); } } static void destroyFunction(void *user_data) { BDBusMethodTable *entry = static_cast(user_data); delete static_cast(entry->method_data); } static BDBusMethodTable make(const char *name, BDBusMethodFlags flags, const M &m) { BDBusMethodTable entry; entry.name = strdup(name); std::string buffer; entry.signature = strdup(buffer.c_str()); buffer.clear(); entry.reply = strdup(buffer.c_str()); entry.function = methodFunction; entry.destroy = destroyFunction; entry.flags = BDBusMethodFlags(flags | G_DBUS_METHOD_FLAG_METHOD_DATA); entry.method_data = new M(m); return entry; } }; template struct TraitsBase { typedef Cb Callback_t; typedef Ret Return_t; struct CallbackData { //only keep connection, for DBusClientCall instance is absent when 'dbus client call' returns //suppose connection is available in the callback handler const DBusConnectionPtr m_conn; Callback_t m_callback; CallbackData(const DBusConnectionPtr &conn, const Callback_t &callback) : m_conn(conn), m_callback(callback) {} }; }; struct VoidReturn {}; struct VoidTraits : public TraitsBase, VoidReturn> { typedef TraitsBase, VoidReturn> base; typedef base::Callback_t Callback_t; typedef base::Return_t Return_t; static Return_t demarshal(DBusMessagePtr &/*reply*/, const DBusConnectionPtr &/*conn*/) { return Return_t(); } static void handleMessage(DBusMessagePtr &/*reply*/, base::CallbackData *data, const std::string &error) { //unmarshal the return results and call user callback if (data->m_callback) { data->m_callback(error); } } }; template struct Ret1Traits : public TraitsBase, R1> { typedef TraitsBase, R1> base; typedef typename base::Callback_t Callback_t; typedef typename base::Return_t Return_t; static Return_t demarshal(DBusMessagePtr &reply, const DBusConnectionPtr &conn) { typename dbus_traits::host_type r; ExtractArgs(conn.get(), reply.get()) >> Get(r); return r; } static void handleMessage(DBusMessagePtr &reply, typename base::CallbackData *data, const std::string &error) { typename dbus_traits::host_type r; if (error.empty()) { ExtractArgs(data->m_conn.get(), reply.get()) >> Get(r); } //unmarshal the return results and call user callback if (data->m_callback) { data->m_callback(r, error); } } }; template struct Ret2Traits : public TraitsBase, std::pair > { typedef TraitsBase, std::pair > base; typedef typename base::Callback_t Callback_t; typedef typename base::Return_t Return_t; static Return_t demarshal(DBusMessagePtr &reply, const DBusConnectionPtr &conn) { Return_t r; ExtractArgs(conn.get(), reply.get()) >> Get(r.first) >> Get(r.second); return r; } static void handleMessage(DBusMessagePtr &reply, typename base::CallbackData *data, const std::string &error) { typename dbus_traits::host_type r1; typename dbus_traits::host_type r2; if (error.empty()) { ExtractArgs(data->m_conn.get(), reply.get()) >> Get(r1) >> Get(r2); } //unmarshal the return results and call user callback if (data->m_callback) { data->m_callback(r1, r2, error); } } }; template struct Ret3Traits : public TraitsBase, boost::tuple > { typedef TraitsBase, boost::tuple > base; typedef typename base::Callback_t Callback_t; typedef typename base::Return_t Return_t; static Return_t demarshal(DBusMessagePtr &reply, const DBusConnectionPtr &conn) { Return_t r; ExtractArgs(conn.get(), reply.get()) >> Get(boost::get<0>(r)) >> Get(boost::get<1>(r)) >> Get(boost::get<2>(r)); return r; } static void handleMessage(DBusMessagePtr &reply, typename base::CallbackData *data, const std::string &error) { typename dbus_traits::host_type r1; typename dbus_traits::host_type r2; typename dbus_traits::host_type r3; if (error.empty()) { ExtractArgs(data->m_conn.get(), reply.get()) >> Get(r1) >> Get(r2) >> Get(r3); } //unmarshal the return results and call user callback if (data->m_callback) { data->m_callback(r1, r2, r3, error); } } }; /** fill buffer with error name and description (if available), return true if error found */ bool CheckError(const DBusMessagePtr &reply, std::string &buffer); template class DBusClientCall { public: typedef typename CallTraits::Callback_t Callback_t; typedef typename CallTraits::Return_t Return_t; typedef typename CallTraits::base::CallbackData CallbackData; protected: const std::string m_destination; const std::string m_path; const std::string m_interface; const std::string m_method; const DBusConnectionPtr m_conn; static void dbusCallback (DBusPendingCall *call, void *user_data) { CallbackData *data = static_cast(user_data); DBusMessagePtr reply = dbus_pending_call_steal_reply (call); std::string error; CheckError(reply, error); CallTraits::handleMessage(reply, data, error); } /** * called by libdbus to free the user_data pointer set in * dbus_pending_call_set_notify() */ static void callDataUnref(void *user_data) { delete static_cast(user_data); } void prepare(DBusMessagePtr &msg) { // Constructor steals reference, reset() doesn't! // Therefore use constructor+copy instead of reset(). msg = DBusMessagePtr(dbus_message_new_method_call(m_destination.c_str(), m_path.c_str(), m_interface.c_str(), m_method.c_str())); if (!msg) { throw std::runtime_error("dbus_message_new_method_call() failed"); } } void send(DBusMessagePtr &msg, const Callback_t &callback) { DBusPendingCall *call; if (!dbus_connection_send_with_reply(m_conn.get(), msg.get(), &call, DBUS_TIMEOUT_INFINITE)) { throw std::runtime_error("dbus_connection_send failed"); } else if (call == NULL) { throw std::runtime_error("received pending call is NULL"); } DBusPendingCallPtr mCall (call); CallbackData *data = new CallbackData(m_conn, callback); dbus_pending_call_set_notify(mCall.get(), dbusCallback, data, callDataUnref); } Return_t sendAndReturn(DBusMessagePtr &msg) { DBusErrorCXX error; // Constructor steals reference, reset() doesn't! // Therefore use constructor+copy instead of reset(). DBusMessagePtr reply = DBusMessagePtr(dbus_connection_send_with_reply_and_block(m_conn.get(), msg.get(), DBUS_TIMEOUT_INFINITE, &error)); if (!reply) { error.throwFailure(m_method); } return CallTraits::demarshal(reply, m_conn); } public: DBusClientCall(const DBusRemoteObject &object, const std::string &method) :m_destination (object.getDestination()), m_path (object.getPath()), m_interface (object.getInterface()), m_method (method), m_conn (object.getConnection()) { } DBusConnection *getConnection() { return m_conn.get(); } std::string getMethod() const { return m_method; } Return_t operator () () { DBusMessagePtr msg; prepare(msg); return sendAndReturn(msg); } void start(const Callback_t &callback) { DBusMessagePtr msg; prepare(msg); send(msg, callback); } template Return_t operator () (const A1 &a1) { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1; return sendAndReturn(msg); } template void start(const A1 &a1, const Callback_t &callback) { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1; send(msg, callback); } template Return_t operator () (const A1 &a1, const A2 &a2) { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2; return sendAndReturn(msg); } template void start(const A1 &a1, const A2 &a2, const Callback_t &callback) { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2; send(msg, callback); } template void operator ()(const A1 &a1, const A2 &a2, const A3 &a3) { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3; sendAndReturn(msg); } template void start(const A1 &a1, const A2 &a2, const A3 &a3, const Callback_t &callback) { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3; send(msg, callback); } template void operator () (const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3 << a4; sendAndReturn(msg); } template void start(const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const Callback_t &callback) { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3 << a4; send(msg, callback); } template void operator () (const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3 << a4 << a5; sendAndReturn(msg); } template void start(const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const Callback_t &callback) { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3 << a4 << a5; send(msg, callback); } template void operator () (const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3 << a4 << a5 << a6; sendAndReturn(msg); } template void start(const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const Callback_t &callback) { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3 << a4 << a5 << a6; send(msg, callback); } template void operator () (const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3 << a4 << a5 << a6 << a7; sendAndReturn(msg); } template void start(const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const Callback_t &callback) { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3 << a4 << a5 << a6 << a7; send(msg, callback); } template void operator () (const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3 << a4 << a5 << a6 << a7 << a8; sendAndReturn(msg); } template void start(const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const Callback_t &callback) { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3 << a4 << a5 << a6 << a7 << a8; send(msg, callback); } template void operator () (const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3 << a4 << a5 << a6 << a7 << a8 << a9; sendAndReturn(msg); } template void start(const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9, const Callback_t &callback) { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3 << a4 << a5 << a6 << a7 << a8 << a9; send(msg, callback); } template void operator () (const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9, const A10 &a10) { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3 << a4 << a5 << a6 << a7 << a8 << a9 << a10; sendAndReturn(msg); } template void start(const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9, const A10 &a10, const Callback_t &callback) { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3 << a4 << a5 << a6 << a7 << a8 << a9 << a10; send(msg, callback); } }; /* * A DBus Client Call object handling zero or more parameter and * zero return value. */ class DBusClientCall0 : public DBusClientCall { public: DBusClientCall0 (const DBusRemoteObject &object, const std::string &method) : DBusClientCall(object, method) { } }; /** 1 return value and 0 or more parameters */ template class DBusClientCall1 : public DBusClientCall > { public: DBusClientCall1 (const DBusRemoteObject &object, const std::string &method) : DBusClientCall >(object, method) { } }; /** 2 return value and 0 or more parameters */ template class DBusClientCall2 : public DBusClientCall > { public: DBusClientCall2 (const DBusRemoteObject &object, const std::string &method) : DBusClientCall >(object, method) { } }; /** 3 return value and 0 or more parameters */ template class DBusClientCall3 : public DBusClientCall > { public: DBusClientCall3 (const DBusRemoteObject &object, const std::string &method) : DBusClientCall >(object, method) { } }; /** * Common functionality of all SignalWatch* classes. * @param T boost::function with the right signature */ template class SignalWatch { public: SignalWatch(const DBusRemoteObject &object, const std::string &signal, bool is_bus_conn = true) : m_object(object), m_signal(signal), m_tag(0), m_is_bus_conn(is_bus_conn) { } ~SignalWatch() { if (m_tag) { DBusConnection *connection = m_object.getConnection(); if (connection) { b_dbus_remove_watch(connection, m_tag); } } } typedef T Callback_t; const Callback_t &getCallback() const{ return m_callback; } protected: const DBusRemoteObject &m_object; std::string m_signal; guint m_tag; T m_callback; bool m_is_bus_conn; std::string makeSignalRule() { std::string rule; rule = "type='signal',path='"; rule += m_object.getPath(); rule += "',interface='"; rule += m_object.getInterface(); rule += "',member='"; rule += m_signal; rule += "'"; return rule; } static gboolean isMatched(DBusMessage *msg, void *data) { SignalWatch *watch = static_cast(data); return dbus_message_has_path(msg, watch->m_object.getPath()) && dbus_message_is_signal(msg, watch->m_object.getInterface(), watch->m_signal.c_str()); } void activateInternal(const Callback_t &callback, gboolean (*cb)(DBusConnection *, DBusMessage *, void *)) { m_callback = callback; std::string rule = makeSignalRule(); m_tag = b_dbus_add_signal_watch(m_object.getConnection(), rule.c_str(), cb, this, NULL, m_is_bus_conn); if (!m_tag) { throw std::runtime_error(std::string("activating signal failed: ") + "path " + m_object.getPath() + " interface " + m_object.getInterface() + " member " + m_signal); } } }; class SignalWatch0 : public SignalWatch< boost::function > { typedef boost::function Callback_t; public: SignalWatch0(const DBusRemoteObject &object, const std::string &signal, bool is_bus_conn = true) : SignalWatch(object, signal, is_bus_conn) { } static gboolean internalCallback(DBusConnection *conn, DBusMessage *msg, void *data) { if(isMatched(msg, data) == FALSE) { return TRUE; } const Callback_t &cb = static_cast< SignalWatch *>(data)->getCallback(); cb(); return TRUE; } void activate(const Callback_t &callback) { SignalWatch< boost::function >::activateInternal(callback, internalCallback); } }; template class SignalWatch1 : public SignalWatch< boost::function > { typedef boost::function Callback_t; public: SignalWatch1(const DBusRemoteObject &object, const std::string &signal, bool is_bus_conn = true) : SignalWatch(object, signal, is_bus_conn) { } static gboolean internalCallback(DBusConnection *conn, DBusMessage *msg, void *data) { if (SignalWatch::isMatched(msg, data) == FALSE) { return TRUE; } const Callback_t &cb =static_cast< SignalWatch *>(data)->getCallback(); typename dbus_traits::host_type a1; DBusMessageIter iter; dbus_message_iter_init(msg, &iter); dbus_traits::get(conn, msg, iter, a1); cb(a1); return TRUE; } void activate(const Callback_t &callback) { SignalWatch< boost::function >::activateInternal(callback, internalCallback); } }; template class SignalWatch2 : public SignalWatch< boost::function > { typedef boost::function Callback_t; public: SignalWatch2(const DBusRemoteObject &object, const std::string &signal, bool is_bus_conn = true) : SignalWatch(object, signal, is_bus_conn) { } static gboolean internalCallback(DBusConnection *conn, DBusMessage *msg, void *data) { if (SignalWatch::isMatched(msg, data) == FALSE) { return TRUE; } const Callback_t &cb = static_cast< SignalWatch *>(data)->getCallback(); typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; DBusMessageIter iter; dbus_message_iter_init(msg, &iter); dbus_traits::get(conn, msg, iter, a1); dbus_traits::get(conn, msg, iter, a2); cb(a1, a2); return TRUE; } void activate(const Callback_t &callback) { SignalWatch< boost::function >::activateInternal(callback, internalCallback); } }; template class SignalWatch3 : public SignalWatch< boost::function > { typedef boost::function Callback_t; public: SignalWatch3(const DBusRemoteObject &object, const std::string &signal, bool is_bus_conn = true) : SignalWatch(object, signal, is_bus_conn) { } static gboolean internalCallback(DBusConnection *conn, DBusMessage *msg, void *data) { if (SignalWatch::isMatched(msg, data) == FALSE) { return TRUE; } const Callback_t &cb =static_cast< SignalWatch *>(data)->getCallback(); typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; DBusMessageIter iter; dbus_message_iter_init(msg, &iter); dbus_traits::get(conn, msg, iter, a1); dbus_traits::get(conn, msg, iter, a2); dbus_traits::get(conn, msg, iter, a3); cb(a1, a2, a3); return TRUE; } void activate(const Callback_t &callback) { SignalWatch< boost::function >::activateInternal(callback, internalCallback); } }; template class SignalWatch4 : public SignalWatch< boost::function > { typedef boost::function Callback_t; public: SignalWatch4(const DBusRemoteObject &object, const std::string &signal, bool is_bus_conn = true) : SignalWatch(object, signal, is_bus_conn) { } static gboolean internalCallback(DBusConnection *conn, DBusMessage *msg, void *data) { if (SignalWatch::isMatched(msg, data) == FALSE) { return TRUE; } const Callback_t &cb = static_cast< SignalWatch *>(data)->getCallback(); typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; DBusMessageIter iter; dbus_message_iter_init(msg, &iter); dbus_traits::get(conn, msg, iter, a1); dbus_traits::get(conn, msg, iter, a2); dbus_traits::get(conn, msg, iter, a3); dbus_traits::get(conn, msg, iter, a4); cb(a1, a2, a3, a4); return TRUE; } void activate(const Callback_t &callback) { SignalWatch< boost::function >::activateInternal(callback, internalCallback); } }; template class SignalWatch5 : public SignalWatch< boost::function > { typedef boost::function Callback_t; public: SignalWatch5(const DBusRemoteObject &object, const std::string &signal, bool is_bus_conn = true) : SignalWatch(object, signal, is_bus_conn) { } static gboolean internalCallback(DBusConnection *conn, DBusMessage *msg, void *data) { if (SignalWatch::isMatched(msg, data) == FALSE) { return TRUE; } const Callback_t &cb = static_cast< SignalWatch *>(data)->getCallback(); typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; typename dbus_traits::host_type a5; DBusMessageIter iter; dbus_message_iter_init(msg, &iter); dbus_traits::get(conn, msg, iter, a1); dbus_traits::get(conn, msg, iter, a2); dbus_traits::get(conn, msg, iter, a3); dbus_traits::get(conn, msg, iter, a4); dbus_traits::get(conn, msg, iter, a5); cb(a1, a2, a3, a4, a5); return TRUE; } void activate(const Callback_t &callback) { SignalWatch< boost::function >::activateInternal(callback, internalCallback); } }; template class SignalWatch6 : public SignalWatch< boost::function > { typedef boost::function Callback_t; public: SignalWatch6(const DBusRemoteObject &object, const std::string &signal, bool is_bus_conn = true) : SignalWatch(object, signal, is_bus_conn) { } static gboolean internalCallback(DBusConnection *conn, DBusMessage *msg, void *data) { if (SignalWatch::isMatched(msg, data) == FALSE) { return TRUE; } const Callback_t &cb = static_cast< SignalWatch *>(data)->getCallback(); typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; typename dbus_traits::host_type a5; typename dbus_traits::host_type a6; DBusMessageIter iter; dbus_message_iter_init(msg, &iter); dbus_traits::get(conn, msg, iter, a1); dbus_traits::get(conn, msg, iter, a2); dbus_traits::get(conn, msg, iter, a3); dbus_traits::get(conn, msg, iter, a4); dbus_traits::get(conn, msg, iter, a5); dbus_traits::get(conn, msg, iter, a6); cb(a1, a2, a3, a4, a5, a6); return TRUE; } void activate(const Callback_t &callback) { SignalWatch< boost::function >::activateInternal(callback, internalCallback); } }; } // namespace GDBusCXX #endif // INCL_BDBUS_CXX_BRIDGE syncevolution_1.4/src/gdbus/gdbus-cxx.h000066400000000000000000000117001230021373600203250ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_BDBUS_CXX #define INCL_BDBUS_CXX #include #include #include #include namespace GDBusCXX { /** * An exception class which can be thrown to create * specific D-Bus exception on the bus. */ class dbus_error : public std::runtime_error { public: /** * @param dbus_name the D-Bus error name, like "org.example.error.Invalid" * @param what a more detailed description */ dbus_error(const std::string &dbus_name, const std::string &what) : std::runtime_error(what), m_dbus_name(dbus_name) {} ~dbus_error() throw() {} const std::string &dbusName() const { return m_dbus_name; } private: std::string m_dbus_name; }; class Watch; /** * Special parameter type that identifies a caller. A string in practice. */ class Caller_t : public std::string { public: Caller_t() {} template Caller_t(T val) : std::string(val) {} template Caller_t &operator = (T val) { assign(val); return *this; } }; /** * Call object which needs to be called with the results * of an asynchronous method call. So instead of * "int foo()" one would implement * "void foo(Result1 > *r)" * and after foo has returned, call r->done(res). Use const * references as type for complex results. * * A Result instance can be copied, but only called once. */ class Result { public: virtual ~Result() {} /** report failure to caller */ virtual void failed(const dbus_error &error) = 0; /** * Calls the given callback once when the peer that the result * would be delivered to disconnects. The callback will also be * called if the peer is already gone by the time that the watch * is requested. * * Alternatively a method can ask to get called with a life Watch * by specifying "const boost::shared_ptr &" as parameter * and then calling its setCallback(). */ virtual Watch *createWatch(const boost::function &callback) = 0; }; class Result0 : virtual public Result { public: /** tell caller that we are done */ virtual void done() = 0; }; template class Result1 : virtual public Result { public: /** return result to caller */ virtual void done(A1 a1) = 0; }; template struct Result2 : virtual public Result { virtual void done(A1 a1, A2 a2) = 0; }; template struct Result3 : virtual public Result { virtual void done(A1 a1, A2 a2, A3 a3) = 0; }; template struct Result4 : virtual public Result { virtual void done(A1 a1, A2 a2, A3 a3, A4 a4) = 0; }; template struct Result5 : virtual public Result { virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) = 0; }; template struct Result6 : virtual public Result { virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) = 0; }; template struct Result7 : virtual public Result { virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) = 0; }; template struct Result8 : virtual public Result { virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) = 0; }; template struct Result9 : virtual public Result { virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9) = 0; }; template struct Result10 : virtual public Result { virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10) = 0; }; } // namespace GDBusCXX #endif // INCL_BDBUS_CXX syncevolution_1.4/src/gdbus/gdbus.am000066400000000000000000000024261230021373600177000ustar00rootroot00000000000000# include_HEADERS = src/gdbus/gdbus.h dist_noinst_DATA += \ src/gdbus/README if ENABLE_MODULES lib_LTLIBRARIES += src/gdbus/libgdbussyncevo.la src_gdbus_version_info = -version-info 0:0:0 else noinst_LTLIBRARIES += src/gdbus/libgdbussyncevo.la endif src_gdbus_libgdbussyncevo_la_SOURCES = \ src/gdbus/debug.h \ src/gdbus/debug.c \ src/gdbus/mainloop.c \ src/gdbus/object.c \ src/gdbus/watch.c \ src/gdbus/gdbus.h \ src/gdbus/gdbus-cxx-bridge.h \ src/gdbus/gdbus-cxx-bridge.cpp \ src/gdbus/gdbus-cxx.h src_gdbus_libgdbussyncevo_la_LDFLAGS = $(src_gdbus_version_info) -export-symbols-regex b_dbus_.* src_gdbus_libgdbussyncevo_la_LIBADD = @GLIB_LIBS@ @DBUS_LIBS@ src_gdbus_libgdbussyncevo_la_CFLAGS = @GLIB_CFLAGS@ @DBUS_CFLAGS@ $(SYNCEVO_WFLAGS) src_gdbus_libgdbussyncevo_la_CXXFLAGS = @GLIB_CFLAGS@ @DBUS_CFLAGS@ $(SYNCEVO_WFLAGS) src_gdbus_libgdbussyncevo_la_CPPFLAGS = $(BOOST_CPPFLAGS) # include_HEADERS = src/gdbus/gdbus-cxx-bridge.h src/gdbus/gdbus-cxx.h noinst_PROGRAMS += src/gdbus/example src_gdbus_example_SOURCES = src/gdbus/test/example.cpp src_gdbus_example_CXXFLAGS = @GLIB_CFLAGS@ @DBUS_CFLAGS@ $(SYNCEVO_WFLAGS) src_gdbus_example_LDADD = src/gdbus/libgdbussyncevo.la @GLIB_LIBS@ @DBUS_LIBS@ src_gdbus_example_CPPFLAGS = -I$(top_srcdir)/src/gdbus $(BOOST_CPPFLAGS) syncevolution_1.4/src/gdbus/gdbus.h000066400000000000000000000210461230021373600175310ustar00rootroot00000000000000/* * * Library for simple D-Bus integration with GLib * * Copyright (C) 2007-2008 Intel Corporation. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License version 2.1 as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef __BDBUS_H #define __BDBUS_H #ifdef __cplusplus extern "C" { #endif #include #include #include #include /** * SECTION:gdbus * @title: D-Bus helper library * @short_description: Library for simple D-Bus integration with GLib */ /** * BDBusDestroyFunction: * @user_data: user data to pass to the function * * Destroy function */ typedef void (* BDBusDestroyFunction) (void *user_data); /** * BDBusWatchFunction: * @connection: a #DBusConnection * @user_data: user data to pass to the function * * Watch function */ typedef void (* BDBusWatchFunction) (DBusConnection *connection, void *user_data); /** * BDBusSignalFunction: * @connection: a #DBusConnection * @message: a #DBusMessage * @user_data: user data to pass to the function * * Signal function * * Returns: #FALSE to remove this watch */ typedef gboolean (* BDBusSignalFunction) (DBusConnection *connection, DBusMessage *message, void *user_data); /** * BDBusMethodFunction: * @connection: a #DBusConnection * @message: a #DBusMessage * @user_data: user data to pass to the function * * Method function * * Returns: #DBusMessage reply */ typedef DBusMessage * (* BDBusMethodFunction) (DBusConnection *connection, DBusMessage *message, void *user_data); /** * BDBusPropertyGetFunction: * @connection: a #DBusConnection * @iter: a #DBusMessageIter * @user_data: user data to pass to the function * * Property get function * * Returns: #TRUE on success */ typedef dbus_bool_t (* BDBusPropertyGetFunction) (DBusConnection *connection, DBusMessageIter *iter, void *user_data); /** * BDBusPropertySetFunction: * @connection: a #DBusConnection * @iter: a #DBusMessageIter * @user_data: user data to pass to the function * * Property set function * * Returns: #TRUE on success */ typedef dbus_bool_t (* BDBusPropertySetFunction) (DBusConnection *connection, DBusMessageIter *iter, void *user_data); /** * BDBusInterfaceFunction: * @user_data: user data to pass to the function * * Callback function for interface */ typedef void (* BDBusInterfaceFunction) (void *user_data); /** * BDBusMethodFlags: * @G_DBUS_METHOD_FLAG_DEPRECATED: annotate deprecated methods * @G_DBUS_METHOD_FLAG_NOREPLY: annotate methods with no reply * @G_DBUS_METHOD_FLAG_ASYNC: annotate asynchronous methods * @G_DBUS_METHOD_FLAG_METHOD_DATA: the method is passed the * BDBusMethodTable method_data pointer * instead of the b_dbus_register_interface() * user_data pointer * * Method flags */ typedef enum { G_DBUS_METHOD_FLAG_NONE = 0, G_DBUS_METHOD_FLAG_DEPRECATED = (1 << 0), G_DBUS_METHOD_FLAG_NOREPLY = (1 << 1), G_DBUS_METHOD_FLAG_ASYNC = (1 << 2), G_DBUS_METHOD_FLAG_METHOD_DATA = (1 << 3), } BDBusMethodFlags; /** * BDBusSignalFlags: * @G_DBUS_SIGNAL_FLAG_DEPRECATED: annotate deprecated signals * * Signal flags */ typedef enum { G_DBUS_SIGNAL_FLAG_NONE = 0, G_DBUS_SIGNAL_FLAG_DEPRECATED = (1 << 0), } BDBusSignalFlags; /** * BDBusPropertyFlags: * @G_DBUS_PROPERTY_FLAG_DEPRECATED: annotate deprecated properties * * Property flags */ typedef enum { G_DBUS_PROPERTY_FLAG_NONE = 0, G_DBUS_PROPERTY_FLAG_DEPRECATED = (1 << 0), } BDBusPropertyFlags; /** * BDBusMethodTable: * @name: method name * @signature: method signature * @reply: reply signature * @function: method function * @flags: method flags * @method_data: passed as BDBusMethodFunction user_data if * G_DBUS_METHOD_FLAG_METHOD_DATA is set * @destroy: destructor function for method table; not called * by gdbus itself, because it never frees BDBusMethodTable * entries, but useful in upper layers * * Method table */ typedef struct { const char *name; const char *signature; const char *reply; BDBusMethodFunction function; BDBusMethodFlags flags; void *method_data; BDBusDestroyFunction destroy; } BDBusMethodTable; /** * BDBusSignalTable: * @name: signal name * @signature: signal signature * @flags: signal flags * * Signal table */ typedef struct { const char *name; const char *signature; BDBusSignalFlags flags; } BDBusSignalTable; /** * BDBusPropertyTable: * @name: property name * @type: property value type * @get: property get function * @set: property set function * @flags: property flags * * Property table */ typedef struct { const char *name; const char *type; BDBusPropertyGetFunction get; BDBusPropertyGetFunction set; BDBusPropertyFlags flags; } BDBusPropertyTable; void b_dbus_setup_connection(DBusConnection *connection, gboolean unshared, GMainContext *context); void b_dbus_cleanup_connection(DBusConnection *connection); void b_dbus_setup_server(DBusServer *server); DBusConnection *b_dbus_setup_bus(DBusBusType type, const char *name, gboolean unshared, DBusError *error); DBusConnection *b_dbus_setup_address(const char *address, DBusError *error); gboolean b_dbus_request_name(DBusConnection *connection, const char *name, DBusError *error); gboolean b_dbus_set_disconnect_function(DBusConnection *connection, BDBusWatchFunction function, void *user_data, BDBusDestroyFunction destroy); gboolean b_dbus_register_interface(DBusConnection *connection, const char *path, const char *name, BDBusMethodTable *methods, BDBusSignalTable *signals, BDBusPropertyTable *properties, void *user_data, BDBusDestroyFunction destroy); gboolean b_dbus_register_interface_with_callback(DBusConnection *connection, const char *path, const char *name, BDBusMethodTable *methods, BDBusSignalTable *signals, BDBusPropertyTable *properties, void *user_data, BDBusDestroyFunction destroy, BDBusInterfaceFunction callback); gboolean b_dbus_unregister_interface(DBusConnection *connection, const char *path, const char *name); DBusMessage *b_dbus_create_error(DBusMessage *message, const char *name, const char *format, ...); DBusMessage *b_dbus_create_error_valist(DBusMessage *message, const char *name, const char *format, va_list args); DBusMessage *b_dbus_create_reply(DBusMessage *message, int type, ...); DBusMessage *b_dbus_create_reply_valist(DBusMessage *message, int type, va_list args); gboolean b_dbus_send_message(DBusConnection *connection, DBusMessage *message); gboolean b_dbus_send_error(DBusConnection *connection, DBusMessage *message, const char *name, const char *format, ...); gboolean b_dbus_send_reply(DBusConnection *connection, DBusMessage *message, int type, ...); gboolean b_dbus_send_reply_valist(DBusConnection *connection, DBusMessage *message, int type, va_list args); gboolean b_dbus_emit_signal(DBusConnection *connection, const char *path, const char *interface, const char *name, int type, ...); gboolean b_dbus_emit_signal_valist(DBusConnection *connection, const char *path, const char *interface, const char *name, int type, va_list args); guint b_dbus_add_service_watch(DBusConnection *connection, const char *name, BDBusWatchFunction connect, BDBusWatchFunction disconnect, void *user_data, BDBusDestroyFunction destroy); guint b_dbus_add_disconnect_watch(DBusConnection *connection, const char *name, BDBusWatchFunction function, void *user_data, BDBusDestroyFunction destroy); guint b_dbus_add_signal_watch(DBusConnection *connection, const char *rule, BDBusSignalFunction function, void *user_data, BDBusDestroyFunction destroy, gboolean is_bus_conn); gboolean b_dbus_remove_watch(DBusConnection *connection, guint tag); void b_dbus_remove_all_watches(DBusConnection *connection); #ifdef __cplusplus } #endif #endif /* __BDBUS_H */ syncevolution_1.4/src/gdbus/mainloop.c000066400000000000000000000371501230021373600202410ustar00rootroot00000000000000/* * * Library for simple D-Bus integration with GLib * * Copyright (C) 2007-2008 Intel Corporation. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License version 2.1 as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #ifdef NEED_DBUS_WATCH_GET_UNIX_FD #define dbus_watch_get_unix_fd dbus_watch_get_fd #endif #include "gdbus.h" #include "debug.h" static dbus_int32_t connection_slot = -1; static dbus_int32_t server_slot = -1; typedef struct { DBusConnection *connection; GMainContext *context; GSource *queue; BDBusWatchFunction disconnect; void *disconnect_data; BDBusDestroyFunction disconnect_destroy; gboolean unshared; } ConnectionData; typedef struct { DBusWatch *watch; GSource *source; } WatchData; typedef struct { DBusTimeout *timeout; guint id; } TimeoutData; typedef struct { GSource source; DBusConnection *connection; } QueueData; static GSList *watches = NULL; static GSList *timeouts = NULL; static gboolean queue_prepare(GSource *source, gint *timeout) { DBusConnection *connection = ((QueueData *) source)->connection; DBG("source %p", source); *timeout = -1; return (dbus_connection_get_dispatch_status(connection) == DBUS_DISPATCH_DATA_REMAINS); } static gboolean queue_check(GSource *source) { DBG("source %p", source); return FALSE; } static gboolean queue_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) { DBusConnection *connection = ((QueueData *) source)->connection; DBG("source %p", source); dbus_connection_ref(connection); dbus_connection_dispatch(connection); dbus_connection_unref(connection); return TRUE; } static GSourceFuncs queue_funcs = { queue_prepare, queue_check, queue_dispatch, NULL }; static gboolean dispatch_watch(GIOChannel *source, GIOCondition condition, gpointer user_data) { WatchData *data = user_data; unsigned int flags = 0; DBG("source %p condition %d watch data %p", source, condition, data); if (condition & G_IO_IN) flags |= DBUS_WATCH_READABLE; if (condition & G_IO_OUT) flags |= DBUS_WATCH_WRITABLE; if (condition & G_IO_ERR) flags |= DBUS_WATCH_ERROR; if (condition & G_IO_HUP) flags |= DBUS_WATCH_HANGUP; dbus_watch_handle(data->watch, flags); return TRUE; } static void finalize_watch(gpointer memory) { DBG("watch data %p", memory); } static void free_watch(void *memory) { DBG("watch data %p", memory); if (memory == NULL) return; watches = g_slist_remove(watches, memory); WatchData *watch_data = (WatchData*)memory; GSource* source = watch_data->source; if (source != NULL) { g_source_destroy(source); watch_data->source = NULL; } g_free(watch_data); } static dbus_bool_t add_watch(DBusWatch *watch, void *user_data) { ConnectionData *data = user_data; GIOCondition condition = G_IO_ERR | G_IO_HUP; GIOChannel *channel; GSource *source; WatchData *watch_data; unsigned int flags; int fd; DBG("watch %p connection data %p", watch, data); if (dbus_watch_get_enabled(watch) == FALSE) return TRUE; flags = dbus_watch_get_flags(watch); if (flags & DBUS_WATCH_READABLE) condition |= G_IO_IN; if (flags & DBUS_WATCH_WRITABLE) condition |= G_IO_OUT; fd = dbus_watch_get_unix_fd(watch); DBG("flags %d fd %d", flags, fd); watch_data = g_new0(WatchData, 1); channel = g_io_channel_unix_new(fd); source = g_io_create_watch(channel, condition); watch_data->watch = watch; watch_data->source = source; g_source_set_callback(source, (GSourceFunc) dispatch_watch, watch_data, finalize_watch); g_source_attach(source, data->context); watches = g_slist_prepend(watches, watch_data); dbus_watch_set_data(watch, watch_data, free_watch); g_io_channel_unref(channel); DBG("watch data %p", watch_data); return TRUE; } static void remove_watch(DBusWatch *watch, void *user_data) { WatchData *watch_data = dbus_watch_get_data(watch); DBG("watch %p watch data %p connection data %p", watch, watch_data, user_data); if (watch_data == NULL) return; watches = g_slist_remove(watches, watch_data); if (watch_data->source != NULL) { g_source_destroy(watch_data->source); // g_source_unref(watch_data->source); watch_data->source = NULL; } // this will call free_watch() and deallocate watch_data dbus_watch_set_data(watch, NULL, NULL); } static void watch_toggled(DBusWatch *watch, void *user_data) { DBG("watch %p connection data %p", watch, user_data); if (dbus_watch_get_enabled(watch) == TRUE) add_watch(watch, user_data); else remove_watch(watch, user_data); } static gboolean dispatch_timeout(gpointer user_data) { TimeoutData *timeout_data = user_data; DBG("timeout data %p", timeout_data); dbus_timeout_handle(timeout_data->timeout); return FALSE; } static void free_timeout(void *memory) { TimeoutData *timeout_data = memory; DBG("timeout data %p", timeout_data); if (timeout_data->id > 0) g_source_remove(timeout_data->id); g_free(timeout_data); } static dbus_bool_t add_timeout(DBusTimeout *timeout, void *user_data) { TimeoutData *timeout_data; DBG("timeout %p connection data %p", timeout, user_data); if (dbus_timeout_get_enabled(timeout) == FALSE) return TRUE; timeout_data = g_new0(TimeoutData, 1); timeout_data->timeout = timeout; timeout_data->id = g_timeout_add(dbus_timeout_get_interval(timeout), dispatch_timeout, timeout_data); timeouts = g_slist_prepend(timeouts, timeout_data); dbus_timeout_set_data(timeout, timeout_data, free_timeout); DBG("timeout data %p", timeout_data); return TRUE; } static void remove_timeout(DBusTimeout *timeout, void *user_data) { TimeoutData *timeout_data = dbus_timeout_get_data(timeout); DBG("timeout %p connection data %p", timeout, user_data); if (timeout_data == NULL) return; timeouts = g_slist_remove(timeouts, timeout_data); if (timeout_data->id > 0) g_source_remove(timeout_data->id); timeout_data->id = 0; } static void timeout_toggled(DBusTimeout *timeout, void *user_data) { DBG("timeout %p connection data %p", timeout, user_data); if (dbus_timeout_get_enabled(timeout) == TRUE) add_timeout(timeout, user_data); else remove_timeout(timeout, user_data); } static void wakeup_context(void *user_data) { ConnectionData *data = user_data; DBG("connection data %p", data); g_main_context_wakeup(data->context); } static ConnectionData *setup_connection(DBusConnection *connection, gboolean unshared, GMainContext *context) { ConnectionData *data; DBG("connection %p context %p", connection, context); data = g_new0(ConnectionData, 1); data->context = g_main_context_ref(context); data->unshared = unshared; DBG("connection data %p", data); if (connection == NULL) return data; data->connection = connection; data->queue = g_source_new(&queue_funcs, sizeof(QueueData)); ((QueueData *) data->queue)->connection = connection; g_source_attach(data->queue, context); return data; } static void free_connection(void *memory) { ConnectionData *data = memory; DBG("connection data %p", data); b_dbus_remove_all_watches(data->connection); //b_dbus_unregister_all_objects(data->connection); if (data->queue) g_source_destroy(data->queue); // At the point when free_connection gets called, // the last unref already happened and the connection // is gone; trying to close it here is too later. // ConnectionData holds *no* reference on data->connection, // so don't unref either. If it did, the connection would // never get freed. #if 0 if (data->unshared) dbus_connection_close(data->connection); dbus_connection_unref(data->connection); #endif if (data->disconnect_destroy) data->disconnect_destroy (data->disconnect_data); g_main_context_unref(data->context); g_free(data); } /** * b_dbus_setup_connection: * @connection: a #DBusConnection * @unshared: the connection is private and must be closed explicitly * @context: a #GMainContext or #NULL for default context * * Setup connection with main context * * Sets the watch and timeout functions of a #DBusConnection * to integrate the connection with the GLib main loop. * Pass in #NULL for the #GMainContext unless you're * doing something specialized. */ void b_dbus_setup_connection(DBusConnection *connection, gboolean unshared, GMainContext *context) { ConnectionData *data; DBG("connection %p context %p", connection, context); if (dbus_connection_allocate_data_slot(&connection_slot) == FALSE) return; DBG("connection slot %d", connection_slot); data = dbus_connection_get_data(connection, connection_slot); if (data != NULL) return; dbus_connection_set_exit_on_disconnect(connection, TRUE); if (context == NULL) context = g_main_context_default(); data = setup_connection(connection, unshared, context); if (data == NULL) return; if (dbus_connection_set_data(connection, connection_slot, data, free_connection) == FALSE) { g_free(data); return; } dbus_connection_set_watch_functions(connection, add_watch, remove_watch, watch_toggled, data, NULL); dbus_connection_set_timeout_functions(connection, add_timeout, remove_timeout, timeout_toggled, data, NULL); dbus_connection_set_wakeup_main_function(connection, wakeup_context, data, NULL); } /** * b_dbus_server_connection: * @server: a #DBusServer * * Setup server with main context * * Sets the watch and timeout functions of a #DBusServer * to integrate the connection with the GLib main loop. */ void b_dbus_setup_server(DBusServer *server) { ConnectionData *data; if (dbus_server_allocate_data_slot(&server_slot) == FALSE) return; DBG("server slot %d", server_slot); data = dbus_server_get_data(server, server_slot); if (data != NULL) return; data = setup_connection(NULL, TRUE, g_main_context_default()); if (data == NULL) return; if (dbus_server_set_data(server, server_slot, data, free_connection) == FALSE) { g_free(data); return; } dbus_server_set_watch_functions(server, add_watch, remove_watch, watch_toggled, data, NULL); dbus_server_set_timeout_functions(server, add_timeout, remove_timeout, timeout_toggled, data, NULL); } /** * b_dbus_cleanup_connection: * @connection: a #DBusConnection * * Cleanup the setup of DBusConnection and free the * allocated memory. */ void b_dbus_cleanup_connection(DBusConnection *connection) { DBG("connection %p slot %d", connection, connection_slot); if (connection_slot < 0) return; dbus_connection_set_data(connection, connection_slot, NULL, NULL); dbus_connection_free_data_slot(&connection_slot); DBG("connection slot %d", connection_slot); } /** * b_dbus_setup_bus: * @type: a #DBusBusType * @name: well known name * @unshared: use dbus_bus_get_private() to ensure that we have the connection * for ourself (otherwise assertions and CRITICAL warnings were triggered * inside glib-dbus when the app also used that) * @error: a #DBusError * * Connect to bus and setup connection * * Returns a connection to the given bus and requests a * well known name for it. Sets the watch and timeout * functions for it. * * Returns: newly setup #DBusConnection */ DBusConnection *b_dbus_setup_bus(DBusBusType type, const char *name, gboolean unshared, DBusError *error) { DBusConnection *connection; DBG("type %d name %s error %p", type, name, error); connection = unshared ? dbus_bus_get_private(type, error) : dbus_bus_get(type, error); if (error != NULL) { if (dbus_error_is_set(error) == TRUE) return NULL; } if (connection == NULL) return NULL; if (name != NULL) { if (dbus_bus_request_name(connection, name, DBUS_NAME_FLAG_DO_NOT_QUEUE, error) != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER ) { goto failed; } if (error != NULL) { if (dbus_error_is_set(error) == TRUE) { goto failed; } } } b_dbus_setup_connection(connection, unshared, NULL); return connection; failed: if (unshared) dbus_connection_close(connection); dbus_connection_unref(connection); return NULL; } /** * b_dbus_setup_address: * @address: bus address * @error: a #DBusError * * Connect to bus and setup connection * * Returns a connection to the bus specified via * the given address and sets the watch and timeout * functions for it. * * Returns: newly setup #DBusConnection */ DBusConnection *b_dbus_setup_address(const char *address, DBusError *error) { DBusConnection *connection; DBG("address %s error %p", address, error); connection = dbus_connection_open(address, error); if (error != NULL) { if (dbus_error_is_set(error) == TRUE) return NULL; } if (connection == NULL) return NULL; b_dbus_setup_connection(connection, FALSE, NULL); return connection; } /** * b_dbus_request_name: * @connection: a #DBusConnection * @name: well known name * @error: a #DBusError * * Requests a well known name for connection. * * Returns: #TRUE on success */ gboolean b_dbus_request_name(DBusConnection *connection, const char *name, DBusError *error) { DBG("connection %p name %s error %p", connection, name, error); if (name == NULL) return FALSE; if (dbus_bus_request_name(connection, name, DBUS_NAME_FLAG_DO_NOT_QUEUE, error) != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER ) return FALSE; if (error != NULL) { if (dbus_error_is_set(error) == TRUE) return FALSE; } return TRUE; } static DBusHandlerResult disconnect_filter(DBusConnection *connection, DBusMessage *message, void *user_data) { ConnectionData *data = user_data; if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected") == FALSE) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; DBG("disconnected"); if (data->disconnect) data->disconnect (connection, data->disconnect_data); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } /** * b_dbus_set_disconnect_function: * @connection: a #DBusConnection * @function: a #BDBusWatchFunction * @user_data: user data to pass to the function * @destroy: a #BDBusDestroyFunction * * Set a callback function that will be called when the * D-Bus message bus exits. * * Returns: #TRUE on success */ gboolean b_dbus_set_disconnect_function(DBusConnection *connection, BDBusWatchFunction function, void *user_data, BDBusDestroyFunction destroy) { ConnectionData *data = dbus_connection_get_data(connection, connection_slot); if (!data) return FALSE; if (data->disconnect_destroy) data->disconnect_destroy (data->disconnect_data); data->disconnect_destroy = NULL; data->disconnect = NULL; data->disconnect_data = NULL; dbus_connection_set_exit_on_disconnect(connection, FALSE); if (dbus_connection_add_filter(connection, disconnect_filter, data, NULL) == FALSE) return FALSE; data = dbus_connection_get_data(connection, connection_slot); data->disconnect = function; data->disconnect_data = user_data; data->disconnect_destroy = destroy; return TRUE; } syncevolution_1.4/src/gdbus/object.c000066400000000000000000001035201230021373600176640ustar00rootroot00000000000000/* * * Library for simple D-Bus integration with GLib * * Copyright (C) 2007-2008 Intel Corporation. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License version 2.1 as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include "gdbus.h" #include "debug.h" static dbus_int32_t connection_slot = -1; typedef struct { GStaticMutex mutex; GSList *objects; } ConnectionData; typedef struct { gint refcount; char *path; GStaticMutex mutex; GSList *interfaces; char *introspect; } ObjectData; typedef struct { char *name; BDBusMethodTable *methods; BDBusSignalTable *signals; BDBusPropertyTable *properties; void *user_data; BDBusDestroyFunction destroy; BDBusInterfaceFunction callback; } InterfaceData; static InterfaceData *find_interface(GSList *interfaces, const char *name) { GSList *list; for (list = interfaces; list; list = list->next) { InterfaceData *interface = list->data; if (strcmp(name, interface->name) == 0) return interface; } return NULL; } static ObjectData *find_object(GSList *objects, const char *path) { GSList *list; for (list = objects; list; list = list->next) { ObjectData *object = list->data; if (strcmp(path, object->path) == 0) return object; } return NULL; } static BDBusPropertyTable *find_property(InterfaceData *interface, const char *name) { BDBusPropertyTable *property; if (interface == NULL) return NULL; for (property = interface->properties; property && property->name; property++) if (strcmp(property->name, name) == 0) return property; return NULL; } static void add_arguments(GString *xml, const char *direction, const char *signature) { DBusSignatureIter iter; dbus_signature_iter_init(&iter, signature); if (dbus_signature_iter_get_current_type(&iter) == DBUS_TYPE_INVALID) return; do { char *sig = dbus_signature_iter_get_signature(&iter); g_string_append_printf(xml, "\t\t\t\n", direction); else g_string_append(xml, "/>\n"); } while (dbus_signature_iter_next(&iter) == TRUE); } static inline void add_annotation(GString *xml, const char *name) { g_string_append_printf(xml, "\t\t\t\n", name); } static inline void add_methods(GString *xml, BDBusMethodTable *methods) { BDBusMethodTable *method; for (method = methods; method && method->name; method++) { g_string_append_printf(xml, "\t\t\n", method->name); add_arguments(xml, "in", method->signature); add_arguments(xml, "out", method->reply); if (method->flags & G_DBUS_METHOD_FLAG_DEPRECATED) add_annotation(xml, "org.freedesktop.DBus.Deprecated"); if (method->flags & G_DBUS_METHOD_FLAG_NOREPLY) add_annotation(xml, "org.freedesktop.DBus.Method.NoReply"); g_string_append(xml, "\t\t\n"); } } static inline void add_signals(GString *xml, BDBusSignalTable *signals) { BDBusSignalTable *signal; for (signal = signals; signal && signal->name; signal++) { g_string_append_printf(xml, "\t\t\n", signal->name); add_arguments(xml, NULL, signal->signature); if (signal->flags & G_DBUS_SIGNAL_FLAG_DEPRECATED) add_annotation(xml, "org.freedesktop.DBus.Deprecated"); g_string_append(xml, "\t\t\n"); } } static inline void add_properties(GString *xml, BDBusPropertyTable *properties) { BDBusPropertyTable *property; for (property = properties; property && property->name; property++) { const char *access; if (property->type == NULL) continue; if (property->get == NULL && property->set == NULL) continue; if (property->get != NULL) { if (property->set == NULL) access = "read"; else access = "readwrite"; } else access = "write"; g_string_append_printf(xml, "\t\t\n", property->name, property->type, access); if (property->flags & G_DBUS_PROPERTY_FLAG_DEPRECATED) add_annotation(xml, "org.freedesktop.DBus.Deprecated"); g_string_append(xml, "\t\t\n"); } } static char *generate_introspect(DBusConnection *connection, const char *path, ObjectData *data) { GString *xml; GSList *list; char **children; int i; DBG("connection %p path %s", connection, path); xml = g_string_new(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE); g_string_append_printf(xml, "\n", path); g_string_append_printf(xml, "\t\n", DBUS_INTERFACE_INTROSPECTABLE); g_string_append(xml, "\t\t\n"); add_arguments(xml, "out", "s"); g_string_append(xml, "\t\t\n"); g_string_append(xml, "\t\n"); g_string_append_printf(xml, "\t\n", DBUS_INTERFACE_PROPERTIES); g_string_append(xml, "\t\t\n"); add_arguments(xml, "in", "ss"); add_arguments(xml, "out", "v"); g_string_append(xml, "\t\t\n"); g_string_append(xml, "\t\t\n"); add_arguments(xml, "in", "ssv"); g_string_append(xml, "\t\t\n"); g_string_append(xml, "\t\t\n"); add_arguments(xml, "in", "s"); add_arguments(xml, "out", "a{sv}"); g_string_append(xml, "\t\t\n"); g_string_append(xml, "\t\n"); for (list = data->interfaces; list; list = list->next) { InterfaceData *interface = list->data; g_string_append_printf(xml, "\t\n", interface->name); add_methods(xml, interface->methods); add_signals(xml, interface->signals); add_properties(xml, interface->properties); g_string_append(xml, "\t\n"); } if (dbus_connection_list_registered(connection, path, &children) == FALSE) goto done; for (i = 0; children[i]; i++) g_string_append_printf(xml, "\t\n", children[i]); dbus_free_string_array(children); done: g_string_append(xml, "\n"); return g_string_free(xml, FALSE); } static void update_parent(DBusConnection *connection, const char *path) { ObjectData *data; char *parent; DBG("connection %p path %s", connection, path); if (strlen(path) < 2 || path[0] != '/') return; parent = g_path_get_dirname(path); if (parent == NULL) return; if (dbus_connection_get_object_path_data(connection, parent, (void *) &data) == FALSE) { update_parent(connection, parent); goto done; } if (data == NULL) { update_parent(connection, parent); goto done; } g_free(data->introspect); data->introspect = generate_introspect(connection, parent, data); done: g_free(parent); } static DBusHandlerResult send_message(DBusConnection *connection, DBusMessage *message) { dbus_bool_t result; result = dbus_connection_send(connection, message, NULL); dbus_message_unref(message); if (result == FALSE) return DBUS_HANDLER_RESULT_NEED_MEMORY; return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult send_error(DBusConnection *connection, DBusMessage *message, const char *name, const char *text) { DBusMessage *error; error = dbus_message_new_error(message, name, text); if (error == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; return send_message(connection, error); } static DBusHandlerResult introspect(DBusConnection *connection, DBusMessage *message, ObjectData *data) { DBusMessage *reply; DBG("connection %p message %p object data %p", connection, message, data); if (data->introspect == NULL) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; if (dbus_message_has_signature(message, DBUS_TYPE_INVALID_AS_STRING) == FALSE) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; reply = dbus_message_new_method_return(message); if (reply == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; dbus_message_append_args(reply, DBUS_TYPE_STRING, &data->introspect, DBUS_TYPE_INVALID); return send_message(connection, reply); } static DBusHandlerResult properties_get(DBusConnection *connection, DBusMessage *message, ObjectData *data) { DBusMessage *reply; BDBusPropertyTable *property; DBusMessageIter iter, value; dbus_bool_t result; const char *interface, *name; InterfaceData *iface; DBG("connection %p message %p object data %p", connection, message, data); if (dbus_message_has_signature(message, DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_INVALID_AS_STRING) == FALSE) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID); DBG("interface %s name %s", interface, name); iface = find_interface(data->interfaces, interface); property = find_property(iface, name); if (property == NULL) return send_error(connection, message, DBUS_ERROR_BAD_ADDRESS, "Property not found"); if (property->get == NULL) return send_error(connection, message, DBUS_ERROR_ACCESS_DENIED, "Reading of property not allowed"); reply = dbus_message_new_method_return(message); if (reply == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; dbus_message_iter_init_append(reply, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, property->type, &value); result = property->get(connection, &value, iface->user_data); dbus_message_iter_close_container(&iter, &value); if (result == FALSE) { dbus_message_unref(reply); return send_error(connection, message, DBUS_ERROR_FAILED, "Reading of property failed"); } return send_message(connection, reply); } static DBusHandlerResult properties_set(DBusConnection *connection, DBusMessage *message, ObjectData *data) { DBusMessage *reply; BDBusPropertyTable *property; DBusMessageIter iter, value; const char *interface, *name; InterfaceData *iface; DBG("connection %p message %p object data %p", connection, message, data); if (dbus_message_has_signature(message, DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_TYPE_INVALID_AS_STRING) == FALSE) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; dbus_message_iter_init(message, &iter); dbus_message_iter_get_basic(&iter, &interface); dbus_message_iter_next(&iter); dbus_message_iter_get_basic(&iter, &name); dbus_message_iter_next(&iter); dbus_message_iter_recurse(&iter, &value); DBG("interface %s name %s", interface, name); iface = find_interface(data->interfaces, interface); property = find_property(iface, name); if (property == NULL) return send_error(connection, message, DBUS_ERROR_BAD_ADDRESS, "Property not found"); if (property->set == NULL) return send_error(connection, message, DBUS_ERROR_ACCESS_DENIED, "Writing to property not allowed"); reply = dbus_message_new_method_return(message); if (reply == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; dbus_message_append_args(reply, DBUS_TYPE_INVALID); if (property->set(connection, &value, iface->user_data) == FALSE) { dbus_message_unref(reply); return send_error(connection, message, DBUS_ERROR_FAILED, "Writing to property failed"); } return send_message(connection, reply); } static inline void append_message(DBusMessageIter *value, DBusMessage *message) { DBusMessageIter iter, temp; dbus_message_iter_init(message, &temp); dbus_message_iter_recurse(&temp, &iter); do { int type = dbus_message_iter_get_arg_type(&iter); void *data; dbus_message_iter_get_basic(&iter, &data); dbus_message_iter_append_basic(value, type, &data); } while (dbus_message_iter_next(&iter) == TRUE); } static void do_getall(DBusConnection *connection, DBusMessageIter *iter, InterfaceData *interface, ObjectData *data) { BDBusPropertyTable *property; if (interface == NULL) return; for (property = interface->properties; property && property->name; property++) { DBusMessage *message; DBusMessageIter entry, value; dbus_bool_t result; if (property->get == NULL) continue; message = dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_RETURN); if (message == NULL) continue; dbus_message_iter_init_append(message, &entry); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, property->type, &value); result = property->get(connection, &value, interface->user_data); dbus_message_iter_close_container(&entry, &value); if (result == TRUE) { dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry); dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &property->name); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, property->type, &value); append_message(&value, message); dbus_message_iter_close_container(&entry, &value); dbus_message_iter_close_container(iter, &entry); } dbus_message_unref(message); } } static DBusHandlerResult properties_getall(DBusConnection *connection, DBusMessage *message, ObjectData *data) { DBusMessage *reply; DBusMessageIter iter, dict; const char *interface; DBG("connection %p message %p object data %p", connection, message, data); if (dbus_message_has_signature(message, DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_INVALID_AS_STRING) == FALSE) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &interface, DBUS_TYPE_INVALID); DBG("interface %s", interface); reply = dbus_message_new_method_return(message); if (reply == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; dbus_message_iter_init_append(reply, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); do_getall(connection, &dict, find_interface(data->interfaces, interface), data); dbus_message_iter_close_container(&iter, &dict); return send_message(connection, reply); } static DBusHandlerResult handle_message(DBusConnection *connection, DBusMessage *message, void *user_data) { ObjectData *data = user_data; InterfaceData *interface; BDBusMethodTable *method; DBG("object data %p", data); if (dbus_message_is_method_call(message, DBUS_INTERFACE_INTROSPECTABLE, "Introspect") == TRUE) return introspect(connection, message, data); if (dbus_message_is_method_call(message, DBUS_INTERFACE_PROPERTIES, "Get") == TRUE) return properties_get(connection, message, data); if (dbus_message_is_method_call(message, DBUS_INTERFACE_PROPERTIES, "Set") == TRUE) return properties_set(connection, message, data); if (dbus_message_is_method_call(message, DBUS_INTERFACE_PROPERTIES, "GetAll") == TRUE) return properties_getall(connection, message, data); interface = find_interface(data->interfaces, dbus_message_get_interface(message)); if (interface == NULL) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; DBG("interface data %p name %s", interface, interface->name); for (method = interface->methods; method->name && method->function; method++) { DBusMessage *reply; BDBusMethodFlags flags = method->flags; if (dbus_message_is_method_call(message, interface->name, method->name) == FALSE) continue; if (dbus_message_has_signature(message, method->signature) == FALSE) continue; if(interface->callback) { interface->callback(interface->user_data); } /* method->function() might disconnect the interface, in which case the "method" pointer itself becomes invalid - don't use it below */ reply = method->function(connection, message, (flags & G_DBUS_METHOD_FLAG_METHOD_DATA) ? method->method_data : interface->user_data); method = NULL; if (flags & G_DBUS_METHOD_FLAG_NOREPLY) { if (reply != NULL) dbus_message_unref(reply); return DBUS_HANDLER_RESULT_HANDLED; } if (flags & G_DBUS_METHOD_FLAG_ASYNC) { if (reply == NULL) return DBUS_HANDLER_RESULT_HANDLED; } if (reply == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; return send_message(connection, reply); } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static void handle_unregister(DBusConnection *connection, void *user_data) { ObjectData *data = user_data; GSList *list; DBG("object data %p path %s", data, data->path); g_free(data->path); for (list = data->interfaces; list; list = list->next) { InterfaceData *interface = list->data; DBG("interface data %p name %s", interface, interface->name); if (interface->destroy) interface->destroy(interface->user_data); g_free(interface->name); g_free(interface); } g_slist_free(data->interfaces); g_free(data->introspect); g_static_mutex_free(&data->mutex); g_free(data); } static DBusObjectPathVTable object_table = { .unregister_function = handle_unregister, .message_function = handle_message, }; /** * b_dbus_register_object: * @connection: the connection * @path: object path * * Registers a path in the object hierarchy. * * Returns: #TRUE on success */ static gboolean b_dbus_register_object(DBusConnection *connection, const char *path) { ConnectionData *data; ObjectData *object; DBG("connection %p path %s", connection, path); if (dbus_connection_allocate_data_slot(&connection_slot) == FALSE) return FALSE; DBG("connection slot %d", connection_slot); data = dbus_connection_get_data(connection, connection_slot); if (data == NULL) { data = g_try_new0(ConnectionData, 1); if (data == NULL) { dbus_connection_free_data_slot(&connection_slot); return 0; } g_static_mutex_init(&data->mutex); if (dbus_connection_set_data(connection, connection_slot, data, NULL) == FALSE) { dbus_connection_free_data_slot(&connection_slot); g_free(data); return 0; } } DBG("connection data %p", data); g_static_mutex_lock(&data->mutex); object = find_object(data->objects, path); if (!object) { object = g_new0(ObjectData, 1); g_static_mutex_init(&object->mutex); object->path = g_strdup(path); object->interfaces = NULL; object->refcount = 0; object->introspect = generate_introspect(connection, path, object); if (dbus_connection_register_object_path(connection, path, &object_table, object) == FALSE) { g_free(object->introspect); g_free(object); return FALSE; } data->objects = g_slist_append(data->objects, object); } object->refcount++; g_static_mutex_unlock(&data->mutex); DBG("object data %p", object); update_parent(connection, path); return TRUE; } /** * b_dbus_unregister_object: * @connection: the connection * @path: object path * * Unregister the given path in the object hierarchy and * free the assigned data structures. * * Returns: #TRUE on success */ static gboolean b_dbus_unregister_object(DBusConnection *connection, const char *path) { ConnectionData *data; ObjectData *object; gboolean result = TRUE; DBG("connection %p path %s", connection, path); data = dbus_connection_get_data(connection, connection_slot); if (data == NULL) return FALSE; if (dbus_connection_get_object_path_data(connection, path, (void *) &object) == FALSE) return FALSE; if (object == NULL) return FALSE; g_static_mutex_lock(&data->mutex); object->refcount--; if (!object->refcount) { result = dbus_connection_unregister_object_path(connection, path); data->objects = g_slist_remove(data->objects, object); if (!data->objects) dbus_connection_set_data(connection, connection_slot, NULL, NULL); } g_static_mutex_unlock(&data->mutex); dbus_connection_free_data_slot(&connection_slot); DBG("connection slot %d", connection_slot); if (!data->objects) { g_static_mutex_free(&data->mutex); g_free(data); } update_parent(connection, path); return result; } #if 0 /** * b_dbus_unregister_object_hierarchy: * @connection: the connection * @path: object path * * Unregister the given path and all subpaths in the * object hierarchy and free all assigned data strcutures. * * Returns: #TRUE on success */ static gboolean b_dbus_unregister_object_hierarchy(DBusConnection *connection, const char *path) { ConnectionData *data; ObjectData *object; GSList *list; size_t pathlen; dbus_bool_t result; DBG("connection %p path %s", connection, path); data = dbus_connection_get_data(connection, connection_slot); if (data == NULL) return FALSE; if (dbus_connection_get_object_path_data(connection, path, (void *) &object) == FALSE) return FALSE; if (object == NULL) return FALSE; pathlen = strlen(path); g_static_mutex_lock(&data->mutex); for (list = data->objects; list; list = list->next) { ObjectData *object = list->data; if (strlen(object->path) <= pathlen) continue; if (strncmp(object->path, path, pathlen) != 0) continue; DBG("object data %p path %s", object, object->path); data->objects = g_slist_remove(data->objects, object); dbus_connection_unregister_object_path(connection, object->path); dbus_connection_free_data_slot(&connection_slot); DBG("connection slot %d", connection_slot); } g_static_mutex_unlock(&data->mutex); result = b_dbus_unregister_object(connection, path); if (result == FALSE) return FALSE; update_parent(connection, path); return TRUE; } /** * b_dbus_unregister_all_objects: * @connection: the connection * * Unregister the all paths in the object hierarchy. */ static void b_dbus_unregister_all_objects(DBusConnection *connection) { ConnectionData *data; GSList *list; DBG("connection %p slot %d", connection, connection_slot); if (connection_slot < 0) return; data = dbus_connection_get_data(connection, connection_slot); if (data == NULL) return; DBG("connection data %p", data); g_static_mutex_lock(&data->mutex); for (list = data->objects; list; list = list->next) { ObjectData *object = list->data; DBG("object data %p path %s", object, object->path); dbus_connection_unregister_object_path(connection, object->path); dbus_connection_free_data_slot(&connection_slot); DBG("connection slot %d", connection_slot); } g_slist_free(data->objects); g_static_mutex_unlock(&data->mutex); g_free(data); } #endif /** * b_dbus_register_interface: * @connection: the connection * @path: object path * @name: interface name * @methods: method table * @signals: signal table * @properties: property table * @user_data: user data to assign to interface * @destroy: function called to destroy user_data * * Registers an interface for the given path in the * object hierarchy with the given methods, signals * and/or properties. * * Returns: #TRUE on success */ gboolean b_dbus_register_interface_with_callback(DBusConnection *connection, const char *path, const char *name, BDBusMethodTable *methods, BDBusSignalTable *signals, BDBusPropertyTable *properties, void *user_data, BDBusDestroyFunction destroy, BDBusInterfaceFunction callback) { ObjectData *object; InterfaceData *interface; DBG("connection %p path %s name %s", connection, path, name); if (b_dbus_register_object(connection, path) == FALSE) return FALSE; if (dbus_connection_get_object_path_data(connection, path, (void *) &object) == FALSE) return FALSE; if (object == NULL) return FALSE; if (find_interface(object->interfaces, name) != NULL) return FALSE; interface = g_new0(InterfaceData, 1); interface->name = g_strdup(name); interface->methods = methods; interface->signals = signals; interface->properties = properties; interface->user_data = user_data; interface->destroy = destroy; interface->callback = callback; g_static_mutex_lock(&object->mutex); object->interfaces = g_slist_append(object->interfaces, interface); g_free(object->introspect); object->introspect = generate_introspect(connection, path, object); g_static_mutex_unlock(&object->mutex); return TRUE; } gboolean b_dbus_register_interface(DBusConnection *connection, const char *path, const char *name, BDBusMethodTable *methods, BDBusSignalTable *signals, BDBusPropertyTable *properties, void *user_data, BDBusDestroyFunction destroy) { return b_dbus_register_interface_with_callback(connection, path, name, methods, signals, properties, user_data, destroy, NULL); } /** * b_dbus_unregister_interface: * @connection: the connection * @path: object path * @name: interface name * * Unregister the given interface for the given path * in the object hierarchy. * * Returns: #TRUE on success */ gboolean b_dbus_unregister_interface(DBusConnection *connection, const char *path, const char *name) { ObjectData *object; InterfaceData *interface; DBG("connection %p path %s name %s", connection, path, name); if (dbus_connection_get_object_path_data(connection, path, (void *) &object) == FALSE) return FALSE; if (object == NULL) return FALSE; interface = find_interface(object->interfaces, name); if (interface == NULL) return FALSE; g_static_mutex_lock(&object->mutex); object->interfaces = g_slist_remove(object->interfaces, interface); g_free(object->introspect); object->introspect = generate_introspect(connection, path, object); g_static_mutex_unlock(&object->mutex); g_free(interface->name); g_free(interface); b_dbus_unregister_object(connection, path); return TRUE; } char *printf_dyn(const char *format, va_list ap) { va_list aq; char *buffer = NULL; ssize_t size = 0; ssize_t realsize = 255; do { // vsnprintf() destroys ap, so make a copy first va_copy(aq, ap); if (size < realsize) { char *oldbuffer = buffer; buffer = (char *)realloc(buffer, realsize + 1); if (!buffer) { if (oldbuffer) { free(oldbuffer); } return strdup(""); } size = realsize; } realsize = vsnprintf(buffer, size + 1, format, aq); if (realsize == -1) { // old-style vnsprintf: exact len unknown, try again with doubled size realsize = size * 2; } va_end(aq); } while(realsize > size); return buffer; } /** * b_dbus_create_error_valist: * @message: the originating message * @name: the error name * @format: the error description * @args: argument list * * Create error reply for the given message. * * Returns: reply message on success */ DBusMessage *b_dbus_create_error_valist(DBusMessage *message, const char *name, const char *format, va_list args) { DBusMessage *msg = NULL; char *descr; DBG("message %p name %s", message, name); descr = printf_dyn(format, args); if (descr) { msg = dbus_message_new_error(message, name, descr); free(descr); } return msg; } /** * b_dbus_create_error: * @message: the originating message * @name: the error name * @format: the error description * @varargs: list of parameters * * Create error reply for the given message. * * Returns: reply message on success */ DBusMessage *b_dbus_create_error(DBusMessage *message, const char *name, const char *format, ...) { va_list args; DBusMessage *reply; DBG("message %p name %s", message, name); va_start(args, format); reply = b_dbus_create_error_valist(message, name, format, args); va_end(args); return reply; } /** * b_dbus_create_reply_valist: * @message: the originating message * @type: first argument type * @args: argument list * * Create reply for the given message. * * Returns: reply message on success */ DBusMessage *b_dbus_create_reply_valist(DBusMessage *message, int type, va_list args) { DBusMessage *reply; DBG("message %p", message); reply = dbus_message_new_method_return(message); if (reply == NULL) return NULL; if (dbus_message_append_args_valist(reply, type, args) == FALSE) { dbus_message_unref(reply); return NULL; } return reply; } /** * b_dbus_create_reply: * @message: the originating message * @type: first argument type * @varargs: list of parameters * * Create reply for the given message. * * Returns: reply message on success */ DBusMessage *b_dbus_create_reply(DBusMessage *message, int type, ...) { va_list args; DBusMessage *reply; DBG("message %p", message); va_start(args, type); reply = b_dbus_create_reply_valist(message, type, args); va_end(args); return reply; } /** * b_dbus_send_message: * @connection: the connection * @message: the message to send * * Send message via the given D-Bus connection. * * The reference count for the message will be decremented by this * function. * * Returns: #TRUE on success */ gboolean b_dbus_send_message(DBusConnection *connection, DBusMessage *message) { dbus_bool_t result; DBG("connection %p message %p", connection, message); result = dbus_connection_send(connection, message, NULL); dbus_message_unref(message); return result; } /** * b_dbus_send_error: * @connection: the connection * @message: the originating message * @name: the error name * @format: the error description * @varargs: list of parameters * * Send error reply for the given message and via the given D-Bus * connection. * * Returns: #TRUE on success */ gboolean b_dbus_send_error(DBusConnection *connection, DBusMessage *message, const char *name, const char *format, ...) { va_list args; DBusMessage *error; DBG("connection %p message %p", connection, message); va_start(args, format); error = b_dbus_create_error_valist(message, name, format, args); va_end(args); if (error == NULL) return FALSE; return b_dbus_send_message(connection, error); } /** * b_dbus_send_reply_valist: * @connection: the connection * @message: the originating message * @type: first argument type * @args: argument list * * Send reply for the given message and via the given D-Bus * connection. * * Returns: #TRUE on success */ gboolean b_dbus_send_reply_valist(DBusConnection *connection, DBusMessage *message, int type, va_list args) { DBusMessage *reply; DBG("connection %p message %p", connection, message); reply = dbus_message_new_method_return(message); if (reply == NULL) return FALSE; if (dbus_message_append_args_valist(reply, type, args) == FALSE) { dbus_message_unref(reply); return FALSE; } return b_dbus_send_message(connection, reply); } /** * b_dbus_send_reply: * @connection: the connection * @message: the originating message * @type: first argument type * @varargs: list of parameters * * Send reply for the given message and via the given D-Bus * connection. * * Returns: #TRUE on success */ gboolean b_dbus_send_reply(DBusConnection *connection, DBusMessage *message, int type, ...) { va_list args; gboolean result; DBG("connection %p message %p", connection, message); va_start(args, type); result = b_dbus_send_reply_valist(connection, message, type, args); va_end(args); return result; } static BDBusSignalTable *find_signal(GSList *interfaces, const char *interface, const char *name) { InterfaceData *data; BDBusSignalTable *signal; data = find_interface(interfaces, interface); if (data == NULL) return NULL; for (signal = data->signals; signal && signal->name; signal++) { if (strcmp(signal->name, name) == 0) break; } return signal; } /** * b_dbus_emit_signal_valist: * @connection: the connection * @path: object path * @interface: interface name * @name: signal name * @type: first argument type * @args: argument list * * Emit a signal for the given path and interface with * the given signal name. * * The signal signature will be check against the registered * signal table. * * Returns: #TRUE on success */ gboolean b_dbus_emit_signal_valist(DBusConnection *connection, const char *path, const char *interface, const char *name, int type, va_list args) { ObjectData *object; BDBusSignalTable *signal; DBusMessage *message; const char *signature; gboolean result = FALSE; DBG("connection %p path %s name %s.%s", connection, path, interface, name); if (dbus_connection_get_object_path_data(connection, path, (void *) &object) == FALSE) return FALSE; if (object == NULL) return FALSE; signal = find_signal(object->interfaces, interface, name); if (signal == NULL) return FALSE; message = dbus_message_new_signal(path, interface, name); if (message == NULL) return FALSE; if (dbus_message_append_args_valist(message, type, args) == FALSE) goto done; signature = dbus_message_get_signature(message); if (strcmp(signal->signature, signature) != 0) goto done; DBG("connection %p signature \"%s\"", connection, signature); if (dbus_connection_send(connection, message, NULL) == FALSE) goto done; result = TRUE; done: dbus_message_unref(message); return result; } /** * b_dbus_emit_signal: * @connection: the connection * @path: object path * @interface: interface name * @name: signal name * @type: first argument type * @varargs: list of parameters * * Emit a signal for the given path and interface with * the given signal name. * * The signal signature will be check against the registered * signal table. * * Returns: #TRUE on success */ gboolean b_dbus_emit_signal(DBusConnection *connection, const char *path, const char *interface, const char *name, int type, ...) { va_list args; gboolean result; DBG("connection %p path %s name %s.%s", connection, path, interface, name); va_start(args, type); result = b_dbus_emit_signal_valist(connection, path, interface, name, type, args); va_end(args); return result; } syncevolution_1.4/src/gdbus/test/000077500000000000000000000000001230021373600172305ustar00rootroot00000000000000syncevolution_1.4/src/gdbus/test/example.cpp000066400000000000000000000174371230021373600214030ustar00rootroot00000000000000/* * * Library for simple D-Bus integration with GLib * * Copyright (C) 2009 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include "gdbus-cxx-bridge.h" #include #include namespace GDBusCXX { struct args { int a; std::string b; std::map c; }; static void hello_global() {} class Test { typedef boost::shared_ptr< Result1 > string_result; struct async { async(const boost::shared_ptr &watch, Watch *watch2, const string_result &result): m_watch(watch), m_watch2(watch2), m_result(result) {} ~async() { delete m_watch2; } boost::shared_ptr m_watch; Watch *m_watch2; string_result m_result; }; static gboolean method_idle(gpointer data) { std::auto_ptr mydata(static_cast(data)); std::cout << "replying to method_async" << std::endl; mydata->m_result->done("Hello World, asynchronous and delayed"); return false; } static void disconnect(const std::string &id, const std::string &peer) { std::cout << id << ": " << peer << " has disconnected." << std::endl; } public: static void hello_static() {} void hello_const() const {} static void hello_world(const char *msg) { puts(msg); } void hello_base() {} void method(std::string &text) { text = "Hello World"; } void method_async(const Caller_t &caller, const boost::shared_ptr &watch, int32_t secs, const string_result &r) { watch->setCallback(boost::bind(disconnect, "watch1", caller)); Watch *watch2 = r->createWatch(boost::bind(disconnect, "watch2", caller)); std::cout << "method_async called by " << caller << " delay " << secs << std::endl; g_timeout_add_seconds(secs, method_idle, new async(watch, watch2, r)); } void method2(int32_t arg, int32_t &ret) { ret = arg * 2; } int32_t method3(int32_t arg) { return arg * 3; } void method8_simple(int32_t a1, int32_t a2, int32_t a3, int32_t a4, int32_t a5, int32_t a6, int32_t a7, int32_t a8) { } void method9_async(Result9 *r) { r->done(1, 2, 3, 4, 5, 6, 7, 8, 9); delete r; } int32_t method9(int32_t a1, int32_t a2, int32_t a3, int32_t a4, int32_t a5, int32_t a6, int32_t a7, int32_t a8, int32_t a9) { return 0; } void hash(const std::map &in, std::map &out) { for (std::map::const_iterator it = in.begin(); it != in.end(); ++it) { out.insert(std::make_pair((int16_t)it->first, it->second * it->second)); } } void array(const std::vector &in, std::vector &out) { for (std::vector::const_iterator it = in.begin(); it != in.end(); ++it) { out.push_back(*it * *it); } } void error() { throw dbus_error("org.example.error.Invalid", "error"); } void argtest(const args &in, args &out) { out = in; out.a = in.a + 1; } }; class Test2 { public: void test2() {} }; template<> struct dbus_traits : public dbus_struct_traits, &args::c> > > > {}; class DBusTest : public Test, private Test2 { DBusObjectHelper m_object; DBusObjectHelper m_secondary; public: DBusTest(const DBusConnectionPtr conn) : m_object(conn, "/test", "org.example.Test"), // same path! m_secondary(conn, m_object.getPath(), "org.example.Secondary"), signal(m_object, "Signal") { m_object.add(this, &Test::method8_simple, "Method8Simple"); // m_object.add(this, &Test::method10_async, "Method10Async", G_DBUS_METHOD_FLAG_ASYNC); // m_object.add(this, &Test::method9, "Method9"); m_object.add(this, &Test::method2, "Method2"); m_object.add(this, &Test::method3, "Method3"); m_object.add(this, &Test::method, "Test"); m_object.add(this, &Test::method_async, "TestAsync"); m_object.add(this, &Test::argtest, "ArgTest"); m_object.add(this, &Test::hash, "Hash"); m_object.add(this, &Test::array, "Array"); m_object.add(this, &Test::error, "Error"); m_object.add(&hello_global, "Global"); m_object.add(&DBusTest::hello_static, "Static"); m_object.add(static_cast(this), &Test2::test2, "Private"); // The hello_const() method cannot be registered // because there is no matching MakeMethodEntry<> // specialization for it or DBusObjectHelper::add() // fails to determine the right function type, // depending how one wants to interpret the problem. // m_object.add2(this, &DBusTest::hello_const, "Const"); m_object.add(signal); m_secondary.add(this, &DBusTest::hello, "Hello"); } ~DBusTest() { } EmitSignal3 &>signal; void hello() {} static void hello_static() {} void hello_const() const {} void activate() { m_secondary.activate(); m_object.activate(); } void deactivate() { m_object.deactivate(); m_secondary.deactivate(); } }; static GMainLoop *main_loop = NULL; static void sig_term(int sig) { g_main_loop_quit(main_loop); } } // namespace GDBusCXX using namespace GDBusCXX; int main(int argc, char *argv[]) { DBusConnectionPtr conn; DBusErrorCXX err; struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = sig_term; sigaction(SIGINT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); sa.sa_handler = SIG_IGN; sigaction(SIGCHLD, &sa, NULL); sigaction(SIGPIPE, &sa, NULL); main_loop = g_main_loop_new(NULL, FALSE); conn = dbus_get_bus_connection("SESSION", "org.example", false, &err); if (!conn) { if (dbus_error_is_set(&err) == TRUE) { fprintf(stderr, "%s\n", err.message); dbus_error_free(&err); } else fprintf(stderr, "Can't register with session bus\n"); exit(1); } std::auto_ptr test(new DBusTest(conn)); test->activate(); test->signal(42, "hello world", std::map()); test->deactivate(); test->activate(); test->signal(123, "here I am again", std::map()); g_main_loop_run(main_loop); test.reset(); g_main_loop_unref(main_loop); return 0; } syncevolution_1.4/src/gdbus/test/test-example000077500000000000000000000022701230021373600215670ustar00rootroot00000000000000#!/usr/bin/python import dbus from dbus.mainloop.glib import DBusGMainLoop import gobject DBusGMainLoop(set_as_default=True) bus = dbus.SessionBus() dummy = dbus.Interface(bus.get_object('org.example', '/test'), 'org.freedesktop.DBus.Introspectable') print dummy.Introspect() object = dbus.Interface(bus.get_object('org.example', '/test'), 'org.example.Secondary') object.Hello() object = dbus.Interface(bus.get_object('org.example', '/test'), 'org.example.Test') print object.Test() print object.Method2(1) print object.Method3(1) print object.Hash({1: 1, 2: 2, 3: 3}) print object.Array([1, 2, 3, 4]) print object.ArgTest((1, 'hello', {'foo': 'bar'})) loop = gobject.MainLoop() def AsyncFinished(x=None): print "TestAsync:", x loop.quit() print object.TestAsync(2, reply_handler=AsyncFinished, error_handler=AsyncFinished) # This will trigger the "caller has disconnect" # because our client-side time out will get us # out of the loop and then we quit. object.TestAsync(600, reply_handler=AsyncFinished, error_handler=AsyncFinished, timeout=5) loop.run() object.Error() syncevolution_1.4/src/gdbus/watch.c000066400000000000000000000310621230021373600175250ustar00rootroot00000000000000/* * * Library for simple D-Bus integration with GLib * * Copyright (C) 2007-2008 Intel Corporation. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License version 2.1 as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include "gdbus.h" #include "debug.h" static dbus_int32_t connection_slot = -1; typedef struct { GSList *watches; GSList *handlers; guint next_id; } ConnectionData; typedef struct { guint id; char *name; void *user_data; char *match; BDBusWatchFunction connect; BDBusWatchFunction disconn; BDBusDestroyFunction destroy; } WatchData; typedef struct { guint id; void *user_data; BDBusWatchFunction function; BDBusDestroyFunction destroy; } DisconnectData; typedef struct { guint id; void *user_data; char *match; BDBusSignalFunction function; BDBusDestroyFunction destroy; } SignalData; static DBusHandlerResult signal_function(DBusConnection *connection, DBusMessage *message, void *user_data) { ConnectionData *data = user_data; GSList *list; DBG("connection %p message %p", connection, message); for (list = data->handlers; list; list = list->next) { SignalData *signal = list->data; gboolean result; if (signal->function == NULL) continue; result = signal->function(connection, message, signal->user_data); if (result == TRUE) continue; if (signal->destroy != NULL) signal->destroy(signal->user_data); } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static DBusHandlerResult owner_function(DBusConnection *connection, DBusMessage *message, void *user_data) { ConnectionData *data = user_data; GSList *list; const char *name, *old, *new; DBG("connection %p message %p", connection, message); if (dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &name, DBUS_TYPE_STRING, &old, DBUS_TYPE_STRING, &new, DBUS_TYPE_INVALID) == FALSE) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; DBG("name %s \"%s\" => \"%s\"", name, old, new); /* * Allow the watch to remove itself. That'll make the "list" pointer * invalid, need to read it before calling the watch. */ list = data->watches; while (list) { WatchData *watch = list->data; list = list->next; if (strcmp(name, watch->name) != 0) continue; if (watch->connect != NULL && *old == '\0' && *new != '\0') watch->connect(connection, watch->user_data); if (watch->disconn != NULL && *old != '\0' && *new == '\0') watch->disconn(connection, watch->user_data); } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static DBusHandlerResult filter_function(DBusConnection *connection, DBusMessage *message, void *user_data) { if (dbus_message_is_signal(message, DBUS_INTERFACE_DBUS, "NameOwnerChanged") == TRUE) return owner_function(connection, message, user_data); if (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_SIGNAL) return signal_function(connection, message, user_data); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static ConnectionData *get_connection_data(DBusConnection *connection) { ConnectionData *data; /* dbus_bool_t result; */ DBG("connection %p", connection); if (dbus_connection_allocate_data_slot(&connection_slot) == FALSE) return NULL; DBG("connection slot %d", connection_slot); data = dbus_connection_get_data(connection, connection_slot); if (data == NULL) { data = g_try_new0(ConnectionData, 1); if (data == NULL) { dbus_connection_free_data_slot(&connection_slot); return NULL; } data->next_id = 1; if (dbus_connection_set_data(connection, connection_slot, data, NULL) == FALSE) { dbus_connection_free_data_slot(&connection_slot); g_free(data); return NULL; } /* result = */ dbus_connection_add_filter(connection, filter_function, data, NULL); } return data; } static void put_connection_data(DBusConnection *connection) { dbus_connection_free_data_slot(&connection_slot); } /** * b_dbus_add_service_watch: * @connection: the connection * @name: unique or well known name * @connect: function called on name connect * @disconnect: function called on name disconnect * @user_data: user data to pass to the function * @destroy: function called to destroy user_data * * Add new watch to listen for connects and/or disconnects * of a client for the given connection. * * Returns: identifier of the watch */ guint b_dbus_add_service_watch(DBusConnection *connection, const char *name, BDBusWatchFunction connect, BDBusWatchFunction disconnect, void *user_data, BDBusDestroyFunction destroy) { ConnectionData *data; WatchData *watch; DBusError error; DBG("connection %p name %s", connection, name); data = get_connection_data(connection); if (data == NULL) return 0; DBG("connection data %p", data); watch = g_try_new0(WatchData, 1); if (watch == NULL) goto error; watch->name = g_strdup(name); if (watch->name == NULL) goto error; watch->user_data = user_data; watch->connect = connect; watch->disconn = disconnect; watch->destroy = destroy; watch->match = g_strdup_printf("interface=%s,member=NameOwnerChanged,arg0=%s", DBUS_INTERFACE_DBUS, name); if (watch->match == NULL) goto error; dbus_error_init(&error); dbus_bus_add_match(connection, watch->match, &error); if (dbus_error_is_set(&error) == TRUE) { dbus_error_free(&error); goto error; } watch->id = data->next_id++; data->watches = g_slist_append(data->watches, watch); DBG("tag %d", watch->id); return watch->id; error: // Doesn't look valid: Possible null pointer dereference: watch - otherwise it is redundant to check it against null. // cppcheck-suppress nullPointer if (watch != NULL) { g_free(watch->name); g_free(watch->match); } g_free(watch); put_connection_data(connection); return 0; } /** * b_dbus_remove_watch: * @connection: the connection * @tag: watch identifier * * Removes the watch for the given identifier. * * Returns: #TRUE on success */ gboolean b_dbus_remove_watch(DBusConnection *connection, guint tag) { ConnectionData *data; GSList *list; DBG("connection %p tag %d", connection, tag); if (connection_slot < 0) return FALSE; data = dbus_connection_get_data(connection, connection_slot); if (data == NULL) return FALSE; for (list = data->watches; list; list = list->next) { WatchData *watch = list->data; if (watch->id == tag) { data->watches = g_slist_remove(data->watches, watch); if (watch->destroy != NULL) watch->destroy(watch->user_data); dbus_bus_remove_match(connection, watch->match, NULL); g_free(watch->name); g_free(watch->match); g_free(watch); goto done; } } for (list = data->handlers; list; list = list->next) { SignalData *signal = list->data; if (signal->id == tag) { data->handlers = g_slist_remove(data->handlers, signal); if (signal->destroy != NULL) signal->destroy(signal->user_data); dbus_bus_remove_match(connection, signal->match, NULL); g_free(signal->match); g_free(signal); goto done; } } return FALSE; done: if (!data->watches && !data->handlers) { dbus_connection_remove_filter(connection, filter_function, data); dbus_connection_set_data(connection, connection_slot, NULL, NULL); g_free(data); } dbus_connection_free_data_slot(&connection_slot); DBG("connection slot %d", connection_slot); return TRUE; } /** * b_dbus_remove_all_watches: * @connection: the connection * * Removes all registered watches. */ void b_dbus_remove_all_watches(DBusConnection *connection) { ConnectionData *data; GSList *list; DBG("connection %p slot %d", connection, connection_slot); if (connection_slot < 0) return; data = dbus_connection_get_data(connection, connection_slot); if (data == NULL) return; DBG("connection data %p", data); for (list = data->watches; list; list = list->next) { WatchData *watch = list->data; DBG("watch data %p tag %d", watch, watch->id); if (watch->destroy != NULL) watch->destroy(watch->user_data); dbus_bus_remove_match(connection, watch->match, NULL); g_free(watch->match); g_free(watch->name); g_free(watch); dbus_connection_free_data_slot(&connection_slot); DBG("connection slot %d", connection_slot); } g_slist_free(data->watches); for (list = data->handlers; list; list = list->next) { SignalData *signal = list->data; DBG("signal data %p tag %d", signal, signal->id); if (signal->destroy != NULL) signal->destroy(signal->user_data); dbus_bus_remove_match(connection, signal->match, NULL); g_free(signal->match); g_free(signal); dbus_connection_free_data_slot(&connection_slot); DBG("connection slot %d", connection_slot); } g_slist_free(data->handlers); dbus_connection_remove_filter(connection, filter_function, data); g_free(data); } static void disconnect_function(DBusConnection *connection, void *user_data) { DisconnectData *data = user_data; // The callback function might remove the watch, // which invalidates the data pointer. Remember // the ID. guint id = data->id; data->function(connection, data->user_data); b_dbus_remove_watch(connection, id); } static void disconnect_release(void *user_data) { g_free(user_data); } /** * b_dbus_add_disconnect_watch: * @connection: the connection * @name: unique or well known name * @function: function called on name disconnect * @user_data: user data to pass to the function * @destroy: function called to destroy user_data * * Add new watch to listen for disconnect of a client * for the given connection. * * After the callback has been called, this watch will be * automatically removed. * * Returns: identifier of the watch */ guint b_dbus_add_disconnect_watch(DBusConnection *connection, const char *name, BDBusWatchFunction function, void *user_data, BDBusDestroyFunction destroy) { DisconnectData *data; data = g_try_new0(DisconnectData, 1); if (data == NULL) return 0; data->user_data = user_data; data->function = function; data->destroy = destroy; data->id = b_dbus_add_service_watch(connection, name, NULL, disconnect_function, data, disconnect_release); if (data->id == 0) { g_free(data); return 0; } return data->id; } /** * b_dbus_add_signal_watch: * @connection: the connection * @rule: matching rule for this signal * @function: function called when signal arrives * @user_data: user data to pass to the function * @destroy: function called to destroy user_data * @is_bus_conn: whether the connection is with the bus * * Add new watch to listen for specific signals of * a client for the given connection. * * If the callback returns #FALSE this watch will be * automatically removed. * * Returns: identifier of the watch */ guint b_dbus_add_signal_watch(DBusConnection *connection, const char *rule, BDBusSignalFunction function, void *user_data, BDBusDestroyFunction destroy, gboolean is_bus_conn) { ConnectionData *data; SignalData *signal; DBusError error; DBG("connection %p rule %s", connection, rule); data = get_connection_data(connection); if (data == NULL) return 0; DBG("connection data %p", data); signal = g_try_new0(SignalData, 1); if (signal == NULL) goto error; signal->match = g_strdup(rule); if (!signal->match) goto error; signal->user_data = user_data; signal->function = function; signal->destroy = destroy; if (is_bus_conn) { dbus_error_init(&error); dbus_bus_add_match(connection, rule, &error); if (dbus_error_is_set(&error) == TRUE) { dbus_error_free(&error); goto error; } } signal->id = data->next_id++; data->handlers = g_slist_append(data->handlers, signal); DBG("tag %d", signal->id); return signal->id; error: // Doesn't look valid: Possible null pointer dereference: signal - otherwise it is redundant to check it against null. // cppcheck-suppress nullPointer if (signal) g_free(signal->match); g_free(signal); put_connection_data(connection); return 0; } syncevolution_1.4/src/gdbusxx/000077500000000000000000000000001230021373600166315ustar00rootroot00000000000000syncevolution_1.4/src/gdbusxx/.gitignore000066400000000000000000000000111230021373600206110ustar00rootroot00000000000000/example syncevolution_1.4/src/gdbusxx/README000066400000000000000000000011111230021373600175030ustar00rootroot00000000000000This is a C++ wrapper for the GLib GDBus d-bus implementation. SyncEvolution also includes a wrapper for another library named gdbus which originated in the Bluez project. This implementation has been included in-tree and getting and sending patches is a somewhat automated process. The plan is to drop the in-tree, Bluez gdbus as soon as Debian stable and Ubuntu LTS both support a GLib >= 2.26 (the version first to include GDBus). Until this happens we'll have to keep the in-tree one around. In order to make our lives easier, both implementation should have the same interface.syncevolution_1.4/src/gdbusxx/gdbus-cxx-bridge.cpp000066400000000000000000000451561230021373600225060ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "gdbus-cxx-bridge.h" #include void intrusive_ptr_add_ref(GDBusConnection *con) { g_object_ref(con); } void intrusive_ptr_release(GDBusConnection *con) { g_object_unref(con); } void intrusive_ptr_add_ref(GDBusMessage *msg) { g_object_ref(msg); } void intrusive_ptr_release(GDBusMessage *msg) { g_object_unref(msg); } static void intrusive_ptr_add_ref(GDBusServer *server) { g_object_ref(server); } static void intrusive_ptr_release(GDBusServer *server) { g_object_unref(server); } namespace GDBusCXX { MethodHandler::MethodMap MethodHandler::m_methodMap; boost::function MethodHandler::m_callback; void appendArgInfo(GPtrArray *pa, const std::string &type) { // Empty if not used in the current direction (in or out), // ignore then. if (type.empty()) { // TODO: replace runtime check with compile-time check // via type specialization... not terribly important, though. return; } GDBusArgInfo *argInfo = g_new0(GDBusArgInfo, 1); argInfo->signature = g_strdup(type.c_str()); argInfo->ref_count = 1; g_ptr_array_add(pa, argInfo); } struct OwnNameAsyncData { enum State { OWN_NAME_WAITING, OWN_NAME_OBTAINED, OWN_NAME_LOST }; OwnNameAsyncData(const std::string &name, const boost::function &obtainedCB) : m_name(name), m_obtainedCB(obtainedCB), m_state(OWN_NAME_WAITING) {} static void busNameAcquired(GDBusConnection *connection, const gchar *name, gpointer userData) throw () { boost::shared_ptr *data = static_cast< boost::shared_ptr *>(userData); (*data)->m_state = OWN_NAME_OBTAINED; try { g_debug("got D-Bus name %s", name); if ((*data)->m_obtainedCB) { (*data)->m_obtainedCB(true); } } catch (...) { (*data)->m_state = OWN_NAME_LOST; } } static void busNameLost(GDBusConnection *connection, const gchar *name, gpointer userData) throw () { boost::shared_ptr *data = static_cast< boost::shared_ptr *>(userData); (*data)->m_state = OWN_NAME_LOST; try { g_debug("lost %s %s", connection ? "D-Bus connection for name" : "D-Bus name", name); if ((*data)->m_obtainedCB) { (*data)->m_obtainedCB(false); } } catch (...) { } } static void freeData(gpointer userData) throw () { delete static_cast< boost::shared_ptr *>(userData); } static boost::shared_ptr ownName(GDBusConnection *conn, const std::string &name, boost::function obtainedCB = boost::function()) { boost::shared_ptr data(new OwnNameAsyncData(name, obtainedCB)); g_bus_own_name_on_connection(conn, data->m_name.c_str(), G_BUS_NAME_OWNER_FLAGS_NONE, OwnNameAsyncData::busNameAcquired, OwnNameAsyncData::busNameLost, new boost::shared_ptr(data), OwnNameAsyncData::freeData); return data; } const std::string m_name; const boost::function m_obtainedCB; State m_state; }; void DBusConnectionPtr::undelay() const { if (!m_name.empty()) { g_debug("starting to acquire D-Bus name %s", m_name.c_str()); boost::shared_ptr data = OwnNameAsyncData::ownName(get(), m_name); while (data->m_state == OwnNameAsyncData::OWN_NAME_WAITING) { g_main_context_iteration(NULL, true); } g_debug("done with acquisition of %s", m_name.c_str()); if (data->m_state == OwnNameAsyncData::OWN_NAME_LOST) { throw std::runtime_error("could not obtain D-Bus name - already running?"); } } g_dbus_connection_start_message_processing(get()); } void DBusConnectionPtr::ownNameAsync(const std::string &name, const boost::function &obtainedCB) const { OwnNameAsyncData::ownName(get(), name, obtainedCB); } DBusConnectionPtr dbus_get_bus_connection(const char *busType, const char *name, bool unshared, DBusErrorCXX *err) { DBusConnectionPtr conn; GError* error = NULL; GBusType type = boost::iequals(busType, "SESSION") ? G_BUS_TYPE_SESSION : G_BUS_TYPE_SYSTEM; if (unshared) { char *address = g_dbus_address_get_for_bus_sync(type, NULL, &error); if(address == NULL) { if (err) { err->set(error); } return NULL; } // Here we set up a private client connection using the chosen bus' address. conn = DBusConnectionPtr(g_dbus_connection_new_for_address_sync(address, (GDBusConnectionFlags) (G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION), NULL, NULL, &error), false); g_free(address); if(conn == NULL) { if (err) { err->set(error); } else { g_clear_error(&error); } return NULL; } } else { // This returns a singleton, shared connection object. conn = DBusConnectionPtr(g_bus_get_sync(type, NULL, &error), false); if(conn == NULL) { if (err) { err->set(error); } else { g_clear_error(&error); } return NULL; } } if (name) { // Request name later in undelay(), after the caller // had a chance to add objects. conn.addName(name); // Acting as client, need to stop when D-Bus daemon dies. g_dbus_connection_set_exit_on_close(conn.get(), TRUE); } return conn; } DBusConnectionPtr dbus_get_bus_connection(const std::string &address, DBusErrorCXX *err, bool delayed /*= false*/) { GError* error = NULL; int flags = G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT; if (delayed) { flags |= G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING; } DBusConnectionPtr conn(g_dbus_connection_new_for_address_sync(address.c_str(), static_cast(flags), NULL, /* GDBusAuthObserver */ NULL, /* GCancellable */ &error), false); if (!conn && err) { err->set(error); } else { g_clear_error(&error); } return conn; } static void ConnectionLost(GDBusConnection *connection, gboolean remotePeerVanished, GError *error, gpointer data) { DBusConnectionPtr::Disconnect_t *cb = static_cast(data); (*cb)(); } static void DestroyDisconnect(gpointer data, GClosure *closure) { DBusConnectionPtr::Disconnect_t *cb = static_cast(data); delete cb; } void DBusConnectionPtr::flush() { // ignore errors g_dbus_connection_flush_sync(get(), NULL, NULL); } void DBusConnectionPtr::setDisconnect(const Disconnect_t &func) { g_signal_connect_closure(get(), "closed", g_cclosure_new(G_CALLBACK(ConnectionLost), new Disconnect_t(func), DestroyDisconnect), true); } boost::shared_ptr DBusServerCXX::listen(const std::string &address, DBusErrorCXX *err) { GDBusServer *server = NULL; const char *realAddr = address.c_str(); char buffer[80]; gchar *guid = g_dbus_generate_guid(); GError *error = NULL; if (address.empty()) { realAddr = buffer; for (int counter = 1; counter < 100 && !server; counter++) { if (error) { // previous attempt failed g_debug("setting up D-Bus server on %s failed, trying next address: %s", realAddr, error->message); g_clear_error(&error); } sprintf(buffer, "unix:abstract=gdbuscxx-%d", counter); server = g_dbus_server_new_sync(realAddr, G_DBUS_SERVER_FLAGS_NONE, guid, NULL, /* GDBusAuthObserver */ NULL, /* GCancellable */ &error); } } else { server = g_dbus_server_new_sync(realAddr, G_DBUS_SERVER_FLAGS_NONE, guid, NULL, /* GDBusAuthObserver */ NULL, /* GCancellable */ &error); } g_free(guid); if (!server) { if (err) { err->set(error); } else { g_clear_error(&error); } return boost::shared_ptr(); } // steals reference to 'server' boost::shared_ptr res(new DBusServerCXX(server, realAddr)); g_signal_connect(server, "new-connection", G_CALLBACK(DBusServerCXX::newConnection), res.get()); return res; } gboolean DBusServerCXX::newConnection(GDBusServer *server, GDBusConnection *newConn, void *data) throw() { DBusServerCXX *me = static_cast(data); if (me->m_newConnection) { GCredentials *credentials; std::string credString; credentials = g_dbus_connection_get_peer_credentials(newConn); if (credentials == NULL) { credString = "(no credentials received)"; } else { gchar *s = g_credentials_to_string(credentials); credString = s; g_free(s); } g_debug("Client connected.\n" "Peer credentials: %s\n" "Negotiated capabilities: unix-fd-passing=%d\n", credString.c_str(), g_dbus_connection_get_capabilities(newConn) & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING); try { // Ref count of connection has to be increased if we want to handle it. // Something inside m_newConnection has to take ownership of connection, // because conn increases ref count only temporarily. DBusConnectionPtr conn(newConn, true); me->m_newConnection(*me, conn); } catch (...) { g_error("handling new D-Bus connection failed with C++ exception"); return FALSE; } return TRUE; } else { return FALSE; } } DBusServerCXX::DBusServerCXX(GDBusServer *server, const std::string &address) : m_server(server, false), // steal reference m_address(address) { g_dbus_server_start(server); } DBusServerCXX::~DBusServerCXX() { g_dbus_server_stop(m_server.get()); } void Watch::nameOwnerChanged(GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, const gchar *interface_name, const gchar *signal_name, GVariant *parameters, gpointer user_data) { Watch *watch = static_cast(user_data); if (!watch->m_called) { gchar *name = NULL, *oldOwner = NULL, *newOwner = NULL; g_variant_get(parameters, "(sss)", &name, &oldOwner, &newOwner); bool matches = name && watch->m_peer == name && newOwner && !*newOwner; g_free(name); g_free(oldOwner); g_free(newOwner); if (matches) { watch->disconnected(); } } } void Watch::disconnected() { if (!m_called) { m_called = true; if (m_callback) { m_callback(); } } } Watch::Watch(const DBusConnectionPtr &conn, const boost::function &callback) : m_conn(conn), m_callback(callback), m_called(false), m_watchID(0) { } void Watch::setCallback(const boost::function &callback) { m_callback = callback; if (m_called && m_callback) { m_callback(); } } void Watch::activate(const char *peer) { if (!peer) { throw std::runtime_error("Watch::activate(): no peer"); } m_peer = peer; // Install watch first ... m_watchID = g_dbus_connection_signal_subscribe(m_conn.get(), NULL, // TODO org.freedesktop.DBus? "org.freedesktop.DBus", "NameOwnerChanged", "/org/freedesktop/DBus", NULL, G_DBUS_SIGNAL_FLAGS_NONE, nameOwnerChanged, this, NULL); if (!m_watchID) { throw std::runtime_error("g_dbus_connection_signal_subscribe(): NameLost failed"); } // ... then check that the peer really exists, // otherwise we'll never notice the disconnect. // If it disconnects while we are doing this, // then disconnect() will be called twice, // but it handles that. GError *error = NULL; GVariant *result = g_dbus_connection_call_sync(m_conn.get(), "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameHasOwner", g_variant_new("(s)", peer), G_VARIANT_TYPE("(b)"), G_DBUS_CALL_FLAGS_NONE, -1, // default timeout NULL, &error); if (result != NULL) { bool actual_result = false; g_variant_get(result, "(b)", &actual_result); if (!actual_result) { disconnected(); } } else { std::string error_message(error->message); g_error_free(error); std::string err_msg("g_dbus_connection_call_sync(): NameHasOwner - "); throw std::runtime_error(err_msg + error_message); } } Watch::~Watch() { if (m_watchID) { g_dbus_connection_signal_unsubscribe(m_conn.get(), m_watchID); m_watchID = 0; } } void getWatch(ExtractArgs &context, boost::shared_ptr &value) { std::auto_ptr watch(new Watch(context.m_conn)); watch->activate((context.m_msg && *context.m_msg) ? g_dbus_message_get_sender(*context.m_msg) : context.m_sender); value.reset(watch.release()); } void ExtractArgs::init(GDBusConnection *conn, GDBusMessage **msg, GVariant *msgBody, const char *sender, const char *path, const char *interface, const char *signal) { m_conn = conn; m_msg = msg; m_sender = sender; m_path = path; m_interface = interface; m_signal = signal; if (msgBody != NULL) { g_variant_iter_init(&m_iter, msgBody); } } ExtractArgs::ExtractArgs(GDBusConnection *conn, GDBusMessage *&msg) { init(conn, &msg, g_dbus_message_get_body(msg), NULL, NULL, NULL, NULL); } ExtractArgs::ExtractArgs(GDBusConnection *conn, const char *sender, const char *path, const char *interface, const char *signal) { init(conn, NULL, NULL, sender, path, interface, signal); } ExtractResponse::ExtractResponse(GDBusConnection *conn, GDBusMessage *msg) { init(conn, NULL, g_dbus_message_get_body(msg), g_dbus_message_get_sender(msg), NULL, NULL, NULL); } } // namespace GDBusCXX syncevolution_1.4/src/gdbusxx/gdbus-cxx-bridge.h000066400000000000000000005774471230021373600221700ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ /** * This file contains everything that a D-Bus server needs to * integrate a normal C++ class into D-Bus. Argument and result * marshaling is done in wrapper functions which convert directly * to normal C++ types (bool, integers, std::string, std::map<>, ...). * See dbus_traits for the full list of supported types. * * Before explaining the binding, some terminology first: * - A function has a return type and multiple parameters. * - Input parameters are read-only arguments of the function. * - The function can return values to the caller via the * return type and output parameters (retvals). * * The C++ binding roughly looks like this: * - Arguments can be passed as plain types or const references: void foo(int arg); void bar(const std::string &str); * - A single result can be returned as return value: * int foo(); * - Multiple results can be copied into instances provided by * the wrapper, passed by reference: void foo(std::string &res); * - A return value, arguments and retvals can be combined * arbitrarily. In the D-Bus reply the return code comes before * all return values. * * Asynchronous methods are possible by declaring one parameter as a * Result pointer and later calling the virtual function provided by * it. Parameter passing of results is less flexible than that of * method parameters: the later allows both std::string as well as * const std::string &, for results only the const reference is * supported. The Result instance is passed as pointer and then owned * by the called method. * * Reference counting via boost::intrusive_ptr ensures that all * D-Bus objects are handled automatically internally. */ #ifndef INCL_GDBUS_CXX_BRIDGE #define INCL_GDBUS_CXX_BRIDGE #include "gdbus-cxx.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* The SyncEvolution exception handler must integrate into the D-Bus * C++ wrapper. In contrast to the rest of the code, that handler uses * some of the internal classes. * * To keep changes to a minimum while supporting both dbus * implementations, this is made to be a define. The intention is to * remove the define once the in-tree gdbus is dropped. */ #define DBUS_NEW_ERROR_MSG g_dbus_message_new_method_error // allow some code to deal with API differences betwee GDBus C++ for GIO // and the version for libdbus #define GDBUS_CXX_GIO 1 // Boost docs want this in the boost:: namespace, but // that fails with clang 2.9 depending on the inclusion order of // header files. Global namespace works in all cases. void intrusive_ptr_add_ref(GDBusConnection *con); void intrusive_ptr_release(GDBusConnection *con); void intrusive_ptr_add_ref(GDBusMessage *msg); void intrusive_ptr_release(GDBusMessage *msg); namespace GDBusCXX { // GDBusCXX aliases for the underlying types. // Useful for some external dbus_traits which // need to pass pointers to these types in their // append()/get() methods without depending on GIO or // libdbus types. typedef GDBusConnection connection_type; typedef GDBusMessage message_type; typedef GVariantBuilder builder_type; typedef GVariantIter reader_type; /** * Simple auto_ptr for GVariant. */ class GVariantCXX : boost::noncopyable { GVariant *m_var; public: /** takes over ownership */ GVariantCXX(GVariant *var = NULL) : m_var(var) {} ~GVariantCXX() { if (m_var) { g_variant_unref(m_var); } } operator GVariant * () { return m_var; } GVariantCXX &operator = (GVariant *var) { if (m_var != var) { if (m_var) { g_variant_unref(m_var); } m_var = var; } return *this; } }; class GVariantIterCXX : boost::noncopyable { GVariantIter *m_var; public: /** takes over ownership */ GVariantIterCXX(GVariantIter *var = NULL) : m_var(var) {} ~GVariantIterCXX() { if (m_var) { g_variant_iter_free(m_var); } } operator GVariantIter * () { return m_var; } GVariantIterCXX &operator = (GVariantIter *var) { if (m_var != var) { if (m_var) { g_variant_iter_free(m_var); } m_var = var; } return *this; } }; class DBusMessagePtr; inline void throwFailure(const std::string &object, const std::string &operation, GError *error) { std::string description = object; if (!description.empty()) { description += ": "; } description += operation; if (error) { description += ": "; description += error->message; g_clear_error(&error); } else { description += " failed"; } throw std::runtime_error(description); } class DBusConnectionPtr : public boost::intrusive_ptr { /** * Bus name of client, as passed to dbus_get_bus_connection(). * The name will be requested in dbus_bus_connection_undelay() = * undelay(), to give the caller a chance to register objects on * the new connection. */ std::string m_name; public: DBusConnectionPtr() {} // connections are typically created once, so increment the ref counter by default DBusConnectionPtr(GDBusConnection *conn, bool add_ref = true) : boost::intrusive_ptr(conn, add_ref) {} GDBusConnection *reference(void) throw() { GDBusConnection *conn = get(); g_object_ref(conn); return conn; } /** * Ensure that all IO is sent out of the process. * Blocks. Only use it right before shutting down. */ void flush(); typedef boost::function Disconnect_t; void setDisconnect(const Disconnect_t &func); // #define GDBUS_CXX_HAVE_DISCONNECT 1 /** * Starts processing of messages, * claims the bus name set with addName() or * when creating the connection (legacy API, * done that way for compatibility with GDBus for libdbus). */ void undelay() const; void addName(const std::string &name) { m_name = name; } /** * Claims another name on the connection. * * The callback will be invoked with true as * parameter once the name was successfully * claimed. If that fails, false will be passed. * * The caller should be prepared to get called * again later on, when loosing an already obtained * name. Currently this shouldn't happen, though, * because name transfer is not enabled when * registering the name. * * The callback is allowed to be empty. */ void ownNameAsync(const std::string &name, const boost::function &obtainedCB) const; }; class DBusMessagePtr : public boost::intrusive_ptr { public: DBusMessagePtr() {} // expected to be used for messages created anew, // so use the reference already incremented for us // and don't increment by default DBusMessagePtr(GDBusMessage *msg, bool add_ref = false) : boost::intrusive_ptr(msg, add_ref) {} GDBusMessage *reference(void) throw() { GDBusMessage *msg = get(); g_object_ref(msg); return msg; } }; /** * wrapper around GError which initializes * the struct automatically, then can be used to * throw an exception */ class DBusErrorCXX { GError *m_error; public: DBusErrorCXX(GError *error = NULL) : m_error(error) { } DBusErrorCXX(const DBusErrorCXX &dbus_error) : m_error(NULL) { if (dbus_error.m_error) { m_error = g_error_copy (dbus_error.m_error); } } DBusErrorCXX & operator=(const DBusErrorCXX &dbus_error) { if (this != &dbus_error) { set(dbus_error.m_error ? g_error_copy(dbus_error.m_error) : NULL); } return *this; } void set(GError *error) { if (m_error) { g_error_free (m_error); } m_error = error; } ~DBusErrorCXX() { if (m_error) { g_error_free (m_error); } } void throwFailure(const std::string &operation, const std::string &explanation = " failed") { std::string error_message(m_error ? (std::string(": ") + m_error->message) : ""); throw std::runtime_error(operation + explanation + error_message); } std::string getMessage() const { return m_error ? m_error->message : ""; } }; DBusConnectionPtr dbus_get_bus_connection(const char *busType, const char *name, bool unshared, DBusErrorCXX *err); DBusConnectionPtr dbus_get_bus_connection(const std::string &address, DBusErrorCXX *err, bool delayed = false); inline void dbus_bus_connection_undelay(const DBusConnectionPtr &conn) { conn.undelay(); } /** * Wrapper around DBusServer. Does intentionally not expose * any of the underlying methods so that the public API * can be implemented differently for GIO libdbus. */ class DBusServerCXX : private boost::noncopyable { public: ~DBusServerCXX(); /** * Called for each new connection. Callback must store the DBusConnectionPtr, * otherwise it will be unref'ed after the callback returns. * If the new connection is not wanted, then it is good style to close it * explicitly in the callback. */ typedef boost::function NewConnection_t; void setNewConnectionCallback(const NewConnection_t &newConnection) { m_newConnection = newConnection; } NewConnection_t getNewConnectionCallback() const { return m_newConnection; } /** * Start listening for new connections on the given address, like unix:abstract=myaddr. * Address may be empty, in which case a new, unused address will chosen. */ static boost::shared_ptr listen(const std::string &address, DBusErrorCXX *err); /** * address used by the server */ std::string getAddress() const { return m_address; } private: DBusServerCXX(GDBusServer *server, const std::string &address); static gboolean newConnection(GDBusServer *server, GDBusConnection *newConn, void *data) throw(); NewConnection_t m_newConnection; boost::intrusive_ptr m_server; std::string m_address; }; /** * Special type for object paths. A string in practice. */ class DBusObject_t : public std::string { public: DBusObject_t() {} template DBusObject_t(T val) : std::string(val) {} template DBusObject_t &operator = (T val) { assign(val); return *this; } }; /** * specializations of this must defined methods for encoding and * decoding type C and declare its signature */ template struct dbus_traits {}; struct dbus_traits_base { /** * A C++ method or function can handle a call asynchronously by * asking to be passed a "boost::shared_ptr" parameter. * The dbus_traits for those parameters have "asynchronous" set to * true, which skips all processing after calling the method. */ static const bool asynchronous = false; }; /** * Append a varying number of parameters as result to the * message, using AppendRetvals(msg) << res1 << res2 << ...; * * Types can be anything that has a dbus_traits, including * types which are normally recognized as input parameters in D-Bus * method calls. */ class AppendRetvals { GDBusMessage *m_msg; GVariantBuilder m_builder; public: AppendRetvals(DBusMessagePtr &msg) { m_msg = msg.get(); g_variant_builder_init(&m_builder, G_VARIANT_TYPE_TUPLE); } ~AppendRetvals() { g_dbus_message_set_body(m_msg, g_variant_builder_end(&m_builder)); } template AppendRetvals & operator << (const A &a) { dbus_traits::append(m_builder, a); return *this; } }; /** * Append a varying number of method parameters as result to the reply * message, using AppendArgs(msg) << Set(res1) << Set(res2) << ...; */ struct AppendArgs { GDBusMessage *m_msg; GVariantBuilder m_builder; AppendArgs(const std::auto_ptr &msg) { m_msg = msg.get(); if (!m_msg) { throw std::runtime_error("NULL GDBusMessage reply"); } g_variant_builder_init(&m_builder, G_VARIANT_TYPE_TUPLE); } ~AppendArgs() { g_dbus_message_set_body(m_msg, g_variant_builder_end(&m_builder)); } /** syntactic sugar: redirect << into Set instance */ template AppendArgs & operator << (const A &a) { return a.set(*this); } /** * Always append argument, including those types which * would be recognized by << as parameters and thus get * skipped. */ template AppendArgs & operator + (const A &a) { dbus_traits::append(m_builder, a); return *this; } }; /** default: skip it, not a result of the method */ template struct Set { Set(typename dbus_traits::host_type &a) {} AppendArgs &set(AppendArgs &context) const { return context; } }; /** same for const reference */ template struct Set { Set(typename dbus_traits::host_type &a) {} AppendArgs &set(AppendArgs &context) const { return context; } }; /** specialization for reference: marshal result */ template struct Set { typename dbus_traits::host_type &m_a; Set(typename dbus_traits::host_type &a) : m_a(a) {} AppendArgs &set(AppendArgs &context) const { dbus_traits::append(context.m_builder, m_a); return context; } }; /** * Extract values from a message, using ExtractArgs(conn, msg) >> Get(val1) >> Get(val2) >> ...; * * This complements AppendArgs: it skips over those method arguments * which are results of the method. Which values are skipped and * which are marshalled depends on the specialization of Get and thus * ultimately on the prototype of the method. */ struct ExtractArgs { // always set GDBusConnection *m_conn; // only set when handling a method call GDBusMessage **m_msg; // only set for method call or response GVariantIter m_iter; // only set when m_msg is NULL (happens when handling signal) const char *m_sender; const char *m_path; const char *m_interface; const char *m_signal; protected: void init(GDBusConnection *conn, GDBusMessage **msg, GVariant *msgBody, const char *sender, const char *path, const char *interface, const char *signal); ExtractArgs() {} public: /** constructor for parsing a method invocation message, which must not be NULL */ ExtractArgs(GDBusConnection *conn, GDBusMessage *&msg); /** constructor for parsing signal parameters */ ExtractArgs(GDBusConnection *conn, const char *sender, const char *path, const char *interface, const char *signal); /** syntactic sugar: redirect >> into Get instance */ template ExtractArgs & operator >> (const A &a) { return a.get(*this); } }; /** * Need separate class because overloading ExtractArgs constructor * with "GDBusMessage &*msg" (for method calls and its special * DBusResult semantic) and "GDBusMessage *msg" (here) is not * possible. We can't just use the former in, for example, * Ret1Traits::demarshal() because we only have a temporary value on * the stack to bind the reference to. */ class ExtractResponse : public ExtractArgs { public: /** constructor for message response */ ExtractResponse(GDBusConnection *conn, GDBusMessage *msg); }; /** default: extract data from message */ template struct Get { typename dbus_traits::host_type &m_a; Get(typename dbus_traits::host_type &a) : m_a(a) {} ExtractArgs &get(ExtractArgs &context) const { dbus_traits::get(context, context.m_iter, m_a); return context; } }; /** same for const reference */ template struct Get { typename dbus_traits::host_type &m_a; Get(typename dbus_traits::host_type &a) : m_a(a) {} ExtractArgs &get(ExtractArgs &context) const { dbus_traits::get(context, context.m_iter, m_a); return context; } }; /** specialization for reference: skip it, not an input parameter */ template struct Get { Get(typename dbus_traits::host_type &a) {} ExtractArgs &get(ExtractArgs &context) const { return context; } }; /** * combines D-Bus connection, path and interface */ class DBusObject { protected: DBusConnectionPtr m_conn; DBusObject_t m_path; std::string m_interface; private: bool m_closeConnection; public: /** * @param closeConnection set to true if the connection * is private and this instance of * DBusObject is meant to be the * last user of the connection; * when this DBusObject deconstructs, * it'll close the connection * (required by libdbus for private * connections; the mechanism in GDBus for * this didn't work) */ DBusObject(const DBusConnectionPtr &conn, const std::string &path, const std::string &interface, bool closeConnection = false) : m_conn(conn), m_path(path), m_interface(interface), m_closeConnection(closeConnection) {} virtual ~DBusObject() { if (m_closeConnection && m_conn) { // TODO: is this also necessary for GIO GDBus? // dbus_connection_close(m_conn.get()); } } GDBusConnection *getConnection() const { return m_conn.get(); } const char *getPath() const { return m_path.c_str(); } const DBusObject_t &getObject() const { return m_path; } const char *getInterface() const { return m_interface.c_str(); } }; /** * adds destination to D-Bus connection, path and interface */ class DBusRemoteObject : public DBusObject { protected: std::string m_destination; public: DBusRemoteObject(const DBusConnectionPtr &conn, const std::string &path, const std::string &interface, const std::string &destination, bool closeConnection = false) : DBusObject(conn, path, interface, closeConnection), m_destination(destination) {} const char *getDestination() const { return m_destination.c_str(); } }; template class EmitSignalHelper { protected: const DBusObject &m_object; const std::string m_signal; EmitSignalHelper(const DBusObject &object, const std::string &signal) : m_object(object), m_signal(signal) {} void sendMsg(const DBusMessagePtr &msg) { if (optional) { g_dbus_connection_send_message(m_object.getConnection(), msg.get(), G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL); } else { if (!msg) { throwFailure(m_signal, "g_dbus_message_new_signal()", NULL); } GError *error = NULL; if (!g_dbus_connection_send_message(m_object.getConnection(), msg.get(), G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, &error)) { throwFailure(m_signal, "g_dbus_connection_send_message()", error); } } } }; template class EmitSignal0Template : private EmitSignalHelper { public: EmitSignal0Template(const DBusObject &object, const std::string &signal) : EmitSignalHelper(object, signal) {} typedef void result_type; void operator () () { DBusMessagePtr msg(g_dbus_message_new_signal(EmitSignalHelper::m_object.getPath(), EmitSignalHelper::m_object.getInterface(), EmitSignalHelper::m_signal.c_str())); if (!msg) { if (optional) { return; } throwFailure(EmitSignalHelper::m_signal, "g_dbus_message_new_signal()", NULL); } EmitSignalHelper::sendMsg(msg); } GDBusSignalInfo *makeSignalEntry() const { GDBusSignalInfo *entry = g_new0(GDBusSignalInfo, 1); entry->name = g_strdup(EmitSignalHelper::m_signal.c_str()); entry->ref_count = 1; return entry; } }; typedef EmitSignal0Template EmitSignal0; void appendArgInfo(GPtrArray *pa, const std::string &type); template void appendNewArg(GPtrArray *pa) { // "in" direction appendArgInfo(pa, dbus_traits::getSignature()); } template void appendNewArgForReply(GPtrArray *pa) { // "out" direction appendArgInfo(pa, dbus_traits::getReply()); } template void appendNewArgForReturn(GPtrArray *pa) { // "out" direction, type must not be skipped appendArgInfo(pa, dbus_traits::getType()); } template class EmitSignal1 : private EmitSignalHelper { public: EmitSignal1(const DBusObject &object, const std::string &signal) : EmitSignalHelper(object, signal) {} typedef void result_type; void operator () (A1 a1) { DBusMessagePtr msg(g_dbus_message_new_signal(EmitSignalHelper::m_object.getPath(), EmitSignalHelper::m_object.getInterface(), EmitSignalHelper::m_signal.c_str())); if (!msg) { if (optional) { return; } throwFailure(EmitSignalHelper::m_signal, "g_dbus_message_new_signal()", NULL); } AppendRetvals(msg) << a1; EmitSignalHelper::sendMsg(msg); } GDBusSignalInfo *makeSignalEntry() const { GDBusSignalInfo *entry = g_new0(GDBusSignalInfo, 1); GPtrArray *args = g_ptr_array_new(); appendNewArg(args); g_ptr_array_add(args, NULL); entry->name = g_strdup(EmitSignalHelper::m_signal.c_str()); entry->args = (GDBusArgInfo **)g_ptr_array_free (args, FALSE); entry->ref_count = 1; return entry; } }; template class EmitSignal2 : private EmitSignalHelper { public: EmitSignal2(const DBusObject &object, const std::string &signal) : EmitSignalHelper(object, signal) {} typedef void result_type; void operator () (A1 a1, A2 a2) { DBusMessagePtr msg(g_dbus_message_new_signal(EmitSignalHelper::m_object.getPath(), EmitSignalHelper::m_object.getInterface(), EmitSignalHelper::m_signal.c_str())); if (!msg) { if (optional) { return; } throwFailure(EmitSignalHelper::m_signal, "g_dbus_message_new_signal()", NULL); } AppendRetvals(msg) << a1 << a2; EmitSignalHelper::sendMsg(msg); } GDBusSignalInfo *makeSignalEntry() const { GDBusSignalInfo *entry = g_new0(GDBusSignalInfo, 1); GPtrArray *args = g_ptr_array_new(); appendNewArg(args); appendNewArg(args); g_ptr_array_add(args, NULL); entry->name = g_strdup(EmitSignalHelper::m_signal.c_str()); entry->args = (GDBusArgInfo **)g_ptr_array_free (args, FALSE); entry->ref_count = 1; return entry; } }; template class EmitSignal3 : private EmitSignalHelper { public: EmitSignal3(const DBusObject &object, const std::string &signal) : EmitSignalHelper(object, signal) {} typedef void result_type; void operator () (A1 a1, A2 a2, A3 a3) { DBusMessagePtr msg(g_dbus_message_new_signal(EmitSignalHelper::m_object.getPath(), EmitSignalHelper::m_object.getInterface(), EmitSignalHelper::m_signal.c_str())); if (!msg) { if (optional) { return; } throwFailure(EmitSignalHelper::m_signal, "g_dbus_message_new_signal()", NULL); } AppendRetvals(msg) << a1 << a2 << a3; EmitSignalHelper::sendMsg(msg); } GDBusSignalInfo *makeSignalEntry() const { GDBusSignalInfo *entry = g_new0(GDBusSignalInfo, 1); GPtrArray *args = g_ptr_array_new(); appendNewArg(args); appendNewArg(args); appendNewArg(args); g_ptr_array_add(args, NULL); entry->name = g_strdup(EmitSignalHelper::m_signal.c_str()); entry->args = (GDBusArgInfo **)g_ptr_array_free (args, FALSE); entry->ref_count = 1; return entry; } }; template class EmitSignal4 : private EmitSignalHelper { public: EmitSignal4(const DBusObject &object, const std::string &signal) : EmitSignalHelper(object, signal) {} typedef void result_type; void operator () (A1 a1, A2 a2, A3 a3, A4 a4) { DBusMessagePtr msg(g_dbus_message_new_signal(EmitSignalHelper::m_object.getPath(), EmitSignalHelper::m_object.getInterface(), EmitSignalHelper::m_signal.c_str())); if (!msg) { if (optional) { return; } throwFailure(EmitSignalHelper::m_signal, "g_dbus_message_new_signal()", NULL); } AppendRetvals(msg) << a1 << a2 << a3 << a4; EmitSignalHelper::sendMsg(msg); } GDBusSignalInfo *makeSignalEntry() const { GDBusSignalInfo *entry = g_new0(GDBusSignalInfo, 1); GPtrArray *args = g_ptr_array_new(); appendNewArg(args); appendNewArg(args); appendNewArg(args); appendNewArg(args); g_ptr_array_add(args, NULL); entry->name = g_strdup(EmitSignalHelper::m_signal.c_str()); entry->args = (GDBusArgInfo **)g_ptr_array_free (args, FALSE); entry->ref_count = 1; return entry; } }; template class EmitSignal5 : private EmitSignalHelper { public: EmitSignal5(const DBusObject &object, const std::string &signal) : EmitSignalHelper(object, signal) {} typedef void result_type; void operator () (A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) { DBusMessagePtr msg(g_dbus_message_new_signal(EmitSignalHelper::m_object.getPath(), EmitSignalHelper::m_object.getInterface(), EmitSignalHelper::m_signal.c_str())); if (!msg) { if (optional) { return; } throwFailure(EmitSignalHelper::m_signal, "g_dbus_message_new_signal()", NULL); } AppendRetvals(msg) << a1 << a2 << a3 << a4 << a5; EmitSignalHelper::sendMsg(msg); } GDBusSignalInfo *makeSignalEntry() const { GDBusSignalInfo *entry = g_new0(GDBusSignalInfo, 1); GPtrArray *args = g_ptr_array_new(); appendNewArg(args); appendNewArg(args); appendNewArg(args); appendNewArg(args); appendNewArg(args); g_ptr_array_add(args, NULL); entry->name = g_strdup(EmitSignalHelper::m_signal.c_str()); entry->args = (GDBusArgInfo **)g_ptr_array_free (args, FALSE); entry->ref_count = 1; return entry; } }; template class EmitSignal6 : private EmitSignalHelper { public: EmitSignal6(const DBusObject &object, const std::string &signal) : EmitSignalHelper(object, signal) {} typedef void result_type; void operator () (A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) { DBusMessagePtr msg(g_dbus_message_new_signal(EmitSignalHelper::m_object.getPath(), EmitSignalHelper::m_object.getInterface(), EmitSignalHelper::m_signal.c_str())); if (!msg) { if (optional) { return; } throwFailure(EmitSignalHelper::m_signal, "g_dbus_message_new_signal()", NULL); } AppendRetvals(msg) << a1 << a2 << a3 << a4 << a5 << a6; EmitSignalHelper::sendMsg(msg); } GDBusSignalInfo *makeSignalEntry() const { GDBusSignalInfo *entry = g_new0(GDBusSignalInfo, 1); GPtrArray *args = g_ptr_array_sized_new(7); appendNewArg(args); appendNewArg(args); appendNewArg(args); appendNewArg(args); appendNewArg(args); appendNewArg(args); g_ptr_array_add(args, NULL); entry->name = g_strdup(EmitSignalHelper::m_signal.c_str()); entry->args = (GDBusArgInfo **)g_ptr_array_free (args, FALSE); entry->ref_count = 1; return entry; } }; struct FunctionWrapperBase { void* m_func_ptr; FunctionWrapperBase(void* func_ptr) : m_func_ptr(func_ptr) {} virtual ~FunctionWrapperBase() {} }; template struct FunctionWrapper : public FunctionWrapperBase { FunctionWrapper(boost::function* func_ptr) : FunctionWrapperBase(reinterpret_cast(func_ptr)) {} virtual ~FunctionWrapper() { delete reinterpret_cast*>(m_func_ptr); } }; struct MethodHandler { typedef GDBusMessage *(*MethodFunction)(GDBusConnection *conn, GDBusMessage *msg, void *data); typedef boost::shared_ptr FuncWrapper; typedef std::pair CallbackPair; typedef std::map MethodMap; static MethodMap m_methodMap; static boost::function m_callback; static std::string make_prefix(const char *object_path) { return std::string(object_path) + "~"; } static std::string make_method_key(const char *object_path, const char *method_name) { return make_prefix(object_path) + method_name; } static void handler(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { MethodMap::iterator it; it = m_methodMap.find(make_method_key(object_path, method_name)); if (it == m_methodMap.end()) { g_dbus_method_invocation_return_dbus_error(invocation, "org.SyncEvolution.NoMatchingMethodName", "No methods registered with this name"); return; } // http://developer.gnome.org/gio/stable/GDBusConnection.html#GDBusInterfaceMethodCallFunc // does not say so explicitly, but it seems that 'invocation' was created for us. // If we don't unref it, it leaks (visible in refdbg). // // The documentation for the class itself says that 'the normal way to obtain a // GDBusMethodInvocation object is to receive it as an argument to the // handle_method_call()' - note the word 'obtain', which seems to imply ownership. // This is consistent with the transfer of ownership to calls like // g_dbus_method_invocation_return_dbus_error(), which take over ownership // of the invocation instance. // // Because we work with messages directly for the reply from now on, we // unref 'invocation' immediately after referencing the underlying message. DBusMessagePtr msg(g_dbus_method_invocation_get_message(invocation), true); g_object_unref(invocation); // Set to NULL, just to be sure we remember that it is gone. // cppcheck-suppress uselessAssignmentPtrArg invocation = NULL; // We are calling callback because we want to keep server alive as long // as possible. This callback is in fact delaying server's autotermination. if (m_callback) { m_callback(); } MethodFunction methodFunc = it->second.first; void *methodData = reinterpret_cast(it->second.second->m_func_ptr); GDBusMessage *reply; reply = (methodFunc)(connection, msg.get(), methodData); if (!reply) { // probably asynchronous, might also be out-of-memory; // either way, don't send a reply now return; } GError *error = NULL; g_dbus_connection_send_message(connection, reply, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, &error); g_object_unref(reply); // Cannot throw an exception, glib event loop won't know what to do with it; // pretend that the problem didn't happen. if (error != NULL) { g_error_free (error); } } }; template struct MakeMethodEntry { // There is no generic implementation of this method. // If you get an error about it missing, then write // a specialization for your type M (the method pointer). // // static GDBusMethodEntry make(const char *name) }; // Wrapper around g_dbus_method_info_unref or g_dbus_signal_info_unref // with additional NULL check. The methods themselves crash on NULL, // which happens when appending the terminating NULL to m_methods/m_signals // below. template void InfoDestroy(gpointer ptr) { if (ptr) { I *info = static_cast(ptr); f(info); } } /** * utility class for registering an interface */ class DBusObjectHelper : public DBusObject { // 0 when not registered (= activated). guint m_connId; GDBusInterfaceInfo m_ifInfo; GDBusInterfaceVTable m_ifVTable; // These arrays must stay valid as long as we are active, // because our GDBusInterfaceInfo points to it without // ever freeing the memory. GPtrArray *m_methods; GPtrArray *m_signals; public: typedef boost::function Callback_t; DBusObjectHelper(DBusConnectionPtr conn, const std::string &path, const std::string &interface, const Callback_t &callback = Callback_t(), bool closeConnection = false) : DBusObject(conn, path, interface, closeConnection), m_connId(0), m_methods(g_ptr_array_new_with_free_func(InfoDestroy)), m_signals(g_ptr_array_new_with_free_func(InfoDestroy)) { memset(&m_ifInfo, 0, sizeof(m_ifInfo)); memset(&m_ifVTable, 0, sizeof(m_ifVTable)); if (!MethodHandler::m_callback) { MethodHandler::m_callback = callback; } } ~DBusObjectHelper() { deactivate(); MethodHandler::MethodMap::iterator iter(MethodHandler::m_methodMap.begin()); MethodHandler::MethodMap::iterator iter_end(MethodHandler::m_methodMap.end()); MethodHandler::MethodMap::iterator first_to_erase(iter_end); MethodHandler::MethodMap::iterator last_to_erase(iter_end); const std::string prefix(MethodHandler::make_prefix(getPath())); while (iter != iter_end) { const bool prefix_equal(!iter->first.compare(0, prefix.size(), prefix)); if (prefix_equal && (first_to_erase == iter_end)) { first_to_erase = iter; } else if (!prefix_equal && (first_to_erase != iter_end)) { last_to_erase = iter; break; } ++iter; } if (first_to_erase != iter_end) { MethodHandler::m_methodMap.erase(first_to_erase, last_to_erase); } g_ptr_array_free(m_methods, TRUE); g_ptr_array_free(m_signals, TRUE); } /** * binds a member to the this pointer of its instance * and invokes it when the specified method is called */ template void add(A1 instance, M C::*method, const char *name) { if (m_connId) { throw std::logic_error("You can't add new methods after registration!"); } typedef MakeMethodEntry< boost::function > entry_type; g_ptr_array_add(m_methods, entry_type::make(name)); boost::function *func = new boost::function(entry_type::boostptr(method, instance)); MethodHandler::FuncWrapper wrapper(new FunctionWrapper(func)); MethodHandler::CallbackPair methodAndData = std::make_pair(entry_type::methodFunction, wrapper); const std::string key(MethodHandler::make_method_key(getPath(), name)); MethodHandler::m_methodMap.insert(std::make_pair(key, methodAndData)); } /** * binds a plain function pointer with no additional arguments and * invokes it when the specified method is called */ template void add(M *function, const char *name) { if (m_connId) { throw std::logic_error("You can't add new functions after registration!"); } typedef MakeMethodEntry< boost::function > entry_type; g_ptr_array_add(m_methods, entry_type::make(name)); boost::function *func = new boost::function(function); MethodHandler::FuncWrapper wrapper(new FunctionWrapper(func)); MethodHandler::CallbackPair methodAndData = std::make_pair(entry_type::methodFunction, wrapper); const std::string key(MethodHandler::make_method_key(getPath(), name)); MethodHandler::m_methodMap.insert(std::make_pair(key, methodAndData)); } /** * add an existing signal entry */ template void add(const S &s) { if (m_connId) { throw std::logic_error("You can't add new signals after registration!"); } g_ptr_array_add(m_signals, s.makeSignalEntry()); } void activate() { // method and signal array must be NULL-terminated. if (m_connId) { throw std::logic_error("This object was already activated."); } if (m_methods->len && m_methods->pdata[m_methods->len - 1] != NULL) { g_ptr_array_add(m_methods, NULL); } if (m_signals->len && m_signals->pdata[m_signals->len - 1] != NULL) { g_ptr_array_add(m_signals, NULL); } // Meta data is owned by this instance, not GDBus. // This is what most examples do and deviating from that // can (did!) lead to memory leaks. For example, // the ownership of the method array cannot be transferred // to GDBusInterfaceInfo. m_ifInfo.ref_count = -1; m_ifInfo.name = const_cast(getInterface()); // Due to ref_count == -1, m_ifInfo.name is not going to get freed despite the missing const. m_ifInfo.methods = (GDBusMethodInfo **)m_methods->pdata; m_ifInfo.signals = (GDBusSignalInfo **)m_signals->pdata; m_ifVTable.method_call = MethodHandler::handler; m_connId = g_dbus_connection_register_object(getConnection(), getPath(), &m_ifInfo, &m_ifVTable, this, NULL, NULL); if (m_connId == 0) { throw std::runtime_error(std::string("g_dbus_connection_register_object() failed for ") + getPath() + " " + getInterface()); } } void deactivate() { if (m_connId) { if (!g_dbus_connection_unregister_object(getConnection(), m_connId)) { throw std::runtime_error(std::string("g_dbus_connection_unregister_object() failed for ") + getPath() + " " + getInterface()); } m_connId = 0; } } }; /** * to be used for plain parameters like int32_t: * treat as arguments which have to be extracted * from the GVariants and can be skipped when * encoding the reply */ struct VariantTypeBoolean { static const GVariantType* getVariantType() { return G_VARIANT_TYPE_BOOLEAN; } }; struct VariantTypeByte { static const GVariantType* getVariantType() { return G_VARIANT_TYPE_BYTE; } }; struct VariantTypeInt16 { static const GVariantType* getVariantType() { return G_VARIANT_TYPE_INT16; } }; struct VariantTypeUInt16 { static const GVariantType* getVariantType() { return G_VARIANT_TYPE_UINT16; } }; struct VariantTypeInt32 { static const GVariantType* getVariantType() { return G_VARIANT_TYPE_INT32; } }; struct VariantTypeUInt32 { static const GVariantType* getVariantType() { return G_VARIANT_TYPE_UINT32; } }; struct VariantTypeInt64 { static const GVariantType* getVariantType() { return G_VARIANT_TYPE_INT64; } }; struct VariantTypeUInt64 { static const GVariantType* getVariantType() { return G_VARIANT_TYPE_UINT64; } }; struct VariantTypeDouble { static const GVariantType* getVariantType() { return G_VARIANT_TYPE_DOUBLE; } }; #define GDBUS_CXX_QUOTE(x) #x #define GDBUS_CXX_LINE(l) GDBUS_CXX_QUOTE(l) #define GDBUS_CXX_SOURCE_INFO __FILE__ ":" GDBUS_CXX_LINE(__LINE__) template struct basic_marshal : public dbus_traits_base { typedef host host_type; typedef host arg_type; /** * copy value from GVariant iterator into variable */ static void get(ExtractArgs &context, GVariantIter &iter, host &value) { GVariantCXX var(g_variant_iter_next_value(&iter)); if (var == NULL || !g_variant_type_equal(g_variant_get_type(var), VariantTraits::getVariantType())) { throw std::runtime_error("g_variant failure " GDBUS_CXX_SOURCE_INFO); } const char *type = g_variant_get_type_string(var); g_variant_get(var, type, &value); } /** * copy value into D-Bus iterator */ static void append(GVariantBuilder &builder, arg_type value) { const gchar *typeStr = g_variant_type_dup_string(VariantTraits::getVariantType()); g_variant_builder_add(&builder, typeStr, value); g_free((gpointer)typeStr); } }; template<> struct dbus_traits : public basic_marshal< uint8_t, VariantTypeByte > { /** * plain type, regardless of whether used as * input or output parameter */ static std::string getType() { return "y"; } /** * plain type => input parameter => non-empty signature */ static std::string getSignature() {return getType(); } /** * plain type => not returned to caller */ static std::string getReply() { return ""; } }; /** if the app wants to use signed char, let it and treat it like a byte */ template<> struct dbus_traits : dbus_traits { typedef int8_t host_type; typedef int8_t arg_type; static void get(ExtractArgs &context, GVariantIter &iter, host_type &value) { dbus_traits::get(context, iter, reinterpret_cast(value)); } }; template<> struct dbus_traits : public basic_marshal< int16_t, VariantTypeInt16 > { static std::string getType() { return "n"; } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } }; template<> struct dbus_traits : public basic_marshal< uint16_t, VariantTypeUInt16 > { static std::string getType() { return "q"; } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } }; template<> struct dbus_traits : public basic_marshal< int32_t, VariantTypeInt32 > { static std::string getType() { return "i"; } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } }; template<> struct dbus_traits : public basic_marshal< uint32_t, VariantTypeUInt32 > { static std::string getType() { return "u"; } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } }; template<> struct dbus_traits : public basic_marshal< int64_t, VariantTypeInt64 > { static std::string getType() { return "x"; } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } }; template<> struct dbus_traits : public basic_marshal< uint64_t, VariantTypeUInt64 > { static std::string getType() { return "t"; } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } }; template<> struct dbus_traits : public basic_marshal< double, VariantTypeDouble > { static std::string getType() { return "d"; } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } }; template<> struct dbus_traits : public dbus_traits_base // cannot use basic_marshal because VariantTypeBoolean packs/unpacks // a gboolean, which is not a C++ bool (4 bytes vs 1 on x86_64) // public basic_marshal< bool, VariantTypeBoolean > { static std::string getType() { return "b"; } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } typedef bool host_type; typedef bool arg_type; static void get(ExtractArgs &context, GVariantIter &iter, bool &value) { GVariantCXX var(g_variant_iter_next_value(&iter)); if (var == NULL || !g_variant_type_equal(g_variant_get_type(var), VariantTypeBoolean::getVariantType())) { throw std::runtime_error("g_variant failure " GDBUS_CXX_SOURCE_INFO); } gboolean buffer; const char *type = g_variant_get_type_string(var); g_variant_get(var, type, &buffer); value = buffer; } static void append(GVariantBuilder &builder, bool value) { const gchar *typeStr = g_variant_type_dup_string(VariantTypeBoolean::getVariantType()); g_variant_builder_add(&builder, typeStr, (gboolean)value); g_free((gpointer)typeStr); } }; template<> struct dbus_traits : public dbus_traits_base { static std::string getType() { return "s"; } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } static void get(ExtractArgs &context, GVariantIter &iter, std::string &value) { GVariantCXX var(g_variant_iter_next_value(&iter)); if (var == NULL || !g_variant_type_equal(g_variant_get_type(var), G_VARIANT_TYPE_STRING)) { throw std::runtime_error("g_variant failure " GDBUS_CXX_SOURCE_INFO); } const char *str = g_variant_get_string(var, NULL); value = str; } static void append(GVariantBuilder &builder, const std::string &value) { g_variant_builder_add_value(&builder, g_variant_new_string(value.c_str())); } typedef std::string host_type; typedef const std::string &arg_type; }; template<> struct dbus_traits : public dbus_traits_base { static std::string getType() { return "s"; } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } // Cannot copy into that type. Can only be used for encoding. static void append(GVariantBuilder &builder, const char *value) { g_variant_builder_add_value(&builder, g_variant_new_string(value ? value : "")); } typedef const char *host_type; typedef const char *arg_type; }; template <> struct dbus_traits : public dbus_traits_base { static std::string getType() { return "o"; } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } static void get(ExtractArgs &context, GVariantIter &iter, DBusObject_t &value) { GVariantCXX var(g_variant_iter_next_value(&iter)); if (var == NULL || !g_variant_type_equal(g_variant_get_type(var), G_VARIANT_TYPE_OBJECT_PATH)) { throw std::runtime_error("g_variant failure " GDBUS_CXX_SOURCE_INFO); } const char *objPath = g_variant_get_string(var, NULL); value = objPath; } static void append(GVariantBuilder &builder, const DBusObject_t &value) { if (!g_variant_is_object_path(value.c_str())) { throw std::runtime_error("g_variant failure " GDBUS_CXX_SOURCE_INFO); } g_variant_builder_add_value(&builder, g_variant_new_object_path(value.c_str())); } typedef DBusObject_t host_type; typedef const DBusObject_t &arg_type; }; /** * pseudo-parameter: not part of D-Bus signature, * but rather extracted from message attributes */ template <> struct dbus_traits : public dbus_traits_base { static std::string getType() { return ""; } static std::string getSignature() { return ""; } static std::string getReply() { return ""; } static void get(ExtractArgs &context, GVariantIter &iter, Caller_t &value) { const char *peer = (context.m_msg && *context.m_msg) ? g_dbus_message_get_sender(*context.m_msg) : context.m_sender; if (!peer) { throw std::runtime_error("D-Bus method call without sender?!"); } value = peer; } typedef Caller_t host_type; typedef const Caller_t &arg_type; }; /** * pseudo-parameter: not part of D-Bus signature, * but rather extracted from message attributes */ template <> struct dbus_traits : public dbus_traits_base { static std::string getType() { return ""; } static std::string getSignature() { return ""; } static std::string getReply() { return ""; } static void get(ExtractArgs &context, GVariantIter &iter, Path_t &value) { const char *path = (context.m_msg && *context.m_msg) ? g_dbus_message_get_path(*context.m_msg) : context.m_path; if (!path) { throw std::runtime_error("D-Bus message without path?!"); } value = path; } typedef Path_t host_type; typedef const Path_t &arg_type; }; /** * pseudo-parameter: not part of D-Bus signature, * but rather extracted from message attributes */ template <> struct dbus_traits : public dbus_traits_base { static std::string getType() { return ""; } static std::string getSignature() { return ""; } static std::string getReply() { return ""; } static void get(ExtractArgs &context, GVariantIter &iter, Interface_t &value) { const char *path = (context.m_msg && *context.m_msg) ? g_dbus_message_get_interface(*context.m_msg) : context.m_interface; if (!path) { throw std::runtime_error("D-Bus message without interface?!"); } value = path; } typedef Interface_t host_type; typedef const Interface_t &arg_type; }; /** * pseudo-parameter: not part of D-Bus signature, * but rather extracted from message attributes */ template <> struct dbus_traits : public dbus_traits_base { static std::string getType() { return ""; } static std::string getSignature() { return ""; } static std::string getReply() { return ""; } static void get(ExtractArgs &context, GVariantIter &iter, Member_t &value) { const char *path = (context.m_msg && *context.m_msg) ? g_dbus_message_get_member(*context.m_msg) : NULL; if (!path) { throw std::runtime_error("D-Bus message without member?!"); } value = path; } typedef Member_t host_type; typedef const Member_t &arg_type; }; /** * a std::pair - maps to D-Bus struct */ template struct dbus_traits< std::pair > : public dbus_traits_base { static std::string getContainedType() { return dbus_traits::getType() + dbus_traits::getType(); } static std::string getType() { return "(" + getContainedType() + ")"; } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } typedef std::pair host_type; typedef const std::pair &arg_type; static void get(ExtractArgs &context, GVariantIter &iter, host_type &pair) { GVariantCXX var(g_variant_iter_next_value(&iter)); if (var == NULL || !g_variant_type_is_subtype_of(g_variant_get_type(var), G_VARIANT_TYPE_TUPLE)) { throw std::runtime_error("g_variant failure " GDBUS_CXX_SOURCE_INFO); } GVariantIter tupIter; g_variant_iter_init(&tupIter, var); dbus_traits::get(context, tupIter, pair.first); dbus_traits::get(context, tupIter, pair.second); } static void append(GVariantBuilder &builder, arg_type pair) { g_variant_builder_open(&builder, G_VARIANT_TYPE(getType().c_str())); dbus_traits::append(builder, pair.first); dbus_traits::append(builder, pair.second); g_variant_builder_close(&builder); } }; /** * a boost::tuple - maps to D-Bus struct */ template struct dbus_traits< boost::tuple > : public dbus_traits_base { static std::string getContainedType() { return dbus_traits::getType() + dbus_traits::getType(); } static std::string getType() { return "(" + getContainedType() + ")"; } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } typedef boost::tuple host_type; typedef const boost::tuple &arg_type; static void get(ExtractArgs &context, GVariantIter &iter, host_type &t) { GVariantCXX var(g_variant_iter_next_value(&iter)); if (var == NULL || !g_variant_type_is_subtype_of(g_variant_get_type(var), G_VARIANT_TYPE_TUPLE)) { throw std::runtime_error("g_variant failure " GDBUS_CXX_SOURCE_INFO); } GVariantIter tupIter; g_variant_iter_init(&tupIter, var); dbus_traits::get(context, tupIter, boost::tuples::get<0>(t)); dbus_traits::get(context, tupIter, boost::tuples::get<1>(t)); } static void append(GVariantBuilder &builder, arg_type t) { g_variant_builder_open(&builder, G_VARIANT_TYPE(getType().c_str())); dbus_traits::append(builder, boost::tuples::get<0>(t)); dbus_traits::append(builder, boost::tuples::get<1>(t)); g_variant_builder_close(&builder); } }; /** * a boost::tuple - maps to D-Bus struct */ template struct dbus_traits< boost::tuple > : public dbus_traits_base { static std::string getContainedType() { return dbus_traits::getType() + dbus_traits::getType() + dbus_traits::getType(); } static std::string getType() { return "(" + getContainedType() + ")"; } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } typedef boost::tuple host_type; typedef const boost::tuple &arg_type; static void get(ExtractArgs &context, GVariantIter &iter, host_type &t) { GVariantCXX var(g_variant_iter_next_value(&iter)); if (var == NULL || !g_variant_type_is_subtype_of(g_variant_get_type(var), G_VARIANT_TYPE_TUPLE)) { throw std::runtime_error("g_variant failure " GDBUS_CXX_SOURCE_INFO); } GVariantIter tupIter; g_variant_iter_init(&tupIter, var); dbus_traits::get(context, tupIter, boost::tuples::get<0>(t)); dbus_traits::get(context, tupIter, boost::tuples::get<1>(t)); dbus_traits::get(context, tupIter, boost::tuples::get<2>(t)); } static void append(GVariantBuilder &builder, arg_type t) { g_variant_builder_open(&builder, G_VARIANT_TYPE(getType().c_str())); dbus_traits::append(builder, boost::tuples::get<0>(t)); dbus_traits::append(builder, boost::tuples::get<1>(t)); dbus_traits::append(builder, boost::tuples::get<2>(t)); g_variant_builder_close(&builder); } }; /** * a boost::tuple - maps to D-Bus struct */ template struct dbus_traits< boost::tuple > : public dbus_traits_base { static std::string getContainedType() { return dbus_traits::getType() + dbus_traits::getType() + dbus_traits::getType() + dbus_traits::getType(); } static std::string getType() { return "(" + getContainedType() + ")"; } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } typedef boost::tuple host_type; typedef const boost::tuple &arg_type; static void get(ExtractArgs &context, GVariantIter &iter, host_type &t) { GVariantCXX var(g_variant_iter_next_value(&iter)); if (var == NULL || !g_variant_type_is_subtype_of(g_variant_get_type(var), G_VARIANT_TYPE_TUPLE)) { throw std::runtime_error("g_variant failure " GDBUS_CXX_SOURCE_INFO); } GVariantIter tupIter; g_variant_iter_init(&tupIter, var); dbus_traits::get(context, tupIter, boost::tuples::get<0>(t)); dbus_traits::get(context, tupIter, boost::tuples::get<1>(t)); dbus_traits::get(context, tupIter, boost::tuples::get<2>(t)); dbus_traits::get(context, tupIter, boost::tuples::get<3>(t)); } static void append(GVariantBuilder &builder, arg_type t) { g_variant_builder_open(&builder, G_VARIANT_TYPE(getType().c_str())); dbus_traits::append(builder, boost::tuples::get<0>(t)); dbus_traits::append(builder, boost::tuples::get<1>(t)); dbus_traits::append(builder, boost::tuples::get<2>(t)); dbus_traits::append(builder, boost::tuples::get<3>(t)); g_variant_builder_close(&builder); } }; /** * dedicated type for chunk of data, to distinguish this case from * a normal std::pair of two values */ template class DBusArray : public std::pair { public: DBusArray() : std::pair(0, NULL) {} DBusArray(size_t len, const V *data) : std::pair(len, data) {} }; template class DBusArray makeDBusArray(size_t len, const V *data) { return DBusArray(len, data); } /** * Pass array of basic type plus its number of entries. * Can only be used in cases where the caller owns the * memory and can discard it when the call returns, in * other words, for method calls, asynchronous replys and * signals, but not for return values. */ template struct dbus_traits< DBusArray > : public dbus_traits_base { static std::string getContainedType() { return dbus_traits::getType(); } static std::string getType() { return std::string("a") + dbus_traits::getType(); } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } typedef DBusArray host_type; typedef const host_type &arg_type; static void get(ExtractArgs &context, GVariantIter &iter, host_type &array) { GVariantCXX var(g_variant_iter_next_value(&iter)); if (var == NULL || !g_variant_type_is_subtype_of(g_variant_get_type(var), G_VARIANT_TYPE_ARRAY)) { throw std::runtime_error("g_variant failure " GDBUS_CXX_SOURCE_INFO); } typedef typename dbus_traits::host_type V_host_type; gsize nelements; V_host_type *data; data = (V_host_type *) g_variant_get_fixed_array(var, &nelements, static_cast(sizeof(V_host_type))); array.first = nelements; array.second = data; } static void append(GVariantBuilder &builder, arg_type array) { g_variant_builder_add_value(&builder, g_variant_new_from_data(G_VARIANT_TYPE(getType().c_str()), (gconstpointer)array.second, array.first, true, // data is trusted to be in serialized form NULL, NULL // no need to free data )); } }; /** * a std::map - treat it like a D-Bus dict */ template struct dbus_traits< std::map > : public dbus_traits_base { static std::string getContainedType() { return std::string("{") + dbus_traits::getType() + dbus_traits::getType() + "}"; } static std::string getType() { return std::string("a") + getContainedType(); } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } typedef std::map host_type; typedef const host_type &arg_type; static void get(ExtractArgs &context, GVariantIter &iter, host_type &dict) { GVariantCXX var(g_variant_iter_next_value(&iter)); if (var == NULL || !g_variant_type_is_subtype_of(g_variant_get_type(var), G_VARIANT_TYPE_ARRAY)) { throw std::runtime_error("g_variant failure " GDBUS_CXX_SOURCE_INFO); } GVariantIter contIter; GVariantCXX child; g_variant_iter_init(&contIter, var); while((child = g_variant_iter_next_value(&contIter)) != NULL) { K key; V value; GVariantIter childIter; g_variant_iter_init(&childIter, child); dbus_traits::get(context, childIter, key); dbus_traits::get(context, childIter, value); dict.insert(std::make_pair(key, value)); } } static void append(GVariantBuilder &builder, arg_type dict) { g_variant_builder_open(&builder, G_VARIANT_TYPE(getType().c_str())); for(typename host_type::const_iterator it = dict.begin(); it != dict.end(); ++it) { g_variant_builder_open(&builder, G_VARIANT_TYPE(getContainedType().c_str())); dbus_traits::append(builder, it->first); dbus_traits::append(builder, it->second); g_variant_builder_close(&builder); } g_variant_builder_close(&builder); } }; /** * A collection of items (works for std::list, std::vector, std::deque, ...). * Maps to D-Bus array, but with inefficient marshaling * because we cannot get a base pointer for the whole array. */ template struct dbus_traits_collection : public dbus_traits_base { static std::string getContainedType() { return dbus_traits::getType(); } static std::string getType() { return std::string("a") + getContainedType(); } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } typedef C host_type; typedef const C &arg_type; static void get(ExtractArgs &context, GVariantIter &iter, host_type &array) { GVariantCXX var(g_variant_iter_next_value(&iter)); if (var == NULL || !g_variant_type_is_subtype_of(g_variant_get_type(var), G_VARIANT_TYPE_ARRAY)) { throw std::runtime_error("g_variant failure " GDBUS_CXX_SOURCE_INFO); } int nelements = g_variant_n_children(var); GVariantIter childIter; g_variant_iter_init(&childIter, var); for(int i = 0; i < nelements; ++i) { V value; dbus_traits::get(context, childIter, value); array.push_back(value); } } static void append(GVariantBuilder &builder, arg_type array) { g_variant_builder_open(&builder, G_VARIANT_TYPE(getType().c_str())); for(typename host_type::const_iterator it = array.begin(); it != array.end(); ++it) { dbus_traits::append(builder, *it); } g_variant_builder_close(&builder); } }; template struct dbus_traits< std::vector > : public dbus_traits_collection, V> {}; template struct dbus_traits< std::list > : public dbus_traits_collection, V> {}; template struct dbus_traits< std::deque > : public dbus_traits_collection, V> {}; struct append_visitor : public boost::static_visitor<> { GVariantBuilder &builder; append_visitor(GVariantBuilder &b) : builder(b) {} template void operator()(const V &v) const { dbus_traits::append(builder, v); } }; /** * A boost::variant maps to a dbus variant, only care about values of * type V but will not throw error if type is not matched, this is useful if * application is interested on only a sub set of possible value types * in variant. */ template struct dbus_traits > : public dbus_traits_base { static std::string getType() { return "v"; } static std::string getSignature() { return getType(); } static std::string getReply() { return ""; } static void get(ExtractArgs &context, GVariantIter &iter, boost::variant &value) { GVariantCXX var(g_variant_iter_next_value(&iter)); if (var == NULL || !g_variant_type_equal(g_variant_get_type(var), G_VARIANT_TYPE_VARIANT)) { throw std::runtime_error("g_variant failure " GDBUS_CXX_SOURCE_INFO); } GVariantIter varIter; g_variant_iter_init(&varIter, var); GVariantCXX varVar(g_variant_iter_next_value(&varIter)); const char *type = g_variant_get_type_string(varVar); if (dbus_traits::getSignature() != type) { //ignore unrecognized sub type in variant return; } V val; // Note: Reset the iterator so that the call to dbus_traits::get() will get the right variant; g_variant_iter_init(&varIter, var); dbus_traits::get(context, varIter, val); value = val; } static void append(GVariantBuilder &builder, const boost::variant &value) { g_variant_builder_open(&builder, G_VARIANT_TYPE(getType().c_str())); boost::apply_visitor(append_visitor(builder), value); g_variant_builder_close(&builder); } typedef boost::variant host_type; typedef const boost::variant &arg_type; }; /** * A boost::variant maps to a dbus variant, only care about values of * type V1, V2 but will not throw error if type is not matched, this is useful if * application is interested on only a sub set of possible value types * in variant. */ template struct dbus_traits > : public dbus_traits_base { static std::string getType() { return "v"; } static std::string getSignature() { return getType(); } static std::string getReply() { return ""; } static void get(ExtractArgs &context, GVariantIter &iter, boost::variant &value) { GVariantCXX var(g_variant_iter_next_value(&iter)); if (var == NULL || !g_variant_type_equal(g_variant_get_type(var), G_VARIANT_TYPE_VARIANT)) { throw std::runtime_error("g_variant failure " GDBUS_CXX_SOURCE_INFO); } GVariantIter varIter; g_variant_iter_init(&varIter, var); GVariantCXX varVar(g_variant_iter_next_value(&varIter)); const char *type = g_variant_get_type_string(varVar); if ((dbus_traits::getSignature() != type) && (dbus_traits::getSignature() != type)) { // ignore unrecognized sub type in variant return; } else if (dbus_traits::getSignature() == type) { V1 val; // Note: Reset the iterator so that the call to dbus_traits::get() will get the right variant; g_variant_iter_init(&varIter, var); dbus_traits::get(context, varIter, val); value = val; } else { V2 val; // Note: Reset the iterator so that the call to dbus_traits::get() will get the right variant; g_variant_iter_init(&varIter, var); dbus_traits::get(context, varIter, val); value = val; } } static void append(GVariantBuilder &builder, const boost::variant &value) { g_variant_builder_open(&builder, G_VARIANT_TYPE(getType().c_str())); boost::apply_visitor(append_visitor(builder), value); g_variant_builder_close(&builder); } typedef boost::variant host_type; typedef const boost::variant &arg_type; }; /** * A recursive variant. Can represent values of a certain type V and * vectors with a mixture of such values and the variant. Limiting * this to vectors is done for the sake of simplicity and because * vector is fairly efficient to work with, in particular when * implementing methods. * * It would be nice to not refer to boost internals here. But using * "typename boost::make_recursive_variant >::type" * instead of the expanded * "boost::variant< boost::detail::variant::recursive_flag, A>" * leads to a compiler error: * class template partial specialization contains a template parameter that can not be deduced; this partial specialization will never be used [-Werror] * ...dbus_traits < typename boost::make_recursive_variant >::type > ... * ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ template struct dbus_traits < boost::variant, A> > : public dbus_traits_base { static std::string getType() { return "v"; } static std::string getSignature() { return getType(); } static std::string getReply() { return ""; } typedef boost::variant, A> host_type; typedef const host_type &arg_type; static void get(ExtractArgs &context, GVariantIter &iter, host_type &value) { GVariantIterCXX itercopy(g_variant_iter_copy(&iter)); // Peek at next element, then decide what to do with it. GVariantCXX var(g_variant_iter_next_value(&iter)); // We accept a variant, the plain type V, or an array. // This is necessary for clients like Python which // send [['foo', 'bar']] as 'aas' when seeing 'v' // as signature. if (var == NULL) { throw std::runtime_error("g_variant failure " GDBUS_CXX_SOURCE_INFO); } const char *type = g_variant_get_type_string(var); if (!strcmp(type, "v")) { // Strip the outer variant and decode the inner value recursively, in // our own else branch. GVariantIter varIter; g_variant_iter_init(&varIter, var); dbus_traits::get(context, varIter, value); } else if (dbus_traits::getSignature() == type) { V val; dbus_traits::get(context, *itercopy, val); value = val; } else if (type[0] == 'a') { std::vector val; dbus_traits< std::vector >::get(context, *itercopy, val); value = val; } else if (type[0] == '(') { // Treat a tuple like an array. We have to iterate ourself here. std::vector val; GVariantIter tupIter; g_variant_iter_init(&tupIter, var); GVariantIterCXX copy(g_variant_iter_copy(&tupIter)); // Step through the elements in lockstep. We need this // because we must not call the get() method when there is // nothing to unpack. while (GVariantCXX(g_variant_iter_next_value(copy))) { host_type tmp; dbus_traits::get(context, tupIter, tmp); val.push_back(tmp); } value = val; } else { // More strict than the other variants, because it is mostly used for // method calls where we don't want to silently ignore parts of the // parameter. throw std::runtime_error(std::string("expected recursive variant containing " + dbus_traits::getSignature() + ", got " + type)); return; } } static void append(GVariantBuilder &builder, const boost::variant &value) { g_variant_builder_open(&builder, G_VARIANT_TYPE(getType().c_str())); boost::apply_visitor(append_visitor(builder), value); g_variant_builder_close(&builder); } }; /** * a single member m of type V in a struct K */ template struct dbus_member_single { static std::string getType() { return dbus_traits::getType(); } typedef V host_type; static void get(ExtractArgs &context, GVariantIter &iter, K &val) { dbus_traits::get(context, iter, val.*m); } static void append(GVariantBuilder &builder, const K &val) { dbus_traits::append(builder, val.*m); } }; /** * a member m of type V in a struct K, followed by another dbus_member * or dbus_member_single to end the chain */ template struct dbus_member { static std::string getType() { return dbus_traits::getType() + M::getType(); } typedef V host_type; static void get(ExtractArgs &context, GVariantIter &iter, K &val) { dbus_traits::get(context, iter, val.*m); M::get(context, iter, val); } static void append(GVariantBuilder &builder, const K &val) { dbus_traits::append(builder, val.*m); M::append(builder, val); } }; /** * a helper class which implements dbus_traits for * a class, use with: * struct foo { int a; std::string b; }; * template<> struct dbus_traits< foo > : dbus_struct_traits< foo, * dbus_member > > {}; */ template struct dbus_struct_traits : public dbus_traits_base { static std::string getContainedType() { return M::getType(); } static std::string getType() { return std::string("(") + getContainedType() + ")"; } static std::string getSignature() {return getType(); } static std::string getReply() { return ""; } typedef K host_type; typedef const K &arg_type; static void get(ExtractArgs &context, GVariantIter &iter, host_type &val) { GVariantCXX var(g_variant_iter_next_value(&iter)); if (var == NULL || !g_variant_type_is_subtype_of(g_variant_get_type(var), G_VARIANT_TYPE_TUPLE)) { throw std::runtime_error("g_variant failure " GDBUS_CXX_SOURCE_INFO); } GVariantIter tupIter; g_variant_iter_init(&tupIter, var); M::get(context, tupIter, val); } static void append(GVariantBuilder &builder, arg_type val) { g_variant_builder_open(&builder, G_VARIANT_TYPE(getType().c_str())); M::append(builder, val); g_variant_builder_close(&builder); } }; /** * a helper class which implements dbus_traits for an enum, * parameterize it with the enum type and an integer type * large enough to hold all valid enum values */ template struct dbus_enum_traits : public dbus_traits { typedef E host_type; typedef E arg_type; // cast from enum to int in append() is implicit; in // get() we have to make it explicit static void get(ExtractArgs &context, GVariantIter &iter, host_type &val) { I ival; dbus_traits::get(context, iter, ival); val = static_cast(ival); } }; /** * special case const reference parameter: * treat like pass-by-value input argument * * Example: const std::string &arg */ template struct dbus_traits : public dbus_traits {}; /** * special case writeable reference parameter: * must be a return value * * Example: std::string &retval */ template struct dbus_traits : public dbus_traits { static std::string getSignature() { return ""; } static std::string getReply() { return dbus_traits::getType(); } }; /** * dbus-cxx base exception thrown in dbus server * org.syncevolution.gdbuscxx.Exception * This base class only contains interfaces, no data members */ class DBusCXXException { public: /** * get exception name, used to convert to dbus error name * subclasses should override it */ virtual std::string getName() const { return "org.syncevolution.gdbuscxx.Exception"; } /** * get error message */ virtual const char* getMessage() const { return "unknown"; } }; static GDBusMessage *handleException(GDBusMessage *&callerMsg) { // We provide a reply to the message. Clear the "msg" variable // in our caller's context to make it as done. GDBusMessage *msg = callerMsg; callerMsg = NULL; try { #ifdef DBUS_CXX_EXCEPTION_HANDLER return DBUS_CXX_EXCEPTION_HANDLER(msg); #else throw; #endif } catch (const dbus_error &ex) { return g_dbus_message_new_method_error_literal(msg, ex.dbusName().c_str(), ex.what()); } catch (const DBusCXXException &ex) { return g_dbus_message_new_method_error_literal(msg, ex.getName().c_str(), ex.getMessage()); } catch (const std::runtime_error &ex) { return g_dbus_message_new_method_error_literal(msg, "org.syncevolution.gdbuscxx.Exception", ex.what()); } catch (...) { return g_dbus_message_new_method_error_literal(msg, "org.syncevolution.gdbuscxx.Exception", "unknown"); } } /** * Check presence of a certain D-Bus client. */ class Watch : private boost::noncopyable { DBusConnectionPtr m_conn; boost::function m_callback; bool m_called; guint m_watchID; std::string m_peer; static void nameOwnerChanged(GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, const gchar *interface_name, const gchar *signal_name, GVariant *parameters, gpointer user_data); void disconnected(); public: Watch(const DBusConnectionPtr &conn, const boost::function &callback = boost::function()); ~Watch(); /** * Changes the callback triggered by this Watch. If the watch has * already fired, the callback is invoked immediately. */ void setCallback(const boost::function &callback); /** * Starts watching for disconnect of that peer * and also checks whether it is currently * still around. */ void activate(const char *peer); }; void getWatch(ExtractArgs &context, boost::shared_ptr &value); /** * pseudo-parameter: not part of D-Bus signature, * but rather extracted from message attributes */ template <> struct dbus_traits< boost::shared_ptr > : public dbus_traits_base { static std::string getType() { return ""; } static std::string getSignature() { return ""; } static std::string getReply() { return ""; } static void get(ExtractArgs &context, GVariantIter &iter, boost::shared_ptr &value) { getWatch(context, value); } static void append(GVariantBuilder &builder, const boost::shared_ptr &value) {} typedef boost::shared_ptr host_type; typedef const boost::shared_ptr &arg_type; }; /** * base class for D-Bus results, * keeps references to required objects and provides the * failed() method */ class DBusResult : virtual public Result { protected: DBusConnectionPtr m_conn; /**< connection via which the message was received */ DBusMessagePtr m_msg; /**< the method invocation message */ bool m_haveOwnership; /**< this class is responsible for sending a method reply */ bool m_replied; /**< a response was sent */ void sendMsg(const DBusMessagePtr &msg) { m_replied = true; GError *error = NULL; if (!g_dbus_connection_send_message(m_conn.get(), msg.get(), G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, &error)) { throwFailure("", "g_dbus_connection_send_message()", error); } } public: DBusResult(GDBusConnection *conn, GDBusMessage *msg) : m_conn(conn, true), m_msg(msg, true), m_haveOwnership(false), m_replied(false) {} ~DBusResult() { if (m_haveOwnership && !m_replied) { try { failed(dbus_error("org.syncevolution.gdbus", "processing the method call failed")); } catch (...) { // Ignore failure, we are probably shutting down // anyway, which will tell the caller that the // method won't be processed. } } } void transferOwnership() throw () { m_haveOwnership = true; } virtual void failed(const dbus_error &error) { DBusMessagePtr errMsg(g_dbus_message_new_method_error(m_msg.get(), error.dbusName().c_str(), "%s", error.what())); sendMsg(errMsg); } virtual Watch *createWatch(const boost::function &callback) { std::auto_ptr watch(new Watch(m_conn, callback)); watch->activate(g_dbus_message_get_sender(m_msg.get())); return watch.release(); } }; class DBusResult0 : public Result0, public DBusResult { public: DBusResult0(GDBusConnection *conn, GDBusMessage *msg) : DBusResult(conn, msg) {} virtual void done() { DBusMessagePtr reply(g_dbus_message_new_method_reply(m_msg.get())); if (!reply) { throw std::runtime_error("no GDBusMessage"); } sendMsg(reply); } static std::string getSignature() { return ""; } }; template class DBusResult1 : public Result1, public DBusResult { public: DBusResult1(GDBusConnection *conn, GDBusMessage *msg) : DBusResult(conn, msg) {} virtual void done(A1 a1) { DBusMessagePtr reply(g_dbus_message_new_method_reply(m_msg.get())); if (!reply) { throw std::runtime_error("no GDBusMessage"); } AppendRetvals(reply) << a1; sendMsg(reply); } static std::string getSignature() { return dbus_traits::getSignature(); } static const bool asynchronous = dbus_traits::asynchronous; }; template class DBusResult2 : public Result2, public DBusResult { public: DBusResult2(GDBusConnection *conn, GDBusMessage *msg) : DBusResult(conn, msg) {} virtual void done(A1 a1, A2 a2) { DBusMessagePtr reply(g_dbus_message_new_method_reply(m_msg.get())); if (!reply) { throw std::runtime_error("no GDBusMessage"); } AppendRetvals(reply) << a1 << a2; sendMsg(reply); } static std::string getSignature() { return dbus_traits::getSignature() + DBusResult1::getSignature(); } static const bool asynchronous = dbus_traits::asynchronous || DBusResult1::asynchronous; }; template class DBusResult3 : public Result3, public DBusResult { public: DBusResult3(GDBusConnection *conn, GDBusMessage *msg) : DBusResult(conn, msg) {} virtual void done(A1 a1, A2 a2, A3 a3) { DBusMessagePtr reply(g_dbus_message_new_method_reply(m_msg.get())); if (!reply) { throw std::runtime_error("no GDBusMessage"); } AppendRetvals(reply) << a1 << a2 << a3; sendMsg(reply); } static std::string getSignature() { return dbus_traits::getSignature() + DBusResult2::getSignature(); } static const bool asynchronous = dbus_traits::asynchronous || DBusResult2::asynchronous; }; template class DBusResult4 : public Result4, public DBusResult { public: DBusResult4(GDBusConnection *conn, GDBusMessage *msg) : DBusResult(conn, msg) {} virtual void done(A1 a1, A2 a2, A3 a3, A4 a4) { DBusMessagePtr reply(g_dbus_message_new_method_reply(m_msg.get())); if (!reply) { throw std::runtime_error("no GDBusMessage"); } AppendRetvals(reply) << a1 << a2 << a3 << a4; sendMsg(reply); } static std::string getSignature() { return dbus_traits::getSignature() + DBusResult3::getSignature(); } static const bool asynchronous = dbus_traits::asynchronous || DBusResult3::asynchronous; }; template class DBusResult5 : public Result5, public DBusResult { public: DBusResult5(GDBusConnection *conn, GDBusMessage *msg) : DBusResult(conn, msg) {} virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) { DBusMessagePtr reply(g_dbus_message_new_method_reply(m_msg.get())); if (!reply) { throw std::runtime_error("no GDBusMessage"); } AppendRetvals(reply) << a1 << a2 << a3 << a4 << a5; sendMsg(reply); } static std::string getSignature() { return dbus_traits::getSignature() + DBusResult4::getSignature(); } static const bool asynchronous = dbus_traits::asynchronous || DBusResult4::asynchronous; }; template class DBusResult6 : public Result6, public DBusResult { public: DBusResult6(GDBusConnection *conn, GDBusMessage *msg) : DBusResult(conn, msg) {} virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) { DBusMessagePtr reply(g_dbus_message_new_method_reply(m_msg.get())); if (!reply) { throw std::runtime_error("no GDBusMessage"); } AppendRetvals(reply) << a1 << a2 << a3 << a4 << a5 << a6; sendMsg(reply); } static std::string getSignature() { return dbus_traits::getSignature() + DBusResult5::getSignature(); } static const bool asynchronous = dbus_traits::asynchronous || DBusResult5::asynchronous; }; template class DBusResult7 : public Result7, public DBusResult { public: DBusResult7(GDBusConnection *conn, GDBusMessage *msg) : DBusResult(conn, msg) {} virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) { DBusMessagePtr reply(g_dbus_message_new_method_reply(m_msg.get())); if (!reply) { throw std::runtime_error("no GDBusMessage"); } AppendRetvals(reply) << a1 << a2 << a3 << a4 << a5 << a6 << a7; sendMsg(reply); } static std::string getSignature() { return dbus_traits::getSignature() + DBusResult6::getSignature(); } static const bool asynchronous = dbus_traits::asynchronous || DBusResult6::asynchronous; }; template class DBusResult8 : public Result8, public DBusResult { public: DBusResult8(GDBusConnection *conn, GDBusMessage *msg) : DBusResult(conn, msg) {} virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) { DBusMessagePtr reply(g_dbus_message_new_method_reply(m_msg.get())); if (!reply) { throw std::runtime_error("no GDBusMessage"); } AppendRetvals(reply) << a1 << a2 << a3 << a4 << a5 << a6 << a7 << a8; sendMsg(reply); } static std::string getSignature() { return dbus_traits::getSignature() + DBusResult7::getSignature(); } static const bool asynchronous = dbus_traits::asynchronous || DBusResult7::asynchronous; }; template class DBusResult9 : public Result9, public DBusResult { public: DBusResult9(GDBusConnection *conn, GDBusMessage *msg) : DBusResult(conn, msg) {} virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9) { DBusMessagePtr reply(g_dbus_message_new_method_reply(m_msg.get())); if (!reply) { throw std::runtime_error("no GDBusMessage"); } AppendRetvals(reply) << a1 << a2 << a3 << a4 << a5 << a6 << a7 << a8 << a9; sendMsg(reply); } static std::string getSignature() { return dbus_traits::getSignature() + DBusResult8::getSignature(); } static const bool asynchronous = dbus_traits::asynchronous || DBusResult8::asynchronous; }; template class DBusResult10 : public Result10, public DBusResult { public: DBusResult10(GDBusConnection *conn, GDBusMessage *msg) : DBusResult(conn, msg) {} virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10) { DBusMessagePtr reply(g_dbus_message_new_method_reply(m_msg.get())); if (!reply) { throw std::runtime_error("no GDBusMessage"); } AppendRetvals(reply) << a1 << a2 << a3 << a4 << a5 << a6 << a7 << a8 << a9 << a10; sendMsg(reply); } static std::string getSignature() { return dbus_traits::getSignature() + DBusResult9::getSignature(); } static const bool asynchronous = dbus_traits::asynchronous || DBusResult9::asynchronous; }; /** * Helper class for constructing a DBusResult: while inside the * initial method call handler, we have a try/catch block which will * reply to the caller. Once we leave that block, this class here * destructs and transfers the responsibility for sending a reply to * the DBusResult instance. */ template class DBusResultGuard : public boost::shared_ptr { GDBusMessage **m_msg; public: DBusResultGuard() : m_msg(NULL) {} ~DBusResultGuard() throw () { DBusR *result = boost::shared_ptr::get(); // Our caller has not cleared its "msg" instance, // which means that from now on it will be our // responsibility to provide a response. if (result && m_msg && *m_msg) { result->transferOwnership(); } } void initDBusResult(ExtractArgs &context) { m_msg = context.m_msg; boost::shared_ptr::reset(new DBusR(context.m_conn, context.m_msg ? *context.m_msg : NULL)); } }; /** * A parameter which points towards one of our Result* structures. * All of the types contained in it count towards the Reply signature. * The requested Result type itself is constructed here. * * @param R Result0, Result1, ... * @param DBusR the class implementing R */ template struct dbus_traits_result { static std::string getType() { return DBusR::getSignature(); } static std::string getSignature() { return ""; } static std::string getReply() { return getType(); } typedef DBusResultGuard host_type; typedef boost::shared_ptr &arg_type; static const bool asynchronous = true; static void get(ExtractArgs &context, GVariantIter &iter, host_type &value) { value.initDBusResult(context); } }; template <> struct dbus_traits< boost::shared_ptr > : public dbus_traits_result {}; template struct dbus_traits< boost::shared_ptr< Result1 > >: public dbus_traits_result< Result1, DBusResult1 > {}; template struct dbus_traits< boost::shared_ptr< Result2 > >: public dbus_traits_result< Result2, DBusResult2 > {}; template struct dbus_traits< boost::shared_ptr< Result3 > >: public dbus_traits_result< Result3, DBusResult3 > {}; template struct dbus_traits< boost::shared_ptr< Result4 > >: public dbus_traits_result< Result4, DBusResult4 > {}; template struct dbus_traits< boost::shared_ptr< Result5 > >: public dbus_traits_result< Result5, DBusResult5 > {}; template struct dbus_traits< boost::shared_ptr< Result6 > >: public dbus_traits_result< Result6, DBusResult6 > {}; template struct dbus_traits< boost::shared_ptr< Result7 > >: public dbus_traits_result< Result7, DBusResult7 > {}; template struct dbus_traits< boost::shared_ptr< Result8 > >: public dbus_traits_result< Result8, DBusResult8 > {}; template struct dbus_traits< boost::shared_ptr< Result9 > >: public dbus_traits_result< Result9, DBusResult9 > {}; template struct dbus_traits< boost::shared_ptr< Result10 > >: public dbus_traits_result< Result10, DBusResult10 > {}; /** ===> 10 parameters */ template struct MakeMethodEntry< boost::function > { typedef void (Mptr)(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10); typedef boost::function M; template static M bind(Mptr C::*method, I instance) { // this fails because bind() only supports up to 9 parameters, including // the initial this pointer return boost::bind(method, instance, _1, _2, _3, _4, _5, _6, _7, _8, _9 /* _10 */); } static const bool asynchronous = dbus_traits< DBusResult10 >::asynchronous; static GDBusMessage *methodFunction(GDBusConnection *conn, GDBusMessage *msg, void *data) { try { std::auto_ptr reply; typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; typename dbus_traits::host_type a5; typename dbus_traits::host_type a6; typename dbus_traits::host_type a7; typename dbus_traits::host_type a8; typename dbus_traits::host_type a9; typename dbus_traits::host_type a10; try { ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3) >> Get(a4) >> Get(a5) >> Get(a6) >> Get(a7) >> Get(a8) >> Get(a9) >> Get(a10); (*static_cast(data))(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); if (asynchronous) { return NULL; } reply.reset(g_dbus_message_new_method_reply(msg)); AppendArgs(reply) << Set(a1) << Set(a2) << Set(a3) << Set(a4) << Set(a5) << Set(a6) << Set(a7) << Set(a8) << Set(a9) << Set(a10); } catch (...) { return handleException(msg); } return reply.release(); } catch (...) { return handleException(msg); } } static GDBusMethodInfo *make(const char *name) { GDBusMethodInfo *entry = g_new0(GDBusMethodInfo, 1); GPtrArray *inArgs = g_ptr_array_new(); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); g_ptr_array_add(inArgs, NULL); GPtrArray *outArgs = g_ptr_array_new(); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); g_ptr_array_add(outArgs, NULL); entry->name = g_strdup(name); entry->in_args = (GDBusArgInfo **)g_ptr_array_free(inArgs, FALSE); entry->out_args = (GDBusArgInfo **)g_ptr_array_free(outArgs, FALSE); entry->ref_count = 1; return entry; } }; /** 9 arguments, 1 return value */ template struct MakeMethodEntry< boost::function > { typedef R (Mptr)(A1, A2, A3, A4, A5, A6, A7, A8, A9); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { // this fails because bind() only supports up to 9 parameters, including // the initial this pointer return boost::bind(method, instance, _1, _2, _3, _4, _5, _6, _7, _8, _9); } static const bool asynchronous = DBusResult9::asynchronous; static GDBusMessage *methodFunction(GDBusConnection *conn, GDBusMessage *msg, void *data) { try { std::auto_ptr reply; typename dbus_traits::host_type r; typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; typename dbus_traits::host_type a5; typename dbus_traits::host_type a6; typename dbus_traits::host_type a7; typename dbus_traits::host_type a8; typename dbus_traits::host_type a9; try { ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3) >> Get(a4) >> Get(a5) >> Get(a6) >> Get(a7) >> Get(a8) >> Get(a9); r = (*static_cast(data))(a1, a2, a3, a4, a5, a6, a7, a8, a9); if (asynchronous) { return NULL; } reply.reset(g_dbus_message_new_method_reply(msg)); AppendArgs(reply) + r << Set(a1) << Set(a2) << Set(a3) << Set(a4) << Set(a5) << Set(a6) << Set(a7) << Set(a8) << Set(a9); } catch (...) { return handleException(msg); } return reply.release(); } catch (...) { return handleException(msg); } } static GDBusMethodInfo *make(const char *name) { GDBusMethodInfo *entry = g_new0(GDBusMethodInfo, 1); GPtrArray *inArgs = g_ptr_array_new(); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); g_ptr_array_add(inArgs, NULL); GPtrArray *outArgs = g_ptr_array_new(); appendNewArgForReturn(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); g_ptr_array_add(outArgs, NULL); entry->name = g_strdup(name); entry->in_args = (GDBusArgInfo **)g_ptr_array_free(inArgs, FALSE); entry->out_args = (GDBusArgInfo **)g_ptr_array_free(outArgs, FALSE); entry->ref_count = 1; return entry; } }; /** ===> 9 parameters */ template struct MakeMethodEntry< boost::function > { typedef void (Mptr)(A1, A2, A3, A4, A5, A6, A7, A8, A9); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2, _3, _4, _5, _6, _7, _8, _9); } static const bool asynchronous = DBusResult9::asynchronous; static GDBusMessage *methodFunction(GDBusConnection *conn, GDBusMessage *msg, void *data) { try { std::auto_ptr reply; typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; typename dbus_traits::host_type a5; typename dbus_traits::host_type a6; typename dbus_traits::host_type a7; typename dbus_traits::host_type a8; typename dbus_traits::host_type a9; try { ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3) >> Get(a4) >> Get(a5) >> Get(a6) >> Get(a7) >> Get(a8) >> Get(a9); (*static_cast(data))(a1, a2, a3, a4, a5, a6, a7, a8, a9); if (asynchronous) { return NULL; } reply.reset(g_dbus_message_new_method_reply(msg)); AppendArgs(reply) << Set(a1) << Set(a2) << Set(a3) << Set(a4) << Set(a5) << Set(a6) << Set(a7) << Set(a8) << Set(a9); } catch (...) { return handleException(msg); } return reply.release(); } catch (...) { return handleException(msg); } } static GDBusMethodInfo *make(const char *name) { GDBusMethodInfo *entry = g_new0(GDBusMethodInfo, 1); GPtrArray *inArgs = g_ptr_array_new(); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); g_ptr_array_add(inArgs, NULL); GPtrArray *outArgs = g_ptr_array_new(); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); g_ptr_array_add(outArgs, NULL); entry->name = g_strdup(name); entry->in_args = (GDBusArgInfo **)g_ptr_array_free(inArgs, FALSE); entry->out_args = (GDBusArgInfo **)g_ptr_array_free(outArgs, FALSE); entry->ref_count = 1; return entry; } }; /** 8 arguments, 1 return value */ template struct MakeMethodEntry< boost::function > { typedef R (Mptr)(A1, A2, A3, A4, A5, A6, A7, A8); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2, _3, _4, _5, _6, _7, _8); } static const bool asynchronous = DBusResult8::asynchronous; static GDBusMessage *methodFunction(GDBusConnection *conn, GDBusMessage *msg, void *data) { try { std::auto_ptr reply; typename dbus_traits::host_type r; typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; typename dbus_traits::host_type a5; typename dbus_traits::host_type a6; typename dbus_traits::host_type a7; typename dbus_traits::host_type a8; try { ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3) >> Get(a4) >> Get(a5) >> Get(a6) >> Get(a7) >> Get(a8); r = (*static_cast(data))(a1, a2, a3, a4, a5, a6, a7, a8); if (asynchronous) { return NULL; } reply.reset(g_dbus_message_new_method_reply(msg)); AppendArgs(reply) + r << Set(a1) << Set(a2) << Set(a3) << Set(a4) << Set(a5) << Set(a6) << Set(a7) << Set(a8); } catch (...) { return handleException(msg); } return reply.release(); } catch (...) { return handleException(msg); } } static GDBusMethodInfo *make(const char *name) { GDBusMethodInfo *entry = g_new0(GDBusMethodInfo, 1); GPtrArray *inArgs = g_ptr_array_new(); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); g_ptr_array_add(inArgs, NULL); GPtrArray *outArgs = g_ptr_array_new(); appendNewArgForReturn(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); g_ptr_array_add(outArgs, NULL); entry->name = g_strdup(name); entry->in_args = (GDBusArgInfo **)g_ptr_array_free(inArgs, FALSE); entry->out_args = (GDBusArgInfo **)g_ptr_array_free(outArgs, FALSE); entry->ref_count = 1; return entry; } }; /** ===> 8 parameters */ template struct MakeMethodEntry< boost::function > { typedef void (Mptr)(A1, A2, A3, A4, A5, A6, A7, A8); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2, _3, _4, _5, _6, _7, _8); } static const bool asynchronous = DBusResult8::asynchronous; static GDBusMessage *methodFunction(GDBusConnection *conn, GDBusMessage *msg, void *data) { try { std::auto_ptr reply; typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; typename dbus_traits::host_type a5; typename dbus_traits::host_type a6; typename dbus_traits::host_type a7; typename dbus_traits::host_type a8; try { ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3) >> Get(a4) >> Get(a5) >> Get(a6) >> Get(a7) >> Get(a8); (*static_cast(data))(a1, a2, a3, a4, a5, a6, a7, a8); if (asynchronous) { return NULL; } reply.reset(g_dbus_message_new_method_reply(msg)); AppendArgs(reply) << Set(a1) << Set(a2) << Set(a3) << Set(a4) << Set(a5) << Set(a6) << Set(a7) << Set(a8); } catch (...) { return handleException(msg); } return reply.release(); } catch (...) { return handleException(msg); } } static GDBusMethodInfo *make(const char *name) { GDBusMethodInfo *entry = g_new0(GDBusMethodInfo, 1); GPtrArray *inArgs = g_ptr_array_new(); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); g_ptr_array_add(inArgs, NULL); GPtrArray *outArgs = g_ptr_array_new(); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); g_ptr_array_add(outArgs, NULL); entry->name = g_strdup(name); entry->in_args = (GDBusArgInfo **)g_ptr_array_free(inArgs, FALSE); entry->out_args = (GDBusArgInfo **)g_ptr_array_free(outArgs, FALSE); entry->ref_count = 1; return entry; } }; /** 7 arguments, 1 return value */ template struct MakeMethodEntry< boost::function > { typedef R (Mptr)(A1, A2, A3, A4, A5, A6, A7); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2, _3, _4, _5, _6, _7); } static const bool asynchronous = DBusResult7::asynchronous; static GDBusMessage *methodFunction(GDBusConnection *conn, GDBusMessage *msg, void *data) { try { std::auto_ptr reply; typename dbus_traits::host_type r; typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; typename dbus_traits::host_type a5; typename dbus_traits::host_type a6; typename dbus_traits::host_type a7; try { ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3) >> Get(a4) >> Get(a5) >> Get(a6) >> Get(a7); r = (*static_cast(data))(a1, a2, a3, a4, a5, a6, a7); if (asynchronous) { return NULL; } reply.reset(g_dbus_message_new_method_reply(msg)); AppendArgs(reply) + r << Set(a1) << Set(a2) << Set(a3) << Set(a4) << Set(a5) << Set(a6) << Set(a7); } catch (...) { return handleException(msg); } return reply.release(); } catch (...) { return handleException(msg); } } static GDBusMethodInfo *make(const char *name) { GDBusMethodInfo *entry = g_new0(GDBusMethodInfo, 1); GPtrArray *inArgs = g_ptr_array_new(); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); g_ptr_array_add(inArgs, NULL); GPtrArray *outArgs = g_ptr_array_new(); appendNewArgForReturn(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); g_ptr_array_add(outArgs, NULL); entry->name = g_strdup(name); entry->in_args = (GDBusArgInfo **)g_ptr_array_free(inArgs, FALSE); entry->out_args = (GDBusArgInfo **)g_ptr_array_free(outArgs, FALSE); entry->ref_count = 1; return entry; } }; /** ===> 7 parameters */ template struct MakeMethodEntry< boost::function > { typedef void (Mptr)(A1, A2, A3, A4, A5, A6, A7); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2, _3, _4, _5, _6, _7); } static const bool asynchronous = DBusResult7::asynchronous; static GDBusMessage *methodFunction(GDBusConnection *conn, GDBusMessage *msg, void *data) { try { std::auto_ptr reply; typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; typename dbus_traits::host_type a5; typename dbus_traits::host_type a6; typename dbus_traits::host_type a7; try { ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3) >> Get(a4) >> Get(a5) >> Get(a6) >> Get(a7); (*static_cast(data))(a1, a2, a3, a4, a5, a6, a7); if (asynchronous) { return NULL; } reply.reset(g_dbus_message_new_method_reply(msg)); AppendArgs(reply) << Set(a1) << Set(a2) << Set(a3) << Set(a4) << Set(a5) << Set(a6) << Set(a7); } catch (...) { return handleException(msg); } return reply.release(); } catch (...) { return handleException(msg); } } static GDBusMethodInfo *make(const char *name) { GDBusMethodInfo *entry = g_new0(GDBusMethodInfo, 1); GPtrArray *inArgs = g_ptr_array_new(); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); g_ptr_array_add(inArgs, NULL); GPtrArray *outArgs = g_ptr_array_new(); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); g_ptr_array_add(outArgs, NULL); entry->name = g_strdup(name); entry->in_args = (GDBusArgInfo **)g_ptr_array_free(inArgs, FALSE); entry->out_args = (GDBusArgInfo **)g_ptr_array_free(outArgs, FALSE); entry->ref_count = 1; return entry; } }; /** 6 arguments, 1 return value */ template struct MakeMethodEntry< boost::function > { typedef R (Mptr)(A1, A2, A3, A4, A5, A6); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2, _3, _4, _5, _6); } static const bool asynchronous = DBusResult6::asynchronous; static GDBusMessage *methodFunction(GDBusConnection *conn, GDBusMessage *msg, void *data) { try { std::auto_ptr reply; typename dbus_traits::host_type r; typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; typename dbus_traits::host_type a5; typename dbus_traits::host_type a6; try { ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3) >> Get(a4) >> Get(a5) >> Get(a6); r = (*static_cast(data))(a1, a2, a3, a4, a5, a6); if (asynchronous) { return NULL; } reply.reset(g_dbus_message_new_method_reply(msg)); AppendArgs(reply) + r << Set(a1) << Set(a2) << Set(a3) << Set(a4) << Set(a5) << Set(a6); } catch (...) { return handleException(msg); } return reply.release(); } catch (...) { return handleException(msg); } } static GDBusMethodInfo *make(const char *name) { GDBusMethodInfo *entry = g_new0(GDBusMethodInfo, 1); GPtrArray *inArgs = g_ptr_array_new(); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); g_ptr_array_add(inArgs, NULL); GPtrArray *outArgs = g_ptr_array_new(); appendNewArgForReturn(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); g_ptr_array_add(outArgs, NULL); entry->name = g_strdup(name); entry->in_args = (GDBusArgInfo **)g_ptr_array_free(inArgs, FALSE); entry->out_args = (GDBusArgInfo **)g_ptr_array_free(outArgs, FALSE); entry->ref_count = 1; return entry; } }; /** ===> 6 parameters */ template struct MakeMethodEntry< boost::function > { typedef void (Mptr)(A1, A2, A3, A4, A5, A6); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2, _3, _4, _5, _6); } static const bool asynchronous = DBusResult6::asynchronous; static GDBusMessage *methodFunction(GDBusConnection *conn, GDBusMessage *msg, void *data) { try { std::auto_ptr reply; typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; typename dbus_traits::host_type a5; typename dbus_traits::host_type a6; try { ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3) >> Get(a4) >> Get(a5) >> Get(a6); (*static_cast(data))(a1, a2, a3, a4, a5, a6); if (asynchronous) { return NULL; } reply.reset(g_dbus_message_new_method_reply(msg)); AppendArgs(reply) << Set(a1) << Set(a2) << Set(a3) << Set(a4) << Set(a5) << Set(a6); } catch (...) { return handleException(msg); } return reply.release(); } catch (...) { return handleException(msg); } } static GDBusMethodInfo *make(const char *name) { GDBusMethodInfo *entry = g_new0(GDBusMethodInfo, 1); GPtrArray *inArgs = g_ptr_array_new(); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); g_ptr_array_add(inArgs, NULL); GPtrArray *outArgs = g_ptr_array_new(); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); g_ptr_array_add(outArgs, NULL); entry->name = g_strdup(name); entry->in_args = (GDBusArgInfo **)g_ptr_array_free(inArgs, FALSE); entry->out_args = (GDBusArgInfo **)g_ptr_array_free(outArgs, FALSE); entry->ref_count = 1; return entry; } }; /** 5 arguments, 1 return value */ template struct MakeMethodEntry< boost::function > { typedef R (Mptr)(A1, A2, A3, A4, A5); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2, _3, _4, _5); } static const bool asynchronous = DBusResult5::asynchronous; static GDBusMessage *methodFunction(GDBusConnection *conn, GDBusMessage *msg, void *data) { try { std::auto_ptr reply; typename dbus_traits::host_type r; typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; typename dbus_traits::host_type a5; try { ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3) >> Get(a4) >> Get(a5); r = (*static_cast(data))(a1, a2, a3, a4, a5); if (asynchronous) { return NULL; } reply.reset(g_dbus_message_new_method_reply(msg)); AppendArgs(reply) + r << Set(a1) << Set(a2) << Set(a3) << Set(a4) << Set(a5); } catch (...) { return handleException(msg); } return reply.release(); } catch (...) { return handleException(msg); } } static GDBusMethodInfo *make(const char *name) { GDBusMethodInfo *entry = g_new0(GDBusMethodInfo, 1); GPtrArray *inArgs = g_ptr_array_new(); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); g_ptr_array_add(inArgs, NULL); GPtrArray *outArgs = g_ptr_array_new(); appendNewArgForReturn(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); g_ptr_array_add(outArgs, NULL); entry->name = g_strdup(name); entry->in_args = (GDBusArgInfo **)g_ptr_array_free(inArgs, FALSE); entry->out_args = (GDBusArgInfo **)g_ptr_array_free(outArgs, FALSE); entry->ref_count = 1; return entry; } }; /** ===> 5 parameters */ template struct MakeMethodEntry< boost::function > { typedef void (Mptr)(A1, A2, A3, A4, A5); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2, _3, _4, _5); } static const bool asynchronous = DBusResult5::asynchronous; static GDBusMessage *methodFunction(GDBusConnection *conn, GDBusMessage *msg, void *data) { try { std::auto_ptr reply; typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; typename dbus_traits::host_type a5; try { ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3) >> Get(a4) >> Get(a5); (*static_cast(data))(a1, a2, a3, a4, a5); if (asynchronous) { return NULL; } reply.reset(g_dbus_message_new_method_reply(msg)); AppendArgs(reply) << Set(a1) << Set(a2) << Set(a3) << Set(a4) << Set(a5); } catch (...) { return handleException(msg); } return reply.release(); } catch (...) { return handleException(msg); } } static GDBusMethodInfo *make(const char *name) { GDBusMethodInfo *entry = g_new0(GDBusMethodInfo, 1); GPtrArray *inArgs = g_ptr_array_new(); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); g_ptr_array_add(inArgs, NULL); GPtrArray *outArgs = g_ptr_array_new(); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); g_ptr_array_add(outArgs, NULL); entry->name = g_strdup(name); entry->in_args = (GDBusArgInfo **)g_ptr_array_free(inArgs, FALSE); entry->out_args = (GDBusArgInfo **)g_ptr_array_free(outArgs, FALSE); entry->ref_count = 1; return entry; } }; /** 4 arguments, 1 return value */ template struct MakeMethodEntry< boost::function > { typedef R (Mptr)(A1, A2, A3, A4); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2, _3, _4); } static const bool asynchronous = DBusResult4::asynchronous; static GDBusMessage *methodFunction(GDBusConnection *conn, GDBusMessage *msg, void *data) { try { std::auto_ptr reply; typename dbus_traits::host_type r; typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; try { ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3) >> Get(a4); r = (*static_cast(data))(a1, a2, a3, a4); if (asynchronous) { return NULL; } reply.reset(g_dbus_message_new_method_reply(msg)); AppendArgs(reply) + r << Set(a1) << Set(a2) << Set(a3) << Set(a4); } catch (...) { return handleException(msg); } return reply.release(); } catch (...) { return handleException(msg); } } static GDBusMethodInfo *make(const char *name) { GDBusMethodInfo *entry = g_new0(GDBusMethodInfo, 1); GPtrArray *inArgs = g_ptr_array_new(); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); g_ptr_array_add(inArgs, NULL); GPtrArray *outArgs = g_ptr_array_new(); appendNewArgForReturn(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); g_ptr_array_add(outArgs, NULL); entry->name = g_strdup(name); entry->in_args = (GDBusArgInfo **)g_ptr_array_free(inArgs, FALSE); entry->out_args = (GDBusArgInfo **)g_ptr_array_free(outArgs, FALSE); entry->ref_count = 1; return entry; } }; /** ===> 4 parameters */ template struct MakeMethodEntry< boost::function > { typedef void (Mptr)(A1, A2, A3, A4); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2, _3, _4); } static const bool asynchronous = DBusResult4::asynchronous; static GDBusMessage *methodFunction(GDBusConnection *conn, GDBusMessage *msg, void *data) { try { std::auto_ptr reply; typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; try { ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3) >> Get(a4); (*static_cast(data))(a1, a2, a3, a4); if (asynchronous) { return NULL; } reply.reset(g_dbus_message_new_method_reply(msg)); AppendArgs(reply) << Set(a1) << Set(a2) << Set(a3) << Set(a4); } catch (...) { return handleException(msg); } return reply.release(); } catch (...) { return handleException(msg); } } static GDBusMethodInfo *make(const char *name) { GDBusMethodInfo *entry = g_new0(GDBusMethodInfo, 1); GPtrArray *inArgs = g_ptr_array_new(); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); g_ptr_array_add(inArgs, NULL); GPtrArray *outArgs = g_ptr_array_new(); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); g_ptr_array_add(outArgs, NULL); entry->name = g_strdup(name); entry->in_args = (GDBusArgInfo **)g_ptr_array_free(inArgs, FALSE); entry->out_args = (GDBusArgInfo **)g_ptr_array_free(outArgs, FALSE); entry->ref_count = 1; return entry; } }; /** 3 arguments, 1 return value */ template struct MakeMethodEntry< boost::function > { typedef R (Mptr)(A1, A2, A3); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2, _3); } static const bool asynchronous = DBusResult3::asynchronous; static GDBusMessage *methodFunction(GDBusConnection *conn, GDBusMessage *msg, void *data) { try { std::auto_ptr reply; typename dbus_traits::host_type r; typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; try { ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3); r = (*static_cast(data))(a1, a2, a3); if (asynchronous) { return NULL; } reply.reset(g_dbus_message_new_method_reply(msg)); AppendArgs(reply) + r << Set(a1) << Set(a2) << Set(a3); } catch (...) { return handleException(msg); } return reply.release(); } catch (...) { return handleException(msg); } } static GDBusMethodInfo *make(const char *name) { GDBusMethodInfo *entry = g_new0(GDBusMethodInfo, 1); GPtrArray *inArgs = g_ptr_array_new(); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); g_ptr_array_add(inArgs, NULL); GPtrArray *outArgs = g_ptr_array_new(); appendNewArgForReturn(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); g_ptr_array_add(outArgs, NULL); entry->name = g_strdup(name); entry->in_args = (GDBusArgInfo **)g_ptr_array_free(inArgs, FALSE); entry->out_args = (GDBusArgInfo **)g_ptr_array_free(outArgs, FALSE); entry->ref_count = 1; return entry; } }; /** ===> 3 parameters */ template struct MakeMethodEntry< boost::function > { typedef void (Mptr)(A1, A2, A3); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2, _3); } static const bool asynchronous = DBusResult3::asynchronous; static GDBusMessage *methodFunction(GDBusConnection *conn, GDBusMessage *msg, void *data) { try { std::auto_ptr reply; typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; try { ExtractArgs(conn, msg) >> Get(a1) >> Get(a2) >> Get(a3); (*static_cast(data))(a1, a2, a3); if (asynchronous) { return NULL; } reply.reset(g_dbus_message_new_method_reply(msg)); AppendArgs(reply) << Set(a1) << Set(a2) << Set(a3); } catch (...) { return handleException(msg); } return reply.release(); } catch (...) { return handleException(msg); } } static GDBusMethodInfo *make(const char *name) { GDBusMethodInfo *entry = g_new0(GDBusMethodInfo, 1); GPtrArray *inArgs = g_ptr_array_new(); appendNewArg(inArgs); appendNewArg(inArgs); appendNewArg(inArgs); g_ptr_array_add(inArgs, NULL); GPtrArray *outArgs = g_ptr_array_new(); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); g_ptr_array_add(outArgs, NULL); entry->name = g_strdup(name); entry->in_args = (GDBusArgInfo **)g_ptr_array_free(inArgs, FALSE); entry->out_args = (GDBusArgInfo **)g_ptr_array_free(outArgs, FALSE); entry->ref_count = 1; return entry; } }; /** 2 arguments, 1 return value */ template struct MakeMethodEntry< boost::function > { typedef R (Mptr)(A1, A2); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2); } static const bool asynchronous = DBusResult2::asynchronous; static GDBusMessage *methodFunction(GDBusConnection *conn, GDBusMessage *msg, void *data) { try { std::auto_ptr reply; typename dbus_traits::host_type r; typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; try { ExtractArgs(conn, msg) >> Get(a1) >> Get(a2); r = (*static_cast(data))(a1, a2); if (asynchronous) { return NULL; } reply.reset(g_dbus_message_new_method_reply(msg)); AppendArgs(reply) + r << Set(a1) << Set(a2); } catch (...) { return handleException(msg); } return reply.release(); } catch (...) { return handleException(msg); } } static GDBusMethodInfo *make(const char *name) { GDBusMethodInfo *entry = g_new0(GDBusMethodInfo, 1); GPtrArray *inArgs = g_ptr_array_new(); appendNewArg(inArgs); appendNewArg(inArgs); g_ptr_array_add(inArgs, NULL); GPtrArray *outArgs = g_ptr_array_new(); appendNewArgForReturn(outArgs); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); g_ptr_array_add(outArgs, NULL); entry->name = g_strdup(name); entry->in_args = (GDBusArgInfo **)g_ptr_array_free(inArgs, FALSE); entry->out_args = (GDBusArgInfo **)g_ptr_array_free(outArgs, FALSE); entry->ref_count = 1; return entry; } }; /** ===> 2 parameters */ template struct MakeMethodEntry< boost::function > { typedef void (Mptr)(A1, A2); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1, _2); } static const bool asynchronous = DBusResult2::asynchronous; static GDBusMessage *methodFunction(GDBusConnection *conn, GDBusMessage *msg, void *data) { try { std::auto_ptr reply; typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; try { ExtractArgs(conn, msg) >> Get(a1) >> Get(a2); (*static_cast(data))(a1, a2); if (asynchronous) { return NULL; } reply.reset(g_dbus_message_new_method_reply(msg)); AppendArgs(reply) << Set(a1) << Set(a2); } catch (...) { return handleException(msg); } return reply.release(); } catch (...) { return handleException(msg); } } static GDBusMethodInfo *make(const char *name) { GDBusMethodInfo *entry = g_new0(GDBusMethodInfo, 1); GPtrArray *inArgs = g_ptr_array_new(); appendNewArg(inArgs); appendNewArg(inArgs); g_ptr_array_add(inArgs, NULL); GPtrArray *outArgs = g_ptr_array_new(); appendNewArgForReply(outArgs); appendNewArgForReply(outArgs); g_ptr_array_add(outArgs, NULL); entry->name = g_strdup(name); entry->in_args = (GDBusArgInfo **)g_ptr_array_free(inArgs, FALSE); entry->out_args = (GDBusArgInfo **)g_ptr_array_free(outArgs, FALSE); entry->ref_count = 1; return entry; } }; /** 1 argument, 1 return value */ template struct MakeMethodEntry< boost::function > { typedef R (Mptr)(A1); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1); } static const bool asynchronous = DBusResult1::asynchronous; static GDBusMessage *methodFunction(GDBusConnection *conn, GDBusMessage *msg, void *data) { try { std::auto_ptr reply; typename dbus_traits::host_type r; typename dbus_traits::host_type a1; try { ExtractArgs(conn, msg) >> Get(a1); r = (*static_cast(data))(a1); if (asynchronous) { return NULL; } reply.reset(g_dbus_message_new_method_reply(msg)); AppendArgs(reply) + r << Set(a1); } catch (...) { return handleException(msg); } return reply.release(); } catch (...) { return handleException(msg); } } static GDBusMethodInfo *make(const char *name) { GDBusMethodInfo *entry = g_new0(GDBusMethodInfo, 1); GPtrArray *inArgs = g_ptr_array_new(); appendNewArg(inArgs); g_ptr_array_add(inArgs, NULL); GPtrArray *outArgs = g_ptr_array_new(); appendNewArgForReturn(outArgs); appendNewArgForReply(outArgs); g_ptr_array_add(outArgs, NULL); entry->name = g_strdup(name); entry->in_args = (GDBusArgInfo **)g_ptr_array_free(inArgs, FALSE); entry->out_args = (GDBusArgInfo **)g_ptr_array_free(outArgs, FALSE); entry->ref_count = 1; return entry; } }; /** ===> 1 parameter */ template struct MakeMethodEntry< boost::function > { typedef void (Mptr)(A1); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance, _1); } static const bool asynchronous = DBusResult1::asynchronous; static GDBusMessage *methodFunction(GDBusConnection *conn, GDBusMessage *msg, void *data) { try { std::auto_ptr reply; typename dbus_traits::host_type a1; try { ExtractArgs(conn, msg) >> Get(a1); (*static_cast(data))(a1); if (asynchronous) { return NULL; } reply.reset(g_dbus_message_new_method_reply(msg)); AppendArgs(reply) << Set(a1); } catch (...) { return handleException(msg); } return reply.release(); } catch (...) { return handleException(msg); } } static GDBusMethodInfo *make(const char *name) { GDBusMethodInfo *entry = g_new0(GDBusMethodInfo, 1); GPtrArray *inArgs = g_ptr_array_new(); appendNewArg(inArgs); g_ptr_array_add(inArgs, NULL); GPtrArray *outArgs = g_ptr_array_new(); appendNewArgForReply(outArgs); g_ptr_array_add(outArgs, NULL); entry->name = g_strdup(name); entry->in_args = (GDBusArgInfo **)g_ptr_array_free(inArgs, FALSE); entry->out_args = (GDBusArgInfo **)g_ptr_array_free(outArgs, FALSE); entry->ref_count = 1; return entry; } }; /** 0 arguments, 1 return value */ template struct MakeMethodEntry< boost::function > { typedef R (Mptr)(); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance); } static GDBusMessage *methodFunction(GDBusConnection *conn, GDBusMessage *msg, void *data) { try { std::auto_ptr reply; typename dbus_traits::host_type r; try { r = (*static_cast(data))(); reply.reset(g_dbus_message_new_method_reply(msg)); AppendArgs(reply) + r; } catch (...) { return handleException(msg); } return reply.release(); } catch (...) { return handleException(msg); } } static GDBusMethodInfo *make(const char *name) { GDBusMethodInfo *entry = g_new0(GDBusMethodInfo, 1); GPtrArray *outArgs = g_ptr_array_new(); appendNewArgForReturn(outArgs); g_ptr_array_add(outArgs, NULL); entry->name = g_strdup(name); entry->in_args = NULL; entry->out_args = (GDBusArgInfo **)g_ptr_array_free(outArgs, FALSE); entry->ref_count = 1; return entry; } }; /** ===> 0 parameter */ template <> struct MakeMethodEntry< boost::function > { typedef void (Mptr)(); typedef boost::function M; template static M boostptr(Mptr C::*method, I instance) { return boost::bind(method, instance); } static GDBusMessage *methodFunction(GDBusConnection *conn, GDBusMessage *msg, void *data) { try { (*static_cast(data))(); GDBusMessage *reply = g_dbus_message_new_method_reply(msg); return reply; } catch (...) { return handleException(msg); } } static GDBusMethodInfo *make(const char *name) { GDBusMethodInfo *entry = g_new0(GDBusMethodInfo, 1); entry->name = g_strdup(name); entry->in_args = NULL; entry->out_args = NULL; entry->ref_count = 1; return entry; } }; template struct TraitsBase { typedef Cb Callback_t; typedef Ret Return_t; struct CallbackData { //only keep connection, for DBusClientCall instance is absent when 'dbus client call' returns //suppose connection is available in the callback handler const DBusConnectionPtr m_conn; Callback_t m_callback; CallbackData(const DBusConnectionPtr &conn, const Callback_t &callback) : m_conn(conn), m_callback(callback) {} }; }; struct VoidReturn {}; struct VoidTraits : public TraitsBase, VoidReturn> { typedef TraitsBase, VoidReturn> base; typedef base::Callback_t Callback_t; typedef base::Return_t Return_t; static Return_t demarshal(DBusMessagePtr &/*reply*/, const DBusConnectionPtr &/*conn*/) { return Return_t(); } static void handleMessage(DBusMessagePtr &reply, base::CallbackData *data, GError* error) { std::string error_msg; //unmarshal the return results and call user callback if (error != NULL || g_dbus_message_to_gerror(reply.get(), &error)) { if (boost::starts_with(error->message, "GDBus.Error:")) { error_msg = error->message + 12; } else { error_msg = error->message; } } if (data->m_callback) { data->m_callback(error_msg); } delete data; if (error != NULL) { g_error_free (error); } } }; template struct Ret1Traits : public TraitsBase, R1> { typedef TraitsBase, R1> base; typedef typename base::Callback_t Callback_t; typedef typename base::Return_t Return_t; static Return_t demarshal(DBusMessagePtr &reply, const DBusConnectionPtr &conn) { typename dbus_traits::host_type r; ExtractResponse(conn.get(), reply.get()) >> Get(r); return r; } static void handleMessage(DBusMessagePtr &reply, typename base::CallbackData *data, GError* error) { typename dbus_traits::host_type r; std::string error_msg; if (error == NULL && !g_dbus_message_to_gerror(reply.get(), &error)) { ExtractResponse(data->m_conn.get(), reply.get()) >> Get(r); } else if (boost::starts_with(error->message, "GDBus.Error:")) { error_msg = error->message + 12; } else { error_msg = error->message; } //unmarshal the return results and call user callback if (data->m_callback) { data->m_callback(r, error_msg); } delete data; // cppcheck-suppress nullPointer // Looks invalid: cppcheck warning: nullPointer - Possible null pointer dereference: error - otherwise it is redundant to check it against null. if (error != NULL) { g_error_free (error); } } }; template struct Ret2Traits : public TraitsBase, std::pair > { typedef TraitsBase, std::pair > base; typedef typename base::Callback_t Callback_t; typedef typename base::Return_t Return_t; static Return_t demarshal(DBusMessagePtr &reply, const DBusConnectionPtr &conn) { Return_t r; ExtractResponse(conn.get(), reply.get()) >> Get(r.first) >> Get(r.second); return r; } static void handleMessage(DBusMessagePtr &reply, typename base::CallbackData *data, GError* error) { typename dbus_traits::host_type r1; typename dbus_traits::host_type r2; std::string error_msg; if (error == NULL && !g_dbus_message_to_gerror(reply.get(), &error)) { ExtractResponse(data->m_conn.get(), reply.get()) >> Get(r1) >> Get(r2); } else if (boost::starts_with(error->message, "GDBus.Error:")) { error_msg = error->message + 12; } else { error_msg = error->message; } //unmarshal the return results and call user callback if (data->m_callback) { data->m_callback(r1, r2, error_msg); } delete data; // cppcheck-suppress nullPointer // Looks invalid: cppcheck warning: nullPointer - Possible null pointer dereference: error - otherwise it is redundant to check it against null. if (error != NULL) { g_error_free (error); } } }; template struct Ret3Traits : public TraitsBase, boost::tuple > { typedef TraitsBase, boost::tuple > base; typedef typename base::Callback_t Callback_t; typedef typename base::Return_t Return_t; static Return_t demarshal(DBusMessagePtr &reply, const DBusConnectionPtr &conn) { Return_t r; ExtractResponse(conn.get(), reply.get()) >> Get(boost::get<0>(r)) >> Get(boost::get<1>(r)) >> Get(boost::get<2>(r)); return r; } static void handleMessage(DBusMessagePtr &reply, typename base::CallbackData *data, GError* error) { typename dbus_traits::host_type r1; typename dbus_traits::host_type r2; typename dbus_traits::host_type r3; std::string error_msg; if (error == NULL && !g_dbus_message_to_gerror(reply.get(), &error)) { ExtractResponse(data->m_conn.get(), reply.get()) >> Get(r1) >> Get(r2) >> Get(r3); } else if (boost::starts_with(error->message, "GDBus.Error:")) { error_msg = error->message + 12; } else { error_msg = error->message; } //unmarshal the return results and call user callback if (data->m_callback) { data->m_callback(r1, r2, r3, error_msg); } delete data; // cppcheck-suppress nullPointer // Looks invalid: cppcheck warning: nullPointer - Possible null pointer dereference: error - otherwise it is redundant to check it against null. if (error != NULL) { g_error_free (error); } } }; template class DBusClientCall { public: typedef typename CallTraits::Callback_t Callback_t; typedef typename CallTraits::Return_t Return_t; typedef typename CallTraits::base::CallbackData CallbackData; protected: const std::string m_destination; const std::string m_path; const std::string m_interface; const std::string m_method; const DBusConnectionPtr m_conn; static void dbusCallback (GObject *src_obj, GAsyncResult *res, void *user_data) throw () { try { CallbackData *data = static_cast(user_data); GError *err = NULL; DBusMessagePtr reply(g_dbus_connection_send_message_with_reply_finish(data->m_conn.get(), res, &err)); CallTraits::handleMessage(reply, data, err); } catch (const std::exception &ex) { g_error("unexpected exception caught in dbusCallback(): %s", ex.what()); } catch (...) { g_error("unexpected exception caught in dbusCallback()"); } } void prepare(DBusMessagePtr &msg) const { // Constructor steals reference, reset() doesn't! // Therefore use constructor+copy instead of reset(). msg = DBusMessagePtr(g_dbus_message_new_method_call(m_destination.c_str(), m_path.c_str(), m_interface.c_str(), m_method.c_str())); if (!msg) { throw std::runtime_error("g_dbus_message_new_method_call() failed"); } } void send(DBusMessagePtr &msg, const Callback_t &callback) const { CallbackData *data = new CallbackData(m_conn, callback); g_dbus_connection_send_message_with_reply(m_conn.get(), msg.get(), G_DBUS_SEND_MESSAGE_FLAGS_NONE, G_MAXINT, // no timeout NULL, NULL, dbusCallback, data); } Return_t sendAndReturn(DBusMessagePtr &msg) const { GError* error = NULL; DBusMessagePtr reply(g_dbus_connection_send_message_with_reply_sync(m_conn.get(), msg.get(), G_DBUS_SEND_MESSAGE_FLAGS_NONE, G_MAXINT, // no timeout NULL, NULL, &error)); if (error || g_dbus_message_to_gerror(reply.get(), &error)) { DBusErrorCXX(error).throwFailure(m_method); } return CallTraits::demarshal(reply, m_conn); } public: DBusClientCall(const DBusRemoteObject &object, const std::string &method) :m_destination (object.getDestination()), m_path (object.getPath()), m_interface (object.getInterface()), m_method (method), m_conn (object.getConnection()) { } GDBusConnection *getConnection() { return m_conn.get(); } std::string getMethod() const { return m_method; } Return_t operator () () { DBusMessagePtr msg; prepare(msg); return sendAndReturn(msg); } void start(const Callback_t &callback) const { DBusMessagePtr msg; prepare(msg); send(msg, callback); } template Return_t operator () (const A1 &a1) const { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1; return sendAndReturn(msg); } template void start(const A1 &a1, const Callback_t &callback) const { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1; send(msg, callback); } template Return_t operator () (const A1 &a1, const A2 &a2) const { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2; return sendAndReturn(msg); } template void start(const A1 &a1, const A2 &a2, const Callback_t &callback) const { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2; send(msg, callback); } template void operator () (const A1 &a1, const A2 &a2, const A3 &a3) const { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3; sendAndReturn(msg); } template void start(const A1 &a1, const A2 &a2, const A3 &a3, const Callback_t &callback) const { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3; send(msg, callback); } template void operator () (const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) const { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3 << a4; sendAndReturn(msg); } template void start(const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const Callback_t &callback) const { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3 << a4; send(msg, callback); } template void operator () (const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) const { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3 << a4 << a5; sendAndReturn(msg); } template void start(const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const Callback_t &callback) const { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3 << a4 << a5; send(msg, callback); } template void operator () (const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) const { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3 << a4 << a5 << a6; sendAndReturn(msg); } template void start(const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const Callback_t &callback) const { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3 << a4 << a5 << a6; send(msg, callback); } template void operator () (const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) const { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3 << a4 << a5 << a6 << a7; sendAndReturn(msg); } template void start(const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const Callback_t &callback) const { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3 << a4 << a5 << a6 << a7; send(msg, callback); } template void operator () (const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) const { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3 << a4 << a5 << a6 << a7 << a8; sendAndReturn(msg); } template void start(const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const Callback_t &callback) const { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3 << a4 << a5 << a6 << a7 << a8; send(msg, callback); } template void operator () (const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) const { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3 << a4 << a5 << a6 << a7 << a8 << a9; sendAndReturn(msg); } template void start(const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9, const Callback_t &callback) const { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3 << a4 << a5 << a6 << a7 << a8 << a9; send(msg, callback); } template void operator () (const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9, const A10 &a10) const { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3 << a4 << a5 << a6 << a7 << a8 << a9 << a10; sendAndReturn(msg); } template void start(const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9, const A10 &a10, const Callback_t &callback) const { DBusMessagePtr msg; prepare(msg); AppendRetvals(msg) << a1 << a2 << a3 << a4 << a5 << a6 << a7 << a8 << a9 << a10; send(msg, callback); } }; /* * A DBus Client Call object handling zero or more parameter and * zero return value. */ class DBusClientCall0 : public DBusClientCall { public: DBusClientCall0 (const DBusRemoteObject &object, const std::string &method) : DBusClientCall(object, method) { } }; /** 1 return value and 0 or more parameters */ template class DBusClientCall1 : public DBusClientCall > { public: DBusClientCall1 (const DBusRemoteObject &object, const std::string &method) : DBusClientCall >(object, method) { } }; /** 2 return value and 0 or more parameters */ template class DBusClientCall2 : public DBusClientCall > { public: DBusClientCall2 (const DBusRemoteObject &object, const std::string &method) : DBusClientCall >(object, method) { } }; /** 3 return value and 0 or more parameters */ template class DBusClientCall3 : public DBusClientCall > { public: DBusClientCall3 (const DBusRemoteObject &object, const std::string &method) : DBusClientCall >(object, method) { } }; /** * Describes which signals are meant to be received by the callback in * a SignalWatch. Only available when using GDBus C++ for GIO (this * code here). GDBus libdbus only supports passing a DBusRemoteObject * to the SignalWatch constructors, which does a match based on object * path, interface name, and signal name. * * Traditionally, all three strings had to be given. Now if any of * them is empty, it is excluded from the filter entirely (any string * matches). * * Using this class adds the possibility to do a path prefix match or, * in the future, other kind of matches. * * TODO: add support for filtering by sender. Not doing so leads to * a situation where a malicious local process can fake signals. * * Avoid situations where different SignalWatches use exactly the same * filter. Creating both watches will work, but when one is destroyed * it might happen that the process stops receiving signals from the * D-Bus daemon because the watch for that signal filter was removed * from the connection (neither D-Bus spec nor D-Bus GIO guarantee * that the match is ref counted). */ class SignalFilter : public DBusRemoteObject { public: enum Flags { SIGNAL_FILTER_NONE, /** * Normally, a path must match completely. With this flag set, * any signal which has the given path as prefix * matches. * * The path filter must not end in a slash. * * Example: "/foo/bar/xyz" matches "/foo/bar" only if * SIGNAL_FILTER_PATH_PREFIX is set. "/foo/barxyz" does not * match it in any case. * */ SIGNAL_FILTER_PATH_PREFIX = 1<<0 }; /** * Match based on object path and interface as stored in object * and based on signal name as passed separately. Does a full * match of all unless a a string is empty, in which case * that criteria is ignored. */ SignalFilter(const DBusRemoteObject &object, const std::string &signal) : DBusRemoteObject(object), m_signal(signal), m_flags(SIGNAL_FILTER_NONE) {} /** * Full control over filtering. */ SignalFilter(const DBusConnectionPtr &conn, const std::string &path, const std::string &interface, const std::string &signal, Flags flags) : DBusRemoteObject(conn, path, interface, ""), m_signal(signal), m_flags(flags) {} const char *getSignal() const { return m_signal.c_str(); } Flags getFlags() const { return Flags(m_flags); } /** * Check that current signal matches the filter. Necessary * because GIO D-Bus does not know about "path_namespace" and * because G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE seems to disable all * signal filtering by GIO. */ bool matches(const ExtractArgs &context) const { return (m_interface.empty() || m_interface == context.m_interface) && (m_signal.empty() || m_signal == context.m_signal) && (m_path.empty() || ((m_flags & SIGNAL_FILTER_PATH_PREFIX) ? (strlen(context.m_path) > m_path.size() && !m_path.compare(0, m_path.size(), context.m_path, m_path.size()) && context.m_path[m_path.size()] == '/') : m_path == context.m_path)); } private: std::string m_signal; Flags m_flags; }; /** * Helper class for builting a match rule. */ class Criteria : public std::list { public: void add(const char *keyword, const char *value) { if (value && value[0]) { std::string buffer; buffer.reserve(strlen(keyword) + strlen(value) + 3); buffer += keyword; buffer += '='; buffer += '\''; buffer += value; buffer += '\''; push_back(buffer); } } std::string createMatchRule() const { return boost::join(*this, ","); } }; /** * Common functionality of all SignalWatch* classes. * @param T boost::function with the right signature */ template class SignalWatch : public SignalFilter { public: SignalWatch(const DBusRemoteObject &object, const std::string &signal, bool = true) : SignalFilter(object, signal), m_tag(0), m_manualMatch(false) { } SignalWatch(const SignalFilter &filter) : SignalFilter(filter), m_tag(0), m_manualMatch(false) { } ~SignalWatch() { try { if (m_tag) { GDBusConnection *connection = getConnection(); if (connection) { g_dbus_connection_signal_unsubscribe(connection, m_tag); } } if (m_manualMatch) { DBusClientCall0(GDBusCXX::DBusRemoteObject(getConnection(), "/org/freedesktop/DBus", "org.freedesktop.DBus", "org.freedesktop.DBus"), "RemoveMatch")(m_matchRule); } } catch (...) { // TODO (?): log error } } typedef T Callback_t; const Callback_t &getCallback() const { return m_callback; } protected: guint m_tag; T m_callback; bool m_manualMatch; std::string m_matchRule; void activateInternal(const Callback_t &callback, GDBusSignalCallback cb) { m_callback = callback; m_tag = g_dbus_connection_signal_subscribe(getConnection(), NULL, getInterface()[0] ? getInterface() : NULL, getSignal()[0] ? getSignal() : NULL, (!(getFlags() & SIGNAL_FILTER_PATH_PREFIX) && getPath()[0]) ? getPath() : NULL, NULL, (getFlags() & SIGNAL_FILTER_PATH_PREFIX) ? G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE : G_DBUS_SIGNAL_FLAGS_NONE, cb, this, NULL); if (!m_tag) { throw std::runtime_error(std::string("activating signal failed: ") + "path " + getPath() + " interface " + getInterface() + " member " + getSignal()); } if (getFlags() & SignalFilter::SIGNAL_FILTER_PATH_PREFIX) { // Need to set up match rules manually. Criteria criteria; criteria.push_back("type='signal'"); criteria.add("interface", getInterface()); criteria.add("member", getSignal()); criteria.add("path_namespace", getPath()); m_matchRule = criteria.createMatchRule(); DBusClientCall0(GDBusCXX::DBusRemoteObject(getConnection(), "/org/freedesktop/DBus", "org.freedesktop.DBus", "org.freedesktop.DBus"), "AddMatch")(m_matchRule); m_manualMatch = true; } } }; class SignalWatch0 : public SignalWatch< boost::function > { typedef boost::function Callback_t; public: SignalWatch0(const DBusRemoteObject &object, const std::string &signal, bool = true) : SignalWatch(object, signal) { } SignalWatch0(const SignalFilter &filter) : SignalWatch(filter) {} static void internalCallback(GDBusConnection *conn, const gchar *sender, const gchar *path, const gchar *interface, const gchar *signal, GVariant *params, gpointer data) throw () { try { SignalWatch *watch = static_cast< SignalWatch *>(data); ExtractArgs context(conn, sender, path, interface, signal); if (!watch->matches(context)) { return; } const Callback_t &cb = watch->getCallback(); cb(); } catch (const std::exception &ex) { g_error("unexpected exception caught in internalCallback(): %s", ex.what()); } catch (...) { g_error("unexpected exception caught in internalCallback()"); } } void activate(const Callback_t &callback) { SignalWatch< boost::function >::activateInternal(callback, internalCallback); } }; template class SignalWatch1 : public SignalWatch< boost::function > { typedef boost::function Callback_t; public: SignalWatch1(const DBusRemoteObject &object, const std::string &signal, bool = true) : SignalWatch(object, signal) { } SignalWatch1(const SignalFilter &filter) : SignalWatch(filter) {} static void internalCallback(GDBusConnection *conn, const gchar *sender, const gchar *path, const gchar *interface, const gchar *signal, GVariant *params, gpointer data) throw() { try { SignalWatch *watch = static_cast< SignalWatch *>(data); ExtractArgs context(conn, sender, path, interface, signal); if (!watch->matches(context)) { return; } typename dbus_traits::host_type a1; GVariantIter iter; g_variant_iter_init(&iter, params); dbus_traits::get(context, iter, a1); const Callback_t &cb = watch->getCallback(); cb(a1); } catch (const std::exception &ex) { g_error("unexpected exception caught in internalCallback(): %s", ex.what()); } catch (...) { g_error("unexpected exception caught in internalCallback()"); } } void activate(const Callback_t &callback) { SignalWatch< boost::function >::activateInternal(callback, internalCallback); } }; template class SignalWatch2 : public SignalWatch< boost::function > { typedef boost::function Callback_t; public: SignalWatch2(const DBusRemoteObject &object, const std::string &signal, bool = true) : SignalWatch(object, signal) { } SignalWatch2(const SignalFilter &filter) : SignalWatch(filter) {} static void internalCallback(GDBusConnection *conn, const gchar *sender, const gchar *path, const gchar *interface, const gchar *signal, GVariant *params, gpointer data) throw () { try { SignalWatch *watch = static_cast< SignalWatch *>(data); ExtractArgs context(conn, sender, path, interface, signal); if (!watch->matches(context)) { return; } typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; GVariantIter iter; g_variant_iter_init(&iter, params); dbus_traits::get(context, iter, a1); dbus_traits::get(context, iter, a2); const Callback_t &cb = watch->getCallback(); cb(a1, a2); } catch (const std::exception &ex) { g_error("unexpected exception caught in internalCallback(): %s", ex.what()); } catch (...) { g_error("unexpected exception caught in internalCallback()"); } } void activate(const Callback_t &callback) { SignalWatch< boost::function >::activateInternal(callback, internalCallback); } }; template class SignalWatch3 : public SignalWatch< boost::function > { typedef boost::function Callback_t; public: SignalWatch3(const DBusRemoteObject &object, const std::string &signal, bool = true) : SignalWatch(object, signal) { } SignalWatch3(const SignalFilter &filter) : SignalWatch(filter) {} static void internalCallback(GDBusConnection *conn, const gchar *sender, const gchar *path, const gchar *interface, const gchar *signal, GVariant *params, gpointer data) throw () { try { SignalWatch *watch = static_cast< SignalWatch *>(data); ExtractArgs context(conn, sender, path, interface, signal); if (!watch->matches(context)) { return; } typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; GVariantIter iter; g_variant_iter_init(&iter, params); dbus_traits::get(context, iter, a1); dbus_traits::get(context, iter, a2); dbus_traits::get(context, iter, a3); const Callback_t &cb = watch->getCallback(); cb(a1, a2, a3); } catch (const std::exception &ex) { g_error("unexpected exception caught in internalCallback(): %s", ex.what()); } catch (...) { g_error("unexpected exception caught in internalCallback()"); } } void activate(const Callback_t &callback) { SignalWatch< boost::function >::activateInternal(callback, internalCallback); } }; template class SignalWatch4 : public SignalWatch< boost::function > { typedef boost::function Callback_t; public: SignalWatch4(const DBusRemoteObject &object, const std::string &signal, bool = true) : SignalWatch(object, signal) { } SignalWatch4(const SignalFilter &filter) : SignalWatch(filter) {} static void internalCallback(GDBusConnection *conn, const gchar *sender, const gchar *path, const gchar *interface, const gchar *signal, GVariant *params, gpointer data) throw () { try { SignalWatch *watch = static_cast< SignalWatch *>(data); ExtractArgs context(conn, sender, path, interface, signal); if (!watch->matches(context)) { return; } typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; GVariantIter iter; g_variant_iter_init(&iter, params); dbus_traits::get(context, iter, a1); dbus_traits::get(context, iter, a2); dbus_traits::get(context, iter, a3); dbus_traits::get(context, iter, a4); const Callback_t &cb = watch->getCallback(); cb(a1, a2, a3, a4); } catch (const std::exception &ex) { g_error("unexpected exception caught in internalCallback(): %s", ex.what()); } catch (...) { g_error("unexpected exception caught in internalCallback()"); } } void activate(const Callback_t &callback) { SignalWatch< boost::function >::activateInternal(callback, internalCallback); } }; template class SignalWatch5 : public SignalWatch< boost::function > { typedef boost::function Callback_t; public: SignalWatch5(const DBusRemoteObject &object, const std::string &signal, bool = true) : SignalWatch(object, signal) { } SignalWatch5(const SignalFilter &filter) : SignalWatch(filter) {} static void internalCallback(GDBusConnection *conn, const gchar *sender, const gchar *path, const gchar *interface, const gchar *signal, GVariant *params, gpointer data) { try { SignalWatch *watch = static_cast< SignalWatch *>(data); ExtractArgs context(conn, sender, path, interface, signal); if (!watch->matches(context)) { return; } typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; typename dbus_traits::host_type a5; GVariantIter iter; g_variant_iter_init(&iter, params); dbus_traits::get(context, iter, a1); dbus_traits::get(context, iter, a2); dbus_traits::get(context, iter, a3); dbus_traits::get(context, iter, a4); dbus_traits::get(context, iter, a5); const Callback_t &cb = watch->getCallback(); cb(a1, a2, a3, a4, a5); } catch (const std::exception &ex) { g_error("unexpected exception caught in internalCallback(): %s", ex.what()); } catch (...) { g_error("unexpected exception caught in internalCallback()"); } } void activate(const Callback_t &callback) { SignalWatch< boost::function >::activateInternal(callback, internalCallback); } }; template class SignalWatch6 : public SignalWatch< boost::function > { typedef boost::function Callback_t; public: SignalWatch6(const DBusRemoteObject &object, const std::string &signal, bool = true) : SignalWatch(object, signal) { } SignalWatch6(const SignalFilter &filter) : SignalWatch(filter) {} static void internalCallback(GDBusConnection *conn, const gchar *sender, const gchar *path, const gchar *interface, const gchar *signal, GVariant *params, gpointer data) throw () { try { SignalWatch *watch = static_cast< SignalWatch *>(data); ExtractArgs context(conn, sender, path, interface, signal); if (!watch->matches(context)) { return; } typename dbus_traits::host_type a1; typename dbus_traits::host_type a2; typename dbus_traits::host_type a3; typename dbus_traits::host_type a4; typename dbus_traits::host_type a5; typename dbus_traits::host_type a6; GVariantIter iter; g_variant_iter_init(&iter, params); dbus_traits::get(context, iter, a1); dbus_traits::get(context, iter, a2); dbus_traits::get(context, iter, a3); dbus_traits::get(context, iter, a4); dbus_traits::get(context, iter, a5); dbus_traits::get(context, iter, a6); const Callback_t &cb = watch->getCallback(); cb(a1, a2, a3, a4, a5, a6); } catch (const std::exception &ex) { g_error("unexpected exception caught in internalCallback(): %s", ex.what()); } catch (...) { g_error("unexpected exception caught in internalCallback()"); } } void activate(const Callback_t &callback) { SignalWatch< boost::function >::activateInternal(callback, internalCallback); } }; } // namespace GDBusCXX #endif // INCL_GDBUS_CXX_BRIDGE syncevolution_1.4/src/gdbusxx/gdbus-cxx.h000066400000000000000000000136541230021373600207170ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_GDBUS_CXX #define INCL_GDBUS_CXX #include #include #include #include namespace GDBusCXX { /** * An exception class which can be thrown to create * specific D-Bus exception on the bus. */ class dbus_error : public std::runtime_error { public: /** * @param dbus_name the D-Bus error name, like "org.example.error.Invalid" * @param what a more detailed description */ dbus_error(const std::string &dbus_name, const std::string &what) : std::runtime_error(what), m_dbus_name(dbus_name) {} ~dbus_error() throw() {} const std::string &dbusName() const { return m_dbus_name; } private: std::string m_dbus_name; }; /** * Special parameter type that identifies a D-Bus bus address. A string in practice. */ class Caller_t : public std::string { public: Caller_t() {} template Caller_t(T val) : std::string(val) {} template Caller_t &operator = (T val) { assign(val); return *this; } }; /** * Special parameter type that identifies the path in a D-Bus message. A string in practice. */ class Path_t : public std::string { public: Path_t() {} template Path_t(T val) : std::string(val) {} template Path_t &operator = (T val) { assign(val); return *this; } }; /** * Special parameter type that identifies the interface in a D-Bus message. A string in practice. */ class Interface_t : public std::string { public: Interface_t() {} template Interface_t(T val) : std::string(val) {} template Interface_t &operator = (T val) { assign(val); return *this; } }; /** * Special parameter type that identifies the member of an interface * (= signal or method) in a D-Bus message. A string in practice. */ class Member_t : public std::string { public: Member_t() {} template Member_t(T val) : std::string(val) {} template Member_t &operator = (T val) { assign(val); return *this; } }; class Watch; /** * Call object which needs to be called with the results * of an asynchronous method call. So instead of * "int foo()" one would implement * "void foo(Result1 > *r)" * and after foo has returned, call r->done(res). Use const * references as type for complex results. * * A Result instance can be copied, but only called once. */ class Result { public: virtual ~Result() {} /** report failure to caller */ virtual void failed(const dbus_error &error) = 0; /** * Calls the given callback once when the peer that the result * would be delivered to disconnects. The callback will also be * called if the peer is already gone by the time that the watch * is requested. * * Alternatively a method can ask to get called with a life Watch * by specifying "const boost::shared_ptr &" as parameter * and then calling its setCallback(). */ virtual Watch *createWatch(const boost::function &callback) = 0; }; class Result0 : virtual public Result { public: /** tell caller that we are done */ virtual void done() = 0; }; template class Result1 : virtual public Result { public: /** return result to caller */ virtual void done(A1 a1) = 0; }; template struct Result2 : virtual public Result { virtual void done(A1 a1, A2 a2) = 0; }; template struct Result3 : virtual public Result { virtual void done(A1 a1, A2 a2, A3 a3) = 0; }; template struct Result4 : virtual public Result { virtual void done(A1 a1, A2 a2, A3 a3, A4 a4) = 0; }; template struct Result5 : virtual public Result { virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) = 0; }; template struct Result6 : virtual public Result { virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) = 0; }; template struct Result7 : virtual public Result { virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) = 0; }; template struct Result8 : virtual public Result { virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) = 0; }; template struct Result9 : virtual public Result { virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9) = 0; }; template struct Result10 : virtual public Result { virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10) = 0; }; } // namespace GDBusCXX #endif // INCL_GDBUS_CXX syncevolution_1.4/src/gdbusxx/gdbusxx.am000066400000000000000000000017371230021373600206440ustar00rootroot00000000000000if ENABLE_MODULES lib_LTLIBRARIES += src/gdbusxx/libgdbussyncevo.la src_gdbusxx_version_info = -version-info 0:0:0 else noinst_LTLIBRARIES += src/gdbusxx/libgdbussyncevo.la endif src_gdbusxx_libgdbussyncevo_la_SOURCES = \ src/gdbusxx/gdbus-cxx-bridge.h \ src/gdbusxx/gdbus-cxx-bridge.cpp \ src/gdbusxx/gdbus-cxx.h src_gdbusxx_libgdbussyncevo_la_LDFLAGS = $(src_gdbus_version_info) src_gdbusxx_libgdbussyncevo_la_LIBADD = $(GLIB_LIBS) $(DBUS_LIBS) src_gdbusxx_libgdbussyncevo_la_CXXFLAGS = $(GLIB_CFLAGS) $(DBUS_CFLAGS) $(SYNCEVO_WFLAGS) src_gdbusxx_libgdbussyncevo_la_CPPFLAGS = $(BOOST_CPPFLAGS) MAINTAINERCLEANFILES += Makefile.in noinst_PROGRAMS += src/gdbusxx/example src_gdbusxx_example_SOURCES = src/gdbusxx/test/example.cpp src_gdbusxx_example_CPPFLAGS = -I$(top_srcdir)/src/gdbusxx/ $(BOOST_CPPFLAGS) src_gdbusxx_example_CXXFLAGS = $(GLIB_CFLAGS) $(DBUS_CFLAGS) $(SYNCEVO_WFLAGS) src_gdbusxx_example_LDADD = src/gdbusxx/libgdbussyncevo.la $(GLIB_LIBS) $(DBUS_LIBS) syncevolution_1.4/src/gdbusxx/test/000077500000000000000000000000001230021373600176105ustar00rootroot00000000000000syncevolution_1.4/src/gdbusxx/test/example.cpp000066400000000000000000000200011230021373600217400ustar00rootroot00000000000000/* * * Library for simple D-Bus integration with GLib * * Copyright (C) 2009 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include "gdbus-cxx-bridge.h" #include #include #include namespace GDBusCXX { struct args { int a; std::string b; std::map c; }; static void hello_global() {} class Test { typedef boost::shared_ptr< Result1 > string_result; struct async : private boost::noncopyable { async(const boost::shared_ptr &watch, Watch *watch2, const string_result &result): m_watch(watch), m_watch2(watch2), m_result(result) {} ~async() { delete m_watch2; } boost::shared_ptr m_watch; Watch *m_watch2; string_result m_result; }; static gboolean method_idle(gpointer data) { std::auto_ptr mydata(static_cast(data)); std::cout << "replying to method_async" << std::endl; mydata->m_result->done("Hello World, asynchronous and delayed"); return false; } static void disconnect(const std::string &id, const std::string &peer) { std::cout << id << ": " << peer << " has disconnected." << std::endl; } public: static void hello_static() {} void hello_const() const {} static void hello_world(const char *msg) { puts(msg); } void hello_base() {} void method(std::string &text) { text = "Hello World"; } void method_async(const Caller_t &caller, const boost::shared_ptr &watch, int32_t secs, const string_result &r) { watch->setCallback(boost::bind(disconnect, "watch1", caller)); Watch *watch2 = r->createWatch(boost::bind(disconnect, "watch2", caller)); std::cout << "method_async called by " << caller << " delay " << secs << std::endl; g_timeout_add_seconds(secs, method_idle, new async(watch, watch2, r)); } void method2(int32_t arg, int32_t &ret) { ret = arg * 2; } int32_t method3(int32_t arg) { return arg * 3; } void method8_simple(int32_t a1, int32_t a2, int32_t a3, int32_t a4, int32_t a5, int32_t a6, int32_t a7, int32_t a8) { } void method9_async(Result9 *r) { r->done(1, 2, 3, 4, 5, 6, 7, 8, 9); delete r; } int32_t method9(int32_t a1, int32_t a2, int32_t a3, int32_t a4, int32_t a5, int32_t a6, int32_t a7, int32_t a8, int32_t a9) { return 0; } void hash(const std::map &in, std::map &out) { for (std::map::const_iterator it = in.begin(); it != in.end(); ++it) { out.insert(std::make_pair((int16_t)it->first, it->second * it->second)); } } void array(const std::vector &in, std::vector &out) { for (std::vector::const_iterator it = in.begin(); it != in.end(); ++it) { out.push_back(*it * *it); } } void error() { throw dbus_error("org.example.error.Invalid", "error"); } void argtest(const args &in, args &out) { out = in; out.a = in.a + 1; } }; class Test2 { public: void test2() {} }; template<> struct dbus_traits : public dbus_struct_traits, &args::c> > > > {}; class DBusTest : public Test, private Test2 { DBusObjectHelper m_object; DBusObjectHelper m_secondary; public: DBusTest(const DBusConnectionPtr &conn) : m_object(conn, "/test", "org.example.Test"), // same path! m_secondary(conn, m_object.getPath(), "org.example.Secondary"), signal(m_object, "Signal") { m_object.add(this, &Test::method8_simple, "Method8Simple"); // m_object.add(this, &Test::method10_async, "Method10Async", G_DBUS_METHOD_FLAG_ASYNC); // m_object.add(this, &Test::method9, "Method9"); m_object.add(this, &Test::method2, "Method2"); m_object.add(this, &Test::method3, "Method3"); m_object.add(this, &Test::method, "Test"); m_object.add(this, &Test::method_async, "TestAsync"); m_object.add(this, &Test::argtest, "ArgTest"); m_object.add(this, &Test::hash, "Hash"); m_object.add(this, &Test::array, "Array"); m_object.add(this, &Test::error, "Error"); m_object.add(&hello_global, "Global"); m_object.add(&DBusTest::hello_static, "Static"); m_object.add(static_cast(this), &Test2::test2, "Private"); // The hello_const() method cannot be registered // because there is no matching MakeMethodEntry<> // specialization for it or DBusObjectHelper::add() // fails to determine the right function type, // depending how one wants to interpret the problem. // m_object.add2(this, &DBusTest::hello_const, "Const"); m_object.add(signal); m_secondary.add(this, &DBusTest::hello, "Hello"); } ~DBusTest() { } EmitSignal3 &>signal; void hello() {} static void hello_static() {} void hello_const() const {} void activate() { m_secondary.activate(); m_object.activate(); } void deactivate() { m_object.deactivate(); m_secondary.deactivate(); } }; static GMainLoop *main_loop = NULL; static void sig_term(int sig) { g_main_loop_quit(main_loop); } } // namespace GDBusCXX using namespace GDBusCXX; int main(int argc, char *argv[]) { DBusConnectionPtr conn; DBusErrorCXX err; struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = sig_term; sigaction(SIGINT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); sa.sa_handler = SIG_IGN; sigaction(SIGCHLD, &sa, NULL); sigaction(SIGPIPE, &sa, NULL); main_loop = g_main_loop_new(NULL, FALSE); conn = dbus_get_bus_connection("SESSION", "org.example", false, &err); if (conn == NULL) { std::string message = err.getMessage(); if (!message.empty()) { fprintf(stderr, "%s\n", message.c_str()); } else fprintf(stderr, "Can't register with session bus\n"); exit(1); } std::auto_ptr test(new DBusTest(conn)); test->activate(); test->signal(42, "hello world", std::map()); test->deactivate(); test->activate(); test->signal(123, "here I am again", std::map()); g_main_loop_run(main_loop); test.reset(); // is this really necessary? // if(!g_dbus_connection_close_sync(conn, NULL, NULL)) { // fprintf(stderr, "Problem closing connection.\n"); // } g_main_loop_unref(main_loop); return 0; } syncevolution_1.4/src/gdbusxx/test/test-example000077500000000000000000000022701230021373600221470ustar00rootroot00000000000000#!/usr/bin/python import dbus from dbus.mainloop.glib import DBusGMainLoop import gobject DBusGMainLoop(set_as_default=True) bus = dbus.SessionBus() dummy = dbus.Interface(bus.get_object('org.example', '/test'), 'org.freedesktop.DBus.Introspectable') print dummy.Introspect() object = dbus.Interface(bus.get_object('org.example', '/test'), 'org.example.Secondary') object.Hello() object = dbus.Interface(bus.get_object('org.example', '/test'), 'org.example.Test') print object.Test() print object.Method2(1) print object.Method3(1) print object.Hash({1: 1, 2: 2, 3: 3}) print object.Array([1, 2, 3, 4]) print object.ArgTest((1, 'hello', {'foo': 'bar'})) loop = gobject.MainLoop() def AsyncFinished(x=None): print "TestAsync:", x loop.quit() print object.TestAsync(2, reply_handler=AsyncFinished, error_handler=AsyncFinished) # This will trigger the "caller has disconnect" # because our client-side time out will get us # out of the loop and then we quit. object.TestAsync(600, reply_handler=AsyncFinished, error_handler=AsyncFinished, timeout=5) loop.run() object.Error() syncevolution_1.4/src/gnome-bluetooth/000077500000000000000000000000001230021373600202555ustar00rootroot00000000000000syncevolution_1.4/src/gnome-bluetooth/gnome-bluetooth.am000066400000000000000000000011501230021373600237010ustar00rootroot00000000000000src_gnome_bluetoothdir = $(GNOMEBLUETOOTH_DIR)/plugins/ src_gnome_bluetooth_LTLIBRARIES = src/gnome-bluetooth/libgbtsyncevolution.la src_gnome_bluetooth_libgbtsyncevolution_la_SOURCES = src/gnome-bluetooth/syncevolution.c src_gnome_bluetooth_libgbtsyncevolution_la_LDFLAGS = -module -avoid-version src_gnome_bluetooth_libgbtsyncevolution_la_LIBADD = $(GUI_LIBS) $(DBUS_GLIB_LIBS) src_gnome_bluetooth_libgbtsyncevolution_la_CPPFLAGS = \ $(GNOMEBLUETOOTH_CFLAGS) \ -DLOCALEDIR=\"$(SYNCEVOLUTION_LOCALEDIR)\" src_gnome_bluetooth_libgbtsyncevolution_la_CFLAGS = $(SYNCEVO_WFLAGS) $(GUI_CFLAGS) $(DBUS_GLIB_CFLAGS) syncevolution_1.4/src/gnome-bluetooth/syncevolution.c000066400000000000000000000074241230021373600233510ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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 * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #define SYNCUI_BINARY "sync-ui" #define SYNCUI_ARG "--show-settings obex+bt://" /*Only detecting SyncML Client*/ static gboolean has_config_widget (const char *bdaddr, const char **uuids) { int i; if (uuids == NULL) { return FALSE; } for (i = 0; uuids[i] != NULL; i++) { if (!g_strcmp0 (uuids[i], "SyncMLClient")) { //find whether "sync-ui" is available if (g_find_program_in_path (SYNCUI_BINARY) == NULL) { return FALSE; } return TRUE; } } return FALSE; } static void button_clicked (GtkButton *button, gpointer user_data) { const char *bdaddr; pid_t midman; pid_t syncui; char args[sizeof (SYNCUI_ARG) + sizeof ("FF:FF:FF:FF:FF:FF")]; bdaddr = g_object_get_data (G_OBJECT (button), "bdaddr"); midman = fork (); if (midman == 0) { //in midman process syncui = fork(); if (syncui == 0) { // in syncui process strcpy (args, SYNCUI_ARG); strncat (args, bdaddr, sizeof ("FF:FF:FF:FF:FF:FF")); if (execlp (SYNCUI_BINARY, args, NULL) == -1){ g_warning ("SyncEvolution plugin failed to launch sync-ui!"); exit (-1); } } else if (syncui == -1) { // error in forking sub process g_warning ("SyncEvolution plugin failed to launch sync-ui!"); exit (-1); } else { //do nothing, just exit. This will cause sync-ui //process an orphan. exit (0); } } else if (midman == -1) { //error in forking sub process g_warning ("SyncEvolution plugin failed to launch sync-ui!"); } else { //in bluetooth-panel process if (waitpid (midman, NULL, 0) == -1){ //error in waitpid g_warning ("SyncEvolution plugin failed to launch sync-ui!"); } } } static GtkWidget * get_config_widgets (const char *bdaddr, const char **uuids) { GtkWidget *hbox; GtkWidget *button; GtkWidget *label1; GtkWidget *label2; int label_max_width = 40; int button_max_width = 10; hbox = gtk_hbox_new (FALSE, 6); gtk_widget_show (hbox); label1 = gtk_label_new (_("Sync in the Sync application")); gtk_label_set_max_width_chars (GTK_LABEL(label1), label_max_width); label2 = gtk_label_new (_("Sync")); gtk_label_set_max_width_chars (GTK_LABEL(label2), button_max_width); button = gtk_button_new (); gtk_container_add (GTK_CONTAINER(button), label2); g_object_set_data_full (G_OBJECT (button), "bdaddr", g_strdup (bdaddr), g_free); gtk_widget_show (label1); gtk_widget_show_all (button); gtk_box_pack_start (GTK_BOX(hbox), label1, FALSE, FALSE, 6); gtk_box_pack_end (GTK_BOX(hbox), button, FALSE, FALSE, 6); /* And set the signal */ g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (button_clicked), NULL); return hbox; } static void device_removed (const char *bdaddr) { //nothing todo } static GbtPluginInfo plugin_info = { "SyncEvolution", has_config_widget, get_config_widgets, device_removed }; GBT_INIT_PLUGIN(plugin_info) syncevolution_1.4/src/gtk-ui/000077500000000000000000000000001230021373600163455ustar00rootroot00000000000000syncevolution_1.4/src/gtk-ui/README000066400000000000000000000006521230021373600172300ustar00rootroot00000000000000SyncEvolution Graphical User Interface for Moblin * Running Syncevolution D-Bus service must be either running or installed properly (so DBus autostart works). the client itself must be installed so glade ui file and rc style file get found. * Support This is the GTK+-2.0 version of the client. It is supported but sohuld be considered to be in maintenance mode: Please consider the version in src/gtk3-ui. syncevolution_1.4/src/gtk-ui/gtk-ui.am000066400000000000000000000066121230021373600200710ustar00rootroot00000000000000EXTRA_DIST += src/gtk-ui/ui.xml if COND_GUI if COND_GTK2 dist_noinst_DATA += \ src/gtk-ui/README src_gtk_ui_applicationsdir = $(datadir)/applications src_gtk_ui_applications_in_files = \ src/gtk-ui/sync.desktop.in \ src/gtk-ui/sync-gtk.desktop.in src_gtk_ui_applications_generated = $(src_gtk_ui_applications_in_files:.desktop.in=.desktop) src_gtk_ui_applications_DATA = @GUI_DESKTOP_FILES@ # if this will pose a problem then see the link below, probably the solution # here will need to be used. # http://mail.gnome.org/archives/commits-list/2010-October/msg05148.html @INTLTOOL_DESKTOP_RULE@ # When installing both the plain GTK and the Moblin-themed version, # the Moblin version uses the normal "Sync - Up to date" name/comment # and the GTK version uses "Sync (GTK)" as name with the same # comment. This is a somewhat arbitrary choice, with the rationale # being that a Moblin user is less likely to care about the # distinction while a GTK user might understand what "(GTK)" means. src/gtk-ui/sync-moblin.desktop: src/gtk-ui/sync.desktop $(AM_V_GEN)cp $< $@ src_gtk_ui_gladedir = $(datadir)/syncevolution/ src_gtk_ui_glade_DATA = src/gtk-ui/ui.xml src_gtk_ui_icondir = $(datadir)/icons/hicolor/48x48/apps dist_src_gtk_ui_icon_DATA = src/gtk-ui/sync.png src_gtk_ui_themercfiles = \ src/gtk-ui/sync-generic.png \ src/gtk-ui/sync-spinner.gif \ src/gtk-ui/sync-ui.rc src_gtk_ui_themercdir = $(datadir)/syncevolution/ dist_src_gtk_ui_themerc_DATA = $(src_gtk_ui_themercfiles) src_gtk_ui_desktopdir = $(datadir)/applications dist_noinst_DATA += \ $(src_gtk_ui_applications_in_files) # sync-ui: default GUI, could be plain GTK or Moblin UX # sync-ui-gtk: GTK GUI # sync-ui-moblin: Moblin UX # # The later two are built when --enable-gui=all was used. EXTRA_PROGRAMS += \ src/gtk-ui/sync-ui \ src/gtk-ui/sync-ui-gtk \ src/gtk-ui/sync-ui-moblin bin_PROGRAMS += @GUI_PROGRAMS@ src_gtk_ui_sync_ui_SOURCES = \ src/gtk-ui/main.c \ src/gtk-ui/sync-ui.c \ src/gtk-ui/sync-ui.h \ src/gtk-ui/sync-ui-config.c \ src/gtk-ui/sync-ui-config.h \ src/gtk-ui/mux-frame.c \ src/gtk-ui/mux-frame.h \ src/gtk-ui/sync-config-widget.c \ src/gtk-ui/sync-config-widget.h \ src/gtk-ui/gtkinfobar.c \ src/gtk-ui/gtkinfobar.h src_gtk_ui_sync_ui_LDADD = \ $(GUI_LIBS) \ $(DBUS_GLIB_LIBS) \ $(top_builddir)/src/dbus/glib/libsyncevo-dbus.la src_gtk_ui_sync_ui_CFLAGS = \ $(GUI_CFLAGS) \ $(DBUS_GLIB_CFLAGS) \ -DGLADEDIR=\""$(src_gtk_ui_gladedir)"\" \ -DTHEMEDIR=\""$(src_gtk_ui_themercdir)"\" \ -DLIBEXECDIR=\"@libexecdir@\" \ -DSYNCEVOLUTION_LOCALEDIR=\"${SYNCEVOLUTION_LOCALEDIR}\" \ $(SYNCEVO_WFLAGS) src_gtk_ui_sync_ui_CPPFLAGS = \ -I$(top_builddir) \ -I$(top_srcdir) \ -I$(top_builddir)/src/dbus/glib \ -I$(top_srcdir)/src/dbus/glib \ $(SYNTHESIS_CFLAGS) src_gtk_ui_sync_ui_gtk_SOURCES = $(src_gtk_ui_sync_ui_SOURCES) src_gtk_ui_sync_ui_gtk_LDADD = $(src_gtk_ui_sync_ui_LDADD) src_gtk_ui_sync_ui_gtk_CFLAGS = $(src_gtk_ui_sync_ui_CFLAGS) src_gtk_ui_sync_ui_gtk_CPPFLAGS = $(src_gtk_ui_sync_ui_CPPFLAGS) src_gtk_ui_sync_ui_moblin_SOURCES = $(src_gtk_ui_sync_ui_SOURCES) src_gtk_ui_sync_ui_moblin_LDADD = $(src_gtk_ui_sync_ui_LDADD) src_gtk_ui_sync_ui_moblin_CFLAGS = $(src_gtk_ui_sync_ui_CFLAGS) src_gtk_ui_sync_ui_moblin_CPPFLAGS = $(src_gtk_ui_sync_ui_CPPFLAGS) -DUSE_MOBLIN_UX CLEANFILES += \ src/gtk-ui/sync-moblin.desktop \ $(src_gtk_ui_applications_generated) endif COND_GTK2 endif COND_GUI syncevolution_1.4/src/gtk-ui/gtkinfobar.c000066400000000000000000001137521230021373600206500ustar00rootroot00000000000000/* * gtkinfobar.c * This file is from GTK+, used here when GTK version < 2.18 * * Copyright (C) 2005 - Paolo Maggi * * 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 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., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ /* * Modified by the gedit Team, 2005. See the AUTHORS file for a * list of people on the gtk Team. * See the gedit ChangeLog files for a list of changes. * * Modified by the GTK+ team, 2008-2009. */ #include "config.h" #ifndef GTK_2_18 #include "gtkinfobar.h" #include #include #include #include #define I_(X) X #define P_(X) X /** * SECTION:gtkinfobar * @short_description: Report important messages to the user * @include: gtk/gtk.h * @see_also: #GtkStatusbar, #GtkMessageDialog * * #GtkInfoBar is a widget that can be used to show messages to * the user without showing a dialog. It is often temporarily shown * at the top or bottom of a document. In contrast to #GtkDialog, which * has a horizontal action area at the bottom, #GtkInfoBar has a * vertical action area at the side. * * The API of #GtkInfoBar is very similar to #GtkDialog, allowing you * to add buttons to the action area with gtk_info_bar_add_button() or * gtk_info_bar_new_with_buttons(). The sensitivity of action widgets * can be controlled with gtk_info_bar_set_response_sensitive(). * To add widgets to the main content area of a #GtkInfoBar, use * gtk_info_bar_get_content_area() and add your widgets to the container. * * Similar to #GtkMessageDialog, the contents of a #GtkInfoBar can by * classified as error message, warning, informational message, etc, * by using gtk_info_bar_set_message_type(). GTK+ uses the message type * to determine the background color of the message area. * * * Simple GtkInfoBar usage. * * /* set up info bar */ * info_bar = gtk_info_bar_new (); * gtk_widget_set_no_show_all (info_bar, TRUE); * message_label = gtk_label_new (""); * gtk_widget_show (message_label); * content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar)); * gtk_container_add (GTK_CONTAINER (content_area), message_label); * gtk_info_bar_add_button (GTK_INFO_BAR (info_bar), * GTK_STOCK_OK, GTK_RESPONSE_OK); * g_signal_connect (info_bar, "response", * G_CALLBACK (gtk_widget_hide), NULL); * gtk_table_attach (GTK_TABLE (table), * info_bar, * 0, 1, 2, 3, * GTK_EXPAND | GTK_FILL, 0, * 0, 0); * * /* ... */ * * /* show an error message */ * gtk_label_set_text (GTK_LABEL (message_label), error_message); * gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), * GTK_MESSAGE_ERROR); * gtk_widget_show (info_bar); * * * * * GtkInfoBar as GtkBuildable * * The GtkInfoBar implementation of the GtkBuildable interface exposes * the content area and action area as internal children with the names * "content_area" and "action_area". * * * GtkInfoBar supports a custom <action-widgets> element, which * can contain multiple <action-widget> elements. The "response" * attribute specifies a numeric response, and the content of the element * is the id of widget (which should be a child of the dialogs @action_area). * * */ #define GTK_INFO_BAR_GET_PRIVATE(object) \ (G_TYPE_INSTANCE_GET_PRIVATE ((object), \ GTK_TYPE_INFO_BAR, \ GtkInfoBarPrivate)) enum { PROP_0, PROP_MESSAGE_TYPE }; struct _GtkInfoBarPrivate { GtkWidget *content_area; GtkWidget *action_area; GtkMessageType message_type; }; typedef struct _ResponseData ResponseData; struct _ResponseData { gint response_id; }; enum { RESPONSE, CLOSE, LAST_SIGNAL }; static guint signals[LAST_SIGNAL]; static void gtk_info_bar_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void gtk_info_bar_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void gtk_info_bar_style_set (GtkWidget *widget, GtkStyle *prev_style); static gboolean gtk_info_bar_expose (GtkWidget *widget, GdkEventExpose *event); static void gtk_info_bar_buildable_interface_init (GtkBuildableIface *iface); static GObject *gtk_info_bar_buildable_get_internal_child (GtkBuildable *buildable, GtkBuilder *builder, const gchar *childname); static gboolean gtk_info_bar_buildable_custom_tag_start (GtkBuildable *buildable, GtkBuilder *builder, GObject *child, const gchar *tagname, GMarkupParser *parser, gpointer *data); static void gtk_info_bar_buildable_custom_finished (GtkBuildable *buildable, GtkBuilder *builder, GObject *child, const gchar *tagname, gpointer user_data); G_DEFINE_TYPE_WITH_CODE (GtkInfoBar, gtk_info_bar, GTK_TYPE_HBOX, G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, gtk_info_bar_buildable_interface_init)) static void gtk_info_bar_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GtkInfoBar *info_bar; GtkInfoBarPrivate *priv; info_bar = GTK_INFO_BAR (object); priv = GTK_INFO_BAR_GET_PRIVATE (info_bar); switch (prop_id) { case PROP_MESSAGE_TYPE: gtk_info_bar_set_message_type (info_bar, g_value_get_enum (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gtk_info_bar_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GtkInfoBar *info_bar; GtkInfoBarPrivate *priv; info_bar = GTK_INFO_BAR (object); priv = GTK_INFO_BAR_GET_PRIVATE (info_bar); switch (prop_id) { case PROP_MESSAGE_TYPE: g_value_set_enum (value, gtk_info_bar_get_message_type (info_bar)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gtk_info_bar_finalize (GObject *object) { G_OBJECT_CLASS (gtk_info_bar_parent_class)->finalize (object); } static void response_data_free (gpointer data) { g_slice_free (ResponseData, data); } static ResponseData * get_response_data (GtkWidget *widget, gboolean create) { ResponseData *ad = g_object_get_data (G_OBJECT (widget), "gtk-info-bar-response-data"); if (ad == NULL && create) { ad = g_slice_new (ResponseData); g_object_set_data_full (G_OBJECT (widget), I_("gtk-info-bar-response-data"), ad, response_data_free); } return ad; } static GtkWidget * find_button (GtkInfoBar *info_bar, gint response_id) { GList *children, *list; GtkWidget *child = NULL; children = gtk_container_get_children (GTK_CONTAINER (info_bar->priv->action_area)); for (list = children; list; list = list->next) { ResponseData *rd = get_response_data (list->data, FALSE); if (rd && rd->response_id == response_id) { child = list->data; break; } } g_list_free (children); return child; } static void gtk_info_bar_close (GtkInfoBar *info_bar) { if (!find_button (info_bar, GTK_RESPONSE_CANCEL)) return; gtk_info_bar_response (GTK_INFO_BAR (info_bar), GTK_RESPONSE_CANCEL); } static gboolean gtk_info_bar_expose (GtkWidget *widget, GdkEventExpose *event) { GtkInfoBarPrivate *priv = GTK_INFO_BAR_GET_PRIVATE (widget); const char* type_detail[] = { "infobar-info", "infobar-warning", "infobar-question", "infobar-error", "infobar" }; if (priv->message_type != GTK_MESSAGE_OTHER) { const char *detail; detail = type_detail[priv->message_type]; gtk_paint_box (widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT, NULL, widget, detail, widget->allocation.x, widget->allocation.y, widget->allocation.width, widget->allocation.height); } if (GTK_WIDGET_CLASS (gtk_info_bar_parent_class)->expose_event) GTK_WIDGET_CLASS (gtk_info_bar_parent_class)->expose_event (widget, event); return FALSE; } static void gtk_info_bar_class_init (GtkInfoBarClass *klass) { GtkWidgetClass *widget_class; GObjectClass *object_class; GtkBindingSet *binding_set; widget_class = GTK_WIDGET_CLASS (klass); object_class = G_OBJECT_CLASS (klass); object_class->get_property = gtk_info_bar_get_property; object_class->set_property = gtk_info_bar_set_property; object_class->finalize = gtk_info_bar_finalize; widget_class->style_set = gtk_info_bar_style_set; widget_class->expose_event = gtk_info_bar_expose; klass->close = gtk_info_bar_close; /** * GtkInfoBar:message-type: * * The type of the message. * * The type is used to determine the colors to use in the info bar. * The following symbolic color names can by used to customize * these colors: * "info_fg_color", "info_bg_color", * "warning_fg_color", "warning_bg_color", * "question_fg_color", "question_bg_color", * "error_fg_color", "error_bg_color". * "other_fg_color", "other_bg_color". * * If the type is #GTK_MESSAGE_OTHER, no info bar is painted but the * colors are still set. * * Since: 2.18 */ g_object_class_install_property (object_class, PROP_MESSAGE_TYPE, g_param_spec_enum ("message-type", P_("Message Type"), P_("The type of message"), GTK_TYPE_MESSAGE_TYPE, GTK_MESSAGE_INFO, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); /** * GtkInfoBar::response: * @info_bar: the object on which the signal is emitted * @response_id: the response ID * * Emitted when an action widget is clicked or the application programmer * calls gtk_dialog_response(). The @response_id depends on which action * widget was clicked. * * Since: 2.18 */ signals[RESPONSE] = g_signal_new (I_("response"), G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GtkInfoBarClass, response), NULL, NULL, g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT); /** * GtkInfoBar::close: * * The ::close signal is a * keybinding signal * which gets emitted when the user uses a keybinding to dismiss * the info bar. * * The default binding for this signal is the Escape key. * * Since: 2.18 */ signals[CLOSE] = g_signal_new (I_("close"), G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GtkInfoBarClass, close), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * GtkInfoBar:content-area-border: * * The width of the border around the content * content area of the info bar. * * Since: 2.18 */ gtk_widget_class_install_style_property (widget_class, g_param_spec_int ("content-area-border", P_("Content area border"), P_("Width of border around the content area"), 0, G_MAXINT, 8, G_PARAM_READABLE)); /** * GtkInfoBar:content-area-spacing: * * The default spacing used between elements of the * content area of the info bar. * * Since: 2.18 */ gtk_widget_class_install_style_property (widget_class, g_param_spec_int ("content-area-spacing", P_("Content area spacing"), P_("Spacing between elements of the area"), 0, G_MAXINT, 16, G_PARAM_READABLE)); /** * GtkInfoBar:button-spacing: * * Spacing between buttons in the action area of the info bar. * * Since: 2.18 */ gtk_widget_class_install_style_property (widget_class, g_param_spec_int ("button-spacing", P_("Button spacing"), P_("Spacing between buttons"), 0, G_MAXINT, 6, G_PARAM_READABLE)); /** * GtkInfoBar:action-area-border: * * Width of the border around the action area of the info bar. * * Since: 2.18 */ gtk_widget_class_install_style_property (widget_class, g_param_spec_int ("action-area-border", P_("Action area border"), P_("Width of border around the action area"), 0, G_MAXINT, 5, G_PARAM_READABLE)); binding_set = gtk_binding_set_by_class (klass); gtk_binding_entry_add_signal (binding_set, GDK_Escape, 0, "close", 0); g_type_class_add_private (object_class, sizeof (GtkInfoBarPrivate)); } static void gtk_info_bar_update_colors (GtkInfoBar *info_bar) { GtkWidget *widget = (GtkWidget*)info_bar; GtkInfoBarPrivate *priv; GdkColor info_default_border_color = { 0, 0xb800, 0xad00, 0x9d00 }; GdkColor info_default_fill_color = { 0, 0xff00, 0xff00, 0xbf00 }; GdkColor warning_default_border_color = { 0, 0xb000, 0x7a00, 0x2b00 }; GdkColor warning_default_fill_color = { 0, 0xfc00, 0xaf00, 0x3e00 }; GdkColor question_default_border_color = { 0, 0x6200, 0x7b00, 0xd960 }; GdkColor question_default_fill_color = { 0, 0x8c00, 0xb000, 0xd700 }; GdkColor error_default_border_color = { 0, 0xa800, 0x2700, 0x2700 }; GdkColor error_default_fill_color = { 0, 0xf000, 0x3800, 0x3800 }; GdkColor other_default_border_color = { 0, 0xb800, 0xad00, 0x9d00 }; GdkColor other_default_fill_color = { 0, 0xff00, 0xff00, 0xbf00 }; GdkColor *fg = NULL; GdkColor *bg = NULL; GdkColor sym_fg, sym_bg; GtkStyle *style; const char* fg_color_name[] = { "info_fg_color", "warning_fg_color", "question_fg_color", "error_fg_color", "other_fg_color" }; const char* bg_color_name[] = { "info_bg_color", "warning_bg_color", "question_bg_color", "error_bg_color", "other_bg_color" }; priv = GTK_INFO_BAR_GET_PRIVATE (info_bar); style = gtk_widget_get_style (widget); if (gtk_style_lookup_color (style, fg_color_name[priv->message_type], &sym_fg) && gtk_style_lookup_color (style, bg_color_name[priv->message_type], &sym_bg)) { fg = &sym_fg; bg = &sym_bg; } else { switch (priv->message_type) { case GTK_MESSAGE_INFO: fg = &info_default_border_color; bg = &info_default_fill_color; break; case GTK_MESSAGE_WARNING: fg = &warning_default_border_color; bg = &warning_default_fill_color; break; case GTK_MESSAGE_QUESTION: fg = &question_default_border_color; bg = &question_default_fill_color; break; case GTK_MESSAGE_ERROR: fg = &error_default_border_color; bg = &error_default_fill_color; break; case GTK_MESSAGE_OTHER: fg = &other_default_border_color; bg = &other_default_fill_color; break; } } if (!gdk_color_equal (bg, &widget->style->bg[GTK_STATE_NORMAL])) gtk_widget_modify_bg (widget, GTK_STATE_NORMAL, bg); if (!gdk_color_equal (fg, &widget->style->fg[GTK_STATE_NORMAL])) gtk_widget_modify_fg (widget, GTK_STATE_NORMAL, fg); } static void gtk_info_bar_style_set (GtkWidget *widget, GtkStyle *prev_style) { GtkInfoBar *info_bar = GTK_INFO_BAR (widget); gint button_spacing; gint action_area_border; gint content_area_spacing; gint content_area_border; gtk_widget_style_get (widget, "button-spacing", &button_spacing, "action-area-border", &action_area_border, "content-area-spacing", &content_area_spacing, "content-area-border", &content_area_border, NULL); gtk_box_set_spacing (GTK_BOX (info_bar->priv->action_area), button_spacing); gtk_container_set_border_width (GTK_CONTAINER (info_bar->priv->action_area), action_area_border); gtk_box_set_spacing (GTK_BOX (info_bar->priv->content_area), content_area_spacing); gtk_container_set_border_width (GTK_CONTAINER (info_bar->priv->content_area), content_area_border); gtk_info_bar_update_colors (info_bar); } static void gtk_info_bar_init (GtkInfoBar *info_bar) { GtkWidget *content_area; GtkWidget *action_area; gtk_widget_push_composite_child (); info_bar->priv = GTK_INFO_BAR_GET_PRIVATE (info_bar); content_area = gtk_hbox_new (FALSE, 0); gtk_widget_show (content_area); gtk_box_pack_start (GTK_BOX (info_bar), content_area, TRUE, TRUE, 0); action_area = gtk_vbutton_box_new (); gtk_widget_show (action_area); gtk_button_box_set_layout (GTK_BUTTON_BOX (action_area), GTK_BUTTONBOX_END); gtk_box_pack_start (GTK_BOX (info_bar), action_area, FALSE, TRUE, 0); gtk_widget_set_app_paintable (GTK_WIDGET (info_bar), TRUE); gtk_widget_set_redraw_on_allocate (GTK_WIDGET (info_bar), TRUE); info_bar->priv->content_area = content_area; info_bar->priv->action_area = action_area; gtk_widget_pop_composite_child (); } static GtkBuildableIface *parent_buildable_iface; static void gtk_info_bar_buildable_interface_init (GtkBuildableIface *iface) { parent_buildable_iface = g_type_interface_peek_parent (iface); iface->get_internal_child = gtk_info_bar_buildable_get_internal_child; iface->custom_tag_start = gtk_info_bar_buildable_custom_tag_start; iface->custom_finished = gtk_info_bar_buildable_custom_finished; } static GObject * gtk_info_bar_buildable_get_internal_child (GtkBuildable *buildable, GtkBuilder *builder, const gchar *childname) { if (strcmp (childname, "content_area") == 0) return G_OBJECT (GTK_INFO_BAR (buildable)->priv->content_area); else if (strcmp (childname, "action_area") == 0) return G_OBJECT (GTK_INFO_BAR (buildable)->priv->action_area); return parent_buildable_iface->get_internal_child (buildable, builder, childname); } static gint get_response_for_widget (GtkInfoBar *info_bar, GtkWidget *widget) { ResponseData *rd; rd = get_response_data (widget, FALSE); if (!rd) return GTK_RESPONSE_NONE; else return rd->response_id; } static void action_widget_activated (GtkWidget *widget, GtkInfoBar *info_bar) { gint response_id; response_id = get_response_for_widget (info_bar, widget); gtk_info_bar_response (info_bar, response_id); } /** * gtk_info_bar_add_action_widget: * @info_bar: a #GtkInfoBar * @child: an activatable widget * @response_id: response ID for @child * * Add an activatable widget to the action area of a #GtkInfoBar, * connecting a signal handler that will emit the #GtkInfoBar::response * signal on the message area when the widget is activated. The widget * is appended to the end of the message areas action area. * * Since: 2.18 */ void gtk_info_bar_add_action_widget (GtkInfoBar *info_bar, GtkWidget *child, gint response_id) { ResponseData *ad; guint signal_id; g_return_if_fail (GTK_IS_INFO_BAR (info_bar)); g_return_if_fail (GTK_IS_WIDGET (child)); ad = get_response_data (child, TRUE); ad->response_id = response_id; if (GTK_IS_BUTTON (child)) signal_id = g_signal_lookup ("clicked", GTK_TYPE_BUTTON); else signal_id = GTK_WIDGET_GET_CLASS (child)->activate_signal; if (signal_id) { GClosure *closure; closure = g_cclosure_new_object (G_CALLBACK (action_widget_activated), G_OBJECT (info_bar)); g_signal_connect_closure_by_id (child, signal_id, 0, closure, FALSE); } else g_warning ("Only 'activatable' widgets can be packed into the action area of a GtkInfoBar"); gtk_box_pack_end (GTK_BOX (info_bar->priv->action_area), child, FALSE, FALSE, 0); if (response_id == GTK_RESPONSE_HELP) gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (info_bar->priv->action_area), child, TRUE); } /** * gtk_info_bar_get_action_area: * @info_bar: a #GtkInfoBar * * Returns the action area of @info_bar. * * Returns: the action area. * * Since: 2.18 */ GtkWidget* gtk_info_bar_get_action_area (GtkInfoBar *info_bar) { g_return_val_if_fail (GTK_IS_INFO_BAR (info_bar), NULL); return info_bar->priv->action_area; } /** * gtk_info_bar_get_content_area: * @info_bar: a #GtkInfoBar * * Returns the content area of @info_bar. * * Returns: the content area. * * Since: 2.18 */ GtkWidget* gtk_info_bar_get_content_area (GtkInfoBar *info_bar) { g_return_val_if_fail (GTK_IS_INFO_BAR (info_bar), NULL); return info_bar->priv->content_area; } /** * gtk_info_bar_add_button: * @info_bar: a #GtkInfoBar * @button_text: text of button, or stock ID * @response_id: response ID for the button * * Adds a button with the given text (or a stock button, if button_text * is a stock ID) and sets things up so that clicking the button will emit * the "response" signal with the given response_id. The button is appended * to the end of the info bars's action area. The button widget is * returned, but usually you don't need it. * * Returns: the button widget that was added * * Since: 2.18 */ GtkWidget* gtk_info_bar_add_button (GtkInfoBar *info_bar, const gchar *button_text, gint response_id) { GtkWidget *button; g_return_val_if_fail (GTK_IS_INFO_BAR (info_bar), NULL); g_return_val_if_fail (button_text != NULL, NULL); button = gtk_button_new_from_stock (button_text); GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); gtk_widget_show (button); gtk_info_bar_add_action_widget (info_bar, button, response_id); return button; } static void add_buttons_valist (GtkInfoBar *info_bar, const gchar *first_button_text, va_list args) { const gchar* text; gint response_id; g_return_if_fail (GTK_IS_INFO_BAR (info_bar)); if (first_button_text == NULL) return; text = first_button_text; response_id = va_arg (args, gint); while (text != NULL) { gtk_info_bar_add_button (info_bar, text, response_id); text = va_arg (args, gchar*); if (text == NULL) break; response_id = va_arg (args, int); } } /** * gtk_info_bar_add_buttons: * @info_bar: a #GtkInfoBar * @first_button_text: button text or stock ID * @...: response ID for first button, then more text-response_id pairs, * ending with %NULL * * Adds more buttons, same as calling gtk_info_bar_add_button() * repeatedly. The variable argument list should be %NULL-terminated * as with gtk_info_bar_new_with_buttons(). Each button must have both * text and response ID. * * Since: 2.18 */ void gtk_info_bar_add_buttons (GtkInfoBar *info_bar, const gchar *first_button_text, ...) { va_list args; va_start (args, first_button_text); add_buttons_valist (info_bar, first_button_text, args); va_end (args); } /** * gtk_info_bar_new: * * Creates a new #GtkInfoBar object. * * Returns: a new #GtkInfoBar object * * Since: 2.18 */ GtkWidget * gtk_info_bar_new (void) { return g_object_new (GTK_TYPE_INFO_BAR, NULL); } /** * gtk_info_bar_new_with_buttons: * @first_button_text: stock ID or text to go in first button, or %NULL * @...: response ID for first button, then additional buttons, ending * with %NULL * * Creates a new #GtkInfoBar with buttons. Button text/response ID * pairs should be listed, with a %NULL pointer ending the list. * Button text can be either a stock ID such as %GTK_STOCK_OK, or * some arbitrary text. A response ID can be any positive number, * or one of the values in the #GtkResponseType enumeration. If the * user clicks one of these dialog buttons, GtkInfoBar will emit * the "response" signal with the corresponding response ID. * * Returns: a new #GtkInfoBar */ GtkWidget* gtk_info_bar_new_with_buttons (const gchar *first_button_text, ...) { GtkInfoBar *info_bar; va_list args; info_bar = GTK_INFO_BAR (gtk_info_bar_new ()); va_start (args, first_button_text); add_buttons_valist (info_bar, first_button_text, args); va_end (args); return GTK_WIDGET (info_bar); } /** * gtk_info_bar_set_response_sensitive: * @info_bar: a #GtkInfoBar * @response_id: a response ID * @setting: TRUE for sensitive * * Calls gtk_widget_set_sensitive (widget, setting) for each * widget in the info bars's action area with the given response_id. * A convenient way to sensitize/desensitize dialog buttons. * * Since: 2.18 */ void gtk_info_bar_set_response_sensitive (GtkInfoBar *info_bar, gint response_id, gboolean setting) { GList *children, *list; g_return_if_fail (GTK_IS_INFO_BAR (info_bar)); children = gtk_container_get_children (GTK_CONTAINER (info_bar->priv->action_area)); for (list = children; list; list = list->next) { GtkWidget *widget = list->data; ResponseData *rd = get_response_data (widget, FALSE); if (rd && rd->response_id == response_id) gtk_widget_set_sensitive (widget, setting); } g_list_free (children); } /** * gtk_info_bar_set_default_response: * @info_bar: a #GtkInfoBar * @response_id: a response ID * * Sets the last widget in the info bar's action area with * the given response_id as the default widget for the dialog. * Pressing "Enter" normally activates the default widget. * * Since: 2.18 */ void gtk_info_bar_set_default_response (GtkInfoBar *info_bar, gint response_id) { GList *children, *list; g_return_if_fail (GTK_IS_INFO_BAR (info_bar)); children = gtk_container_get_children (GTK_CONTAINER (info_bar->priv->action_area)); for (list = children; list; list = list->next) { GtkWidget *widget = list->data; ResponseData *rd = get_response_data (widget, FALSE); if (rd && rd->response_id == response_id) gtk_widget_grab_default (widget); } g_list_free (children); } /** * gtk_info_bar_response: * @info_bar: a #GtkInfoBar * @response_id: a response ID * * Emits the 'response' signal with the given @response_id. * * Since: 2.18 */ void gtk_info_bar_response (GtkInfoBar *info_bar, gint response_id) { g_return_if_fail (GTK_IS_INFO_BAR (info_bar)); g_signal_emit (info_bar, signals[RESPONSE], 0, response_id); } typedef struct { gchar *widget_name; gchar *response_id; } ActionWidgetInfo; typedef struct { GtkInfoBar *info_bar; GtkBuilder *builder; GSList *items; gchar *response; } ActionWidgetsSubParserData; static void attributes_start_element (GMarkupParseContext *context, const gchar *element_name, const gchar **names, const gchar **values, gpointer user_data, GError **error) { ActionWidgetsSubParserData *parser_data = (ActionWidgetsSubParserData*)user_data; guint i; if (strcmp (element_name, "action-widget") == 0) { for (i = 0; names[i]; i++) if (strcmp (names[i], "response") == 0) parser_data->response = g_strdup (values[i]); } else if (strcmp (element_name, "action-widgets") == 0) return; else g_warning ("Unsupported tag for GtkInfoBar: %s\n", element_name); } static void attributes_text_element (GMarkupParseContext *context, const gchar *text, gsize text_len, gpointer user_data, GError **error) { ActionWidgetsSubParserData *parser_data = (ActionWidgetsSubParserData*)user_data; ActionWidgetInfo *item; if (!parser_data->response) return; item = g_new (ActionWidgetInfo, 1); item->widget_name = g_strndup (text, text_len); item->response_id = parser_data->response; parser_data->items = g_slist_prepend (parser_data->items, item); parser_data->response = NULL; } static const GMarkupParser attributes_parser = { attributes_start_element, NULL, attributes_text_element, }; gboolean gtk_info_bar_buildable_custom_tag_start (GtkBuildable *buildable, GtkBuilder *builder, GObject *child, const gchar *tagname, GMarkupParser *parser, gpointer *data) { ActionWidgetsSubParserData *parser_data; if (child) return FALSE; if (strcmp (tagname, "action-widgets") == 0) { parser_data = g_slice_new0 (ActionWidgetsSubParserData); parser_data->info_bar = GTK_INFO_BAR (buildable); parser_data->items = NULL; *parser = attributes_parser; *data = parser_data; return TRUE; } return parent_buildable_iface->custom_tag_start (buildable, builder, child, tagname, parser, data); } static void gtk_info_bar_buildable_custom_finished (GtkBuildable *buildable, GtkBuilder *builder, GObject *child, const gchar *tagname, gpointer user_data) { GSList *l; ActionWidgetsSubParserData *parser_data; GObject *object; GtkInfoBar *info_bar; ResponseData *ad; guint signal_id; if (strcmp (tagname, "action-widgets")) { parent_buildable_iface->custom_finished (buildable, builder, child, tagname, user_data); return; } info_bar = GTK_INFO_BAR (buildable); parser_data = (ActionWidgetsSubParserData*)user_data; parser_data->items = g_slist_reverse (parser_data->items); for (l = parser_data->items; l; l = l->next) { ActionWidgetInfo *item = l->data; object = gtk_builder_get_object (builder, item->widget_name); if (!object) { g_warning ("Unknown object %s specified in action-widgets of %s", item->widget_name, gtk_buildable_get_name (GTK_BUILDABLE (buildable))); continue; } ad = get_response_data (GTK_WIDGET (object), TRUE); ad->response_id = atoi (item->response_id); if (GTK_IS_BUTTON (object)) signal_id = g_signal_lookup ("clicked", GTK_TYPE_BUTTON); else signal_id = GTK_WIDGET_GET_CLASS (object)->activate_signal; if (signal_id) { GClosure *closure; closure = g_cclosure_new_object (G_CALLBACK (action_widget_activated), G_OBJECT (info_bar)); g_signal_connect_closure_by_id (object, signal_id, 0, closure, FALSE); } if (ad->response_id == GTK_RESPONSE_HELP) gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (info_bar->priv->action_area), GTK_WIDGET (object), TRUE); g_free (item->widget_name); g_free (item->response_id); g_free (item); } g_slist_free (parser_data->items); g_slice_free (ActionWidgetsSubParserData, parser_data); } /** * gtk_info_bar_set_message_type: * @info_bar: a #GtkInfoBar * @message_type: a #GtkMessageType * * Sets the message type of the message area. * GTK+ uses this type to determine what color to use * when drawing the message area. * * Since: 2.18 */ void gtk_info_bar_set_message_type (GtkInfoBar *info_bar, GtkMessageType message_type) { GtkInfoBarPrivate *priv; AtkObject *atk_obj; g_return_if_fail (GTK_IS_INFO_BAR (info_bar)); priv = GTK_INFO_BAR_GET_PRIVATE (info_bar); if (priv->message_type != message_type) { priv->message_type = message_type; gtk_info_bar_update_colors (info_bar); gtk_widget_queue_draw (GTK_WIDGET (info_bar)); atk_obj = gtk_widget_get_accessible (GTK_WIDGET (info_bar)); if (GTK_IS_ACCESSIBLE (atk_obj)) { GtkStockItem item; const char *stock_id = NULL; atk_object_set_role (atk_obj, ATK_ROLE_ALERT); switch (message_type) { case GTK_MESSAGE_INFO: stock_id = GTK_STOCK_DIALOG_INFO; break; case GTK_MESSAGE_QUESTION: stock_id = GTK_STOCK_DIALOG_QUESTION; break; case GTK_MESSAGE_WARNING: stock_id = GTK_STOCK_DIALOG_WARNING; break; case GTK_MESSAGE_ERROR: stock_id = GTK_STOCK_DIALOG_ERROR; break; case GTK_MESSAGE_OTHER: break; default: g_warning ("Unknown GtkMessageType %u", message_type); break; } if (stock_id) { gtk_stock_lookup (stock_id, &item); atk_object_set_name (atk_obj, item.label); } } g_object_notify (G_OBJECT (info_bar), "message-type"); } } /** * gtk_info_bar_get_message_type: * @info_bar: a #GtkInfoBar * * Returns the message type of the message area. * * Returns: the message type of the message area. * * Since: 2.18 */ GtkMessageType gtk_info_bar_get_message_type (GtkInfoBar *info_bar) { GtkInfoBarPrivate *priv; g_return_val_if_fail (GTK_IS_INFO_BAR (info_bar), GTK_MESSAGE_OTHER); priv = GTK_INFO_BAR_GET_PRIVATE (info_bar); return priv->message_type; } #define __GTK_INFO_BAR_C__ #endif syncevolution_1.4/src/gtk-ui/gtkinfobar.h000066400000000000000000000106641230021373600206530ustar00rootroot00000000000000/* * gtkinfobar.h * This file is from GTK+, used here when GTK version < 2.18 * * Copyright (C) 2005 - Paolo Maggi * * 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 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., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ /* * Modified by the gedit Team, 2005. See the gedit AUTHORS file for a * list of people on the gedit Team. * See the gedit ChangeLog files for a list of changes. * * Modified by the GTK+ Team, 2008-2009. */ #include "config.h" #ifndef GTK_2_18 #ifndef __GTK_INFO_BAR_H__ #define __GTK_INFO_BAR_H__ #include G_BEGIN_DECLS /* * Type checking and casting macros */ #define GTK_TYPE_INFO_BAR (gtk_info_bar_get_type()) #define GTK_INFO_BAR(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GTK_TYPE_INFO_BAR, GtkInfoBar)) #define GTK_INFO_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GTK_TYPE_INFO_BAR, GtkInfoBarClass)) #define GTK_IS_INFO_BAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GTK_TYPE_INFO_BAR)) #define GTK_IS_INFO_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_INFO_BAR)) #define GTK_INFO_BAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTK_TYPE_INFO_BAR, GtkInfoBarClass)) typedef struct _GtkInfoBarPrivate GtkInfoBarPrivate; typedef struct _GtkInfoBarClass GtkInfoBarClass; typedef struct _GtkInfoBar GtkInfoBar; struct _GtkInfoBar { GtkHBox parent; /*< private > */ GtkInfoBarPrivate *priv; }; struct _GtkInfoBarClass { GtkHBoxClass parent_class; /* Signals */ void (* response) (GtkInfoBar *info_bar, gint response_id); /* Keybinding signals */ void (* close) (GtkInfoBar *info_bar); /* Padding for future expansion */ void (*_gtk_reserved1) (void); void (*_gtk_reserved2) (void); void (*_gtk_reserved3) (void); void (*_gtk_reserved4) (void); void (*_gtk_reserved5) (void); void (*_gtk_reserved6) (void); }; GType gtk_info_bar_get_type (void) G_GNUC_CONST; GtkWidget *gtk_info_bar_new (void); GtkWidget *gtk_info_bar_new_with_buttons (const gchar *first_button_text, ...); GtkWidget *gtk_info_bar_get_action_area (GtkInfoBar *info_bar); GtkWidget *gtk_info_bar_get_content_area (GtkInfoBar *info_bar); void gtk_info_bar_add_action_widget (GtkInfoBar *info_bar, GtkWidget *child, gint response_id); GtkWidget *gtk_info_bar_add_button (GtkInfoBar *info_bar, const gchar *button_text, gint response_id); void gtk_info_bar_add_buttons (GtkInfoBar *info_bar, const gchar *first_button_text, ...); void gtk_info_bar_set_response_sensitive (GtkInfoBar *info_bar, gint response_id, gboolean setting); void gtk_info_bar_set_default_response (GtkInfoBar *info_bar, gint response_id); /* Emit response signal */ void gtk_info_bar_response (GtkInfoBar *info_bar, gint response_id); void gtk_info_bar_set_message_type (GtkInfoBar *info_bar, GtkMessageType message_type); GtkMessageType gtk_info_bar_get_message_type (GtkInfoBar *info_bar); G_END_DECLS #endif /* __GTK_INFO_BAR_H__ */ #endif syncevolution_1.4/src/gtk-ui/main.c000066400000000000000000000117721230021373600174450ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include "config.h" #include "sync-ui.h" static char *settings_id = NULL; static GOptionEntry entries[] = { { "show-settings", 0, 0, G_OPTION_ARG_STRING, &settings_id, "Open sync settings for given sync url or configuration name", "url or config name" }, { NULL } }; static void set_app_name_and_icon () { /* TRANSLATORS: this is the application name that may be used by e.g. the windowmanager */ g_set_application_name (_("Sync")); gtk_window_set_default_icon_name ("sync"); } static void init (int argc, char *argv[]) { GError *error = NULL; GOptionContext *context; gtk_init (&argc, &argv); bindtextdomain (GETTEXT_PACKAGE, SYNCEVOLUTION_LOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); textdomain (GETTEXT_PACKAGE); context = g_option_context_new ("- synchronise PIM data with Syncevolution"); g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE); g_option_context_add_group (context, gtk_get_option_group (TRUE)); if (!g_option_context_parse (context, &argc, &argv, &error)) { g_warning ("option parsing failed: %s\n", error->message); } } #ifdef ENABLE_UNIQUE #include enum { COMMAND_0, COMMAND_SHOW_CONFIGURATION /* no sync-ui specific commands */ }; static UniqueResponse message_received_cb (UniqueApp *app, gint command, UniqueMessageData *message, guint time_, app_data *data) { char *arg; GtkWindow *main_win; main_win = sync_ui_get_main_window (data); switch (command) { case UNIQUE_ACTIVATE: if (GTK_IS_WINDOW (main_win)) { /* move the main window to the screen that sent us the command */ gtk_window_set_screen (GTK_WINDOW (main_win), unique_message_data_get_screen (message)); gtk_window_present (GTK_WINDOW (main_win)); } break; case COMMAND_SHOW_CONFIGURATION: arg = unique_message_data_get_text (message); if (GTK_IS_WINDOW (main_win) && arg) { /* move the main window to the screen that sent us the command */ gtk_window_set_screen (GTK_WINDOW (main_win), unique_message_data_get_screen (message)); sync_ui_show_settings (data, arg); } break; default: break; } return UNIQUE_RESPONSE_OK; } int main (int argc, char *argv[]) { UniqueApp *app; init (argc, argv); app = unique_app_new_with_commands ("org.Moblin.Sync", NULL, "show-configuration", COMMAND_SHOW_CONFIGURATION, NULL); if (unique_app_is_running (app)) { UniqueMessageData *message = NULL; UniqueCommand command = UNIQUE_ACTIVATE; if (settings_id) { command = COMMAND_SHOW_CONFIGURATION; message = unique_message_data_new (); unique_message_data_set_text (message, settings_id, -1); } unique_app_send_message (app, command, message); unique_message_data_free (message); } else { app_data *data; set_app_name_and_icon (); data = sync_ui_create (); if (data) { /* UniqueApp watches the main window so it can terminate * the startup notification sequence for us */ unique_app_watch_window (app, sync_ui_get_main_window (data)); /* handle notifications from new app launches */ g_signal_connect (app, "message-received", G_CALLBACK (message_received_cb), data); if (settings_id) { sync_ui_show_settings (data, settings_id); } gtk_main (); } } g_object_unref (app); return 0; } #else int main (int argc, char *argv[]) { app_data *data; init (argc, argv); set_app_name_and_icon (); data = sync_ui_create (); if (settings_id) { sync_ui_show_settings (data, settings_id); } gtk_main (); return 0; } #endif /* ENABLE_UNIQUE */ syncevolution_1.4/src/gtk-ui/mux-frame.c000066400000000000000000000306721230021373600204220ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "mux-frame.h" #include static GdkColor mux_frame_default_border_color = { 0, 0xdddd, 0xe2e2, 0xe5e5 }; static GdkColor mux_frame_default_bullet_color = { 0, 0xaaaa, 0xaaaa, 0xaaaa }; static gfloat mux_frame_bullet_size_factor = 1.3; #define MUX_FRAME_BULLET_PADDING 10 static void mux_frame_buildable_init (GtkBuildableIface *iface); static void mux_frame_buildable_add_child (GtkBuildable *buildable, GtkBuilder *builder, GObject *child, const gchar *type); G_DEFINE_TYPE_WITH_CODE (MuxFrame, mux_frame, GTK_TYPE_FRAME, G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, mux_frame_buildable_init)) static void mux_frame_dispose (GObject *object) { G_OBJECT_CLASS (mux_frame_parent_class)->dispose (object); } static void mux_frame_finalize (GObject *object) { G_OBJECT_CLASS (mux_frame_parent_class)->finalize (object); } static void label_changed_cb (MuxFrame *frame) { char *font = NULL; GtkFrame *gtk_frame = GTK_FRAME (frame); GtkWidget *label = gtk_frame_get_label_widget (gtk_frame); if (!label) return; /* ensure font is correct */ gtk_widget_style_get (GTK_WIDGET (frame), "title-font", &font, NULL); if (font) { PangoFontDescription *desc; desc = pango_font_description_from_string (font); gtk_widget_modify_font (label, desc); pango_font_description_free (desc); g_free (font); } gtk_misc_set_alignment (GTK_MISC (label), 0.0, 1.0); } static void mux_frame_update_style (MuxFrame *frame) { GdkColor *border_color, *bullet_color; gtk_widget_style_get (GTK_WIDGET (frame), "border-color", &border_color, "bullet-color", &bullet_color, NULL); if (border_color) { frame->border_color = *border_color; gdk_color_free (border_color); } else { frame->border_color = mux_frame_default_border_color; } if (bullet_color) { frame->bullet_color = *bullet_color; gdk_color_free (bullet_color); } else { frame->bullet_color = mux_frame_default_bullet_color; } label_changed_cb (frame); } static void rounded_rectangle (cairo_t * cr, double x, double y, double w, double h, guint radius) { if (radius > w / 2) radius = w / 2; if (radius > h / 2) radius = h / 2; cairo_move_to (cr, x + radius, y); cairo_arc (cr, x + w - radius, y + radius, radius, M_PI * 1.5, M_PI * 2); cairo_arc (cr, x + w - radius, y + h - radius, radius, 0, M_PI * 0.5); cairo_arc (cr, x + radius, y + h - radius, radius, M_PI * 0.5, M_PI); cairo_arc (cr, x + radius, y + radius, radius, M_PI, M_PI * 1.5); } static void mux_frame_paint (GtkWidget *widget, GdkRectangle *area) { MuxFrame *frame = MUX_FRAME (widget); cairo_t *cairo; GtkStyle *style; guint width; g_return_if_fail (widget != NULL); g_return_if_fail (MUX_IS_FRAME (widget)); g_return_if_fail (area != NULL); style = gtk_widget_get_style (widget); cairo = gdk_cairo_create (widget->window); width = gtk_container_get_border_width (GTK_CONTAINER (widget)); /* draw border */ if (width != 0) { gdk_cairo_set_source_color (cairo, &frame->border_color); rounded_rectangle (cairo, widget->allocation.x, widget->allocation.y, widget->allocation.width, widget->allocation.height, width); cairo_clip (cairo); gdk_cairo_rectangle (cairo, area); cairo_clip (cairo); cairo_paint (cairo); } /* draw background */ gdk_cairo_set_source_color (cairo, &style->bg[GTK_WIDGET_STATE(widget)]); rounded_rectangle (cairo, widget->allocation.x + width, widget->allocation.y + width, widget->allocation.width - 2 * width, widget->allocation.height- 2 * width, width); cairo_clip (cairo); gdk_cairo_rectangle (cairo, area); cairo_clip (cairo); cairo_paint (cairo); /* draw bullet before title */ if (gtk_frame_get_label_widget (GTK_FRAME (frame))) { gdk_cairo_set_source_color (cairo, &frame->bullet_color); rounded_rectangle (cairo, frame->bullet_allocation.x, frame->bullet_allocation.y, frame->bullet_allocation.height, frame->bullet_allocation.height, 4); cairo_clip (cairo); gdk_cairo_rectangle (cairo, area); cairo_clip (cairo); cairo_paint (cairo); } cairo_destroy (cairo); } static gboolean mux_frame_expose(GtkWidget *widget, GdkEventExpose *event) { GtkWidgetClass *grand_parent; if (gtk_widget_is_drawable (widget)) { mux_frame_paint (widget, &event->area); grand_parent = GTK_WIDGET_CLASS (g_type_class_peek_parent (mux_frame_parent_class)); grand_parent->expose_event (widget, event); } return FALSE; } static void mux_frame_size_request (GtkWidget *widget, GtkRequisition *requisition) { GtkWidget *label = gtk_frame_get_label_widget (GTK_FRAME (widget)); GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget)); GtkRequisition child_req; GtkRequisition title_req; child_req.width = child_req.height = 0; if (child) gtk_widget_size_request (child, &child_req); title_req.width = title_req.height = 0; if (label) { gtk_widget_size_request (label, &title_req); /* add room for bullet */ title_req.height = title_req.height * mux_frame_bullet_size_factor + 2 * MUX_FRAME_BULLET_PADDING; title_req.width += title_req.height * mux_frame_bullet_size_factor + 2 * MUX_FRAME_BULLET_PADDING; } requisition->width = MAX (child_req.width, title_req.width) + 2 * (GTK_CONTAINER (widget)->border_width + GTK_WIDGET (widget)->style->xthickness); requisition->height = title_req.height + child_req.height + 2 * (GTK_CONTAINER (widget)->border_width + GTK_WIDGET (widget)->style->ythickness); } static void mux_frame_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { GtkBin *bin = GTK_BIN (widget); MuxFrame *mux_frame = MUX_FRAME (widget); GtkFrame *frame = GTK_FRAME (widget); GtkAllocation child_allocation; int xmargin, ymargin, title_height; widget->allocation = *allocation; xmargin = GTK_CONTAINER (widget)->border_width + widget->style->xthickness; ymargin = GTK_CONTAINER (widget)->border_width + widget->style->ythickness; title_height = 0; if (frame->label_widget) { GtkAllocation title_allocation; GtkRequisition title_req; gtk_widget_get_child_requisition (frame->label_widget, &title_req); /* the bullet is bigger than the text */ title_height = title_req.height * mux_frame_bullet_size_factor + 2 * MUX_FRAME_BULLET_PADDING; /* x allocation starts after bullet */ title_allocation.x = allocation->x + xmargin + title_height; title_allocation.y = allocation->y + ymargin + MUX_FRAME_BULLET_PADDING; title_allocation.width = MIN (title_req.width, allocation->width - 2 * xmargin - title_height); title_allocation.height = title_height - 2 * MUX_FRAME_BULLET_PADDING; gtk_widget_size_allocate (frame->label_widget, &title_allocation); mux_frame->bullet_allocation.x = allocation->x + xmargin + MUX_FRAME_BULLET_PADDING; mux_frame->bullet_allocation.y = allocation->y + ymargin + MUX_FRAME_BULLET_PADDING; mux_frame->bullet_allocation.width = title_allocation.height; mux_frame->bullet_allocation.height = title_allocation.height; } child_allocation.x = allocation->x + xmargin; child_allocation.y = allocation->y + ymargin + title_height; child_allocation.width = allocation->width - 2 * xmargin; child_allocation.height = allocation->height - 2 * ymargin - title_height; if (GTK_WIDGET_MAPPED (widget) && (child_allocation.x != frame->child_allocation.x || child_allocation.y != frame->child_allocation.y || child_allocation.width != frame->child_allocation.width || child_allocation.height != frame->child_allocation.height)) { gdk_window_invalidate_rect (widget->window, &widget->allocation, FALSE); } if (bin->child && GTK_WIDGET_VISIBLE (bin->child)) { gtk_widget_size_allocate (bin->child, &child_allocation); } frame->child_allocation = child_allocation; } static void mux_frame_style_set (GtkWidget *widget, GtkStyle *previous) { MuxFrame *frame = MUX_FRAME (widget); mux_frame_update_style (frame); GTK_WIDGET_CLASS (mux_frame_parent_class)->style_set (widget, previous); } static void mux_frame_class_init (MuxFrameClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GParamSpec *pspec; object_class->dispose = mux_frame_dispose; object_class->finalize = mux_frame_finalize; widget_class->expose_event = mux_frame_expose; widget_class->size_request = mux_frame_size_request; widget_class->size_allocate = mux_frame_size_allocate; widget_class->style_set = mux_frame_style_set; pspec = g_param_spec_boxed ("border-color", "Border color", "Color of the outside border", GDK_TYPE_COLOR, G_PARAM_READABLE); gtk_widget_class_install_style_property(widget_class, pspec); pspec = g_param_spec_boxed ("bullet-color", "Bullet color", "Color of the rounded rectangle before a title", GDK_TYPE_COLOR, G_PARAM_READABLE); gtk_widget_class_install_style_property(widget_class, pspec); pspec = g_param_spec_string ("title-font", "Title font", "Pango font description string for title text", "12", G_PARAM_READWRITE); gtk_widget_class_install_style_property(widget_class, pspec); } static void mux_frame_buildable_add_child (GtkBuildable *buildable, GtkBuilder *builder, GObject *child, const gchar *type) { if (!type) gtk_container_add (GTK_CONTAINER (buildable), GTK_WIDGET (child)); else GTK_BUILDER_WARN_INVALID_CHILD_TYPE (MUX_FRAME (buildable), type); } static void mux_frame_buildable_init (GtkBuildableIface *iface) { iface->add_child = mux_frame_buildable_add_child; } static void mux_frame_init (MuxFrame *self) { g_signal_connect (self, "notify::label-widget", G_CALLBACK (label_changed_cb), NULL); } GtkWidget* mux_frame_new (void) { return g_object_new (MUX_TYPE_FRAME, "border-width", 4, NULL); } syncevolution_1.4/src/gtk-ui/mux-frame.h000066400000000000000000000032511230021373600204200ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef _MUX_FRAME #define _MUX_FRAME #include #include G_BEGIN_DECLS #define MUX_TYPE_FRAME mux_frame_get_type() #define MUX_FRAME(obj) \ (G_TYPE_CHECK_INSTANCE_CAST ((obj), MUX_TYPE_FRAME, MuxFrame)) #define MUX_FRAME_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST ((klass), MUX_TYPE_FRAME, MuxFrameClass)) #define MUX_IS_FRAME(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MUX_TYPE_FRAME)) #define MUX_IS_FRAME_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE ((klass), MUX_TYPE_FRAME)) #define MUX_FRAME_GET_CLASS(obj) \ (G_TYPE_INSTANCE_GET_CLASS ((obj), MUX_TYPE_FRAME, MuxFrameClass)) typedef struct { GtkFrame parent; GtkAllocation bullet_allocation; GdkColor bullet_color; GdkColor border_color; } MuxFrame; typedef struct { GtkFrameClass parent_class; } MuxFrameClass; GType mux_frame_get_type (void); GtkWidget* mux_frame_new (void); G_END_DECLS #endif syncevolution_1.4/src/gtk-ui/sync-config-widget.c000066400000000000000000002232101230021373600222110ustar00rootroot00000000000000#include "config.h" #include #include #include #include #include #ifdef USE_MOBLIN_UX #ifdef MX_GTK_0_99_1 #include #else #include #endif #endif #include "sync-ui.h" #include "sync-config-widget.h" /* local copy of GtkInfoBar, used when GTK+ < 2.18 */ #include "gtkinfobar.h" #define INDICATOR_SIZE 16 #define CHILD_PADDING 3 G_DEFINE_TYPE (SyncConfigWidget, sync_config_widget, GTK_TYPE_CONTAINER) typedef struct source_widgets { char *name; GtkWidget *label; GtkWidget *entry; GtkWidget *check; GtkWidget *source_toggle_label; guint count; } source_widgets; enum { PROP_0, PROP_SERVER, PROP_NAME, PROP_CONFIG, PROP_CURRENT, PROP_HAS_TEMPLATE, PROP_CONFIGURED, PROP_CURRENT_SERVICE_NAME, PROP_EXPANDED, }; enum { SIGNAL_CHANGED, LAST_SIGNAL }; static guint32 signals[LAST_SIGNAL] = {0, }; typedef struct save_config_data { SyncConfigWidget *widget; gboolean delete; gboolean temporary; source_widgets *widgets; char *basename; } save_config_data; static void start_session_for_config_write_cb (SyncevoServer *server, char *path, GError *error, save_config_data *data); static void sync_config_widget_update_label (SyncConfigWidget *self); static void sync_config_widget_set_name (SyncConfigWidget *self, const char *name); static void remove_child (GtkWidget *widget, GtkContainer *container) { gtk_container_remove (container, widget); } const char* get_service_description (const char *service) { if (!service) return NULL; /* TRANSLATORS: Descriptions for specific services, shown in service * configuration form */ if (strcmp (service, "ScheduleWorld") == 0) { return _("ScheduleWorld enables you to keep your contacts, events, " "tasks, and notes in sync."); }else if (strcmp (service, "Google") == 0) { return _("Google Sync can back up and synchronize your contacts " "with your Gmail contacts."); }else if (strcmp (service, "Funambol") == 0) { /* TRANSLATORS: Please include the word "demo" (or the equivalent in your language): Funambol is going to be a 90 day demo service in the future */ return _("Back up your contacts and calendar. Sync with a single " "click, anytime, anywhere (DEMO)."); }else if (strcmp (service, "Mobical") == 0) { return _("Mobical Backup and Restore service allows you to securely " "back up your personal mobile data for free."); }else if (strcmp (service, "ZYB") == 0) { return _("ZYB is a simple way for people to store and share mobile " "information online."); }else if (strcmp (service, "Memotoo") == 0) { return _("Memotoo lets you access your personal data from any " "computer connected to the Internet."); } return NULL; } static void update_source_uri (char *name, GHashTable *source_configuration, SyncConfigWidget *self) { const char *uri; source_widgets *widgets; widgets = (source_widgets*)g_hash_table_lookup (self->sources, name); if (!widgets) { return; } uri = gtk_entry_get_text (GTK_ENTRY (widgets->entry)); g_hash_table_insert (source_configuration, g_strdup ("uri"), g_strdup (uri)); } static source_widgets * source_widgets_ref (source_widgets *widgets) { if (widgets) { widgets->count++; } return widgets; } static void source_widgets_unref (source_widgets *widgets) { if (widgets) { widgets->count--; if (widgets->count == 0) g_slice_free (source_widgets, widgets); } } static void check_source_cb (SyncevoSession *session, GError *error, source_widgets *widgets) { gboolean show = TRUE; if (error) { if(error->code == DBUS_GERROR_REMOTE_EXCEPTION && dbus_g_error_has_name (error, SYNCEVO_DBUS_ERROR_SOURCE_UNUSABLE)) { show = FALSE; } else { g_warning ("CheckSource failed: %s", error->message); /* non-fatal, ignore in UI */ } g_error_free (error); } if (widgets->count > 1) { if (show) { /* NOTE: with the new two sources per row layout not showing things * may look weird in some cases... the layout should really only be * done at this point */ gtk_widget_show (widgets->source_toggle_label); gtk_widget_show (widgets->label); gtk_widget_show (widgets->entry); gtk_widget_show (widgets->check); } else { /* next save should disable this source */ toggle_set_active (widgets->check, FALSE); } } source_widgets_unref (widgets); g_object_unref (session); } static void set_config_cb (SyncevoSession *session, GError *error, save_config_data *data) { if (error) { g_warning ("Error in Session.SetConfig: %s", error->message); g_error_free (error); g_object_unref (session); show_error_dialog (GTK_WIDGET (data->widget), _("Sorry, failed to save the configuration")); return; } if (data->temporary) { syncevo_session_check_source (session, data->widgets->name, (SyncevoSessionGenericCb)check_source_cb, data->widgets); } else { data->widget->configured = TRUE; g_signal_emit (data->widget, signals[SIGNAL_CHANGED], 0); g_object_unref (session); } } static void get_config_for_overwrite_prevention_cb (SyncevoSession *session, SyncevoConfig *config, GError *error, save_config_data *data) { static int index = 0; char *name; if (error) { index = 0; if (error->code == DBUS_GERROR_REMOTE_EXCEPTION && dbus_g_error_has_name (error, SYNCEVO_DBUS_ERROR_NO_SUCH_CONFIG)) { /* Config does not exist (as expected), we can now save */ syncevo_session_set_config (session, data->temporary, data->temporary, data->widget->config, (SyncevoSessionGenericCb)set_config_cb, data); return; } g_warning ("Unexpected error in Session.GetConfig: %s", error->message); g_error_free (error); g_object_unref (session); return; } /* Config exists when we are trying to create a new config... * Need to start a new session with another name */ g_object_unref (session); name = g_strdup_printf ("%s__%d", data->basename, ++index); sync_config_widget_set_name (data->widget, name); g_free (name); syncevo_server_start_no_sync_session (data->widget->server, data->widget->config_name, (SyncevoServerStartSessionCb)start_session_for_config_write_cb, data); } static void save_config (save_config_data *data, SyncevoSession *session) { SyncConfigWidget *w = data->widget; if (data->delete) { syncevo_config_free (w->config); w->config = g_hash_table_new (g_str_hash, g_str_equal); } /* if this is a client peer (a device) and not configured, we * need to test that we aren't overwriting existing * configs */ /* TODO: This might be a good thing to do for any configurations.*/ if (peer_is_client (w->config) && !w->configured && !data->temporary) { syncevo_session_get_config (session, FALSE, (SyncevoSessionGetConfigCb)get_config_for_overwrite_prevention_cb, data); } else { syncevo_session_set_config (session, data->temporary, data->temporary, data->widget->config, (SyncevoSessionGenericCb)set_config_cb, data); } } static void status_changed_for_config_write_cb (SyncevoSession *session, SyncevoSessionStatus status, guint error_code, SyncevoSourceStatuses *source_statuses, save_config_data *data) { if (status == SYNCEVO_STATUS_IDLE) { save_config (data, session); } } static void get_status_for_config_write_cb (SyncevoSession *session, SyncevoSessionStatus status, guint error_code, SyncevoSourceStatuses *source_statuses, GError *error, save_config_data *data) { if (error) { g_warning ("Error in Session.GetStatus: %s", error->message); g_error_free (error); g_object_unref (session); /* TODO show in UI: save failed in service list */ return; } syncevo_source_statuses_free (source_statuses); if (status == SYNCEVO_STATUS_IDLE) { save_config (data, session); } } static void start_session_for_config_write_cb (SyncevoServer *server, char *path, GError *error, save_config_data *data) { SyncevoSession *session; if (error) { g_warning ("Error in Server.StartSession: %s", error->message); g_error_free (error); /* TODO show in UI: save failed in service list */ return; } session = syncevo_session_new (path); /* we want to know about status changes to our session */ g_signal_connect (session, "status-changed", G_CALLBACK (status_changed_for_config_write_cb), data); syncevo_session_get_status (session, (SyncevoSessionGetStatusCb)get_status_for_config_write_cb, data); } static void stop_clicked_cb (GtkButton *btn, SyncConfigWidget *self) { save_config_data *data; if (!self->config) { return; } syncevo_config_set_value (self->config, NULL, "defaultPeer", ""); sync_config_widget_set_current (self, FALSE); data = g_slice_new (save_config_data); data->widget = self; data->delete = FALSE; data->temporary = FALSE; syncevo_server_start_no_sync_session (self->server, self->config_name, (SyncevoServerStartSessionCb)start_session_for_config_write_cb, data); } static void use_clicked_cb (GtkButton *btn, SyncConfigWidget *self) { save_config_data *data; const char *username, *password, *sync_url, *pretty_name; char *real_url, *device; gboolean send, receive; SyncevoSyncMode mode; if (!self->config) { return; } if (!self->config_name || strlen (self->config_name) == 0) { g_free (self->config_name); self->config_name = g_strdup (gtk_entry_get_text (GTK_ENTRY (self->entry))); } if (self->mode_changed) { GHashTableIter iter; source_widgets *widgets; char *name; gboolean client = peer_is_client (self->config); send = toggle_get_active (self->send_check); receive = toggle_get_active (self->receive_check); if (send && receive) { mode = SYNCEVO_SYNC_TWO_WAY; } else if (send) { mode = client ? SYNCEVO_SYNC_ONE_WAY_FROM_SERVER : SYNCEVO_SYNC_ONE_WAY_FROM_CLIENT; } else if (receive) { mode = client ? SYNCEVO_SYNC_ONE_WAY_FROM_CLIENT : SYNCEVO_SYNC_ONE_WAY_FROM_SERVER; } else { mode = SYNCEVO_SYNC_NONE; } g_hash_table_iter_init (&iter, self->sources); while (g_hash_table_iter_next (&iter, (gpointer)&name, (gpointer)&widgets)) { const char *mode_str; gboolean active; active = toggle_get_active (widgets->check) && GTK_WIDGET_SENSITIVE (widgets->check); if (active) { mode_str = syncevo_sync_mode_to_string (mode); } else { mode_str = "none"; } syncevo_config_set_value (self->config, name, "sync", mode_str); } } username = gtk_entry_get_text (GTK_ENTRY (self->username_entry)); syncevo_config_set_value (self->config, NULL, "username", username); sync_url = gtk_entry_get_text (GTK_ENTRY (self->baseurl_entry)); /* make a wild guess if no scheme in url */ if (strstr (sync_url, "://") == NULL) { real_url = g_strdup_printf ("http://%s", sync_url); } else { real_url = g_strdup (sync_url); } syncevo_config_set_value (self->config, NULL, "syncURL", real_url); password = gtk_entry_get_text (GTK_ENTRY (self->password_entry)); syncevo_config_set_value (self->config, NULL, "password", password); syncevo_config_get_value (self->config, NULL, "deviceName", &device); if (!device || strlen (device) == 0) { if (!self->config_name || strlen (self->config_name) == 0 || !sync_url || strlen (sync_url) == 0) { show_error_dialog (GTK_WIDGET (self), _("Service must have a name and server URL")); return; } } syncevo_config_foreach_source (self->config, (ConfigFunc)update_source_uri, self); pretty_name = gtk_entry_get_text (GTK_ENTRY (self->entry)); syncevo_config_set_value (self->config, NULL, "PeerName", pretty_name); syncevo_config_get_value (self->config, NULL, "PeerName", &self->pretty_name); syncevo_config_set_value (self->config, NULL, "defaultPeer", self->config_name); sync_config_widget_set_current (self, TRUE); data = g_slice_new (save_config_data); data->widget = self; data->delete = FALSE; data->temporary = FALSE; data->basename = g_strdup (self->config_name); syncevo_server_start_no_sync_session (self->server, self->config_name, (SyncevoServerStartSessionCb)start_session_for_config_write_cb, data); g_free (real_url); } static void reset_delete_clicked_cb (GtkButton *btn, SyncConfigWidget *self) { char *msg, *yes, *no; save_config_data *data; if (!self->config) { return; } if (self->has_template) { /*TRANSLATORS: warning dialog text for resetting pre-defined services */ msg = g_strdup_printf (_("Do you want to reset the settings for %s? " "This will not remove any synced information on either end."), self->pretty_name); /*TRANSLATORS: buttons in reset-service warning dialog */ yes = _("Yes, reset"); no = _("No, keep settings"); } else { /*TRANSLATORS: warning dialog text for deleting user-defined services */ msg = g_strdup_printf (_("Do you want to delete the settings for %s? " "This will not remove any synced information on either " "end but it will remove these settings."), self->pretty_name); /*TRANSLATORS: buttons in delete-service warning dialog */ yes = _("Yes, delete"); no = _("No, keep settings"); } /*TRANSLATORS: decline button in "Reset/delete service" warning dialogs */ if (!show_confirmation (GTK_WIDGET (self), msg, yes, no)) { g_free (msg); return; } g_free (msg); if (self->current) { sync_config_widget_set_current (self, FALSE); } data = g_slice_new (save_config_data); data->widget = self; data->delete = TRUE; data->temporary = FALSE; syncevo_server_start_no_sync_session (self->server, self->config_name, (SyncevoServerStartSessionCb)start_session_for_config_write_cb, data); } static void update_buttons (SyncConfigWidget *self) { if (self->has_template) { /* TRANSLATORS: button labels in service configuration form */ gtk_button_set_label (GTK_BUTTON (self->reset_delete_button), _("Reset settings")); } else { gtk_button_set_label (GTK_BUTTON (self->reset_delete_button), _("Delete settings")); } if (self->configured) { gtk_widget_show (GTK_WIDGET (self->reset_delete_button)); } else { gtk_widget_hide (GTK_WIDGET (self->reset_delete_button)); } if (self->current || !self->current_service_name) { gtk_button_set_label (GTK_BUTTON (self->use_button), _("Save and use")); } else { gtk_button_set_label (GTK_BUTTON (self->use_button), _("Save and replace\ncurrent service")); } if (self->current && self->config) { if (peer_is_client (self->config)) { gtk_button_set_label (GTK_BUTTON (self->stop_button), _("Stop using device")); } else { gtk_button_set_label (GTK_BUTTON (self->stop_button), _("Stop using service")); } gtk_widget_show (self->stop_button); } else { gtk_widget_hide (self->stop_button); } } static void mode_widget_notify_active_cb (GtkWidget *widget, GParamSpec *pspec, SyncConfigWidget *self) { self->mode_changed = TRUE; } static void source_entry_notify_text_cb (GObject *gobject, GParamSpec *pspec, source_widgets *widgets) { gboolean new_editable, old_editable; const char *text; text = gtk_entry_get_text (GTK_ENTRY (widgets->entry)); new_editable = (strlen (text) > 0); old_editable = GTK_WIDGET_SENSITIVE (widgets->check); if (new_editable != old_editable) { gtk_widget_set_sensitive (widgets->check, new_editable); toggle_set_active (widgets->check, new_editable); } } static GtkWidget* add_toggle_widget (SyncConfigWidget *self, const char *title, gboolean active, guint row, guint col) { GtkWidget *toggle; int padding; padding = (col == 1) ? 0 : 32; #ifdef USE_MOBLIN_UX GtkWidget *label; col = col * 2; label = gtk_label_new (title); gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END); gtk_widget_set_size_request (label, 260, -1); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_table_attach (GTK_TABLE (self->mode_table), label, col, col + 1, row, row + 1, GTK_FILL, GTK_FILL, 0, 0); toggle = mx_gtk_light_switch_new (); g_signal_connect_swapped (toggle, "hide", G_CALLBACK (gtk_widget_hide), label); g_signal_connect_swapped (toggle, "show", G_CALLBACK (gtk_widget_show), label); toggle_set_active (toggle, active); g_signal_connect (toggle, "switch-flipped", G_CALLBACK (mode_widget_notify_active_cb), self); #else toggle = gtk_check_button_new_with_label (title); gtk_widget_set_size_request (toggle, 260, -1); toggle_set_active (toggle, active); g_signal_connect (toggle, "notify::active", G_CALLBACK (mode_widget_notify_active_cb), self); #endif gtk_table_attach (GTK_TABLE (self->mode_table), toggle, col + 1, col + 2, row, row + 1, GTK_FILL, GTK_FILL, padding, 0); return toggle; } /* check if config includes a virtual source that covers the given * source */ static gboolean virtual_source_exists (SyncevoConfig *config, const char *name) { GHashTableIter iter; const char *source_string; GHashTable *source_config; g_hash_table_iter_init (&iter, config); while (g_hash_table_iter_next (&iter, (gpointer)&source_string, (gpointer)&source_config)) { char **strs; if (g_str_has_prefix (source_string, "source/")) { const char *uri, *type; type = g_hash_table_lookup (source_config, "backend"); uri = g_hash_table_lookup (source_config, "uri"); if (!uri || !type || !g_str_has_prefix (type, "virtual:")) { /* this source is not defined, or not virtual */ continue; } strs = g_strsplit (source_string + 7, "+", 0); if (g_strv_length (strs) > 1) { int i; for (i = 0; strs[i]; i++) { if (g_strcmp0 (name, strs[i]) == 0) { g_strfreev (strs); return TRUE; } } } g_strfreev (strs); } } return FALSE; } static void init_source (char *name, GHashTable *source_configuration, SyncConfigWidget *self) { char *str, *pretty_name; const char *uri, *type; guint rows; guint row; static guint col = 0; source_widgets *widgets; SyncevoSyncMode mode; save_config_data *data; type = g_hash_table_lookup (source_configuration, "backend"); uri = g_hash_table_lookup (source_configuration, "uri"); if (!type || strlen (type) == 0) { return; } if (g_str_has_prefix (type, "virtual:") && !uri) { /* undefined virtual source */ return; } if (virtual_source_exists (self->config, name)) { return; } g_object_get (self->mode_table, "n-rows", &rows, NULL); if (!self->no_source_toggles && col == 0) { col = 1; row = rows - 1; } else { col = 0; row = rows; } self->no_source_toggles = FALSE; widgets = g_slice_new0 (source_widgets); widgets->name = name; widgets->count = 1; g_hash_table_insert (self->sources, name, widgets); widgets->source_toggle_label = self->source_toggle_label; pretty_name = get_pretty_source_name (name); mode = syncevo_sync_mode_from_string (g_hash_table_lookup (source_configuration, "sync")); widgets->check = add_toggle_widget (self, pretty_name, (mode > SYNCEVO_SYNC_NONE), row, col); /* TRANSLATORS: label for an entry in service configuration form. * Placeholder is a source name. * Example: "Appointments URI" */ str = g_strdup_printf (_("%s URI"), pretty_name); widgets->label = gtk_label_new (str); g_free (str); g_free (pretty_name); g_object_get (self->server_settings_table, "n-rows", &row, NULL); gtk_misc_set_alignment (GTK_MISC (widgets->label), 0.0, 0.5); gtk_table_attach (GTK_TABLE (self->server_settings_table), widgets->label, 0, 1, row, row + 1, GTK_FILL, GTK_EXPAND, 0, 0); widgets->entry = gtk_entry_new (); gtk_entry_set_max_length (GTK_ENTRY (widgets->entry), 99); gtk_entry_set_width_chars (GTK_ENTRY (widgets->entry), 80); if (uri) { gtk_entry_set_text (GTK_ENTRY (widgets->entry), uri); } gtk_table_attach_defaults (GTK_TABLE (self->server_settings_table), widgets->entry, 1, 2, row, row + 1); g_signal_connect (widgets->entry, "notify::text", G_CALLBACK (source_entry_notify_text_cb), widgets); gtk_widget_set_sensitive (widgets->check, uri && strlen (uri) > 0); /* start a session so we save a temporary config so we can do * CheckSource, and show the source-related widgets if the * source is available */ data = g_slice_new (save_config_data); data->widget = self; data->delete = FALSE; data->temporary = TRUE; data->widgets = source_widgets_ref (widgets); syncevo_server_start_no_sync_session (self->server, self->config_name, (SyncevoServerStartSessionCb)start_session_for_config_write_cb, data); } static void get_common_mode (char *name, GHashTable *source_configuration, SyncevoSyncMode *common_mode) { SyncevoSyncMode mode; char *mode_str, *type; type = g_hash_table_lookup (source_configuration, "backend"); if (!type || strlen (type) == 0) { return; } mode_str = g_hash_table_lookup (source_configuration, "sync"); mode = syncevo_sync_mode_from_string (mode_str); if (mode == SYNCEVO_SYNC_NONE) { return; } if (*common_mode == SYNCEVO_SYNC_NONE) { *common_mode = mode; } else if (mode != *common_mode) { *common_mode = SYNCEVO_SYNC_UNKNOWN; } } void sync_config_widget_expand_id (SyncConfigWidget *self, const char *id) { if (id && self->config) { char *sync_url; if (syncevo_config_get_value (self->config, NULL, "syncURL", &sync_url) && strncmp (sync_url, id, strlen (id)) == 0) { sync_config_widget_set_expanded (self, TRUE); } else if (self->config_name && g_ascii_strcasecmp (self->config_name, id) == 0) { sync_config_widget_set_expanded (self, TRUE); } } } static void sync_config_widget_update_expander (SyncConfigWidget *self) { char *username = ""; char *password = ""; char *sync_url = ""; const char *descr; char *str; GtkWidget *label, *align; SyncevoSyncMode mode = SYNCEVO_SYNC_NONE; gboolean send, receive; gboolean client; gtk_container_foreach (GTK_CONTAINER (self->server_settings_table), (GtkCallback)remove_child, self->server_settings_table); gtk_table_resize (GTK_TABLE (self->server_settings_table), 2, 1); gtk_container_foreach (GTK_CONTAINER (self->mode_table), (GtkCallback)remove_child, self->mode_table); gtk_table_resize (GTK_TABLE (self->mode_table), 2, 1); client = peer_is_client (self->config); if (client) { if (!self->device_template_selected) { gtk_widget_hide (self->settings_box); gtk_widget_show (self->device_selector_box); /* temporary solution for device template selection: * show list of templates only */ } else { gtk_widget_show (self->settings_box); gtk_widget_hide (self->device_selector_box); gtk_widget_hide (self->userinfo_table); gtk_widget_hide (self->fake_expander); } } else { gtk_widget_show (self->settings_box); gtk_widget_hide (self->device_selector_box); gtk_widget_show (self->userinfo_table); gtk_widget_show (self->fake_expander); } syncevo_config_foreach_source (self->config, (ConfigFunc)get_common_mode, &mode); switch (mode) { case SYNCEVO_SYNC_TWO_WAY: send = receive = TRUE; break; case SYNCEVO_SYNC_ONE_WAY_FROM_CLIENT: if (client) { send = FALSE; receive = TRUE; } else { send = TRUE; receive = FALSE; } break; case SYNCEVO_SYNC_ONE_WAY_FROM_SERVER: if (client) { send = TRUE; receive = FALSE; } else { send = FALSE; receive = TRUE; } break; default: gtk_widget_show (self->complex_config_info_bar); send = FALSE; receive = FALSE; } self->mode_changed = FALSE; if (self->pretty_name) { gtk_entry_set_text (GTK_ENTRY (self->entry), self->pretty_name); } if (!self->config_name || strlen (self->config_name) == 0) { gtk_expander_set_expanded (GTK_EXPANDER (self->expander), TRUE); } descr = get_service_description (self->config_name); if (descr) { gtk_label_set_text (GTK_LABEL (self->description_label), get_service_description (self->config_name)); gtk_widget_show (self->description_label); } else { gtk_widget_hide (self->description_label); } update_buttons (self); /* TRANSLATORS: toggles in service configuration form, placeholder is service * or device name */ str = g_strdup_printf (_("Send changes to %s"), self->pretty_name); self->send_check = add_toggle_widget (self, str, send, 0, 0); gtk_widget_show (self->send_check); g_free (str); str = g_strdup_printf (_("Receive changes from %s"), self->pretty_name); self->receive_check = add_toggle_widget (self, str, receive, 0, 1); gtk_widget_show (self->receive_check); g_free (str); align = gtk_alignment_new (0.0, 1.0, 0.0, 0.0); gtk_alignment_set_padding (GTK_ALIGNMENT (align), 10, 0, 0, 0); gtk_widget_show (align); gtk_table_attach (GTK_TABLE (self->mode_table), align, 0, 1, 1, 2, GTK_FILL, GTK_FILL, 0, 0); self->source_toggle_label = gtk_label_new (""); /* TRANSLATORS: Label for the source toggles in configuration form. This is a verb, as in "Sync Calendar". */ gtk_label_set_markup (GTK_LABEL (self->source_toggle_label), _("Sync")); gtk_widget_show (self->source_toggle_label); gtk_container_add (GTK_CONTAINER (align), self->source_toggle_label); syncevo_config_get_value (self->config, NULL, "username", &username); syncevo_config_get_value (self->config, NULL, "password", &password); syncevo_config_get_value (self->config, NULL, "syncURL", &sync_url); if (username) { gtk_entry_set_text (GTK_ENTRY (self->username_entry), username); } if (password) { gtk_entry_set_text (GTK_ENTRY (self->password_entry), password); } // TRANSLATORS: label of a entry in service configuration label = gtk_label_new (_("Server address")); gtk_misc_set_alignment (GTK_MISC (label), 9.0, 0.5); gtk_widget_show (label); gtk_table_attach (GTK_TABLE (self->server_settings_table), label, 0, 1, 0, 1, GTK_FILL, GTK_EXPAND, 0, 0); self->baseurl_entry = gtk_entry_new (); gtk_entry_set_max_length (GTK_ENTRY (self->baseurl_entry), 99); gtk_entry_set_width_chars (GTK_ENTRY (self->baseurl_entry), 80); if (sync_url) { gtk_entry_set_text (GTK_ENTRY (self->baseurl_entry), sync_url); } gtk_widget_show (self->baseurl_entry); gtk_table_attach_defaults (GTK_TABLE (self->server_settings_table), self->baseurl_entry, 1, 2, 0, 1); /* update source widgets */ if (self->sources) { g_hash_table_destroy (self->sources); } self->sources = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)source_widgets_unref); self->no_source_toggles = TRUE; syncevo_config_foreach_source (self->config, (ConfigFunc)init_source, self); } /* only adds config to hashtable and combo */ static void sync_config_widget_add_config (SyncConfigWidget *self, const char *name, SyncevoConfig *config) { GtkListStore *store; GtkTreeIter iter; const char *guess_name; SyncevoConfig *guess_config; int score = 1; int guess_score, second_guess_score = -1; char *str; store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (self->combo))); if (syncevo_config_get_value (config, NULL, "score", &str)) { score = (int)strtol (str, NULL, 10); } gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, 0, name, 1, config, 2, score, -1); /* make an educated guess if possible */ gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter); gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, 0, &guess_name, 1, &guess_config, 2, &guess_score, -1); if (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter)) { gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, 2, &second_guess_score, -1); } if (guess_score > 1 && guess_score > second_guess_score) { gtk_combo_box_set_active (GTK_COMBO_BOX (self->combo), 0); /* TRANSLATORS: explanation before a device template combobox. * Placeholder is a device name like 'Nokia N85' or 'Syncevolution * Client' */ str = g_strdup_printf (_("This device looks like it might be a '%s'. " "If this is not correct, please take a look at " "the list of supported devices and pick yours " "if it is listed"), guess_name); } else { gtk_combo_box_set_active (GTK_COMBO_BOX (self->combo), -1); str = g_strdup (_("We don't know what this device is exactly. " "Please take a look at the list of " "supported devices and pick yours if it " "is listed")); } gtk_label_set_text (GTK_LABEL (self->device_text), str); g_free (str); } static void sync_config_widget_update_pretty_name (SyncConfigWidget *self) { self->pretty_name = NULL; if (self->config) { syncevo_config_get_value (self->config, NULL, "PeerName", &self->pretty_name); if (!self->pretty_name) { syncevo_config_get_value (self->config, NULL, "deviceName", &self->pretty_name); } } if (!self->pretty_name) { self->pretty_name = self->config_name; } } static void sync_config_widget_set_config (SyncConfigWidget *self, SyncevoConfig *config) { self->config = config; sync_config_widget_update_pretty_name (self); } static void setup_service_clicked (GtkButton *btn, SyncConfigWidget *self) { sync_config_widget_set_expanded (self, TRUE); } static void sync_config_widget_set_name (SyncConfigWidget *self, const char *name) { g_free (self->config_name); self->config_name = g_strdup (name); sync_config_widget_update_pretty_name (self); } static void device_selection_btn_clicked_cb (GtkButton *btn, SyncConfigWidget *self) { GtkTreeIter iter; if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (self->combo), &iter)) { const char *name; SyncevoConfig *config; GtkTreeModel *model; self->device_template_selected = TRUE; model = gtk_combo_box_get_model(GTK_COMBO_BOX (self->combo)); gtk_tree_model_get (model, &iter, 0, &name, -1 ); gtk_tree_model_get (model, &iter, 1, &config, -1 ); sync_config_widget_set_config (self, config); sync_config_widget_update_expander (self); } } static void server_settings_notify_expand_cb (GtkExpander *expander, GParamSpec *pspec, SyncConfigWidget *self) { /* NOTE: expander can be the fake or real one... */ if (gtk_expander_get_expanded (GTK_EXPANDER (self->fake_expander))) { g_signal_handlers_disconnect_by_func (self->fake_expander, server_settings_notify_expand_cb, self); gtk_widget_hide (self->fake_expander); gtk_expander_set_expanded (GTK_EXPANDER (self->fake_expander), FALSE); gtk_expander_set_expanded (GTK_EXPANDER (self->expander), TRUE); gtk_widget_show (self->expander); g_signal_connect (self->expander, "notify::expanded", G_CALLBACK (server_settings_notify_expand_cb), self); } else { g_signal_handlers_disconnect_by_func (self->expander, server_settings_notify_expand_cb, self); gtk_widget_hide (self->expander); gtk_widget_show (self->fake_expander); g_signal_connect (self->fake_expander, "notify::expanded", G_CALLBACK (server_settings_notify_expand_cb), self); } } static GdkPixbuf* load_icon (const char *uri, guint icon_size) { GError *error = NULL; GdkPixbuf *pixbuf; const char *filename; if (uri && strlen (uri) > 0) { if (g_str_has_prefix (uri, "file://")) { filename = uri+7; } else { g_warning ("only file:// icon uri is supported: %s", uri); filename = THEMEDIR "sync-generic.png"; } } else { filename = THEMEDIR "sync-generic.png"; } pixbuf = gdk_pixbuf_new_from_file_at_scale (filename, icon_size, icon_size, TRUE, &error); if (!pixbuf) { g_warning ("Failed to load service icon: %s", error->message); g_error_free (error); return NULL; } return pixbuf; } static void sync_config_widget_update_label (SyncConfigWidget *self) { if (self->config && self->pretty_name) { char *url, *sync_url; char *str; syncevo_config_get_value (self->config, NULL, "WebURL", &url); syncevo_config_get_value (self->config, NULL, "syncURL", &sync_url); if (self->current) { str = g_strdup_printf ("%s", self->pretty_name); } else { str = g_strdup_printf ("%s", self->pretty_name); } if (g_str_has_prefix (sync_url, "obex-bt://")) { char *tmp = g_strdup_printf (_("%s - Bluetooth device"), str); g_free (str); str = tmp; } else if (!self->has_template) { /* TRANSLATORS: service title for services that are not based on a * template in service list, the placeholder is the name of the service */ char *tmp = g_strdup_printf (_("%s - manually setup"), str); g_free (str); str = tmp; } else if (url && strlen (url) > 0) { char *tmp = g_strdup_printf ("%s -",str); g_free (str); str = tmp; } gtk_label_set_markup (GTK_LABEL (self->label), str); g_free (str); } } void sync_config_widget_set_current_service_name (SyncConfigWidget *self, const char *name) { g_free (self->current_service_name); self->current_service_name = g_strdup (name); update_buttons (self); } void sync_config_widget_set_current (SyncConfigWidget *self, gboolean current) { if (self->current != current) { self->current = current; sync_config_widget_update_label (self); } } static void set_session (SyncConfigWidget *self, const char *path) { g_free (self->running_session); self->running_session = g_strdup (path); gtk_widget_set_sensitive (GTK_WIDGET (self->reset_delete_button), !self->running_session); gtk_widget_set_sensitive (GTK_WIDGET (self->use_button), !self->running_session); /* TODO: maybe add a explanation text somewhere: * "Configuration changes are not possible while a sync is in progress" */ } static void session_changed_cb (SyncevoServer *server, char *path, gboolean started, SyncConfigWidget *self) { if (started) { set_session (self, path); } else if (g_strcmp0 (self->running_session, path) == 0 ) { set_session (self, NULL); } } static void get_sessions_cb (SyncevoServer *server, SyncevoSessions *sessions, GError *error, SyncConfigWidget *self) { if (error) { g_warning ("Server.GetSessions failed: %s", error->message); g_error_free (error); /* non-fatal, ignore in UI */ g_object_unref (self); return; } set_session (self, syncevo_sessions_index (sessions, 0)); syncevo_sessions_free (sessions); g_object_unref (self); } void sync_config_widget_set_server (SyncConfigWidget *self, SyncevoServer *server) { if (self->server) { g_signal_handlers_disconnect_by_func (self->server, session_changed_cb, self); g_object_unref (self->server); self->server = NULL; } if (!server && !self->server) { return; } self->server = g_object_ref (server); /* monitor sessions so we can set editing buttons insensitive */ g_signal_connect (self->server, "session-changed", G_CALLBACK (session_changed_cb), self); /* reference is released in callback */ g_object_ref (self); syncevo_server_get_sessions (self->server, (SyncevoServerGetSessionsCb)get_sessions_cb, self); } static void sync_config_widget_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { SyncConfigWidget *self = SYNC_CONFIG_WIDGET (object); switch (property_id) { case PROP_SERVER: sync_config_widget_set_server (self, g_value_get_pointer (value)); break; case PROP_NAME: sync_config_widget_set_name (self, g_value_get_string (value)); break; case PROP_CONFIG: sync_config_widget_set_config (self, g_value_get_pointer (value)); break; case PROP_CURRENT: sync_config_widget_set_current (self, g_value_get_boolean (value)); break; case PROP_HAS_TEMPLATE: sync_config_widget_set_has_template (self, g_value_get_boolean (value)); break; case PROP_CONFIGURED: sync_config_widget_set_configured (self, g_value_get_boolean (value)); break; case PROP_CURRENT_SERVICE_NAME: sync_config_widget_set_current_service_name (self, g_value_get_string (value)); break; case PROP_EXPANDED: sync_config_widget_set_expanded (self, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void sync_config_widget_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { SyncConfigWidget *self = SYNC_CONFIG_WIDGET (object); switch (property_id) { case PROP_SERVER: g_value_set_pointer (value, self->server); case PROP_NAME: g_value_set_string (value, self->config_name); case PROP_CONFIG: g_value_set_pointer (value, self->config); case PROP_CURRENT: g_value_set_boolean (value, self->current); case PROP_HAS_TEMPLATE: g_value_set_boolean (value, self->has_template); case PROP_CONFIGURED: g_value_set_boolean (value, self->configured); case PROP_CURRENT_SERVICE_NAME: g_value_set_string (value, self->current_service_name); case PROP_EXPANDED: g_value_set_boolean (value, self->expanded); default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void sync_config_widget_dispose (GObject *object) { SyncConfigWidget *self = SYNC_CONFIG_WIDGET (object); sync_config_widget_set_server (self, NULL); if (self->config) { syncevo_config_free (self->config); } self->config = NULL; g_free (self->config_name); self->config_name = NULL; g_free (self->current_service_name); self->current_service_name = NULL; g_free (self->running_session); self->running_session = NULL; if (self->sources) { g_hash_table_destroy (self->sources); self->sources = NULL; } G_OBJECT_CLASS (sync_config_widget_parent_class)->dispose (object); } static void init_default_config (SyncConfigWidget *self) { sync_config_widget_set_name (self, ""); self->has_template = FALSE; syncevo_config_set_value (self->config, NULL, "username", ""); syncevo_config_set_value (self->config, NULL, "password", ""); syncevo_config_set_value (self->config, NULL, "syncURL", ""); syncevo_config_set_value (self->config, NULL, "WebURL", ""); syncevo_config_set_value (self->config, "memo", "uri", ""); syncevo_config_set_value (self->config, "todo", "uri", ""); syncevo_config_set_value (self->config, "addressbook", "uri", ""); syncevo_config_set_value (self->config, "calendar", "uri", ""); } static gboolean label_button_expose_cb (GtkWidget *widget, GdkEventExpose *event, SyncConfigWidget *self) { GtkExpanderStyle style; gint indicator_x, indicator_y; indicator_x = widget->style->xthickness + INDICATOR_SIZE / 2; indicator_y = widget->style->ythickness + widget->allocation.height / 2; if (self->expanded) { style = GTK_EXPANDER_EXPANDED; } else { style = GTK_EXPANDER_COLLAPSED; } gtk_paint_expander (widget->style, widget->window, widget->state, NULL, GTK_WIDGET (self), NULL, indicator_x, indicator_y, style); return FALSE; } static gboolean sync_config_widget_expose_event (GtkWidget *widget, GdkEventExpose *event) { GdkRectangle rect; SyncConfigWidget *self = SYNC_CONFIG_WIDGET (widget); rect.x = widget->allocation.x; rect.y = widget->allocation.y; rect.height = widget->allocation.height; rect.width = widget->allocation.width; gtk_paint_box (widget->style, widget->window, widget->state, GTK_SHADOW_OUT, &rect, widget, NULL, rect.x, rect.y, rect.width, rect.height); gtk_container_propagate_expose (GTK_CONTAINER (self), self->label_box, event); gtk_container_propagate_expose (GTK_CONTAINER (self), self->expando_box, event); return FALSE; } static void sync_config_widget_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { GtkRequisition req; GtkAllocation alloc; SyncConfigWidget *self = SYNC_CONFIG_WIDGET (widget); GTK_WIDGET_CLASS (sync_config_widget_parent_class)->size_allocate (widget, allocation); gtk_widget_size_request (self->label_box, &req); alloc.x = allocation->x + widget->style->xthickness; alloc.y = allocation->y + widget->style->ythickness; alloc.width = allocation->width - 2 * widget->style->xthickness; alloc.height = req.height; gtk_widget_size_allocate (self->label_box, &alloc); if (self->expanded) { gtk_widget_size_request (self->expando_box, &req); alloc.x = allocation->x + 2 * widget->style->xthickness; alloc.y = allocation->y + widget->style->ythickness + alloc.height + CHILD_PADDING; alloc.width = allocation->width - 4 * widget->style->xthickness; alloc.height = req.height; gtk_widget_size_allocate (self->expando_box, &alloc); } } static void sync_config_widget_size_request (GtkWidget *widget, GtkRequisition *requisition) { SyncConfigWidget *self = SYNC_CONFIG_WIDGET (widget); GtkRequisition req; requisition->width = widget->style->xthickness * 2; requisition->height = widget->style->ythickness * 2; gtk_widget_size_request (self->label_box, &req); requisition->width += req.width; requisition->height = MAX (req.height, INDICATOR_SIZE) + widget->style->ythickness * 2; if (self->expanded) { gtk_widget_size_request (self->expando_box, &req); requisition->width = MAX (requisition->width, req.width + widget->style->xthickness * 4); requisition->height += req.height + 2 * widget->style->ythickness; } } static GObject * sync_config_widget_constructor (GType gtype, guint n_properties, GObjectConstructParam *properties) { SyncConfigWidget *self; GObjectClass *parent_class; char *url, *icon; GdkPixbuf *buf; parent_class = G_OBJECT_CLASS (sync_config_widget_parent_class); self = SYNC_CONFIG_WIDGET (parent_class->constructor (gtype, n_properties, properties)); if (!self->config || !self->server) { g_warning ("No SyncevoServer or Syncevoconfig set for SyncConfigWidget"); return G_OBJECT (self); } if (g_strcmp0 (self->config_name, "default") == 0) { init_default_config (self); gtk_widget_show (self->entry); gtk_widget_hide (self->label); } else { gtk_widget_hide (self->entry); gtk_widget_show (self->label); } syncevo_config_get_value (self->config, NULL, "WebURL", &url); syncevo_config_get_value (self->config, NULL, "IconURI", &icon); buf = load_icon (icon, SYNC_UI_LIST_ICON_SIZE); gtk_image_set_from_pixbuf (GTK_IMAGE (self->image), buf); g_object_unref (buf); if (url && strlen (url) > 0) { gtk_link_button_set_uri (GTK_LINK_BUTTON (self->link), url); gtk_widget_show (self->link); } else { gtk_widget_hide (self->link); } sync_config_widget_update_label (self); sync_config_widget_update_expander (self); /* hack to get focus in the right place on "Setup new service" */ if (gtk_widget_get_visible (self->entry)) { gtk_widget_grab_focus (self->entry); } return G_OBJECT (self); } static void sync_config_widget_map (GtkWidget *widget) { SyncConfigWidget *self = SYNC_CONFIG_WIDGET (widget); if (self->label_box && gtk_widget_get_visible (self->expando_box)) { gtk_widget_map (self->label_box); } if (self->expando_box && gtk_widget_get_visible (self->expando_box)) { gtk_widget_map (self->expando_box); } GTK_WIDGET_CLASS (sync_config_widget_parent_class)->map (widget); } static void sync_config_widget_unmap (GtkWidget *widget) { SyncConfigWidget *self = SYNC_CONFIG_WIDGET (widget); GTK_WIDGET_CLASS (sync_config_widget_parent_class)->unmap (widget); if (self->label_box) { gtk_widget_unmap (self->label_box); } if (self->expando_box) { gtk_widget_unmap (self->expando_box); } } static void sync_config_widget_add (GtkContainer *container, GtkWidget *widget) { g_warning ("Can't add widgets in to SyncConfigWidget!"); } static void sync_config_widget_remove (GtkContainer *container, GtkWidget *widget) { SyncConfigWidget *self = SYNC_CONFIG_WIDGET (container); if (self->label_box == widget) { gtk_widget_unparent (widget); self->label_box = NULL; } if (self->expando_box == widget) { gtk_widget_unparent (widget); self->expando_box = NULL; } } static void sync_config_widget_forall (GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data) { SyncConfigWidget *self = SYNC_CONFIG_WIDGET (container); if (self->label_box) { (* callback) (self->label_box, callback_data); } if (self->expando_box) { (* callback) (self->expando_box, callback_data); } } static void sync_config_widget_class_init (SyncConfigWidgetClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *w_class = GTK_WIDGET_CLASS (klass); GtkContainerClass *c_class = GTK_CONTAINER_CLASS (klass); GParamSpec *pspec; object_class->set_property = sync_config_widget_set_property; object_class->get_property = sync_config_widget_get_property; object_class->dispose = sync_config_widget_dispose; object_class->constructor = sync_config_widget_constructor; w_class->expose_event = sync_config_widget_expose_event; w_class->size_request = sync_config_widget_size_request; w_class->size_allocate = sync_config_widget_size_allocate; w_class->map = sync_config_widget_map; w_class->unmap = sync_config_widget_unmap; c_class->add = sync_config_widget_add; c_class->remove = sync_config_widget_remove; c_class->forall = sync_config_widget_forall; pspec = g_param_spec_pointer ("server", "SyncevoServer", "The SyncevoServer to use in Syncevolution DBus calls", G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_SERVER, pspec); pspec = g_param_spec_string ("name", "Configuration name", "The name of the Syncevolution service configuration", NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_NAME, pspec); pspec = g_param_spec_pointer ("config", "SyncevoConfig", "The SyncevoConfig struct this widget represents. Takes ownership.", G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_CONFIG, pspec); pspec = g_param_spec_boolean ("current", "Current", "Whether the service is currently used", FALSE, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_CURRENT, pspec); pspec = g_param_spec_boolean ("has-template", "has template", "Whether the service has a matching template", FALSE, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_HAS_TEMPLATE, pspec); pspec = g_param_spec_boolean ("configured", "Configured", "Whether the service has a configuration already", FALSE, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_CONFIGURED, pspec); pspec = g_param_spec_string ("current-service-name", "Current service name", "The name of the currently used service or NULL", NULL, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_CURRENT_SERVICE_NAME, pspec); pspec = g_param_spec_boolean ("expanded", "Expanded", "Whether the expander is open or closed", FALSE, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_EXPANDED, pspec); signals[SIGNAL_CHANGED] = g_signal_new ("changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE, G_STRUCT_OFFSET (SyncConfigWidgetClass, changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } static void label_enter_notify_cb (GtkWidget *widget, GdkEventCrossing *event, SyncConfigWidget *self) { if (!self->expanded) { gtk_widget_show (self->button); } gtk_widget_set_state (self->label_box, GTK_STATE_PRELIGHT); } static void label_leave_notify_cb (GtkWidget *widget, GdkEventCrossing *event, SyncConfigWidget *self) { if (event->detail != GDK_NOTIFY_INFERIOR) { gtk_widget_hide (self->button); gtk_widget_set_state (self->label_box, GTK_STATE_NORMAL); } } static void device_combo_changed (GtkComboBox *combo, SyncConfigWidget *self) { int active; active = gtk_combo_box_get_active (GTK_COMBO_BOX (combo)); gtk_widget_set_sensitive (self->device_select_btn, active > -1); } static void label_button_release_cb (GtkWidget *widget, GdkEventButton *event, SyncConfigWidget *self) { if (event->button == 1) { sync_config_widget_set_expanded (self, !sync_config_widget_get_expanded (self)); } } static gint compare_list_items (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, SyncConfigWidget *self) { int score_a, score_b; gtk_tree_model_get(model, a, 2, &score_a, -1); gtk_tree_model_get(model, b, 2, &score_b, -1); return score_a - score_b; } static void sync_config_widget_init (SyncConfigWidget *self) { GtkWidget *tmp_box, *hbox, *cont, *vbox, *label; GtkListStore *store; GtkCellRenderer *renderer; gtk_widget_set_has_window (GTK_WIDGET (self), FALSE); self->label_box = gtk_event_box_new (); gtk_widget_set_app_paintable (self->label_box, TRUE); gtk_widget_show (self->label_box); gtk_widget_set_parent (self->label_box, GTK_WIDGET (self)); gtk_widget_set_size_request (self->label_box, -1, SYNC_UI_LIST_ICON_SIZE + 6); g_signal_connect (self->label_box, "enter-notify-event", G_CALLBACK (label_enter_notify_cb), self); g_signal_connect (self->label_box, "leave-notify-event", G_CALLBACK (label_leave_notify_cb), self); g_signal_connect (self->label_box, "button-release-event", G_CALLBACK (label_button_release_cb), self); g_signal_connect (self->label_box, "expose-event", G_CALLBACK (label_button_expose_cb), self); hbox = gtk_hbox_new (FALSE, 0); gtk_widget_show (hbox); gtk_container_add (GTK_CONTAINER (self->label_box), hbox); self->image = gtk_image_new (); /* leave room for drawing the expander indicator in expose handler */ gtk_widget_set_size_request (self->image, SYNC_UI_LIST_ICON_SIZE + INDICATOR_SIZE, SYNC_UI_LIST_ICON_SIZE); gtk_misc_set_alignment (GTK_MISC (self->image), 1.0, 0.5); gtk_widget_show (self->image); gtk_box_pack_start (GTK_BOX (hbox), self->image, FALSE, FALSE, 8); tmp_box = gtk_hbox_new (FALSE, 0); gtk_widget_show (tmp_box); gtk_box_pack_start (GTK_BOX (hbox), tmp_box, FALSE, FALSE, 8); self->label = gtk_label_new (""); gtk_label_set_max_width_chars (GTK_LABEL (self->label), 60); gtk_label_set_ellipsize (GTK_LABEL (self->label), PANGO_ELLIPSIZE_END); gtk_misc_set_alignment (GTK_MISC (self->label), 0.0, 0.5); gtk_widget_show (self->label); gtk_box_pack_start (GTK_BOX (tmp_box), self->label, FALSE, FALSE, 0); self->entry = gtk_entry_new (); gtk_widget_set_no_show_all (self->entry, TRUE); gtk_box_pack_start (GTK_BOX (tmp_box), self->entry, FALSE, FALSE, 4); vbox = gtk_vbox_new (FALSE, 0); gtk_widget_show (vbox); gtk_box_pack_start (GTK_BOX (tmp_box), vbox, FALSE, FALSE, 0); /* TRANSLATORS: link button in service configuration form */ self->link = gtk_link_button_new_with_label ("", _("Launch website")); gtk_widget_set_no_show_all (self->link, TRUE); gtk_box_pack_start (GTK_BOX (vbox), self->link, TRUE, FALSE, 0); vbox = gtk_vbox_new (FALSE, 0); gtk_widget_show (vbox); gtk_box_pack_end (GTK_BOX (hbox), vbox, FALSE, FALSE, 32); /* TRANSLATORS: button in service configuration form */ self->button = gtk_button_new_with_label (_("Set up now")); gtk_widget_set_size_request (self->button, SYNC_UI_LIST_BTN_WIDTH, -1); g_signal_connect (self->button, "clicked", G_CALLBACK (setup_service_clicked), self); gtk_box_pack_start (GTK_BOX (vbox), self->button, TRUE, FALSE, 0); /* label_box built, now build expando_box */ self->expando_box = gtk_hbox_new (FALSE, 0); gtk_widget_set_no_show_all (self->expando_box, TRUE); gtk_widget_set_parent (self->expando_box, GTK_WIDGET (self)); self->device_selector_box = gtk_vbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (self->expando_box), self->device_selector_box, TRUE, TRUE, 16); hbox = gtk_hbox_new (FALSE, 8); gtk_widget_show (hbox); gtk_box_pack_start (GTK_BOX (self->device_selector_box), hbox, FALSE, TRUE, 8); self->device_text = gtk_label_new (_("We don't know what this device is exactly. " "Please take a look at the list of " "supported devices and pick yours if it " "is listed")); gtk_widget_show (self->device_text); gtk_label_set_line_wrap (GTK_LABEL (self->device_text), TRUE); gtk_widget_set_size_request (self->device_text, 600, -1); gtk_box_pack_start (GTK_BOX (hbox), self->device_text, FALSE, TRUE, 0); hbox = gtk_hbox_new (FALSE, 16); gtk_widget_show (hbox); gtk_box_pack_start (GTK_BOX (self->device_selector_box), hbox, FALSE, TRUE, 16); store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_INT); gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), 2, GTK_SORT_DESCENDING); gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store), 2, (GtkTreeIterCompareFunc)compare_list_items, NULL, NULL); self->combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store)); g_object_unref (G_OBJECT (store)); gtk_widget_set_size_request (self->combo, 200, -1); gtk_widget_show (self->combo); gtk_box_pack_start (GTK_BOX (hbox), self->combo, FALSE, TRUE, 0); renderer = gtk_cell_renderer_text_new (); gtk_cell_layout_pack_start (GTK_CELL_LAYOUT(self->combo), renderer, TRUE); gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT(self->combo), renderer, "text", 0, NULL); g_signal_connect (self->combo, "changed", G_CALLBACK (device_combo_changed), self); self->device_select_btn = gtk_button_new_with_label (_("Use these settings")); gtk_widget_set_sensitive (self->device_select_btn, FALSE); gtk_widget_show (self->device_select_btn); gtk_box_pack_start (GTK_BOX (hbox), self->device_select_btn, FALSE, TRUE, 0); g_signal_connect (self->device_select_btn, "clicked", G_CALLBACK (device_selection_btn_clicked_cb), self); /* settings_box has normal expander contents */ self->settings_box = gtk_vbox_new (FALSE, 0); gtk_widget_show (self->settings_box); gtk_box_pack_start (GTK_BOX (self->expando_box), self->settings_box, TRUE, TRUE, 16); vbox = gtk_vbox_new (FALSE, 8); gtk_widget_show (vbox); gtk_box_pack_start (GTK_BOX (self->settings_box), vbox, TRUE, TRUE, 0); tmp_box = gtk_vbox_new (FALSE, 0); gtk_widget_show (tmp_box); gtk_box_pack_start (GTK_BOX (vbox), tmp_box, FALSE, FALSE, 8); self->description_label = gtk_label_new (""); gtk_misc_set_alignment (GTK_MISC (self->description_label), 0.0, 0.5); gtk_widget_set_size_request (self->description_label, 700, -1); gtk_label_set_line_wrap (GTK_LABEL (self->description_label), TRUE); gtk_box_pack_start (GTK_BOX (tmp_box), self->description_label, FALSE, FALSE, 0); tmp_box = gtk_hbox_new (FALSE, 0); gtk_widget_show (tmp_box); gtk_box_pack_start (GTK_BOX (vbox), tmp_box, FALSE, FALSE, 0); self->userinfo_table = gtk_table_new (4, 2, FALSE); gtk_table_set_row_spacings (GTK_TABLE (self->userinfo_table), 2); gtk_table_set_col_spacings (GTK_TABLE (self->userinfo_table), 5); gtk_widget_show (self->userinfo_table); gtk_box_pack_start (GTK_BOX (tmp_box), self->userinfo_table, FALSE, FALSE, 0); /* TRANSLATORS: labels of entries in service configuration form */ label = gtk_label_new (_("Username")); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_widget_show (label); gtk_table_attach_defaults (GTK_TABLE (self->userinfo_table), label, 0, 1, 0, 1); self->username_entry = gtk_entry_new (); gtk_widget_show (self->username_entry); gtk_entry_set_width_chars (GTK_ENTRY (self->username_entry), 40); gtk_entry_set_max_length (GTK_ENTRY (self->username_entry), 99); gtk_table_attach_defaults (GTK_TABLE (self->userinfo_table), self->username_entry, 1, 2, 0, 1); label = gtk_label_new (_("Password")); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_widget_show (label); gtk_table_attach_defaults (GTK_TABLE (self->userinfo_table), label, 0, 1, 1, 2); self->password_entry = gtk_entry_new (); gtk_widget_show (self->password_entry); gtk_entry_set_width_chars (GTK_ENTRY (self->password_entry), 40); gtk_entry_set_visibility (GTK_ENTRY (self->password_entry), FALSE); gtk_entry_set_max_length (GTK_ENTRY (self->password_entry), 99); gtk_table_attach_defaults (GTK_TABLE (self->userinfo_table), self->password_entry, 1, 2, 1, 2); self->complex_config_info_bar = gtk_info_bar_new (); gtk_info_bar_set_message_type (GTK_INFO_BAR (self->complex_config_info_bar), GTK_MESSAGE_WARNING); gtk_box_pack_start (GTK_BOX (vbox), self->complex_config_info_bar, FALSE, FALSE, 0); /* TRANSLATORS: warning in service configuration form for people who have modified the configuration via other means. */ label = gtk_label_new (_("Current configuration is more complex " "than what can be shown here. Changes to sync " "mode or synced data types will overwrite that " "configuration.")); gtk_widget_set_size_request (label, 600, -1); gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); gtk_widget_show (label); cont = gtk_info_bar_get_content_area ( GTK_INFO_BAR (self->complex_config_info_bar)); gtk_container_add (GTK_CONTAINER (cont), label); self->mode_table = gtk_table_new (4, 1, FALSE); gtk_table_set_row_spacings (GTK_TABLE (self->mode_table), 2); gtk_table_set_col_spacings (GTK_TABLE (self->mode_table), 5); gtk_widget_show (self->mode_table); gtk_box_pack_start (GTK_BOX (vbox), self->mode_table, FALSE, FALSE, 0); /* TRANSLATORS: this is the epander label for server settings in service configuration form */ self->expander = gtk_expander_new (_("Hide server settings")); gtk_box_pack_start (GTK_BOX (vbox), self->expander, FALSE, FALSE, 8); tmp_box = gtk_hbox_new (FALSE, 0); gtk_widget_show (tmp_box); gtk_container_add (GTK_CONTAINER (self->expander), tmp_box); self->server_settings_table = gtk_table_new (1, 1, FALSE); gtk_table_set_row_spacings (GTK_TABLE (self->server_settings_table), 2); gtk_table_set_col_spacings (GTK_TABLE (self->server_settings_table), 5); gtk_widget_show (self->server_settings_table); gtk_box_pack_start (GTK_BOX (tmp_box), self->server_settings_table, FALSE, FALSE, 0); tmp_box = gtk_hbox_new (FALSE, 0); gtk_widget_show (tmp_box); gtk_box_pack_start (GTK_BOX (vbox), tmp_box, FALSE, FALSE, 8); /* TRANSLATORS: this is the epander label for server settings in service configuration form */ self->fake_expander = gtk_expander_new (_("Show server settings")); gtk_widget_show (self->fake_expander); gtk_box_pack_start (GTK_BOX (tmp_box), self->fake_expander, FALSE, FALSE, 0); g_signal_connect (self->fake_expander, "notify::expanded", G_CALLBACK (server_settings_notify_expand_cb), self); self->use_button = gtk_button_new (); gtk_widget_show (self->use_button); gtk_box_pack_end (GTK_BOX (tmp_box), self->use_button, FALSE, FALSE, 8); g_signal_connect (self->use_button, "clicked", G_CALLBACK (use_clicked_cb), self); self->stop_button = gtk_button_new (); gtk_box_pack_end (GTK_BOX (tmp_box), self->stop_button, FALSE, FALSE, 8); g_signal_connect (self->stop_button, "clicked", G_CALLBACK (stop_clicked_cb), self); self->reset_delete_button = gtk_button_new (); gtk_widget_show (self->reset_delete_button); gtk_box_pack_end (GTK_BOX (tmp_box), self->reset_delete_button, FALSE, FALSE, 8); g_signal_connect (self->reset_delete_button, "clicked", G_CALLBACK (reset_delete_clicked_cb), self); } GtkWidget* sync_config_widget_new (SyncevoServer *server, const char *name, SyncevoConfig *config, gboolean current, const char *current_service_name, gboolean configured, gboolean has_template) { return g_object_new (SYNC_TYPE_CONFIG_WIDGET, "server", server, "name", name, "config", config, "current", current, "current-service-name", current_service_name, "configured", configured, "has-template", has_template, NULL); } void sync_config_widget_set_expanded (SyncConfigWidget *self, gboolean expanded) { g_return_if_fail (SYNC_IS_CONFIG_WIDGET (self)); if (self->expanded != expanded) { self->expanded = expanded; if (self->expanded) { gtk_widget_hide (self->button); gtk_widget_show (self->expando_box); if (gtk_widget_get_visible (self->entry)) { gtk_widget_grab_focus (self->entry); } else { gtk_widget_grab_focus (self->username_entry); } } else { gtk_widget_show (self->button); gtk_widget_hide (self->expando_box); } g_object_notify (G_OBJECT (self), "expanded"); } } gboolean sync_config_widget_get_expanded (SyncConfigWidget *self) { return self->expanded; } void sync_config_widget_set_has_template (SyncConfigWidget *self, gboolean has_template) { if (self->has_template != has_template) { self->has_template = has_template; update_buttons (self); sync_config_widget_update_label (self); } } gboolean sync_config_widget_get_has_template (SyncConfigWidget *self) { return self->has_template; } void sync_config_widget_set_configured (SyncConfigWidget *self, gboolean configured) { if (self->configured != configured) { self->configured = configured; self->device_template_selected = configured; update_buttons (self); } } gboolean sync_config_widget_get_configured (SyncConfigWidget *self) { return self->configured; } gboolean sync_config_widget_get_current (SyncConfigWidget *widget) { return widget->current; } const char* sync_config_widget_get_name (SyncConfigWidget *widget) { return widget->config_name; } void sync_config_widget_add_alternative_config (SyncConfigWidget *self, const char *template_name, SyncevoConfig *config, gboolean configured) { sync_config_widget_add_config (self, template_name, config); if (configured) { sync_config_widget_set_config (self, config); sync_config_widget_set_configured (self, TRUE); } sync_config_widget_update_expander (self); } syncevolution_1.4/src/gtk-ui/sync-config-widget.h000066400000000000000000000076341230021373600222300ustar00rootroot00000000000000#ifndef _SYNC_CONFIG_WIDGET #define _SYNC_CONFIG_WIDGET #include #include #include "syncevo-server.h" G_BEGIN_DECLS #define SYNC_TYPE_CONFIG_WIDGET sync_config_widget_get_type() #define SYNC_CONFIG_WIDGET(obj) \ (G_TYPE_CHECK_INSTANCE_CAST ((obj), SYNC_TYPE_CONFIG_WIDGET, SyncConfigWidget)) #define SYNC_CONFIG_WIDGET_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST ((klass), SYNC_TYPE_CONFIG_WIDGET, SyncConfigWidgetClass)) #define SYNC_IS_CONFIG_WIDGET(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SYNC_TYPE_CONFIG_WIDGET)) #define SYNC_IS_CONFIG_WIDGET_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE ((klass), SYNC_TYPE_CONFIG_WIDGET)) #define SYNC_CONFIG_WIDGET_GET_CLASS(obj) \ (G_TYPE_INSTANCE_GET_CLASS ((obj), SYNC_TYPE_CONFIG_WIDGET, SyncConfigWidgetClass)) typedef struct { GtkContainer parent; GtkWidget *expando_box; GtkWidget *label_box; GtkWidget *device_selector_box; GtkWidget *device_text; GtkWidget *combo; GtkWidget *device_select_btn; GtkWidget *settings_box; gboolean current; /* is this currently used config */ char *current_service_name; /* name of the current service */ gboolean configured; /* actual service configuration exists on server */ gboolean device_template_selected; gboolean has_template; /* this service configuration has a matching template */ gboolean expanded; SyncevoServer *server; SyncevoConfig *config; GHashTable *configs; /* possible configs. config above is one of these */ char *config_name; char *pretty_name; char *running_session; char *expand_id; /* label */ GtkWidget *image; GtkWidget *label; GtkWidget *entry; GtkWidget *link; GtkWidget *button; /* content */ GtkWidget *description_label; GtkWidget *userinfo_table; GtkWidget *name_label; GtkWidget *name_entry; GtkWidget *complex_config_info_bar; GtkWidget *mode_table; GtkWidget *send_check; GtkWidget *receive_check; GtkWidget *username_entry; GtkWidget *password_entry; GtkWidget *source_toggle_label; GtkWidget *baseurl_entry; GtkWidget *expander; GtkWidget *fake_expander; GtkWidget *server_settings_table; GtkWidget *reset_delete_button; GtkWidget *stop_button; GtkWidget *use_button; GHashTable *sources; /* key is source name, value is source_widgets */ gboolean mode_changed; gboolean no_source_toggles; } SyncConfigWidget; typedef struct { GtkContainerClass parent_class; void (*changed) (SyncConfigWidget *widget); } SyncConfigWidgetClass; GType sync_config_widget_get_type (void); GtkWidget *sync_config_widget_new (SyncevoServer *server, const char *name, SyncevoConfig *config, gboolean current, const char *current_service_name, gboolean configured, gboolean has_template); void sync_config_widget_set_expanded (SyncConfigWidget *widget, gboolean expanded); gboolean sync_config_widget_get_expanded (SyncConfigWidget *widget); gboolean sync_config_widget_get_current (SyncConfigWidget *widget); void sync_config_widget_set_current (SyncConfigWidget *self, gboolean current); void sync_config_widget_set_has_template (SyncConfigWidget *self, gboolean has_template); gboolean sync_config_widget_get_has_template (SyncConfigWidget *self); void sync_config_widget_set_configured (SyncConfigWidget *self, gboolean configured); gboolean sync_config_widget_get_configured (SyncConfigWidget *self); const char *sync_config_widget_get_name (SyncConfigWidget *widget); void sync_config_widget_expand_id (SyncConfigWidget *self, const char *id); void sync_config_widget_add_alternative_config (SyncConfigWidget *self, const char *name, SyncevoConfig *config, gboolean configured); G_END_DECLS #endif syncevolution_1.4/src/gtk-ui/sync-generic.png000066400000000000000000000032051230021373600214410ustar00rootroot00000000000000PNG  IHDR@@iqsBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDATxkUUk9FY KBCR$FB B (/HB(PkrLKttV:sܹsιwafYk?>ҟ6q@{ uaU^&"QLJvCIm @ d'SF°;T@DF۟GRl/U=+oU5 [s08 LHag 'SՔDUPsAEM-@ޒ4d""2h[ ۀ`mYP-VUz\$4vsSZWlX p^Jۀu/eԒD@ҡukT vH+Ӂk T |+)5:no)NV?Z;EV("Sf`10Y+WJ+GUXN3^r\ĩ`~xY kfw..gQ`IvB~"МR,C !Y jԽDE9R -===kccE=ɌgX9,'V`U$ P ^3g;pr`<0 #b P`waBP3 =NPWh C]eV7kikxS n8.R`rq̷zmqpBph.gn(tv7Ŵqa^.9*ES2߮x;qЈKn~{طFJo?{~j +\vnAvw{ikicad>c 37"lOxFxbF]La&xL;=զD,'IBlFcR #^"2ODDv]ƴxWjǚ=`1&|j) | Qd<۽ĬJ@3 a(. b͸8x/ J̦ b^!1.r"(nN.DY @pkp(\O;Bq17p)$D lnmOm>0I&v`Fv(p7n;63{[Mwᶺ? xjt򸈲Ѧr  %F`c˞4$v0J]O2$ n nT$d/ l[MkHKBṕhUjI@9$dB9ݢOI@Z2#ћ63E@ Zy> +S$3E|b{U}X(+4Bnu3'sb  Y4rXU;jVJ@\ݪ9RKrfY!FB}#;j.n'7y; yy; yK.{+"|_#`7.[O NJ}*: ?+KďBO([MFdIENDB`syncevolution_1.4/src/gtk-ui/sync-gtk.desktop.in000066400000000000000000000002711230021373600221040ustar00rootroot00000000000000[Desktop Entry] _Name=SyncEvolution (GTK) _Comment=Synchronize PIM data Version=1.0 Type=Application Exec=sync-ui Icon=sync Categories=Office;PDA;GTK; Terminal=false StartupNotify=true syncevolution_1.4/src/gtk-ui/sync-spinner.gif000066400000000000000000000041461230021373600214710ustar00rootroot00000000000000GIF89a! ! NETSCAPE2.0,7/Eyo3gyb#uW)cj챾+{2L& C"ˤ%o̦3Q! ,HB5L3=a"رtVh!jԪ! ,N@. ^ r'NJ9c woChC!^ѐO쎣f5%~]]N ! ,H"IEκ ncVZ§0%K(h8)`˧'!Q:NE ! ,?H0JŒ5_^a5fnj:urՔ>,b2\ 0,!! ,6H0Jj犷qE`Ȣ,3mưX_1+k:! ,2 /[u RefhiȞSʙwEYL* ! ,5*naXYWAC䲪{ Yތ&%)A! ,7+ޡq|2"SxY%X린%ko@xPz̦P! ,JD*s~Stb^'b'.Rθl )!c)Ґ#=YJ~Kn! ,<Kޡq|`-)Tx lg̒"JTD! ,<<1`p%dԺ+|t^߭x\ JTC! ,:+|"y-i14go(ʠc3z̦ J! ,9H0J%ݍ%BH69+{VS➙j"DkXrl:Ш! ,H&@I@Jx@E)kG"ߢ%G0:27>a&p"ZR` ! ,K&"S #include #include #include "sync-ui-config.h" #include "sync-ui.h" void server_config_free (server_config *server) { if (!server) return; g_free (server->name); syncevo_config_free (server->config); g_slice_free (server_config, server); } void server_config_update_from_entry (server_config *server, GtkEntry *entry) { char **str; const char *new_str; /* all entries have a pointer to the correct string in server_config */ str = g_object_get_data (G_OBJECT (entry), "value"); g_assert (str); new_str = gtk_entry_get_text (entry); if ((*str == NULL && strlen (new_str) != 0) || (*str != NULL && strcmp (*str, new_str) != 0)) { server->changed = TRUE; g_free (*str); *str = g_strdup (new_str); } } static void add_source_config (char *name, GHashTable *syncevo_source_config, GHashTable *source_configs) { source_config *new_conf; new_conf = g_slice_new0 (source_config); new_conf->name = name; new_conf->supported_locally = TRUE; new_conf->stats_set = FALSE; new_conf->config = syncevo_source_config; g_hash_table_insert (source_configs, name, new_conf); } void server_config_init (server_config *server, SyncevoConfig *config) { server->config = config; /* build source_configs */ server->source_configs = g_hash_table_new (g_str_hash, g_str_equal); syncevo_config_foreach_source (config, (ConfigFunc)add_source_config, server->source_configs); if (!syncevo_config_get_value (config, NULL, "PeerName", &server->pretty_name)) { server->pretty_name = server->name; } } gboolean source_config_is_usable (source_config *source) { const char *source_uri; source_uri = g_hash_table_lookup (source->config, "uri"); if (!source_config_is_enabled (source) || !source_uri || strlen (source_uri) == 0 || !source->supported_locally) { return FALSE; } return TRUE; } gboolean source_config_is_enabled (source_config *source) { char *mode; mode = g_hash_table_lookup (source->config, "sync"); if (mode && (strcmp (mode, "none") == 0 || strcmp (mode, "disabled") == 0)) { return FALSE; } return TRUE; } server_data* server_data_new (const char *name, gpointer *data) { server_data *serv_data; serv_data = g_slice_new0 (server_data); serv_data->data = data; serv_data->config = g_slice_new0 (server_config); serv_data->config->name = g_strdup (name); return serv_data; } void server_data_free (server_data *data, gboolean free_config) { if (!data) return; if (free_config && data->config) { server_config_free (data->config); } if (data->options_override) { /* g_ptr_array_foreach (data->options_override, (GFunc)syncevo_option_free, NULL); */ g_ptr_array_free (data->options_override, TRUE); } g_slice_free (server_data, data); } gboolean peer_is_client (SyncevoConfig *config) { char *is_client; g_return_val_if_fail (config, FALSE); syncevo_config_get_value (config, NULL, "PeerIsClient", &is_client); return is_client && g_strcmp0 ("1", is_client) == 0; } syncevolution_1.4/src/gtk-ui/sync-ui-config.h000066400000000000000000000060571230021373600213600ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef SYNC_UI_CONFIG_H #define SYNC_UI_CONFIG_H #include #include "syncevo-session.h" #include "syncevo-server.h" typedef struct source_config { char *name; gboolean supported_locally; SyncevoSourcePhase phase; gboolean stats_set; long status; long local_changes; long remote_changes; long local_rejections; long remote_rejections; GtkWidget *info_bar; /* info/error bar, after ui has been constructed */ GtkWidget *label; /* source report label, after ui has been constructed */ GtkWidget *box; /* source box, after ui has been constructed */ GHashTable *config; /* link to a "sub-hashtable" inside server_config->config */ } source_config; typedef struct server_config { char *name; char *pretty_name; char *password; /* any field in config has changed */ gboolean changed; /* a authentication detail (base_url/username/password) has changed */ gboolean auth_changed; gboolean password_changed; GHashTable *source_configs; /* source_config's*/ SyncevoConfig *config; } server_config; gboolean source_config_is_usable (source_config *source); gboolean source_config_is_enabled (source_config *source); void source_config_free (source_config *source); void server_config_init (server_config *server, SyncevoConfig *config); void server_config_free (server_config *server); void server_config_update_from_entry (server_config *server, GtkEntry *entry); GPtrArray* server_config_get_option_array (server_config *server); void server_config_disable_unsupported_sources (server_config *server); void server_config_ensure_default_sources_exist (server_config *server); /* data structure for syncevo_service_get_template_config_async and * syncevo_service_get_server_config_async. server is the server that * the method was called for. options_override are options that should * be overridden on the config we get. */ typedef struct server_data { server_config *config; GPtrArray *options_override; gpointer *data; } server_data; server_data* server_data_new (const char *name, gpointer *data); void server_data_free (server_data *data, gboolean free_config); /** * utility function: TRUE if the config belongs to a client (PeerIsClient) */ gboolean peer_is_client (SyncevoConfig *config); #endif syncevolution_1.4/src/gtk-ui/sync-ui.c000066400000000000000000003635601230021373600201150ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include "syncevo-server.h" #include "syncevo-session.h" /* for return value definitions */ /* TODO: would be nice to have a non-synthesis-dependent API but for now it's like this... */ #include #include "config.h" #include "sync-ui-config.h" #include "sync-ui.h" #include "sync-config-widget.h" /* local copy of GtkInfoBar, used when GTK+ < 2.18 */ #include "gtkinfobar.h" #ifdef USE_MOBLIN_UX #include "mux-frame.h" #ifdef MX_GTK_0_99_1 #include #else #include #endif #endif static gboolean support_canceling = FALSE; #define REPORTS_PER_CALL 10 #define SYNC_UI_ICON_SIZE 48 #define STRING_VARIANT_HASHTABLE (dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE)) enum { PAGE_MAIN, PAGE_SETTINGS, PAGE_EMERGENCY, }; typedef enum bluetooth_type { SYNC_BLUETOOTH_NONE, SYNC_BLUETOOTH_GNOME, SYNC_BLUETOOTH_MOBLIN } bluetooth_type; typedef enum app_state { SYNC_UI_STATE_CURRENT_STATE, SYNC_UI_STATE_GETTING_SERVER, SYNC_UI_STATE_NO_SERVER, SYNC_UI_STATE_SERVER_OK, SYNC_UI_STATE_SERVER_FAILURE, SYNC_UI_STATE_SYNCING, } app_state; typedef enum ui_operation { OP_SYNC, /* use sync mode from config */ OP_SYNC_SLOW, OP_SYNC_REFRESH_FROM_CLIENT, OP_SYNC_REFRESH_FROM_SERVER, OP_SAVE, OP_RESTORE, } ui_operation; typedef struct operation_data { app_data *data; ui_operation operation; gboolean started; const char *dir; /* for OP_RESTORE */ } operation_data; struct _app_data { GtkWidget *sync_win; GtkWidget *services_win; /* will be NULL when USE_MOBLIN_UX is set*/ GtkWidget *emergency_win; /* will be NULL when USE_MOBLIN_UX is set*/ #ifdef USE_MOBLIN_UX GtkWidget *notebook; /* only in use with USE_MOBLIN_UX */ GtkWidget *back_btn; /* only in use with USE_MOBLIN_UX */ #endif GtkWidget *settings_btn; /* only in use with USE_MOBLIN_UX */ guint settings_id; GtkWidget *service_box; GtkWidget *info_bar; GtkWidget *no_connection_box; GtkWidget *main_frame; GtkWidget *log_frame; GtkWidget *server_icon_box; GtkWidget *offline_label; GtkWidget *progress; GtkWidget *sync_status_label; GtkWidget *spinner_image; GtkWidget *sync_btn; GtkWidget *change_service_btn; GtkWidget *emergency_btn; GtkWidget *server_label; GtkWidget *autosync_box; GtkWidget *autosync_toggle; GtkWidget *last_synced_label; GtkWidget *sources_box; GtkWidget *new_service_btn; GtkWidget *new_device_btn; GtkWidget *services_box; GtkWidget *devices_box; GtkWidget *scrolled_window; GtkWidget *expanded_config; GtkWidget *settings_close_btn; GtkWidget *emergency_label; GtkWidget *emergency_expander; GtkWidget *emergency_source_table; GtkWidget *refresh_from_server_btn_label; GtkWidget *refresh_from_client_btn_label; GtkWidget *emergency_backup_table; GtkWidget *emergency_close_btn; GtkWidget *password_dialog_entry; char *password_dialog_id; gboolean forced_emergency; GHashTable *emergency_sources; guint backup_count; gboolean online; gboolean syncing; gboolean synced_this_session; int last_sync; guint last_sync_src_id; ui_operation current_operation; server_config *current_service; app_state current_state; guint service_list_updates_left; gboolean open_current; /* should the service list open the current service when it populates next time*/ char *config_id_to_open; SyncevoServer *server; SyncevoSession *running_session; /* session that is currently active */ bluetooth_type bluetooth_wizard; }; static void set_sync_progress (app_data *data, float progress, char *status); static void set_app_state (app_data *data, app_state state); static void show_main_view (app_data *data); static void update_emergency_view (app_data *data); static void update_emergency_expander (app_data *data); static void show_emergency_view (app_data *data); static void show_services_list (app_data *data, const char *config_id_to_open); static void update_services_list (app_data *data); static void update_service_ui (app_data *data); static void setup_new_service_clicked (GtkButton *btn, app_data *data); static gboolean source_config_update_widget (source_config *source); static void get_presence_cb (SyncevoServer *server, char *status, char **transport, GError *error, app_data *data); static void get_reports_cb (SyncevoServer *server, SyncevoReports *reports, GError *error, app_data *data); static void start_session_cb (SyncevoServer *server, char *path, GError *error, operation_data *op_data); static void get_config_for_main_win_cb (SyncevoServer *server, SyncevoConfig *config, GError *error, app_data *data); void toggle_set_active (GtkWidget *toggle, gboolean active) { #ifdef USE_MOBLIN_UX /* MxGtkLightSwitch does not have "active" property yet */ mx_gtk_light_switch_set_active (MX_GTK_LIGHT_SWITCH (toggle), active); #else g_object_set (toggle, "active", active, NULL); #endif } gboolean toggle_get_active (GtkWidget *toggle) { #ifdef USE_MOBLIN_UX /* MxGtkLightSwitch does not have "active" property yet */ return mx_gtk_light_switch_get_active (MX_GTK_LIGHT_SWITCH (toggle)); #else gboolean active; g_object_get (toggle, "active", &active, NULL); return active; #endif } void show_error_dialog (GtkWidget *widget, const char* message) { GtkWindow *window = GTK_WINDOW (gtk_widget_get_toplevel (widget)); GtkWidget *w; w = gtk_message_dialog_new (window, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", message); gtk_dialog_run (GTK_DIALOG (w)); gtk_widget_destroy (w); } static void remove_child (GtkWidget *widget, GtkContainer *container) { gtk_container_remove (container, widget); } static void change_service_clicked_cb (GtkButton *btn, app_data *data) { /* data->open_current = TRUE; */ show_services_list (data, NULL); } static void emergency_clicked_cb (GtkButton *btn, app_data *data) { show_emergency_view (data); } char* get_pretty_source_name (const char *source_name) { /* TRANSLATORS: There have been name changes to keep things in line with * the rest of the moblin UI. Please make sure the name you use matches * the ones in e.g. the panels. */ if (strcmp (source_name, "addressbook") == 0) { return g_strdup (_("Contacts")); } else if (strcmp (source_name, "calendar") == 0) { return g_strdup (_("Appointments")); } else if (strcmp (source_name, "todo") == 0) { return g_strdup (_("Tasks")); } else if (strcmp (source_name, "memo") == 0) { return g_strdup (_("Notes")); } else if (strcmp (source_name, "calendar+todo") == 0) { /* TRANSLATORS: This is a "combination source" for syncing with devices * that combine appointments and tasks. the name should match the ones * used for calendar and todo above */ return g_strdup (_("Appointments & Tasks")); } else { char *tmp; tmp = g_strdup (source_name); tmp[0] = g_ascii_toupper (tmp[0]); return tmp; } } char* get_pretty_source_name_markup (const char *source_name) { char *plain, *markup; plain = get_pretty_source_name (source_name); markup = g_markup_escape_text (plain, -1); g_free (plain); return markup; } static void reload_config (app_data *data, const char *server) { server_config_free (data->current_service); data->forced_emergency = FALSE; g_hash_table_remove_all (data->emergency_sources); if (!server || strlen (server) == 0) { data->current_service = NULL; update_service_ui (data); set_app_state (data, SYNC_UI_STATE_SERVER_OK); } else { data->synced_this_session = FALSE; data->current_service = g_slice_new0 (server_config); data->current_service->name = g_strdup (server); set_app_state (data, SYNC_UI_STATE_GETTING_SERVER); syncevo_server_get_config (data->server, data->current_service->name, FALSE, (SyncevoServerGetConfigCb)get_config_for_main_win_cb, data); } } static void abort_sync_cb (SyncevoSession *session, GError *error, app_data *data) { if (error) { /* TODO show in UI: failed to abort sync (while syncing) */ g_error_free (error); } /* status change handler takes care of updating UI */ } static void sync_cb (SyncevoSession *session, GError *error, app_data *data) { if (error) { /* TODO show in UI: sync failed (failed to even start) */ g_error_free (error); g_object_unref (session); return; } set_sync_progress (data, 0.0, _("Starting sync")); /* stop updates of "last synced" */ if (data->last_sync_src_id > 0) g_source_remove (data->last_sync_src_id); set_app_state (data, SYNC_UI_STATE_SYNCING); } gboolean show_confirmation (GtkWidget *widget, const char *message, const char *yes, const char *no) { GtkWidget *w; int ret; w = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (widget)), GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message); gtk_dialog_add_buttons (GTK_DIALOG (w), no, GTK_RESPONSE_NO, yes, GTK_RESPONSE_YES, NULL); ret = gtk_dialog_run (GTK_DIALOG (w)); gtk_widget_destroy (w); return (ret == GTK_RESPONSE_YES); } static void slow_sync (app_data *data) { operation_data *op_data; char *message; /* TRANSLATORS: slow sync confirmation dialog message. Placeholder * is service/device name */ message = g_strdup_printf (_("Do you want to slow sync with %s?"), data->current_service->pretty_name); /* TRANSLATORS: slow sync confirmation dialog buttons */ if (!show_confirmation (data->sync_win, message, _("Yes, do slow sync"), _("No, cancel sync"))) { g_free (message); return; } g_free (message); op_data = g_slice_new (operation_data); op_data->data = data; op_data->operation = OP_SYNC_SLOW; op_data->started = FALSE; syncevo_server_start_session (data->server, data->current_service->name, (SyncevoServerStartSessionCb)start_session_cb, op_data); show_main_view (data); } static void slow_sync_clicked_cb (GtkButton *btn, app_data *data) { slow_sync (data); } static void refresh_from_server_clicked_cb (GtkButton *btn, app_data *data) { operation_data *op_data; char *message; /* TRANSLATORS: confirmation dialog for "refresh from peer". Placeholder * is service/device name */ message = g_strdup_printf (_("Do you want to delete all local data and replace it with " "data from %s? This is not usually advised."), data->current_service->pretty_name); /* TRANSLATORS: "refresh from peer" confirmation dialog buttons */ if (!show_confirmation (data->sync_win, message, _("Yes, delete and replace"), _("No"))) { g_free (message); return; } g_free (message); op_data = g_slice_new (operation_data); op_data->data = data; op_data->operation = peer_is_client (data->current_service->config) ? OP_SYNC_REFRESH_FROM_CLIENT : OP_SYNC_REFRESH_FROM_SERVER; op_data->started = FALSE; syncevo_server_start_session (data->server, data->current_service->name, (SyncevoServerStartSessionCb)start_session_cb, op_data); show_main_view (data); } static void refresh_from_client_clicked_cb (GtkButton *btn, app_data *data) { operation_data *op_data; char *message; /* TRANSLATORS: confirmation dialog for "refresh from local side". Placeholder * is service/device name */ message = g_strdup_printf (_("Do you want to delete all data in %s and replace it with " "your local data? This is not usually advised."), data->current_service->pretty_name); /* TRANSLATORS: "refresh from local side" confirmation dialog buttons */ if (!show_confirmation (data->sync_win, message, _("Yes, delete and replace"), _("No"))) { g_free (message); return; } g_free (message); op_data = g_slice_new (operation_data); op_data->data = data; op_data->operation = peer_is_client (data->current_service->config) ? OP_SYNC_REFRESH_FROM_SERVER : OP_SYNC_REFRESH_FROM_CLIENT; op_data->started = FALSE; syncevo_server_start_session (data->server, data->current_service->name, (SyncevoServerStartSessionCb)start_session_cb, op_data); show_main_view (data); } static void start_sync (app_data *data) { operation_data *op_data; if (data->syncing) { syncevo_session_abort (data->running_session, (SyncevoSessionGenericCb)abort_sync_cb, data); set_sync_progress (data, -1.0, _("Trying to cancel sync")); } else { op_data = g_slice_new (operation_data); op_data->data = data; op_data->operation = OP_SYNC; op_data->started = FALSE; syncevo_server_start_session (data->server, data->current_service->name, (SyncevoServerStartSessionCb)start_session_cb, op_data); } } static void sync_clicked_cb (GtkButton *btn, app_data *data) { g_return_if_fail (data->current_service); start_sync (data); } #define DAY 60 * 60 * 24 #define HALF_DAY 60 * 60 * 12 #define HOUR 60 * 60 #define HALF_HOUR 60 * 30 #define MINUTE 60 #define HALF_MINUTE 30 static gboolean refresh_last_synced_label (app_data *data) { GTimeVal val; glong diff; char *msg; int delay; g_get_current_time (&val); diff = val.tv_sec - data->last_sync; if (!data->current_service) { msg = g_strdup (_("No service or device selected")); delay = -1; } else if (data->last_sync <= 0) { msg = g_strdup (data->current_service->pretty_name); /* we don't know */ delay = -1; } else if (diff < HALF_MINUTE) { /* TRANSLATORS: This is the title on main view. Placeholder is * the service name. Example: "Google - synced just now" */ msg = g_strdup_printf (_("%s - synced just now"), data->current_service->pretty_name); delay = 10; } else if (diff < MINUTE + HALF_MINUTE) { msg = g_strdup_printf (_("%s - synced a minute ago"), data->current_service->pretty_name); delay = MINUTE; } else if (diff < HOUR) { msg = g_strdup_printf (_("%s - synced %ld minutes ago"), data->current_service->pretty_name, (diff + HALF_MINUTE) / MINUTE); delay = MINUTE; } else if (diff < HOUR + HALF_HOUR) { msg = g_strdup_printf (_("%s - synced an hour ago"), data->current_service->pretty_name); delay = HOUR; } else if (diff < DAY) { msg = g_strdup_printf (_("%s - synced %ld hours ago"), data->current_service->pretty_name, (diff + HALF_HOUR) / (HOUR)); delay = HOUR; } else if (diff < DAY + HALF_DAY) { msg = g_strdup_printf (_("%s - synced a day ago"), data->current_service->pretty_name); delay = HOUR; } else { msg = g_strdup_printf (_("%s - synced %ld days ago"), data->current_service->pretty_name, (diff + HALF_DAY) / (DAY)); delay = HOUR; } gtk_label_set_text (GTK_LABEL (data->server_label), msg); g_free (msg); if (data->last_sync_src_id > 0) g_source_remove (data->last_sync_src_id); if (delay > 0) data->last_sync_src_id = g_timeout_add_seconds (delay, (GSourceFunc)refresh_last_synced_label, data); return FALSE; } static void set_sync_progress (app_data *data, float progress, char *status) { if (progress >= 0) gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (data->progress), progress); if (status) gtk_progress_bar_set_text (GTK_PROGRESS_BAR (data->progress), status); } static void set_info_bar (GtkWidget *widget, GtkMessageType type, SyncErrorResponse response_id, const char *message) { GtkWidget *container, *label; GtkInfoBar *bar = GTK_INFO_BAR (widget); if (!message) { gtk_widget_hide (widget); return; } container = gtk_info_bar_get_action_area (bar); gtk_container_foreach (GTK_CONTAINER (container), (GtkCallback)remove_child, container); switch (response_id) { case SYNC_ERROR_RESPONSE_SYNC: /* TRANSLATORS: Action button in info bar in main view. Shown with e.g. * "You've just restored a backup. The changes have not been " * "synced with %s yet" */ gtk_info_bar_add_button (bar, _("Sync now"), response_id); break; case SYNC_ERROR_RESPONSE_EMERGENCY: /* TRANSLATORS: Action button in info bar in main view. Shown with e.g. * "A normal sync is not possible at this time..." message. * "Other options" will open Emergency view */ gtk_info_bar_add_button (bar, _("Slow sync"), SYNC_ERROR_RESPONSE_EMERGENCY_SLOW_SYNC); gtk_info_bar_add_button (bar, _("Other options..."), response_id); break; case SYNC_ERROR_RESPONSE_SETTINGS_SELECT: /* TRANSLATORS: Action button in info bar in main view. Shown e.g. * when no service is selected. Will open configuration view */ gtk_info_bar_add_button (bar, _("Select sync service"), response_id); break; case SYNC_ERROR_RESPONSE_SETTINGS_OPEN: /* TRANSLATORS: Action button in info bar in main view. Shown e.g. * login to service fails. Will open configuration view for this service */ gtk_info_bar_add_button (bar, _("Edit service settings"), response_id); break; case SYNC_ERROR_RESPONSE_NONE: break; default: g_warn_if_reached (); } gtk_info_bar_set_message_type (bar, type); container = gtk_info_bar_get_content_area (bar); gtk_container_foreach (GTK_CONTAINER (container), (GtkCallback)remove_child, container); label = gtk_label_new (message); gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); gtk_widget_set_size_request (label, 450, -1); gtk_box_pack_start (GTK_BOX (container), label, FALSE, FALSE, 8); gtk_widget_show (label); gtk_widget_show (widget); } static void set_app_state (app_data *data, app_state state) { if (state != SYNC_UI_STATE_CURRENT_STATE) data->current_state = state; switch (data->current_state) { case SYNC_UI_STATE_GETTING_SERVER: gtk_widget_hide (data->service_box); gtk_widget_hide (data->info_bar); gtk_label_set_text (GTK_LABEL (data->sync_status_label), ""); refresh_last_synced_label (data); gtk_widget_set_sensitive (data->main_frame, TRUE); gtk_widget_set_sensitive (data->sync_btn, FALSE); gtk_widget_set_sensitive (data->change_service_btn, FALSE); gtk_widget_set_sensitive (data->emergency_btn, FALSE); if (data->settings_btn) gtk_widget_set_sensitive (data->settings_btn, FALSE); break; case SYNC_UI_STATE_SERVER_FAILURE: gtk_widget_hide (data->service_box); gtk_widget_hide (data->autosync_box); gtk_widget_hide (data->progress); refresh_last_synced_label (data); /* info bar content should be set earlier */ gtk_widget_show (data->info_bar); gtk_label_set_text (GTK_LABEL (data->sync_status_label), ""); gtk_widget_set_sensitive (data->main_frame, FALSE); gtk_widget_set_sensitive (data->sync_btn, FALSE); gtk_widget_set_sensitive (data->emergency_btn, FALSE); gtk_widget_set_sensitive (data->change_service_btn, FALSE); if (data->settings_btn) gtk_widget_set_sensitive (data->settings_btn, FALSE); break; case SYNC_UI_STATE_SERVER_OK: if (data->online) { gtk_widget_hide (data->no_connection_box); } else { gtk_widget_show (data->no_connection_box); } /* TRANSLATORS: These are for the button in main view, right side. Keep line length below ~20 characters, use two lines if needed */ gtk_button_set_label (GTK_BUTTON (data->sync_btn), _("Sync now")); if (!data->current_service) { gtk_widget_hide (data->service_box); gtk_widget_hide (data->autosync_box); gtk_widget_hide (data->progress); set_info_bar (data->info_bar, GTK_MESSAGE_INFO, SYNC_ERROR_RESPONSE_SETTINGS_SELECT, _("You haven't selected a sync service or device yet. " "Sync services let you synchronize your data " "between your netbook and a web service. You can " "also sync directly with some devices.")); refresh_last_synced_label (data); gtk_label_set_text (GTK_LABEL (data->sync_status_label), ""); gtk_widget_set_sensitive (data->sync_btn, FALSE); gtk_widget_set_sensitive (data->emergency_btn, FALSE); gtk_window_set_focus (GTK_WINDOW (data->sync_win), data->change_service_btn); } else { gtk_widget_hide (data->info_bar); gtk_widget_show (data->service_box); gtk_widget_show (data->autosync_box); gtk_widget_set_sensitive (data->sync_btn, data->online); gtk_widget_set_sensitive (data->emergency_btn, TRUE); if (data->synced_this_session && data->current_operation != OP_RESTORE) { gtk_button_set_label (GTK_BUTTON (data->sync_btn), _("Sync again")); } else { gtk_widget_hide (data->progress); } gtk_window_set_focus (GTK_WINDOW (data->sync_win), data->sync_btn); } gtk_widget_set_sensitive (data->main_frame, TRUE); gtk_widget_set_sensitive (data->change_service_btn, TRUE); if (data->settings_btn) gtk_widget_set_sensitive (data->settings_btn, TRUE); data->syncing = FALSE; break; case SYNC_UI_STATE_SYNCING: /* we have a active session, and a session is running (the running session may or may not be ours) */ gtk_widget_show (data->progress); if (data->current_operation == OP_RESTORE) { gtk_label_set_text (GTK_LABEL (data->sync_status_label), _("Restoring")); } else { gtk_label_set_text (GTK_LABEL (data->sync_status_label), _("Syncing")); } gtk_widget_set_sensitive (data->main_frame, FALSE); gtk_widget_set_sensitive (data->change_service_btn, FALSE); gtk_widget_set_sensitive (data->emergency_btn, FALSE); if (data->settings_btn) gtk_widget_set_sensitive (data->settings_btn, FALSE); gtk_widget_set_sensitive (data->sync_btn, support_canceling && data->current_operation != OP_RESTORE); if (support_canceling && support_canceling && data->current_operation != OP_RESTORE) { /* TRANSLATORS: This is for the button in main view, right side. Keep line length below ~20 characters, use two lines if needed */ gtk_button_set_label (GTK_BUTTON (data->sync_btn), _("Cancel sync")); } data->syncing = TRUE; break; default: g_assert_not_reached (); } } #ifdef USE_MOBLIN_UX static GtkWidget* switch_dummy_to_mux_frame (GtkWidget *dummy) { GtkWidget *frame, *parent; const char *title; g_assert (GTK_IS_BIN (dummy)); frame = mux_frame_new (); gtk_widget_set_name (frame, gtk_widget_get_name (dummy)); title = gtk_frame_get_label (GTK_FRAME(dummy)); if (title && strlen (title) > 0) gtk_frame_set_label (GTK_FRAME (frame), title); parent = gtk_widget_get_parent (dummy); g_assert (GTK_IS_BOX (parent)); gtk_widget_reparent (gtk_bin_get_child (GTK_BIN (dummy)), frame); gtk_container_remove (GTK_CONTAINER (parent), dummy); gtk_box_pack_start (GTK_BOX (parent), frame, TRUE, TRUE, 0); gtk_widget_show (frame); return frame; } static void set_page (app_data *data, int page) { int current = gtk_notebook_get_current_page (GTK_NOTEBOOK (data->notebook)); if (page != current) { gtk_notebook_set_current_page (GTK_NOTEBOOK (data->notebook), page); if (page != PAGE_MAIN) { gtk_widget_show (data->back_btn); } else { gtk_widget_hide (data->back_btn); } /* make sure the toggle is correct */ g_signal_handler_block (data->settings_btn, data->settings_id); if (page == PAGE_SETTINGS) { gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (data->settings_btn), TRUE); } else if (current == PAGE_SETTINGS) { gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (data->settings_btn), FALSE); } g_signal_handler_unblock (data->settings_btn, data->settings_id); } gtk_window_present (GTK_WINDOW (data->sync_win)); } static void settings_toggled (GtkToggleButton *button, app_data *data) { int page = gtk_notebook_get_current_page (GTK_NOTEBOOK (data->notebook)); if (page == PAGE_SETTINGS) { show_main_view (data); } else { show_services_list (data, NULL); } } static gboolean key_press_cb (GtkWidget *widget, GdkEventKey *event, app_data *data) { int page = gtk_notebook_get_current_page (GTK_NOTEBOOK (data->notebook)); if (event->keyval == GDK_KEY_Escape && page != PAGE_MAIN) { show_main_view (data); } return FALSE; } /* For some reason metacity sometimes won't maximize but will if asked * another time. For the record, I'm not proud of writing this */ static gboolean try_maximize (GtkWindow *win) { static int count = 0; count++; gtk_window_maximize (win); return (count < 10); } static void setup_windows (app_data *data, GtkWidget *main, GtkWidget *settings, GtkWidget *emergency) { GtkWidget *tmp, *toolbar, *close_btn; GtkToolItem *item; g_assert (GTK_IS_WINDOW (main)); g_assert (GTK_IS_WINDOW (settings)); g_assert (GTK_IS_WINDOW (emergency)); data->sync_win = main; data->services_win = NULL; data->emergency_win = NULL; /* populate the notebook with window contents */ data->notebook = gtk_notebook_new (); gtk_widget_show (data->notebook); gtk_notebook_set_show_tabs (GTK_NOTEBOOK (data->notebook), FALSE); gtk_notebook_set_show_border (GTK_NOTEBOOK (data->notebook), FALSE); gtk_window_maximize (GTK_WINDOW (data->sync_win)); g_timeout_add (10, (GSourceFunc)try_maximize, data->sync_win); gtk_window_set_decorated (GTK_WINDOW (data->sync_win), FALSE); gtk_widget_set_name (data->sync_win, "meego_win"); g_signal_connect (data->sync_win, "key-press-event", G_CALLBACK (key_press_cb), data); tmp = g_object_ref (gtk_bin_get_child (GTK_BIN (data->sync_win))); gtk_container_remove (GTK_CONTAINER (data->sync_win), tmp); gtk_notebook_append_page (GTK_NOTEBOOK (data->notebook), tmp, NULL); g_object_unref (tmp); tmp = g_object_ref (gtk_bin_get_child (GTK_BIN (settings))); gtk_container_remove (GTK_CONTAINER (settings), tmp); gtk_notebook_append_page (GTK_NOTEBOOK (data->notebook), tmp, NULL); g_object_unref (tmp); tmp = g_object_ref (gtk_bin_get_child (GTK_BIN (emergency))); gtk_container_remove (GTK_CONTAINER (emergency), tmp); gtk_notebook_append_page (GTK_NOTEBOOK (data->notebook), tmp, NULL); g_object_unref (tmp); tmp = gtk_vbox_new (FALSE, 0); gtk_widget_show (tmp); gtk_container_add (GTK_CONTAINER (data->sync_win), tmp); gtk_box_pack_end (GTK_BOX (tmp), data->notebook, TRUE, TRUE, 0); /* create the window toolbar */ toolbar = gtk_toolbar_new (); gtk_widget_set_name (toolbar, "moblin-toolbar"); gtk_box_pack_start (GTK_BOX (tmp), toolbar, FALSE, FALSE, 0); data->back_btn = gtk_button_new_with_label (_("Back to sync")); gtk_widget_set_name (data->back_btn, "moblin-toolbar-button"); gtk_widget_set_can_focus (data->back_btn, FALSE); gtk_widget_set_no_show_all (data->back_btn, TRUE); g_signal_connect_swapped (data->back_btn, "clicked", G_CALLBACK (show_main_view), data); item = gtk_tool_item_new (); gtk_container_add (GTK_CONTAINER (item), data->back_btn); gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, 0); item = gtk_tool_item_new (); gtk_tool_item_set_expand (item, TRUE); gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, 1); data->settings_btn = gtk_toggle_button_new (); gtk_widget_set_can_focus (data->settings_btn, FALSE); gtk_widget_set_name (data->settings_btn, "moblin-settings-button"); data->settings_id = g_signal_connect (data->settings_btn, "toggled", G_CALLBACK (settings_toggled), data); gtk_container_add (GTK_CONTAINER (data->settings_btn), gtk_image_new_from_icon_name ("preferences-other", GTK_ICON_SIZE_LARGE_TOOLBAR)); item = gtk_tool_item_new (); gtk_container_add (GTK_CONTAINER (item), data->settings_btn); gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1); close_btn = gtk_button_new (); gtk_widget_set_can_focus (close_btn, FALSE); gtk_widget_set_name (close_btn, "moblin-close-button"); g_signal_connect (close_btn, "clicked", G_CALLBACK (gtk_main_quit), NULL); gtk_container_add (GTK_CONTAINER (close_btn), gtk_image_new_from_icon_name ("window-close", GTK_ICON_SIZE_LARGE_TOOLBAR)); item = gtk_tool_item_new (); gtk_container_add (GTK_CONTAINER (item), close_btn); gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1); gtk_widget_show_all (toolbar); /* no need for close buttons */ gtk_widget_hide (data->settings_close_btn); gtk_widget_hide (data->emergency_close_btn); } static void show_emergency_view (app_data *data) { update_emergency_view (data); set_page (data, PAGE_EMERGENCY); } static void show_services_list (app_data *data, const char *config_id_to_open) { g_free (data->config_id_to_open); data->config_id_to_open = g_strdup (config_id_to_open); set_page (data, PAGE_SETTINGS); update_services_list (data); } static void show_main_view (app_data *data) { set_page (data, PAGE_MAIN); } #else /* return the placeholders themselves when not using Moblin UX */ static GtkWidget* switch_dummy_to_mux_frame (GtkWidget *dummy) { return dummy; } static void setup_windows (app_data *data, GtkWidget *main, GtkWidget *settings, GtkWidget *emergency) { data->sync_win = main; data->services_win = settings; data->emergency_win = emergency; gtk_window_set_transient_for (GTK_WINDOW (data->services_win), GTK_WINDOW (data->sync_win)); gtk_window_set_transient_for (GTK_WINDOW (data->emergency_win), GTK_WINDOW (data->sync_win)); g_signal_connect (data->services_win, "delete-event", G_CALLBACK (gtk_widget_hide_on_delete), NULL); g_signal_connect (data->emergency_win, "delete-event", G_CALLBACK (gtk_widget_hide_on_delete), NULL); } static void show_emergency_view (app_data *data) { update_emergency_view (data); gtk_widget_hide (data->services_win); gtk_window_present (GTK_WINDOW (data->emergency_win)); } static void show_services_list (app_data *data, const char *config_id_to_open) { g_free (data->config_id_to_open); data->config_id_to_open = g_strdup (config_id_to_open); gtk_widget_hide (data->emergency_win); gtk_window_present (GTK_WINDOW (data->services_win)); update_services_list (data); } static void show_main_view (app_data *data) { gtk_widget_hide (data->services_win); gtk_widget_hide (data->emergency_win); gtk_window_present (GTK_WINDOW (data->sync_win)); } #endif /* This is a hacky way to achieve autoscrolling when the expanders open/close */ static void services_box_allocate_cb (GtkWidget *widget, GtkAllocation *allocation, app_data *data) { if (GTK_IS_WIDGET (data->expanded_config)) { int y; GtkAdjustment *adj; GtkAllocation alloc; gtk_widget_translate_coordinates (data->expanded_config, data->services_box, 0, 0, NULL, &y); gtk_widget_get_allocation (data->expanded_config, &alloc); adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (data->scrolled_window)); gtk_adjustment_clamp_page (adj, y, y + alloc.height); data->expanded_config = NULL; } } static void info_bar_response_cb (GtkInfoBar *info_bar, SyncErrorResponse response_id, app_data *data) { switch (response_id) { case SYNC_ERROR_RESPONSE_SYNC: start_sync (data); break; case SYNC_ERROR_RESPONSE_EMERGENCY_SLOW_SYNC: slow_sync (data); break; case SYNC_ERROR_RESPONSE_EMERGENCY: show_emergency_view (data); break; case SYNC_ERROR_RESPONSE_SETTINGS_OPEN: data->open_current = TRUE; show_services_list (data, NULL); break; case SYNC_ERROR_RESPONSE_SETTINGS_SELECT: show_services_list (data, NULL); break; default: g_warn_if_reached (); } } static void new_device_clicked_cb (GtkButton *btn, app_data *data) { DBusGProxy *proxy; DBusGConnection *bus; char *argv[2] = {"bluetooth-wizard", NULL}; GError *error = NULL; switch (data->bluetooth_wizard) { case SYNC_BLUETOOTH_MOBLIN: bus = dbus_g_bus_get (DBUS_BUS_SESSION, NULL); if (bus) { proxy = dbus_g_proxy_new_for_name (bus, "org.moblin.UX.Shell.Toolbar", "/org/moblin/UX/Shell/Toolbar", "org.moblin.UX.Shell.Toolbar"); dbus_g_proxy_call_no_reply (proxy, "ShowPanel", G_TYPE_STRING, "bluetooth-panel", G_TYPE_INVALID, G_TYPE_INVALID); g_object_unref (proxy); } break; case SYNC_BLUETOOTH_GNOME: if (!gdk_spawn_on_screen (gtk_window_get_screen (GTK_WINDOW (data->sync_win)), NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error)) { g_warning ("Failed to spawn bluetooth-wizard: %s", error->message); g_error_free (error); return; } break; default: ; } } static void name_has_owner_cb (DBusGProxy *proxy, gboolean has_owner, GError *error, app_data *data) { if (has_owner) { gtk_widget_show (data->new_device_btn); data->bluetooth_wizard = SYNC_BLUETOOTH_MOBLIN; } g_object_unref (proxy); } static void init_bluetooth_ui (app_data *data) { char *bt_wizard; DBusGConnection *bus; DBusGProxy *proxy; data->bluetooth_wizard = SYNC_BLUETOOTH_NONE; /* look for gnome bluetooth wizard first */ bt_wizard = g_find_program_in_path ("bluetooth-wizard"); if (bt_wizard) { gtk_widget_show (data->new_device_btn); data->bluetooth_wizard = SYNC_BLUETOOTH_GNOME; g_free (bt_wizard); } else { /* try Moblin shell next (bluetooth panel integrates bt wizard) */ bus = dbus_g_bus_get (DBUS_BUS_SESSION, NULL); proxy = dbus_g_proxy_new_for_name (bus, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS); if (proxy) { org_freedesktop_DBus_name_has_owner_async (proxy, "org.moblin.UX.Shell.Toolbar", (org_freedesktop_DBus_name_has_owner_reply)name_has_owner_cb, data); } } } static void autosync_toggle_cb (GtkWidget *widget, gpointer x, app_data *data) { if (data->current_service && data->current_service->config) { gboolean new_active, old_active; char *autosync = NULL; new_active = toggle_get_active (widget); syncevo_config_get_value (data->current_service->config, NULL, "autoSync", &autosync); old_active = (g_strcmp0 (autosync, "1") == 0); if (old_active != new_active) { char *new_val; operation_data *op_data; new_val = new_active ? "1": "0"; syncevo_config_set_value (data->current_service->config, NULL, "autoSync", new_val); op_data = g_slice_new (operation_data); op_data->data = data; op_data->operation = OP_SAVE; op_data->started = FALSE; syncevo_server_start_no_sync_session (data->server, data->current_service->name, (SyncevoServerStartSessionCb)start_session_cb, op_data); } } } static void build_autosync_ui (app_data *data) { char *txt; /* TRANSLATORS: label for checkbutton/toggle in main view. * Please stick to similar length strings or break the line with * "\n" if absolutely needed */ txt = _("Automatic sync"); #ifdef USE_MOBLIN_UX GtkWidget *lbl; lbl = gtk_label_new (txt); gtk_widget_show (lbl); gtk_box_pack_end (GTK_BOX (data->autosync_box), lbl, FALSE, FALSE, 0); data->autosync_toggle = mx_gtk_light_switch_new (); gtk_widget_show (data->autosync_toggle); gtk_box_pack_end (GTK_BOX (data->autosync_box), data->autosync_toggle, FALSE, FALSE, 0); g_signal_connect (data->autosync_toggle, "switch-flipped", G_CALLBACK (autosync_toggle_cb), data); #else GtkWidget *align; align = gtk_alignment_new (0.5, 1.0, 1.0, 0.0); gtk_widget_show (align); gtk_box_pack_start (GTK_BOX (data->autosync_box), align, TRUE, TRUE, 0); data->autosync_toggle = gtk_check_button_new_with_label (txt); gtk_container_add (GTK_CONTAINER (align), data->autosync_toggle); g_signal_connect (data->autosync_toggle, "notify::active", G_CALLBACK (autosync_toggle_cb), data); #endif gtk_widget_show (data->autosync_toggle); } static void glade_name_workaround (GtkBuilder *builder, const char *name) { GtkWidget *w; w = GTK_WIDGET (gtk_builder_get_object (builder, name)); if (w) { gtk_widget_set_name (w, name); } } static gboolean init_ui (app_data *data) { GtkBuilder *builder; GError *error = NULL; GtkWidget /* *frame, */ * service_error_box, *btn; GtkAdjustment *adj; gtk_rc_parse (THEMEDIR "sync-ui.rc"); builder = gtk_builder_new (); gtk_builder_add_from_file (builder, GLADEDIR "ui.xml", &error); if (error) { g_printerr ("Failed to load user interface from %s: %s\n", GLADEDIR "ui.xml", error->message); g_error_free (error); g_object_unref (builder); return FALSE; } data->service_box = GTK_WIDGET (gtk_builder_get_object (builder, "service_box")); service_error_box = GTK_WIDGET (gtk_builder_get_object (builder, "service_error_box")); data->info_bar = gtk_info_bar_new (); gtk_widget_set_no_show_all (data->info_bar, TRUE); g_signal_connect (data->info_bar, "response", G_CALLBACK (info_bar_response_cb), data); gtk_box_pack_start (GTK_BOX (service_error_box), data->info_bar, TRUE, TRUE, 16); data->no_connection_box = GTK_WIDGET (gtk_builder_get_object (builder, "no_connection_box")); data->server_icon_box = GTK_WIDGET (gtk_builder_get_object (builder, "server_icon_box")); data->offline_label = GTK_WIDGET (gtk_builder_get_object (builder, "offline_label")); data->progress = GTK_WIDGET (gtk_builder_get_object (builder, "progressbar")); data->change_service_btn = GTK_WIDGET (gtk_builder_get_object (builder, "change_service_btn")); data->emergency_btn = GTK_WIDGET (gtk_builder_get_object (builder, "emergency_btn")); data->sync_btn = GTK_WIDGET (gtk_builder_get_object (builder, "sync_btn")); data->sync_status_label = GTK_WIDGET (gtk_builder_get_object (builder, "sync_status_label")); data->spinner_image = GTK_WIDGET (gtk_builder_get_object (builder, "spinner_image")); gtk_image_set_from_file (GTK_IMAGE (data->spinner_image), THEMEDIR "sync-spinner.gif"); gtk_widget_set_no_show_all (data->spinner_image, TRUE); gtk_widget_hide (data->spinner_image); data->autosync_box = GTK_WIDGET (gtk_builder_get_object (builder, "autosync_box")); build_autosync_ui (data); data->server_label = GTK_WIDGET (gtk_builder_get_object (builder, "sync_service_label")); data->last_synced_label = GTK_WIDGET (gtk_builder_get_object (builder, "last_synced_label")); data->sources_box = GTK_WIDGET (gtk_builder_get_object (builder, "sources_box")); data->new_service_btn = GTK_WIDGET (gtk_builder_get_object (builder, "new_service_btn")); gtk_widget_set_size_request (data->new_service_btn, SYNC_UI_LIST_BTN_WIDTH, SYNC_UI_LIST_ICON_SIZE); g_signal_connect (data->new_service_btn, "clicked", G_CALLBACK (setup_new_service_clicked), data); /* service list view */ data->scrolled_window = GTK_WIDGET (gtk_builder_get_object (builder, "scrolledwindow")); adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (data->scrolled_window)); data->services_box = GTK_WIDGET (gtk_builder_get_object (builder, "services_box")); gtk_container_set_focus_vadjustment (GTK_CONTAINER (data->services_box), adj); g_signal_connect(data->services_box, "size-allocate", G_CALLBACK (services_box_allocate_cb), data); data->devices_box = GTK_WIDGET (gtk_builder_get_object (builder, "devices_box")); data->settings_close_btn = GTK_WIDGET (gtk_builder_get_object (builder, "settings_close_btn")); /* emergency view */ btn = GTK_WIDGET (gtk_builder_get_object (builder, "slow_sync_btn")); g_signal_connect (btn, "clicked", G_CALLBACK (slow_sync_clicked_cb), data); data->refresh_from_server_btn_label = GTK_WIDGET (gtk_builder_get_object (builder, "refresh_from_server_btn_label")); g_signal_connect (GTK_WIDGET (gtk_builder_get_object (builder, "refresh_from_server_btn")), "clicked", G_CALLBACK (refresh_from_server_clicked_cb), data); data->refresh_from_client_btn_label = GTK_WIDGET (gtk_builder_get_object (builder, "refresh_from_client_btn_label")); g_signal_connect (GTK_WIDGET (gtk_builder_get_object (builder, "refresh_from_client_btn")), "clicked", G_CALLBACK (refresh_from_client_clicked_cb), data); data->emergency_label = GTK_WIDGET (gtk_builder_get_object (builder, "emergency_label")); data->emergency_expander = GTK_WIDGET (gtk_builder_get_object (builder, "emergency_expander")); data->emergency_source_table = GTK_WIDGET (gtk_builder_get_object (builder, "emergency_source_table")); data->emergency_backup_table = GTK_WIDGET (gtk_builder_get_object (builder, "emergency_backup_table")); data->emergency_close_btn = GTK_WIDGET (gtk_builder_get_object (builder, "emergency_close_btn")); /* No (documented) way to add own widgets to gtkbuilder it seems... swap the all dummy widgets with Muxwidgets */ setup_windows (data, GTK_WIDGET (gtk_builder_get_object (builder, "sync_win")), GTK_WIDGET (gtk_builder_get_object (builder, "services_win")), GTK_WIDGET (gtk_builder_get_object (builder, "emergency_win"))); data->main_frame = switch_dummy_to_mux_frame (GTK_WIDGET (gtk_builder_get_object (builder, "main_frame"))); data->log_frame = switch_dummy_to_mux_frame (GTK_WIDGET (gtk_builder_get_object (builder, "log_frame"))); /* frame = */ switch_dummy_to_mux_frame (GTK_WIDGET (gtk_builder_get_object (builder, "services_list_frame"))); /* frame = */ switch_dummy_to_mux_frame (GTK_WIDGET (gtk_builder_get_object (builder, "emergency_frame"))); g_signal_connect (data->sync_win, "destroy", G_CALLBACK (gtk_main_quit), NULL); g_signal_connect (data->change_service_btn, "clicked", G_CALLBACK (change_service_clicked_cb), data); g_signal_connect (data->emergency_btn, "clicked", G_CALLBACK (emergency_clicked_cb), data); g_signal_connect (data->sync_btn, "clicked", G_CALLBACK (sync_clicked_cb), data); g_signal_connect_swapped (data->emergency_close_btn, "clicked", G_CALLBACK (show_main_view), data); g_signal_connect_swapped (data->settings_close_btn, "clicked", G_CALLBACK (show_main_view), data); g_signal_connect (data->emergency_btn, "clicked", G_CALLBACK (emergency_clicked_cb), data); data->new_device_btn = GTK_WIDGET (gtk_builder_get_object (builder, "new_device_btn")); g_signal_connect (data->new_device_btn, "clicked", G_CALLBACK (new_device_clicked_cb), data); /* workarounds for glade not working with gtkbuilder >= 2.20: * widgets do not get names. */ glade_name_workaround (builder, "meego_win"); glade_name_workaround (builder, "sync_data_and_type_box"); glade_name_workaround (builder, "log_frame"); glade_name_workaround (builder, "backup_frame"); glade_name_workaround (builder, "services_frame"); glade_name_workaround (builder, "sync_service_label"); glade_name_workaround (builder, "sync_status_label"); glade_name_workaround (builder, "no_server_label"); glade_name_workaround (builder, "sync_failure_label"); glade_name_workaround (builder, "sync_btn"); init_bluetooth_ui (data); g_object_unref (builder); return TRUE; } static void load_icon (const char *uri, GtkBox *icon_box, guint icon_size) { GError *error = NULL; GdkPixbuf *pixbuf; GtkWidget *image; const char *filename; if (uri && strlen (uri) > 0) { if (g_str_has_prefix (uri, "file://")) { filename = uri+7; } else { g_warning ("only file:// icon uri is supported: %s", uri); filename = THEMEDIR "sync-generic.png"; } } else { filename = THEMEDIR "sync-generic.png"; } pixbuf = gdk_pixbuf_new_from_file_at_scale (filename, icon_size, icon_size, TRUE, &error); if (!pixbuf) { g_warning ("Failed to load service icon: %s", error->message); g_error_free (error); return; } image = gtk_image_new_from_pixbuf (pixbuf); gtk_widget_set_size_request (image, icon_size, icon_size); g_object_unref (pixbuf); gtk_container_foreach (GTK_CONTAINER(icon_box), (GtkCallback)remove_child, icon_box); gtk_box_pack_start (icon_box, image, FALSE, FALSE, 0); gtk_widget_show (image); } static void emergency_toggle_notify_active_cb (GtkWidget *widget, gpointer p, app_data *data) { gboolean active; char *source; active = toggle_get_active (widget); source = g_object_get_data (G_OBJECT (widget), "source"); g_return_if_fail (source); if (active) { g_hash_table_insert (data->emergency_sources, g_strdup (source), ""); } else { g_hash_table_remove (data->emergency_sources, source); } update_emergency_expander (data); } static GtkWidget* add_emergency_toggle_widget (app_data *data, const char *title, gboolean active, guint row, guint col) { GtkWidget *toggle; #ifdef USE_MOBLIN_UX GtkWidget *label; col = col * 2; label = gtk_label_new (title); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_widget_show (label); gtk_table_attach (GTK_TABLE (data->emergency_source_table), label, col, col + 1, row, row + 1, GTK_FILL, GTK_FILL, 16, 0); toggle = mx_gtk_light_switch_new (); toggle_set_active (toggle, active); g_signal_connect (toggle, "switch-flipped", G_CALLBACK (emergency_toggle_notify_active_cb), data); #else toggle = gtk_check_button_new_with_label (title); toggle_set_active (toggle, active); g_signal_connect (toggle, "notify::active", G_CALLBACK (emergency_toggle_notify_active_cb), data); #endif gtk_widget_show (toggle); gtk_table_attach (GTK_TABLE (data->emergency_source_table), toggle, col + 1, col + 2, row, row + 1, GTK_FILL, GTK_FILL, 0, 0); return toggle; } static void update_emergency_expander (app_data *data) { char *text, *sources = NULL; GHashTableIter iter; char *name; g_hash_table_iter_init (&iter, data->emergency_sources); while (g_hash_table_iter_next (&iter, (gpointer)&name, NULL)) { char *pretty, *tmp; pretty = get_pretty_source_name (name); if (sources) { tmp = g_strdup_printf ("%s, %s", sources, pretty); g_free (sources); g_free (pretty); sources = tmp; } else { sources = pretty; } } if (sources) { /* This is the expander label in emergency view. It summarizes the * currently selected data sources. First placeholder is service/device * name, second a comma separeted list of sources. * E.g. "Affected data: Google Contacts, Appointments" */ text = g_strdup_printf (_("Affected data: %s %s"), data->current_service->pretty_name, sources); g_free (sources); } else { text = g_strdup_printf (_("Affected data: none")); } gtk_expander_set_label (GTK_EXPANDER (data->emergency_expander), text); g_free (text); } static void add_emergency_source (const char *name, GHashTable *source, app_data *data) { source_config *conf; GtkWidget *toggle; guint rows, cols; guint row; static guint col; gboolean active = TRUE; char *pretty_name; conf = g_hash_table_lookup (data->current_service->source_configs, name); g_object_get (data->emergency_source_table, "n-rows", &rows, "n-columns", &cols, NULL); if (cols != 1 && col == 0){ col = 1; row = rows - 1; } else { col = 0; row = rows; } active = (g_hash_table_lookup (data->emergency_sources, name) != NULL); pretty_name = get_pretty_source_name (name); toggle = add_emergency_toggle_widget (data, pretty_name, active, row, col); gtk_widget_set_sensitive (toggle, source_config_is_usable (conf)); g_object_set_data_full (G_OBJECT (toggle), "source", g_strdup (name), g_free); g_free (pretty_name); } static void update_backup_visibilities (app_data *data) { char *key; GHashTableIter iter; GList *l, *widgets; widgets = gtk_container_get_children ( GTK_CONTAINER (data->emergency_backup_table)); gtk_widget_show_all (data->emergency_backup_table); /* hide backup widgets that do not contain selected sources */ g_hash_table_iter_init (&iter, data->emergency_sources); while (g_hash_table_iter_next (&iter, (gpointer)&key, NULL)) { for (l = widgets; l; l = l->next) { if (!g_object_get_data (G_OBJECT (l->data), key)) { gtk_widget_hide (GTK_WIDGET (l->data)); } } } g_list_free (widgets); } static void restore_clicked_cb (GtkButton *btn, app_data *data) { const char *dir, *time_str; operation_data *op_data; char *message; dir = g_object_get_data (G_OBJECT (btn), "dir"); time_str = g_object_get_data (G_OBJECT (btn), "time"); g_return_if_fail (dir && time_str); /* TRANSLATORS: confirmation for restoring a backup. placeholder is the * backup time string defined below */ message = g_strdup_printf (_("Do you want to restore the backup from %s? " "All changes you have made since then will be lost."), time_str); if (!show_confirmation (data->sync_win, message, _("Yes, restore"), _("No"))) { g_free (message); return; } g_free (message); op_data = g_slice_new (operation_data); op_data->data = data; op_data->operation = OP_RESTORE; op_data->dir = dir; op_data->started = FALSE; syncevo_server_start_session (data->server, data->current_service->name, (SyncevoServerStartSessionCb)start_session_cb, op_data); show_main_view (data); } static void add_backup (app_data *data, const char *peername, const char *dir, long endtime, GList *sources) { GtkWidget *timelabel, *label, *blabel, *button, *box;; guint rows; char *text; char time_str[60]; struct tm *tim; tim = localtime (&endtime); /* TRANSLATORS: date/time for strftime(), used in emergency view backup * label. Any time format that shows date and time is good. */ strftime (time_str, sizeof (time_str), _("%x %X"), tim); g_object_get (data->emergency_backup_table, "n-rows", &rows, NULL); box = gtk_vbox_new (TRUE, 6); gtk_table_attach (GTK_TABLE (data->emergency_backup_table), box, 0, 1, rows, rows + 1, GTK_EXPAND|GTK_FILL, GTK_FILL, 16, 0); timelabel = gtk_label_new (time_str); gtk_misc_set_alignment (GTK_MISC (timelabel), 0.0, 0.5); gtk_label_set_line_wrap (GTK_LABEL (timelabel), TRUE); gtk_widget_set_size_request (timelabel, 600, -1); gtk_box_pack_start (GTK_BOX (box), timelabel, TRUE, TRUE, 0); /* TRANSLATORS: label for a backup in emergency view. Placeholder is * service or device name */ text = g_strdup_printf (_("Backed up before syncing with %s"), peername); label = gtk_label_new (text); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); gtk_widget_set_size_request (label, 600, -1); gtk_box_pack_start (GTK_BOX (box), label, TRUE, TRUE, 0); g_free (text); button = gtk_button_new (); gtk_table_attach (GTK_TABLE (data->emergency_backup_table), button, 1, 2, rows, rows + 1, GTK_FILL, GTK_SHRINK, 32, 0); g_object_set_data_full (G_OBJECT (button), "dir", g_strdup(dir), g_free); g_object_set_data_full (G_OBJECT (button), "time", g_strdup(time_str), g_free); g_signal_connect (button, "clicked", G_CALLBACK (restore_clicked_cb), data); blabel = gtk_label_new (_("Restore")); gtk_misc_set_padding (GTK_MISC (blabel), 32, 0); gtk_container_add (GTK_CONTAINER (button), blabel); for (; sources; sources = sources->next) { g_object_set_data (G_OBJECT (box), (char *)sources->data, ""); g_object_set_data (G_OBJECT (button), (char *)sources->data, ""); } } static void get_reports_for_backups_cb (SyncevoServer *server, SyncevoReports *reports, GError *error, app_data *data) { guint len, i; if (error) { g_warning ("Error in Session.GetReports: %s", error->message); g_error_free (error); /* non-fatal, unknown error */ return; } len = syncevo_reports_get_length (reports); for (i = 0; i < len; i++) { GHashTable *report = syncevo_reports_index (reports, i); GHashTableIter iter; char *key, *val; /* long status = -1; */ long endtime = -1; char *peername = NULL; char *dir = NULL; GList *backup_sources = NULL; g_hash_table_iter_init (&iter, report); while (g_hash_table_iter_next (&iter, (gpointer)&key, (gpointer)&val)) { char **strs; strs = g_strsplit (key, "-", 6); if (!strs) { continue; } if (g_strcmp0 (strs[0], "source") == 0 && g_strcmp0 (strs[2], "backup") == 0 && g_strcmp0 (strs[3], "before") == 0) { backup_sources = g_list_prepend (backup_sources, g_strdup (strs[1])); } else if (g_strcmp0 (strs[0], "end") == 0) { endtime = strtol (val, NULL, 10); } else if (g_strcmp0 (strs[0], "status") == 0) { /* status = strtol (val, NULL, 10); */ } else if (g_strcmp0 (strs[0], "peer") == 0) { peername = val; } else if (g_strcmp0 (strs[0], "dir") == 0) { dir = val; } g_strfreev (strs); } if (peername && dir && endtime > 0) { add_backup (data, peername, dir, endtime, backup_sources); } g_list_foreach (backup_sources, (GFunc)g_free, NULL); g_list_free (backup_sources); } data->backup_count += len; if (len == REPORTS_PER_CALL) { syncevo_server_get_reports (data->server, "", data->backup_count, REPORTS_PER_CALL, (SyncevoServerGetReportsCb)get_reports_for_backups_cb, data); } update_backup_visibilities (data); } static const char* get_syncevo_context (const char *config_name) { char *context; context = g_strrstr (config_name, "@"); if (!context) { context = ""; } return context; } static void update_emergency_view (app_data *data) { char *text; if (!data->current_service) { g_warning ("no service defined in Emergency view"); return; } if (data->forced_emergency) { text = g_strdup_printf ( /* TRANSLATORS: this is an explanation in Emergency view. * Placeholder is a service/device name */ _("A normal sync with %s is not possible at this time. " "You can do a slow two-way sync or start from scratch. You " "can also restore a backup, but a slow sync or starting from " "scratch will still be required before normal sync is " "possible."), data->current_service->pretty_name); } else { /* TRANSLATORS: this is an explanation in Emergency view. * Placeholder is a service/device name */ text = g_strdup_printf ( _("If something has gone horribly wrong, you can try a " "slow sync, start from scratch or restore from backup.")); } gtk_label_set_text (GTK_LABEL (data->emergency_label), text); g_free (text); /* TRANSLATORS: These are a buttons in Emergency view. Placeholder is a * service/device name. Please don't use too long lines, but feel free to * use several lines. */ text = g_strdup_printf (_("Delete all your local\n" "data and replace with\n" "data from %s"), data->current_service->pretty_name); gtk_label_set_text (GTK_LABEL (data->refresh_from_server_btn_label), text); g_free (text); text = g_strdup_printf (_("Delete all data on\n" "%s and replace\n" "with your local data"), data->current_service->pretty_name); gtk_label_set_text (GTK_LABEL (data->refresh_from_client_btn_label), text); g_free (text); gtk_container_foreach (GTK_CONTAINER (data->emergency_source_table), (GtkCallback)remove_child, data->emergency_source_table); gtk_table_resize (GTK_TABLE (data->emergency_source_table), 1, 1); /* using this instead of current_service->source_configs * to get the same order as the configuration has... */ syncevo_config_foreach_source (data->current_service->config, (ConfigFunc)add_emergency_source, data); update_emergency_expander (data); data->backup_count = 0; gtk_container_foreach (GTK_CONTAINER (data->emergency_backup_table), (GtkCallback)remove_child, data->emergency_backup_table); gtk_table_resize (GTK_TABLE (data->emergency_backup_table), 1, 1); syncevo_server_get_reports (data->server, get_syncevo_context (data->current_service->name), 0, REPORTS_PER_CALL, (SyncevoServerGetReportsCb)get_reports_for_backups_cb, data); } static void update_service_source_ui (const char *name, source_config *conf, app_data *data) { GtkWidget *lbl, *box; char *pretty_name, *title; if (!source_config_is_usable (conf)) { return; } conf->box = gtk_vbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (data->sources_box), conf->box, FALSE, FALSE, 8); pretty_name = get_pretty_source_name_markup (name); title = g_strdup_printf ("%s", pretty_name); lbl = gtk_label_new (NULL); gtk_label_set_markup (GTK_LABEL (lbl), title); g_free (pretty_name); g_free (title); gtk_misc_set_alignment (GTK_MISC (lbl), 0.0, 0.5); gtk_box_pack_start (GTK_BOX (conf->box), lbl, TRUE, TRUE, 0); box = gtk_hbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (conf->box), box, FALSE, FALSE, 0); conf->info_bar = gtk_info_bar_new (); gtk_box_pack_start (GTK_BOX (box), conf->info_bar, TRUE, TRUE, 16); gtk_widget_set_no_show_all (conf->info_bar, TRUE); g_signal_connect (conf->info_bar, "response", G_CALLBACK (info_bar_response_cb), data); conf->label = gtk_label_new (NULL); gtk_misc_set_alignment (GTK_MISC (conf->label), 0.0, 0.5); gtk_box_pack_start (GTK_BOX (conf->box), conf->label, TRUE, TRUE, 0); source_config_update_widget (conf); gtk_widget_show_all (conf->box); } static void check_source_cb (SyncevoSession *session, GError *error, source_config *source) { if (error) { if(error->code == DBUS_GERROR_REMOTE_EXCEPTION && dbus_g_error_has_name (error, SYNCEVO_DBUS_ERROR_SOURCE_UNUSABLE)) { /* source is not supported locally */ if (source) { source->supported_locally = FALSE; if (source->box) { /* widget is already on screen, hide it */ gtk_widget_hide (source->box); } } } else { g_warning ("CheckSource failed: %s", error->message); /* non-fatal, unknown error */ } g_error_free (error); return; } } static void update_service_ui (app_data *data) { char *icon_uri = NULL; char *autosync = NULL; gtk_container_foreach (GTK_CONTAINER (data->sources_box), (GtkCallback)remove_child, data->sources_box); if (data->current_service && data->current_service->config) { syncevo_config_get_value (data->current_service->config, NULL, "IconURI", &icon_uri); syncevo_config_get_value (data->current_service->config, NULL, "autoSync", &autosync); g_hash_table_foreach (data->current_service->source_configs, (GHFunc)update_service_source_ui, data); } load_icon (icon_uri, GTK_BOX (data->server_icon_box), SYNC_UI_ICON_SIZE); toggle_set_active (data->autosync_toggle, g_strcmp0 (autosync, "1") == 0); refresh_last_synced_label (data); gtk_widget_show_all (data->sources_box); } static void unexpand_config_widget (GtkWidget *w, GtkWidget *exception) { if (SYNC_IS_CONFIG_WIDGET (w) && exception && exception != w) { sync_config_widget_set_expanded (SYNC_CONFIG_WIDGET (w), FALSE); } } static void config_widget_expanded_cb (GtkWidget *widget, GParamSpec *pspec, app_data *data) { if (sync_config_widget_get_expanded (SYNC_CONFIG_WIDGET (widget))) { data->expanded_config = widget; gtk_container_foreach (GTK_CONTAINER (data->services_box), (GtkCallback)unexpand_config_widget, widget); } } static void config_widget_changed_cb (GtkWidget *widget, app_data *data) { if (sync_config_widget_get_current (SYNC_CONFIG_WIDGET (widget))) { const char *name = NULL; name = sync_config_widget_get_name (SYNC_CONFIG_WIDGET (widget)); reload_config (data, name); show_main_view (data); } else { reload_config (data, NULL); update_services_list (data); } } static SyncConfigWidget* add_configuration_to_box (GtkBox *box, SyncevoConfig *config, const char *name, gboolean has_template, gboolean has_configuration, app_data *data) { GtkWidget *item = NULL; gboolean current = FALSE; const char *current_name = NULL; if (data->current_service) { current_name = data->current_service->pretty_name; if (data->current_service->name && name && g_ascii_strcasecmp (name, data->current_service->name) == 0) { current = TRUE; } } item = sync_config_widget_new (data->server, name, config, current, current_name, has_configuration, has_template); g_signal_connect (item, "changed", G_CALLBACK (config_widget_changed_cb), data); g_signal_connect (item, "notify::expanded", G_CALLBACK (config_widget_expanded_cb), data); gtk_widget_show (item); gtk_box_pack_start (box, item, FALSE, FALSE, 0); if (current) { sync_config_widget_set_expanded (SYNC_CONFIG_WIDGET (item), data->open_current); } if (g_strcmp0 (name, "default") == 0) { sync_config_widget_set_expanded (SYNC_CONFIG_WIDGET (item), TRUE); } if (data->config_id_to_open) { sync_config_widget_expand_id (SYNC_CONFIG_WIDGET (item), data->config_id_to_open); } return SYNC_CONFIG_WIDGET (item); } static void find_new_service_config (SyncConfigWidget *w, GtkWidget **found) { if (SYNC_IS_CONFIG_WIDGET (w)) { if (!sync_config_widget_get_configured (w) && !sync_config_widget_get_has_template (w)) { *found = GTK_WIDGET (w); } } } typedef struct config_data { app_data *data; char *name; gboolean has_configuration; gboolean has_template; GHashTable *device_templates; } config_data; #define LEGAL_CONFIG_NAME_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVXYZ1234567890-_" static void get_config_for_config_widget_cb (SyncevoServer *server, SyncevoConfig *config, GError *error, config_data *c_data) { char *ready, *is_peer, *url, *type; c_data->data->service_list_updates_left--; if (error) { /* show in UI? */ g_warning ("Server.GetConfig() failed: %s", error->message); g_error_free (error); return; } syncevo_config_get_value (config, NULL, "ConsumerReady", &ready); syncevo_config_get_value (config, NULL, "PeerIsClient", &is_peer); syncevo_config_get_value (config, NULL, "syncURL", &url); syncevo_config_get_value (config, NULL, "peerType", &type); if (g_strcmp0 ("1", ready) != 0 || (type && g_strcmp0 ("WebDAV", type) == 0) || (url && g_str_has_prefix (url, "local://@"))) { /* Ignore existing configs and templates unless they are explicitly marked as "ConsumerReady. Also ignore webdav (and the local syncs used for webdav) for now */ } else if (is_peer && g_strcmp0 ("1", is_peer) == 0) { if (url) { SyncConfigWidget *w; char *fp, *tmp, *template_name, *device_name = NULL; char **fpv = NULL; syncevo_config_get_value (config, NULL, "deviceName", &tmp); if (!tmp) { device_name = g_strdup (c_data->name); } else { device_name = g_strcanon (g_strdup (tmp), LEGAL_CONFIG_NAME_CHARS, '-'); } syncevo_config_get_value (config, NULL, "templateName", &template_name); if (!template_name) { syncevo_config_get_value (config, NULL, "fingerPrint", &fp); if (fp) { fpv = g_strsplit_set (fp, ",;", 2); if (g_strv_length (fpv) > 0) { template_name = fpv[0]; } } } /* keep a list of added devices */ w = g_hash_table_lookup (c_data->device_templates, url); if (!w) { w = add_configuration_to_box (GTK_BOX (c_data->data->devices_box), config, device_name, c_data->has_template, c_data->has_configuration, c_data->data); g_hash_table_insert (c_data->device_templates, url, w); sync_config_widget_add_alternative_config (w, template_name, config, c_data->has_configuration); } else { /* TODO: might want to add a new widget, if user has created more * configs for same device: this really requires us to look at * all configs / templates, then decide what to sho w*/ /* there is a widget for this device already, add this info there*/ sync_config_widget_add_alternative_config (w, template_name, config, c_data->has_configuration); } g_free (device_name); g_strfreev (fpv); } } else { add_configuration_to_box (GTK_BOX (c_data->data->services_box), config, c_data->name, c_data->has_template, c_data->has_configuration, c_data->data); } g_free (c_data->name); g_hash_table_unref (c_data->device_templates); g_slice_free (config_data, c_data); } static void get_config_for_config_widget (app_data *data, const char *config, gboolean has_template, gboolean has_configuration, GHashTable *device_templates) { config_data *c_data; data->service_list_updates_left++; c_data = g_slice_new0 (config_data); c_data->data = data; c_data->name = g_strdup (config); c_data->has_template = has_template; c_data->has_configuration = has_configuration; if (device_templates) { c_data->device_templates = g_hash_table_ref (device_templates); } syncevo_server_get_config (data->server, config, !has_configuration, (SyncevoServerGetConfigCb)get_config_for_config_widget_cb, c_data); } static void setup_new_service_clicked (GtkButton *btn, app_data *data) { GtkWidget *widget = NULL; gtk_container_foreach (GTK_CONTAINER (data->services_box), (GtkCallback)unexpand_config_widget, NULL); /* if a new service config has already been added, use that. * Otherwise add one. */ gtk_container_foreach (GTK_CONTAINER (data->services_box), (GtkCallback)find_new_service_config, &widget); if (!widget) { get_config_for_config_widget (data, "default", TRUE, FALSE, NULL); } else { sync_config_widget_set_expanded (SYNC_CONFIG_WIDGET (widget), TRUE); } } typedef struct templates_data { app_data *data; char **templates; } templates_data; static void get_configs_cb (SyncevoServer *server, char **configs, GError *error, templates_data *templ_data) { char **config_iter, **template_iter, **templates; app_data *data; GHashTable *device_templates; templ_data->data->service_list_updates_left = 0; templates = templ_data->templates; data = templ_data->data; g_slice_free (templates_data, templ_data); if (error) { show_main_view (data); g_warning ("Server.GetConfigs() failed: %s", error->message); g_strfreev (templates); g_error_free (error); return; } device_templates = g_hash_table_new (g_str_hash, g_str_equal); for (template_iter = templates; *template_iter; template_iter++){ gboolean found_config = FALSE; for (config_iter = configs; *config_iter; config_iter++) { if (*template_iter && *config_iter && g_ascii_strncasecmp (*template_iter, *config_iter, strlen (*config_iter)) == 0) { /* have template and config */ get_config_for_config_widget (data, *config_iter, TRUE, TRUE, device_templates); found_config = TRUE; break; } } if (!found_config) { /* have template, no config */ get_config_for_config_widget (data, *template_iter, TRUE, FALSE, device_templates); } } for (config_iter = configs; *config_iter; config_iter++) { gboolean found_template = FALSE; for (template_iter = templates; *template_iter; template_iter++) { if (*template_iter && *config_iter && g_ascii_strncasecmp (*template_iter, *config_iter, strlen (*config_iter)) == 0) { found_template = TRUE; break; } } if (!found_template) { /* have config, no template */ get_config_for_config_widget (data, *config_iter, FALSE, TRUE, device_templates); } } /* config initialization might ref/unref as well... */ g_hash_table_unref (device_templates); g_strfreev (configs); g_strfreev (templates); } static void get_template_configs_cb (SyncevoServer *server, char **templates, GError *error, app_data *data) { templates_data *templ_data; if (error) { data->service_list_updates_left = 0; show_main_view (data); show_error_dialog (data->sync_win, _("Failed to get list of supported services from SyncEvolution")); g_warning ("Server.GetConfigs() failed: %s", error->message); g_error_free (error); return; } templ_data = g_slice_new (templates_data); templ_data->data = data; templ_data->templates = templates; syncevo_server_get_configs (data->server, FALSE, (SyncevoServerGetConfigsCb)get_configs_cb, templ_data); } static void update_services_list (app_data *data) { if (data->service_list_updates_left > 0) { return; } gtk_container_foreach (GTK_CONTAINER (data->services_box), (GtkCallback)remove_child, data->services_box); gtk_container_foreach (GTK_CONTAINER (data->devices_box), (GtkCallback)remove_child, data->devices_box); /* set temp number before we know the real one */ data->service_list_updates_left = 1; syncevo_server_get_configs (data->server, TRUE, (SyncevoServerGetConfigsCb)get_template_configs_cb, data); } static void get_config_for_main_win_cb (SyncevoServer *server, SyncevoConfig *config, GError *error, app_data *data) { if (error) { if (error->code == DBUS_GERROR_REMOTE_EXCEPTION && dbus_g_error_has_name (error, SYNCEVO_DBUS_ERROR_NO_SUCH_CONFIG)) { /* another syncevolution client probably removed the config */ reload_config (data, NULL); } else { g_warning ("Error in Server.GetConfig: %s", error->message); /* TRANSLATORS: message in main view */ set_info_bar (data->info_bar, GTK_MESSAGE_ERROR, SYNC_ERROR_RESPONSE_NONE, _("There was a problem communicating with the " "sync process. Please try again later.")); set_app_state (data, SYNC_UI_STATE_SERVER_FAILURE); } g_error_free (error); return; } if (config) { GHashTableIter iter; char *name; source_config *source; server_config_init (data->current_service, config); set_app_state (data, SYNC_UI_STATE_SERVER_OK); /* get "locally supported" status for all sources */ g_hash_table_iter_init (&iter, data->current_service->source_configs); while (g_hash_table_iter_next (&iter, (gpointer)&name, (gpointer)&source)) { syncevo_server_check_source (data->server, data->current_service->name, name, (SyncevoServerGenericCb)check_source_cb, source); } syncevo_server_get_presence (server, data->current_service->name, (SyncevoServerGetPresenceCb)get_presence_cb, data); syncevo_server_get_reports (server, data->current_service->name, 0, 1, (SyncevoServerGetReportsCb)get_reports_cb, data); update_service_ui (data); } } static void set_running_session_status (app_data *data, SyncevoSessionStatus status, int error_code) { if (status & SYNCEVO_STATUS_QUEUEING) { g_warning ("Running session is queued, this shouldn't happen..."); } else if (status & SYNCEVO_STATUS_IDLE) { set_app_state (data, SYNC_UI_STATE_SERVER_OK); } else if (status & SYNCEVO_STATUS_DONE) { char *err; err = get_error_string_for_code (error_code, NULL); if (err) { if (data->current_operation == OP_RESTORE) { gtk_label_set_text (GTK_LABEL (data->sync_status_label), _("Restore failed")); } else { gtk_label_set_text (GTK_LABEL (data->sync_status_label), _("Sync failed")); } g_free (err); } else { if (data->current_operation == OP_RESTORE) { gtk_label_set_text (GTK_LABEL (data->sync_status_label), _("Restore complete")); } else { gtk_label_set_text (GTK_LABEL (data->sync_status_label), _("Sync complete")); } } set_app_state (data, SYNC_UI_STATE_SERVER_OK); set_sync_progress (data, 1.0, ""); } else if (status & SYNCEVO_STATUS_RUNNING || status & SYNCEVO_STATUS_SUSPENDING || status & SYNCEVO_STATUS_ABORTING) { set_app_state (data, SYNC_UI_STATE_SYNCING); } if (status & SYNCEVO_STATUS_WAITING) { gtk_widget_show (data->spinner_image); } else { gtk_widget_hide (data->spinner_image); } } static void running_session_status_changed_cb (SyncevoSession *session, SyncevoSessionStatus status, guint error_code, SyncevoSourceStatuses *source_statuses, app_data *data) { set_running_session_status (data, status, error_code); } static void get_running_session_status_cb (SyncevoSession *session, SyncevoSessionStatus status, guint error_code, SyncevoSourceStatuses *source_statuses, GError *error, app_data *data) { if (error) { g_warning ("Error in Session.GetStatus: %s", error->message); g_error_free (error); /* non-fatal, unknown error */ return; } set_running_session_status (data, status, error_code); } typedef struct source_progress_data { app_data *data; SyncevoSourcePhase phase; const char *source; } source_progress_data; static void find_updated_source_progress (const char *name, SyncevoSourcePhase phase, source_progress_data *prog_data) { GHashTable *configs = prog_data->data->current_service ? prog_data->data->current_service->source_configs : NULL; source_config *config; config = configs ? g_hash_table_lookup (configs, name) : NULL; if (config) { if (phase != config->phase) { config->phase = phase; prog_data->phase = config->phase; prog_data->source = name; } } } static void running_session_progress_changed_cb (SyncevoSession *session, int progress, SyncevoSourceProgresses *source_progresses, app_data *data) { source_progress_data *prog_data = g_slice_new0 (source_progress_data); prog_data->data = data; prog_data->phase = SYNCEVO_PHASE_NONE; prog_data->source = NULL; syncevo_source_progresses_foreach (source_progresses, (SourceProgressFunc)find_updated_source_progress, prog_data); if (!prog_data->source) { set_sync_progress (data, ((float)progress) / 100, NULL); } else { char *name; char *msg = NULL; name = get_pretty_source_name (prog_data->source); switch (prog_data->phase) { case SYNCEVO_PHASE_PREPARING: msg = g_strdup_printf (_("Preparing '%s'"), name); break; case SYNCEVO_PHASE_RECEIVING: msg = g_strdup_printf (_("Receiving '%s'"), name); break; case SYNCEVO_PHASE_SENDING: msg = g_strdup_printf (_("Sending '%s'"), name); break; default: ; } if (msg) { set_sync_progress (data, ((float)progress) / 100, msg); } g_free (msg); g_free (name); } g_slice_free (source_progress_data, prog_data); } typedef struct source_stats { long status; long mode; long local_changes; long remote_changes; long local_rejections; long remote_rejections; } source_stats; static void free_source_stats (source_stats *stats) { g_slice_free (source_stats, stats); } static gboolean handle_source_report_item (char **strs, const char *value, GHashTable *sources) { source_stats *stats; char **tmp; char *name; if (g_strv_length (strs) < 3) { return FALSE; } /* replace '__' with '_' and '_+' with '-' */ tmp = g_strsplit (strs[1], "__", 0); name = g_strjoinv ("_", tmp); g_strfreev (tmp); tmp = g_strsplit (name, "_+", 0); g_free (name); name = g_strjoinv ("-", tmp); g_strfreev (tmp); stats = g_hash_table_lookup (sources, name); if (!stats) { stats = g_slice_new0 (source_stats); g_hash_table_insert (sources, g_strdup (name), stats); } g_free (name); if (strcmp (strs[2], "stat") == 0) { if (g_strv_length (strs) != 6) { return FALSE; } if (strcmp (strs[3], "remote") == 0) { if (strcmp (strs[4], "added") == 0 || strcmp (strs[4], "updated") == 0 || strcmp (strs[4], "removed") == 0) { stats->remote_changes += strtol (value, NULL, 10); } else if (strcmp (strs[5], "reject") == 0) { stats->remote_rejections += strtol (value, NULL, 10); } } else if (strcmp (strs[3], "local") == 0) { if (strcmp (strs[4], "added") == 0 || strcmp (strs[4], "updated") == 0 || strcmp (strs[4], "removed") == 0) { stats->local_changes += strtol (value, NULL, 10); } else if (strcmp (strs[5], "reject") == 0) { stats->local_rejections += strtol (value, NULL, 10); } } else { return FALSE; } } else if (strcmp (strs[2], "mode") == 0) { stats->mode = strtol (value, NULL, 10); } else if (strcmp (strs[2], "status") == 0) { stats->status = strtol (value, NULL, 10); } else if (strcmp (strs[2], "resume") == 0) { } else if (strcmp (strs[2], "first") == 0) { } else if (strcmp (strs[2], "backup") == 0) { if (g_strv_length (strs) != 4) { return FALSE; } if (strcmp (strs[3], "before") == 0) { } else if (strcmp (strs[3], "after") == 0) { } else { return FALSE; } } else { return FALSE; } return TRUE; } static char* get_report_summary (source_config *source) { char *rejects = NULL; char *changes = NULL; char *msg = NULL; if (!source->stats_set) { return g_strdup (""); } if (source->local_rejections + source->remote_rejections == 0) { rejects = NULL; } else if (source->local_rejections == 0) { rejects = g_strdup_printf (ngettext ("There was one remote rejection.", "There were %ld remote rejections.", source->remote_rejections), source->remote_rejections); } else if (source->remote_rejections == 0) { rejects = g_strdup_printf (ngettext ("There was one local rejection.", "There were %ld local rejections.", source->local_rejections), source->local_rejections); } else { rejects = g_strdup_printf (_ ("There were %ld local rejections and %ld remote rejections."), source->local_rejections, source->remote_rejections); } if (source->local_changes + source->remote_changes == 0) { changes = g_strdup_printf (_("Last time: No changes.")); } else if (source->local_changes == 0) { changes = g_strdup_printf (ngettext ("Last time: Sent one change.", "Last time: Sent %ld changes.", source->remote_changes), source->remote_changes); } else if (source->remote_changes == 0) { // This is about changes made to the local data. Not all of these // changes were requested by the remote server, so "applied" // is a better word than "received" (bug #5185). changes = g_strdup_printf (ngettext ("Last time: Applied one change.", "Last time: Applied %ld changes.", source->local_changes), source->local_changes); } else { changes = g_strdup_printf (_("Last time: Applied %ld changes and sent %ld changes."), source->local_changes, source->remote_changes); } if (rejects) msg = g_strdup_printf ("%s\n%s", changes, rejects); else msg = g_strdup (changes); g_free (rejects); g_free (changes); return msg; } /* return TRUE if no errors are shown */ static gboolean source_config_update_widget (source_config *source) { char *msg; gboolean show_error; SyncErrorResponse response; if (!source->label) { return TRUE; } msg = get_error_string_for_code (source->status, &response); if (msg) { show_error = TRUE; set_info_bar (source->info_bar, GTK_MESSAGE_ERROR, response, msg); } else { show_error = FALSE; gtk_widget_hide (source->info_bar); msg = get_report_summary (source); gtk_label_set_text (GTK_LABEL (source->label), msg); } g_free (msg); return !show_error; } static void get_reports_cb (SyncevoServer *server, SyncevoReports *reports, GError *error, app_data *data) { long status = -1; long common_status = -1; source_stats *stats; GHashTable *sources; /* key is source name, value is a source_stats */ GHashTableIter iter; const char *key, *val; source_config *source_conf; char *error_msg; SyncErrorResponse response; gboolean have_source_errors; GHashTable *report = NULL; guint len; if (error) { g_warning ("Error in Session.GetReports: %s", error->message); g_error_free (error); /* non-fatal, unknown error */ return; } sources = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)free_source_stats); len = syncevo_reports_get_length (reports); if (len > 0) { report = syncevo_reports_index (reports, 0); val = g_hash_table_lookup (report, "dir"); if (!val || strlen (val) == 0) { /* dummy report for first time sync info*/ if (len > 1) { report = syncevo_reports_index (reports, 1); } else { report = NULL; } } } if (report) { g_hash_table_iter_init (&iter, report); while (g_hash_table_iter_next (&iter, (gpointer)&key, (gpointer)&val)) { char **strs; strs = g_strsplit (key, "-", 6); if (!strs) { continue; } if (strcmp (strs[0], "source") == 0) { if (!handle_source_report_item (strs, val, sources)) { g_warning ("Unidentified sync report item: %s=%s", key, val); } } else if (strcmp (strs[0], "start") == 0) { /* not used */ } else if (strcmp (strs[0], "end") == 0) { data->last_sync = strtol (val, NULL, 10); } else if (strcmp (strs[0], "status") == 0) { status = strtol (val, NULL, 10); } else if (strcmp (strs[0], "peer") == 0) { /* not used */ } else if (strcmp (strs[0], "error") == 0) { /* not used */ } else if (strcmp (strs[0], "dir") == 0) { /* not used */ } else { g_warning ("Unidentified sync report item: %s=%s", key, val); } g_strfreev (strs); } } else { common_status = 0; } /* sources now has all statistics we want */ /* ficure out if all sources have same status or if there's a slow sync */ data->forced_emergency = FALSE; g_hash_table_remove_all (data->emergency_sources); g_hash_table_iter_init (&iter, sources); while (g_hash_table_iter_next (&iter, (gpointer)&key, (gpointer)&stats)) { if (stats->status == 22001) { /* ignore abort because of another source slow syncing */ } else if (stats->status == 22000) { common_status = stats->status; data->forced_emergency = TRUE; g_hash_table_insert (data->emergency_sources, g_strdup (key), ""); } else if (common_status == -1) { common_status = stats->status; } else if (common_status != stats->status) { common_status = 0; } } if (status != 200) { /* don't want to show a sync time for failed syncs */ data->last_sync = -1; } if (!data->forced_emergency) { /* if user initiates a emergency sync wihtout forced_emergency, enable all sources by default*/ g_hash_table_iter_init (&iter, data->current_service->source_configs); while (g_hash_table_iter_next (&iter, (gpointer)&key, (gpointer)&source_conf)) { if (source_config_is_usable (source_conf)) { g_hash_table_insert (data->emergency_sources, g_strdup (key), ""); } } } /* get common error message */ error_msg = get_error_string_for_code (common_status, &response); have_source_errors = FALSE; g_hash_table_iter_init (&iter, sources); while (g_hash_table_iter_next (&iter, (gpointer)&key, (gpointer)&stats)) { /* store the statistics in source config */ source_conf = g_hash_table_lookup (data->current_service->source_configs, key); if (source_conf) { source_conf->stats_set = TRUE; source_conf->local_changes = stats->local_changes; source_conf->remote_changes = stats->remote_changes; source_conf->local_rejections = stats->local_rejections; source_conf->remote_rejections = stats->remote_rejections; if (error_msg) { /* there is a service-wide error, no need to show here */ source_conf->status = 0; } else { source_conf->status = stats->status; } /* if ui has been constructed already, update it */ if (!source_config_update_widget (source_conf)) { have_source_errors = TRUE; } } } if (!error_msg && !have_source_errors) { /* no common source errors or individual source errors: it's still possible that there are sync errors */ error_msg = get_error_string_for_code (status, &response); } /* update service UI */ refresh_last_synced_label (data); if (error_msg) { GtkMessageType type = GTK_MESSAGE_ERROR; if (response == SYNC_ERROR_RESPONSE_EMERGENCY) { type = GTK_MESSAGE_QUESTION; } if (!data->synced_this_session) { /* TRANSLATORS: the placeholder is a error message (hopefully) * explaining the problem */ char *msg = g_strdup_printf (_("There was a problem with last sync:\n%s"), error_msg); g_free (error_msg); error_msg = msg; } set_info_bar (data->info_bar, type, response, error_msg); g_free (error_msg); } else if (data->current_operation == OP_RESTORE) { /* special case for just after restoring */ error_msg = g_strdup_printf (_("You've just restored a backup. The changes have not been " "synced with %s yet"), data->current_service->pretty_name); set_info_bar (data->info_bar, GTK_MESSAGE_INFO, SYNC_ERROR_RESPONSE_SYNC, error_msg); } g_hash_table_destroy (sources); } static void set_config_cb (SyncevoSession *session, GError *error, app_data *data) { if (error) { g_warning ("Error in Session.SetConfig: %s", error->message); g_error_free (error); /* TODO show in UI: save failed */ } g_object_unref (session); } static void restore_cb (SyncevoSession *session, GError *error, app_data *data) { if (error) { g_warning ("Error in Session.Restore: %s", error->message); g_error_free (error); return; } } static void restore_backup (app_data *data, SyncevoSession *session, const char *dir) { char **sources; GHashTableIter iter; int i = 0; char *source; sources = g_malloc0 (sizeof (char*) * (g_hash_table_size (data->emergency_sources) + 1)); g_hash_table_iter_init (&iter, data->emergency_sources); while (g_hash_table_iter_next (&iter, (gpointer)&source, NULL)) { sources[i++] = g_strdup (source); } sources[i] = NULL; syncevo_session_restore (session, dir, TRUE, (const char**)sources, (SyncevoSessionGenericCb)restore_cb, data); g_strfreev (sources); } static void save_config (app_data *data, SyncevoSession *session) { syncevo_session_set_config (session, TRUE, FALSE, data->current_service->config, (SyncevoSessionGenericCb)set_config_cb, data); } static void do_sync (operation_data *op_data, SyncevoSession *session) { GHashTable *source_modes; GHashTableIter iter; source_config *source; SyncevoSyncMode mode = SYNCEVO_SYNC_NONE; app_data *data = op_data->data; source_modes = syncevo_source_modes_new (); if (op_data->operation != OP_SYNC) { /* in an emergency sync, set non-emergency sources to not sync*/ g_hash_table_iter_init (&iter, data->current_service->source_configs); while (g_hash_table_iter_next (&iter, NULL, (gpointer)&source)) { if (g_hash_table_lookup (data->emergency_sources, source->name) == NULL) { syncevo_source_modes_add (source_modes, source->name, SYNCEVO_SYNC_NONE); } } } /* override all non-supported with "none". */ g_hash_table_iter_init (&iter, data->current_service->source_configs); while (g_hash_table_iter_next (&iter, NULL, (gpointer)&source)) { if (!source->supported_locally) { syncevo_source_modes_add (source_modes, source->name, SYNCEVO_SYNC_NONE); } } /* use given mode or use default for normal syncs */ switch (op_data->operation) { case OP_SYNC: mode = SYNCEVO_SYNC_DEFAULT; break; case OP_SYNC_SLOW: mode = SYNCEVO_SYNC_SLOW; break; case OP_SYNC_REFRESH_FROM_CLIENT: mode = SYNCEVO_SYNC_REFRESH_FROM_CLIENT; break; case OP_SYNC_REFRESH_FROM_SERVER: mode = SYNCEVO_SYNC_REFRESH_FROM_SERVER; break; default: g_warn_if_reached(); } syncevo_session_sync (session, mode, source_modes, (SyncevoSessionGenericCb)sync_cb, data); syncevo_source_modes_free (source_modes); } static void set_config_for_sync_cb (SyncevoSession *session, GError *error, operation_data *op_data) { if (error) { g_warning ("Error in Session.SetConfig: %s", error->message); g_error_free (error); /* TODO show in UI: sync failed (failed to even start) */ return; } do_sync (op_data, session); } static void run_operation (operation_data *op_data, SyncevoSession *session) { SyncevoConfig *config; /* when we first get idle, start the operation */ if (op_data->started) { return; } op_data->started = TRUE; op_data->data->current_operation = op_data->operation; /* time for business */ switch (op_data->operation) { case OP_SYNC: case OP_SYNC_SLOW: case OP_SYNC_REFRESH_FROM_CLIENT: case OP_SYNC_REFRESH_FROM_SERVER: /* Make sure we don't get change diffs printed out, then sync */ config = g_hash_table_new (g_str_hash, g_str_equal); syncevo_config_set_value (config, NULL, "printChanges", "0"); syncevo_session_set_config (session, TRUE, TRUE, config, (SyncevoSessionGenericCb)set_config_for_sync_cb, op_data); syncevo_config_free (config); break; case OP_SAVE: save_config (op_data->data, session); break; case OP_RESTORE: restore_backup (op_data->data, session, op_data->dir); break; default: g_warn_if_reached (); } } /* Our sync session status */ static void status_changed_cb (SyncevoSession *session, SyncevoSessionStatus status, guint error_code, SyncevoSourceStatuses *source_statuses, operation_data *op_data) { switch (status) { case SYNCEVO_STATUS_IDLE: run_operation (op_data, session); break; case SYNCEVO_STATUS_DONE: op_data->data->synced_this_session = TRUE; /* no need for sync session anymore */ g_object_unref (session); /* refresh stats -- the service may no longer be the one syncing, * and we might have only saved config but what the heck... */ syncevo_server_get_reports (op_data->data->server, op_data->data->current_service->name, 0, 1, (SyncevoServerGetReportsCb)get_reports_cb, op_data->data); g_slice_free (operation_data, op_data); default: ; } } /* Our sync (or config-save) session status */ static void get_status_cb (SyncevoSession *session, SyncevoSessionStatus status, guint error_code, SyncevoSourceStatuses *source_statuses, GError *error, operation_data *op_data) { if (error) { g_warning ("Error in Session.GetStatus: %s", error->message); g_error_free (error); g_object_unref (session); switch (op_data->operation) { case OP_SYNC: /* TODO show in UI: sync failed (failed to even start) */ break; case OP_SAVE: /* TODO show in UI: save failed */ break; default: g_warn_if_reached (); } g_slice_free (operation_data, op_data); return; } if (status == SYNCEVO_STATUS_IDLE) { run_operation (op_data, session); } } static void start_session_cb (SyncevoServer *server, char *path, GError *error, operation_data *op_data) { SyncevoSession *session; app_data *data = op_data->data; if (error) { g_warning ("Error in Server.StartSession: %s", error->message); g_error_free (error); g_free (path); switch (op_data->operation) { case OP_SYNC: /* TODO show in UI: sync failed (failed to even start) */ break; case OP_SAVE: /* TODO show in UI: save failed */ break; default: g_warn_if_reached (); } g_slice_free (operation_data, op_data); return; } session = syncevo_session_new (path); if (data->running_session && strcmp (path, syncevo_session_get_path (data->running_session)) != 0) { /* This is a really unfortunate event: Someone got a session and we did not have time to set UI insensitive... */ gtk_label_set_markup (GTK_LABEL (data->server_label), _("Waiting for current operation to finish...")); gtk_widget_show_all (data->sources_box); } /* we want to know about status changes to our session */ g_signal_connect (session, "status-changed", G_CALLBACK (status_changed_cb), op_data); syncevo_session_get_status (session, (SyncevoSessionGetStatusCb)get_status_cb, op_data); g_free (path); } /* TODO: this function should accept source/peer name as param */ char* get_error_string_for_code (int error_code, SyncErrorResponse *response) { if (response) { *response = SYNC_ERROR_RESPONSE_NONE; } switch (error_code) { case -1: /* no errorcode */ case 0: case 200: case LOCERR_USERABORT: case LOCERR_USERSUSPEND: return NULL; case 22000: if (response) { *response = SYNC_ERROR_RESPONSE_EMERGENCY; } /* TRANSLATORS: next strings are error messages. */ return g_strdup (_("A normal sync is not possible at this time. The server " "suggests a slow sync, but this might not always be " "what you want if both ends already have data.")); case 22002: return g_strdup (_("The sync process died unexpectedly.")); case 22003: if (response) { *response = SYNC_ERROR_RESPONSE_SETTINGS_OPEN; } return g_strdup (_("Password request was not answered. You can save the " "password in the settings to prevent the request.")); case 506: /* TODO use the service device name here, this is a remote problem */ return g_strdup (_("There was a problem processing sync request. " "Trying again may help.")); case DB_Unauthorized: if (response) { *response = SYNC_ERROR_RESPONSE_SETTINGS_OPEN; } return g_strdup(_("Failed to login. Could there be a problem with " "your username or password?")); case DB_Forbidden: return g_strdup(_("Forbidden")); case DB_NotFound: if (response) { *response = SYNC_ERROR_RESPONSE_SETTINGS_OPEN; } /* TRANSLATORS: data source means e.g. calendar or addressbook */ return g_strdup(_("A data source could not be found. Could there be a " "problem with the settings?")); case DB_Fatal: case DB_Error: return g_strdup(_("Remote database error")); case LOCAL_STATUS_CODE + DB_Fatal: /* This can happen when EDS is borked, restart it may help... */ return g_strdup(_("There is a problem with the local database. " "Syncing again or rebooting may help.")); case DB_Full: return g_strdup(_("No space on disk")); case LOCERR_PROCESSMSG: return g_strdup(_("Failed to process SyncML")); case LOCERR_AUTHFAIL: return g_strdup(_("Server authorization failed")); case LOCERR_CFGPARSE: return g_strdup(_("Failed to parse configuration file")); case LOCERR_CFGREAD: return g_strdup(_("Failed to read configuration file")); case LOCERR_NOCFG: return g_strdup(_("No configuration found")); case LOCERR_NOCFGFILE: return g_strdup(_("No configuration file found")); case LOCERR_BADCONTENT: return g_strdup(_("Server sent bad content")); case LOCERR_CERT_EXPIRED: return g_strdup(_("Connection certificate has expired")); case LOCERR_CERT_INVALID: return g_strdup(_("Connection certificate is invalid")); case LOCERR_CONN: case LOCERR_NOCONN: case LOCERR_TRANSPFAIL: case LOCERR_TIMEOUT: if (response) { *response = SYNC_ERROR_RESPONSE_SETTINGS_OPEN; } return g_strdup(_("We were unable to connect to the server. The problem " "could be temporary or there could be something wrong " "with the settings.")); case LOCERR_BADURL: if (response) { *response = SYNC_ERROR_RESPONSE_SETTINGS_OPEN; } return g_strdup(_("The server URL is bad")); case LOCERR_SRVNOTFOUND: if (response) { *response = SYNC_ERROR_RESPONSE_SETTINGS_OPEN; } return g_strdup(_("The server was not found")); default: return g_strdup_printf (_("Error %d"), error_code); } } static void server_shutdown_cb (SyncevoServer *server, app_data *data) { if (data->syncing) { gtk_label_set_text (GTK_LABEL (data->sync_status_label), _("Sync failed")); set_sync_progress (data, 1.0 , ""); set_app_state (data, SYNC_UI_STATE_SERVER_OK); } /* re-init server here */ } static void set_running_session (app_data *data, const char *path) { if (data->running_session) { g_object_unref (data->running_session); } if (!path) { data->running_session = NULL; return; } data->running_session = syncevo_session_new (path); g_signal_connect (data->running_session, "progress-changed", G_CALLBACK (running_session_progress_changed_cb), data); g_signal_connect (data->running_session, "status-changed", G_CALLBACK (running_session_status_changed_cb), data); syncevo_session_get_status (data->running_session, (SyncevoSessionGetStatusCb)get_running_session_status_cb, data); } static void set_online_status (app_data *data, gboolean online) { if (online != data->online) { data->online = online; if (data->current_state == SYNC_UI_STATE_SERVER_OK) { if (data->online) { gtk_widget_hide (data->no_connection_box); } else { gtk_widget_show (data->no_connection_box); } } gtk_widget_set_sensitive (data->sync_btn, data->online); } } static void get_presence_cb (SyncevoServer *server, char *status, char **transports, GError *error, app_data *data) { if (error) { g_warning ("Server.GetSessions failed: %s", error->message); g_error_free (error); /* non-fatal, ignore in UI */ return; } if (data->current_service && status) { set_online_status (data, strcmp (status, "") == 0); } g_free (status); g_strfreev (transports); } static void password_dialog_response_cb (GtkWidget *dialog, int response, app_data *data) { const char *password; GHashTable *return_dict; return_dict = g_hash_table_new (g_str_hash, g_str_equal); if (response == GTK_RESPONSE_OK) { password = gtk_entry_get_text (GTK_ENTRY (data->password_dialog_entry)); g_hash_table_insert (return_dict, "password", (gpointer)password); } syncevo_server_info_response (data->server, data->password_dialog_id, "response", return_dict, NULL, NULL); g_hash_table_destroy (return_dict); g_free (data->password_dialog_id); data->password_dialog_id = NULL; gtk_widget_destroy (dialog); } static void info_request_cb (SyncevoServer *syncevo, char *id, char *session_path, char *state, char *handler_path, char *type, GHashTable *parameters, app_data *data) { GHashTable *t; GtkWidget *dialog, *content, *label, *align; char *msg; if (g_strcmp0 (state, "request") != 0 || g_strcmp0 (type, "password") != 0) { /* not handling other stuff */ return; } if (!data->running_session || g_strcmp0 (session_path, syncevo_session_get_path (data->running_session)) != 0) { /* not our problem */ return; } t = g_hash_table_new (g_str_hash, g_str_equal); syncevo_server_info_response (syncevo, id, "working", t, NULL, NULL); g_hash_table_destroy (t); data->password_dialog_id = g_strdup (id); /* TRANSLATORS: password request dialog contents: title, cancel button * and ok button */ dialog = gtk_dialog_new_with_buttons (_("Password is required for sync"), GTK_WINDOW (data->sync_win), GTK_DIALOG_DESTROY_WITH_PARENT, _("Cancel sync"), GTK_RESPONSE_CANCEL, _("Sync with password"), GTK_RESPONSE_OK, NULL); content = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); align = gtk_alignment_new (0.0, 0.5, 0.0, 0.0); gtk_alignment_set_padding (GTK_ALIGNMENT (align), 0, 0, 16, 16); gtk_widget_show (align); gtk_box_pack_start (GTK_BOX (content), align, FALSE, FALSE, 6); /* TRANSLATORS: password request dialog message, placeholder is service name */ msg = g_strdup_printf (_("Please enter password for syncing with %s:"), data->current_service->pretty_name); label = gtk_label_new (msg); gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END); gtk_widget_set_size_request (label, 500, -1); gtk_widget_show (label); gtk_container_add (GTK_CONTAINER (align), label); g_free (msg); align = gtk_alignment_new (0.0, 0.5, 0.0, 0.0); gtk_alignment_set_padding (GTK_ALIGNMENT (align), 0, 0, 16, 16); gtk_widget_show (align); gtk_box_pack_start (GTK_BOX (content), align, FALSE, FALSE, 6); data->password_dialog_entry = gtk_entry_new (); gtk_entry_set_max_length (GTK_ENTRY (data->password_dialog_entry), 99); gtk_entry_set_width_chars (GTK_ENTRY (data->password_dialog_entry), 30); gtk_entry_set_visibility (GTK_ENTRY (data->password_dialog_entry), FALSE); gtk_widget_show (data->password_dialog_entry); gtk_container_add (GTK_CONTAINER (align), data->password_dialog_entry); g_signal_connect (dialog, "response", G_CALLBACK (password_dialog_response_cb), data); gtk_window_present (GTK_WINDOW (dialog)); gtk_widget_grab_focus (data->password_dialog_entry); } static void server_presence_changed_cb (SyncevoServer *server, char *config_name, char *status, char *transport, app_data *data) { if (data->current_service && config_name && status && g_ascii_strcasecmp (data->current_service->name, config_name) == 0) { set_online_status (data, strcmp (status, "") == 0); } } static void server_templates_changed_cb (SyncevoServer *server, app_data *data) { if (gtk_widget_get_visible (data->services_box)) { update_services_list (data); } } static void server_session_changed_cb (SyncevoServer *server, char *path, gboolean started, app_data *data) { if (started) { set_running_session (data, path); } else if (data->running_session && strcmp (syncevo_session_get_path (data->running_session), path) == 0 ) { set_running_session (data, NULL); } } static void get_sessions_cb (SyncevoServer *server, SyncevoSessions *sessions, GError *error, app_data *data) { const char *path; if (error) { g_warning ("Server.GetSessions failed: %s", error->message); g_error_free (error); /* TODO show in UI: failed first syncevo call (unexpected, fatal?) */ return; } /* assume first one is active */ path = syncevo_sessions_index (sessions, 0); set_running_session (data, path); syncevo_sessions_free (sessions); } static void get_config_for_default_peer_cb (SyncevoServer *syncevo, SyncevoConfig *config, GError *error, app_data *data) { char *name; if (error) { g_warning ("Server.GetConfig failed: %s", error->message); g_error_free (error); /* TODO show in UI: failed first syncevo call (unexpected, fatal?) */ return; } syncevo_config_get_value (config, NULL, "defaultPeer", &name); reload_config (data, name); syncevo_config_free (config); } app_data* sync_ui_create () { app_data *data; data = g_slice_new0 (app_data); data->online = TRUE; data->current_state = SYNC_UI_STATE_GETTING_SERVER; data->forced_emergency = FALSE; data->emergency_sources = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); if (!init_ui (data)) { return NULL; } data->server = syncevo_server_get_default(); g_signal_connect (data->server, "shutdown", G_CALLBACK (server_shutdown_cb), data); g_signal_connect (data->server, "session-changed", G_CALLBACK (server_session_changed_cb), data); g_signal_connect (data->server, "presence-changed", G_CALLBACK (server_presence_changed_cb), data); g_signal_connect (data->server, "templates-changed", G_CALLBACK (server_templates_changed_cb), data); g_signal_connect (data->server, "info-request", G_CALLBACK (info_request_cb), data); syncevo_server_get_config (data->server, "", FALSE, (SyncevoServerGetConfigCb)get_config_for_default_peer_cb, data); syncevo_server_get_sessions (data->server, (SyncevoServerGetSessionsCb)get_sessions_cb, data); gtk_window_present (GTK_WINDOW (data->sync_win)); return data; } void sync_ui_show_settings (app_data *data, const char *id) { show_services_list (data, id); } GtkWindow* sync_ui_get_main_window (app_data *data) { return GTK_WINDOW(data->sync_win); } syncevolution_1.4/src/gtk-ui/sync-ui.h000066400000000000000000000035041230021373600201070ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef SYNC_UI_H #define SYNC_UI_H #include #include "config.h" #include "sync-ui-config.h" #include "sync-ui.h" #define SYNC_UI_LIST_ICON_SIZE 32 #define SYNC_UI_LIST_BTN_WIDTH 150 typedef struct _app_data app_data; typedef enum SyncErrorResponse { SYNC_ERROR_RESPONSE_NONE, SYNC_ERROR_RESPONSE_SYNC, SYNC_ERROR_RESPONSE_SETTINGS_SELECT, SYNC_ERROR_RESPONSE_SETTINGS_OPEN, SYNC_ERROR_RESPONSE_EMERGENCY, SYNC_ERROR_RESPONSE_EMERGENCY_SLOW_SYNC, } SyncErrorResponse; char* get_pretty_source_name (const char *source_name); char* get_error_string_for_code (int error_code, SyncErrorResponse *response); void show_error_dialog (GtkWidget *widget, const char* message); gboolean show_confirmation (GtkWidget *widget, const char *message, const char *yes, const char *no); app_data *sync_ui_create (); GtkWindow *sync_ui_get_main_window (app_data *data); void sync_ui_show_settings (app_data *data, const char *id); void toggle_set_active (GtkWidget *toggle, gboolean active); gboolean toggle_get_active (GtkWidget *toggle); #endif syncevolution_1.4/src/gtk-ui/sync-ui.rc000066400000000000000000000023601230021373600202630ustar00rootroot00000000000000# generic rules for MuxWidgets style "mux-frame" { bg[NORMAL] = "#ffffff" bg[INSENSITIVE] = "#ffffff" MuxFrame::border-color = "#dee2e5" MuxFrame::bullet-color = "#aaaaaa" MuxFrame::title-font = "Bold 12" } class "MuxFrame" style "mux-frame" # sync-ui specific rules style "meego-win" { bg[NORMAL] = "#4a535a" } widget "meego_win" style "meego-win" style "data-box" { bg[NORMAL] = "#ececec" bg[INSENSITIVE] = "#ececec" } widget "*.sync_data_and_type_box" style "data-box" style "insensitive-frame" { bg[INSENSITIVE] = "#d2d6d9" } widget "*.log_frame*" style "insensitive-frame" widget "*.backup_frame*" style "insensitive-frame" widget "*.services_frame*" style "insensitive-frame" style "service-title" { font_name = "Bold 14" } widget "*.sync_service_label" style "service-title" widget "*.sync_status_label" style "service-title" widget "*.no_server_label" style "service-title" widget "*.sync_failure_label" style "service-title" style "big-button" { GtkButton::inner-border = { 10,10,10,10 } font_name = "Bold 11" } widget "*.sync_btn*" style "big-button" style "bread-crumb-button" { GtkButton::inner-border = { 5,5,0,0 } } widget "*.mux_window_bread_crumbs.*GtkButton*" style "bread-crumb-button" syncevolution_1.4/src/gtk-ui/sync.desktop.in000066400000000000000000000002351230021373600213210ustar00rootroot00000000000000[Desktop Entry] _Name=Sync _Comment=Up to date Version=1.0 Type=Application Exec=sync-ui Icon=sync Categories=Network;GTK; Terminal=false StartupNotify=true syncevolution_1.4/src/gtk-ui/sync.png000066400000000000000000000060271230021373600200340ustar00rootroot00000000000000PNG  IHDR00WtEXtSoftwareAdobe ImageReadyqe< IDATxY[l粻]z&M.pI 4*Q URՊB U[P>VRJTBT+B@b'k:~ٙxmCpϙs|?ѣGd7uSSS: VʭY$iǎt/Z5Mm8D/I>33s=,Ͻ{s{nx}OuuwڸiӍLFϟP(<dݱM`E398mM*cEӠ#*ݓPh;ںg)>.-~?4oC#e$(ԣ GXf6 _vko'Vc(ǿU$}\ձۡrѢr&== B!aؾRM4l!iTxrJe_=nth<",}>M}UFYd(gьI3W;^4eɚQT5Wd ^.-y%z*npmzV$6UzbLgf}ŒA[" Q=zUZ?3,1^jO0F}@#% q(dt[2@pKb_Uh"ydgi=\g!AaU9 }(Bšp*/0H 4*M-s`WL[]3voQ` <7[dhoŶ{2yծ s;0Z^ot 3eAK!n<7:+e4k HW̎s|.@E(2yJqq'Iݡ&YCGK>VF7EU!N/5F[;08ăZ)آm[P 8p(bZ*ZtF*s:ٖ@KtP~1"I_Op(n%N wE\wB7&8]a_8;~ӃthX3"Hf n vn ɦנƗ+rцf5דeZh{w~wSJ|%byI䂊ppa"(!7ڂr؂Y}~.uNzUZBxOd6'}}y=!zrkDlA׶ ,*W' o}[F@VSt٬YqY_*x@8QGG%=loh>3\a4KϏd0G1DKEz#UOΗ TaA&ZMCiʆe0uΦJԹ6꧈_}V1TZZ$""sdz`RSZ\zgXyH/`499YaG:Z"Z-7P/6Ssi Ls/<~> 3Nڷ"SemJrWU h ^JW–2`"0p˰?*]2Kh z?Jϝdӽ%X݋>Rpd(7۸@B(Y,"Ct[-|\;sۗ .]J48 ȕnaq㰩yoL穜7Q>W1oznYzim>],x]u*` ~0!MUۦێM,NEcS%|*---=bQ0I*n1 ?pY\]|_J E/Na"I'qJnRsRtZvł-Y./wOLon ^q O{Up8B'OC7\91kZ1^XB$~aArB屬vpd` !;}+J <9L Mv\f;;Q߷~6ïH1R:3,Kd)VNض9NϨ80lWWϧff}\%D&˲DhS%yLč̜Sm6M3mh[$nTO=\B7ZM P88>>>077BFV xlP88,l hFY{p GXC878Ds{kG GMgz6;:˝d2cr9utؔsYHR&!A߭\:J$j~mԲ]G Z%PQJXNv%3FL83ԩ)Aynx얫 6Y3pOҜ9OV2bU]–qNam,<&o!o٨Y[sUH,2&K\op0+O2Ox0$+z ̜ ﵁-"Uoob1g׮]H, B T4!/ɤ ̱$Ll6j*%T,6yo6kBl f" ͌0[lq 0-:1 IENDB`syncevolution_1.4/src/gtk-ui/ui.xml000066400000000000000000002774721230021373600175270ustar00rootroot00000000000000 1024 False 5 Sync Emergency True 800 550 True sync_win True False 4 True False 0 none True True never True False queue True False True False 8 True False 8 800 True False 0 If something has gone horribly wrong you can try a slow sync, start from scratch or restore from backup. True False True 0 True True True False 0 20 True False 2 Calendar True True False False True Tasks True True False False True 1 2 True False Affected data: False True 1 False True 8 0 True False 5 True False 0 <big>Slow sync</big> True False True 0 True False 0 0 0 40 40 True False 6 True True True False True False 16 Slow sync True True 0 True False 8 A slow sync compares items from both sides and tries to merge them. This may fail in some cases, leading to duplicates or lost information. True True 1 False True 1 False True 8 1 True False 5 True False 0 <big>Start from scratch</big> True False True 0 True False 0 0 0 40 40 True False True True True False True False 16 Delete all your local information and replace with data from Zyb False True 0 True False 15 <b>or</b> True False False 1 True True True False True False 16 Delete all data on Zyb and replace with your local information False True 2 False True 1 False True 8 2 True False 5 True False 0 <big>Restore from backup</big> True False True 0 True False queue 40 40 True False 800 True False 0 Backups are made before every time we Sync. Choose a backup to restore. Any changes you have made since then will be lost. True False False 0 True True True False queue True False 10 2 16 True False 0 5 Backup before syncing Dec 5th 2009 16:35 2 3 GTK_FILL GTK_FILL True False 0 5 Backup before syncing Dec 5th 2009 13:35 3 4 GTK_FILL GTK_FILL True False 0 5 Backup before syncing Dec 5th 2009 11:35 4 5 GTK_FILL GTK_FILL True False 0 5 Backup before syncing Dec 3rd 5 6 GTK_FILL GTK_FILL True False 0 5 Backup before syncing Dec 1st 6 7 GTK_FILL GTK_FILL True False 0 5 Backup before syncing yesterday 1 2 GTK_FILL GTK_FILL True False 0 5 Backup before syncing two hours ago GTK_FILL Restore True True True False 1 2 GTK_FILL GTK_FILL 16 True True 1 True True 1 True True 8 3 False True 40 0 True False True True True 0 True False Close True True True False False True 12 end 0 False True 1 1024 False 5 Settings True 800 550 True sync_win True False 4 True False 0 none True True never True False queue True False True False 12 36 12 True False 12 True False 5 True False 0 <big>Network sync</big> True False True 0 True False True False To sync you'll need a network connection and an account with a sync service. We support the following services: False True 0 False True 1 300 True True in True False queue True False True True 2 True False True False 0 If you don't see your service above but know that your sync provider uses SyncML you can setup a service manually. True True 0 Add new service 150 True True True False False True 8 1 False False 3 True True 8 0 400 True False 5 True False 0 <big>Direct sync</big> True False True 0 True False True False Use Bluetooth to Sync your data from one device to another. False True 0 False True 1 300 True True in True False queue True False True True 2 True False True False 0 You will need to add Bluetooth devices before they can be synced. True True 0 Add new device 150 True True False False True 8 1 False False 3 True True 1 True True 0 True False True True True 6 0 True False Close True True True False False True 12 end 0 False True 1 1024 False Sync 800 550 True False True False 4 4 True False True False 0 none True False True False 48 48 True False True False gtk-missing-image 6 True True 0 False True 10 0 True False 0 0.20000000298023224 True end 42 False True 5 1 True False 6 4 False False 8 end 2 False True 16 0 True False True False True False True False True False True False False True 0 False True False 0 none True False 5 12 True False False False 0 False True 1 True True 55 0 False False 6 0 False True 0 False True 1 True False True False True 0 True True 0 250 True False 5 True False True False 0 in True False True False True False True False 0 False False 6 0 24 True False True False gtk-missing-image False True 0 False True 1 False True 0 False True 10 0 True False True False False False True 0 False True 5 0 False True 5 1 25 True False False end True True 5 end 0 False False 2 True False True False 10 True False 0 <b>Actions</b> True False True 0 Sync now True True True False False False 1 True True True False True False Change or edit sync service center False False 2 Fix a sync emergency True True True False False False 3 True True 30 0 False True 20 3 True False True True True 0 True True 0 False True end 1 True True 0 syncevolution_1.4/src/gtk3-ui/000077500000000000000000000000001230021373600164305ustar00rootroot00000000000000syncevolution_1.4/src/gtk3-ui/README000066400000000000000000000003751230021373600173150ustar00rootroot00000000000000SyncEvolution Graphical User Interface for Moblin * Running Syncevolution D-Bus service must be either running or installed properly (so DBus autostart works). the client itself must be installed so glade ui file and rc style file get found. syncevolution_1.4/src/gtk3-ui/gtk-ui.am000066400000000000000000000066231230021373600201560ustar00rootroot00000000000000EXTRA_DIST += src/gtk3-ui/ui.xml if COND_GUI if !COND_GTK2 dist_noinst_DATA += \ src/gtk3-ui/README src_gtk3_ui_applicationsdir = $(datadir)/applications src_gtk3_ui_applications_in_files = \ src/gtk3-ui/sync.desktop.in \ src/gtk3-ui/sync-gtk.desktop.in src_gtk3_ui_applications_generated = $(src_gtk3_ui_applications_in_files:.desktop.in=.desktop) src_gtk3_ui_applications_DATA = @GUI_DESKTOP_FILES@ # if this will pose a problem then see the link below, probably the solution # here will need to be used. # http://mail.gnome.org/archives/commits-list/2010-October/msg05148.html @INTLTOOL_DESKTOP_RULE@ # When installing both the plain GTK and the Moblin-themed version, # the Moblin version uses the normal "Sync - Up to date" name/comment # and the GTK version uses "Sync (GTK)" as name with the same # comment. This is a somewhat arbitrary choice, with the rationale # being that a Moblin user is less likely to care about the # distinction while a GTK user might understand what "(GTK)" means. src/gtk3-ui/sync-moblin.desktop: src/gtk3-ui/sync.desktop $(AM_V_GEN)cp $< $@ src_gtk3_ui_gladedir = $(datadir)/syncevolution/ src_gtk3_ui_glade_DATA = src/gtk3-ui/ui.xml src_gtk3_ui_icondir = $(datadir)/icons/hicolor/48x48/apps dist_src_gtk3_ui_icon_DATA = src/gtk3-ui/sync.png src_gtk3_ui_themercfiles = \ src/gtk3-ui/sync-generic.png \ src/gtk3-ui/sync-spinner.gif \ src/gtk3-ui/sync-ui.css src_gtk3_ui_themercdir = $(datadir)/syncevolution/ dist_src_gtk3_ui_themerc_DATA = $(src_gtk3_ui_themercfiles) src_gtk3_ui_desktopdir = $(datadir)/applications dist_noinst_DATA += \ $(src_gtk3_ui_applications_in_files) # sync-ui: default GUI, could be plain GTK or Moblin UX # sync-ui-gtk: GTK GUI # sync-ui-moblin: Moblin UX # # The later two are built when --enable-gui=all was used. EXTRA_PROGRAMS += \ src/gtk3-ui/sync-ui \ src/gtk3-ui/sync-ui-gtk \ src/gtk3-ui/sync-ui-moblin bin_PROGRAMS += @GUI_PROGRAMS@ src_gtk3_ui_sync_ui_SOURCES = \ src/gtk3-ui/main.c \ src/gtk3-ui/sync-ui.c \ src/gtk3-ui/sync-ui.h \ src/gtk3-ui/sync-ui-config.c \ src/gtk3-ui/sync-ui-config.h \ src/gtk3-ui/mux-frame.c \ src/gtk3-ui/mux-frame.h \ src/gtk3-ui/sync-config-widget.c \ src/gtk3-ui/sync-config-widget.h src_gtk3_ui_sync_ui_LDADD = \ $(GUI_LIBS) \ $(DBUS_GLIB_LIBS) \ $(top_builddir)/src/dbus/glib/libsyncevo-dbus.la src_gtk3_ui_sync_ui_CFLAGS = \ $(GUI_CFLAGS) \ $(DBUS_GLIB_CFLAGS) \ -DGLADEDIR=\""$(src_gtk3_ui_gladedir)"\" \ -DTHEMEDIR=\""$(src_gtk3_ui_themercdir)"\" \ -DLIBEXECDIR=\"@libexecdir@\" \ -DSYNCEVOLUTION_LOCALEDIR=\"${SYNCEVOLUTION_LOCALEDIR}\" \ $(SYNCEVO_WFLAGS) src_gtk3_ui_sync_ui_CPPFLAGS = \ -I$(top_builddir) \ -I$(top_srcdir) \ -I$(top_builddir)/src/dbus/glib \ -I$(top_srcdir)/src/dbus/glib \ $(SYNTHESIS_CFLAGS) src_gtk3_ui_sync_ui_gtk_SOURCES = $(src_gtk3_ui_sync_ui_SOURCES) src_gtk3_ui_sync_ui_gtk_LDADD = $(src_gtk3_ui_sync_ui_LDADD) src_gtk3_ui_sync_ui_gtk_CFLAGS = $(src_gtk3_ui_sync_ui_CFLAGS) src_gtk3_ui_sync_ui_gtk_CPPFLAGS = $(src_gtk3_ui_sync_ui_CPPFLAGS) src_gtk3_ui_sync_ui_moblin_SOURCES = $(src_gtk3_ui_sync_ui_SOURCES) src_gtk3_ui_sync_ui_moblin_LDADD = $(src_gtk3_ui_sync_ui_LDADD) src_gtk3_ui_sync_ui_moblin_CFLAGS = $(src_gtk3_ui_sync_ui_CFLAGS) src_gtk3_ui_sync_ui_moblin_CPPFLAGS = $(src_gtk3_ui_sync_ui_CPPFLAGS) -DUSE_MOBLIN_UX CLEANFILES += \ src/gtk3-ui/sync-moblin.desktop \ $(src_gtk3_ui_applications_generated) endif !COND_GTK2 endif COND_GUI syncevolution_1.4/src/gtk3-ui/main.c000066400000000000000000000117501230021373600175240ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include "config.h" #include "sync-ui.h" static char *settings_id = NULL; static GOptionEntry entries[] = { { "show-settings", 0, 0, G_OPTION_ARG_STRING, &settings_id, "Open sync settings for given sync url or configuration name", "url or config name" }, { NULL } }; static void set_app_name_and_icon () { /* TRANSLATORS: this is the application name that may be used by e.g. the windowmanager */ g_set_application_name (_("Sync")); gtk_window_set_default_icon_name ("sync"); } static void init (int argc, char *argv[]) { GError *error = NULL; GOptionContext *context; gtk_init (&argc, &argv); bindtextdomain (GETTEXT_PACKAGE, SYNCEVOLUTION_LOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); textdomain (GETTEXT_PACKAGE); context = g_option_context_new ("- synchronise PIM data with Syncevolution"); g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE); g_option_context_add_group (context, gtk_get_option_group (TRUE)); if (!g_option_context_parse (context, &argc, &argv, &error)) { g_warning ("option parsing failed: %s\n", error->message); } } #ifdef ENABLE_UNIQUE #include enum { COMMAND_0, COMMAND_SHOW_CONFIGURATION /* no sync-ui specific commands */ }; static UniqueResponse message_received_cb (UniqueApp *app, gint command, UniqueMessageData *message, guint time_, app_data *data) { char *arg; GtkWindow *main_win; main_win = sync_ui_get_main_window (data); switch (command) { case UNIQUE_ACTIVATE: if (GTK_IS_WINDOW (main_win)) { /* move the main window to the screen that sent us the command */ gtk_window_set_screen (GTK_WINDOW (main_win), unique_message_data_get_screen (message)); gtk_window_present (GTK_WINDOW (main_win)); } break; case COMMAND_SHOW_CONFIGURATION: arg = unique_message_data_get_text (message); if (GTK_IS_WINDOW (main_win) && arg) { /* move the main window to the screen that sent us the command */ gtk_window_set_screen (GTK_WINDOW (main_win), unique_message_data_get_screen (message)); sync_ui_show_settings (data, arg); } break; default: break; } return UNIQUE_RESPONSE_OK; } int main (int argc, char *argv[]) { UniqueApp *app; init (argc, argv); app = unique_app_new_with_commands ("org.Moblin.Sync", NULL, "show-configuration", COMMAND_SHOW_CONFIGURATION, NULL); if (unique_app_is_running (app)) { UniqueMessageData *message = NULL; UniqueCommand command = UNIQUE_ACTIVATE; if (settings_id) { command = COMMAND_SHOW_CONFIGURATION; message = unique_message_data_new (); unique_message_data_set_text (message, settings_id, -1); } unique_app_send_message (app, command, message); unique_message_data_free (message); } else { app_data *data; set_app_name_and_icon (); data = sync_ui_create (); if (data) { /* UniqueApp watches the main window so it can terminate * the startup notification sequence for us */ unique_app_watch_window (app, sync_ui_get_main_window (data)); /* handle notifications from new app launches */ g_signal_connect (app, "message-received", G_CALLBACK (message_received_cb), data); if (settings_id) { sync_ui_show_settings (data, settings_id); } gtk_main (); } } g_object_unref (app); return 0; } #else int main (int argc, char *argv[]) { app_data *data; init (argc, argv); set_app_name_and_icon (); data = sync_ui_create (); if (settings_id) { sync_ui_show_settings (data, settings_id); } gtk_main (); return 0; } #endif /* ENABLE_UNIQUE */ syncevolution_1.4/src/gtk3-ui/mux-frame.c000066400000000000000000000310341230021373600204760ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "mux-frame.h" #include static GdkColor mux_frame_default_border_color = { 0, 0xdddd, 0xe2e2, 0xe5e5 }; static GdkColor mux_frame_default_bullet_color = { 0, 0xaaaa, 0xaaaa, 0xaaaa }; static gfloat mux_frame_bullet_size_factor = 1.3; #define MUX_FRAME_BULLET_PADDING 10 static void mux_frame_buildable_init (GtkBuildableIface *iface); static void mux_frame_buildable_add_child (GtkBuildable *buildable, GtkBuilder *builder, GObject *child, const gchar *type); G_DEFINE_TYPE_WITH_CODE (MuxFrame, mux_frame, GTK_TYPE_FRAME, G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, mux_frame_buildable_init)) static void mux_frame_dispose (GObject *object) { G_OBJECT_CLASS (mux_frame_parent_class)->dispose (object); } static void mux_frame_finalize (GObject *object) { G_OBJECT_CLASS (mux_frame_parent_class)->finalize (object); } static void label_changed_cb (MuxFrame *frame) { char *font = NULL; GtkWidget *label = gtk_frame_get_label_widget (GTK_FRAME (frame)); if (!label) return; /* ensure font is correct */ gtk_widget_style_get (GTK_WIDGET (frame), "title-font", &font, NULL); if (font) { PangoFontDescription *desc; desc = pango_font_description_from_string (font); gtk_widget_modify_font (label, desc); pango_font_description_free (desc); g_free (font); } gtk_misc_set_alignment (GTK_MISC (label), 0.0, 1.0); } static void mux_frame_update_style (MuxFrame *frame) { GdkColor *border_color, *bullet_color; gtk_widget_style_get (GTK_WIDGET (frame), "border-color", &border_color, "bullet-color", &bullet_color, NULL); if (border_color) { frame->border_color = *border_color; gdk_color_free (border_color); } else { frame->border_color = mux_frame_default_border_color; } if (bullet_color) { frame->bullet_color = *bullet_color; gdk_color_free (bullet_color); } else { frame->bullet_color = mux_frame_default_bullet_color; } label_changed_cb (frame); } static void rounded_rectangle (cairo_t * cr, double x, double y, double w, double h, guint radius) { if (radius > w / 2) radius = w / 2; if (radius > h / 2) radius = h / 2; cairo_move_to (cr, x + radius, y); cairo_arc (cr, x + w - radius, y + radius, radius, M_PI * 1.5, M_PI * 2); cairo_arc (cr, x + w - radius, y + h - radius, radius, 0, M_PI * 0.5); cairo_arc (cr, x + radius, y + h - radius, radius, M_PI * 0.5, M_PI); cairo_arc (cr, x + radius, y + radius, radius, M_PI, M_PI * 1.5); } static void mux_frame_paint (GtkWidget *widget, cairo_t *cairo) { MuxFrame *frame = MUX_FRAME (widget); GtkStyle *style; guint width; GtkAllocation allocation; g_return_if_fail (widget != NULL); g_return_if_fail (MUX_IS_FRAME (widget)); style = gtk_widget_get_style (widget); width = gtk_container_get_border_width (GTK_CONTAINER (widget)); gtk_widget_get_allocation (widget, &allocation); /* draw border */ if (width != 0) { gdk_cairo_set_source_color (cairo, &frame->border_color); rounded_rectangle (cairo, 0, 0, allocation.width, allocation.height, width); cairo_clip (cairo); cairo_paint (cairo); } /* draw background */ gdk_cairo_set_source_color (cairo, &style->bg[gtk_widget_get_state (widget)]); rounded_rectangle (cairo, width, width, allocation.width - 2 * width, allocation.height - 2 * width, width); cairo_clip (cairo); cairo_paint (cairo); /* draw bullet before title */ if (gtk_frame_get_label_widget (GTK_FRAME (frame))) { gdk_cairo_set_source_color (cairo, &frame->bullet_color); rounded_rectangle (cairo, 0, 0, frame->bullet_allocation.width, frame->bullet_allocation.height, 4); cairo_clip (cairo); cairo_paint (cairo); } } static gboolean mux_frame_draw(GtkWidget *widget, cairo_t *cr) { GtkWidgetClass *grand_parent; if (gtk_widget_is_drawable (widget)) { mux_frame_paint (widget, cr); grand_parent = GTK_WIDGET_CLASS (g_type_class_peek_parent (mux_frame_parent_class)); grand_parent->draw (widget, cr); } return FALSE; } static void mux_frame_size_request (GtkWidget *widget, GtkRequisition *requisition) { GtkWidget *label = gtk_frame_get_label_widget (GTK_FRAME (widget)); GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget)); GtkRequisition child_req; GtkRequisition title_req; child_req.width = child_req.height = 0; if (child) gtk_widget_size_request (child, &child_req); title_req.width = title_req.height = 0; if (label) { gtk_widget_size_request (label, &title_req); /* add room for bullet */ title_req.height = title_req.height * mux_frame_bullet_size_factor + 2 * MUX_FRAME_BULLET_PADDING; title_req.width += title_req.height * mux_frame_bullet_size_factor + 2 * MUX_FRAME_BULLET_PADDING; } requisition->width = MAX (child_req.width, title_req.width) + 2 * (gtk_container_get_border_width (GTK_CONTAINER (widget)) + gtk_widget_get_style (widget)->xthickness); requisition->height = title_req.height + child_req.height + 2 * (gtk_container_get_border_width (GTK_CONTAINER (widget)) + gtk_widget_get_style (widget)->ythickness); } static void mux_frame_get_preferred_width (GtkWidget *widget, gint *minimal_width, gint *natural_width) { GtkRequisition requisition; mux_frame_size_request (widget, &requisition); *minimal_width = *natural_width = requisition.width; } static void mux_frame_get_preferred_height (GtkWidget *widget, gint *minimal_height, gint *natural_height) { GtkRequisition requisition; mux_frame_size_request (widget, &requisition); *minimal_height = *natural_height = requisition.height; } static void mux_frame_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { GtkBin *bin = GTK_BIN (widget); MuxFrame *mux_frame = MUX_FRAME (widget); GtkFrame *frame = GTK_FRAME (widget); GtkAllocation child_allocation; GtkWidget *child; int xmargin, ymargin, title_height; gtk_widget_set_allocation (widget, allocation); xmargin = gtk_container_get_border_width (GTK_CONTAINER (widget)) + gtk_widget_get_style (widget)->xthickness; ymargin = gtk_container_get_border_width (GTK_CONTAINER (widget)) + gtk_widget_get_style (widget)->ythickness; title_height = 0; if (gtk_frame_get_label_widget (frame)) { GtkAllocation title_allocation; GtkRequisition title_req; gtk_widget_get_child_requisition (gtk_frame_get_label_widget (frame), &title_req); /* the bullet is bigger than the text */ title_height = title_req.height * mux_frame_bullet_size_factor + 2 * MUX_FRAME_BULLET_PADDING; /* x allocation starts after bullet */ title_allocation.x = allocation->x + xmargin + title_height; title_allocation.y = allocation->y + ymargin + MUX_FRAME_BULLET_PADDING; title_allocation.width = MIN (title_req.width, allocation->width - 2 * xmargin - title_height); title_allocation.height = title_height - 2 * MUX_FRAME_BULLET_PADDING; gtk_widget_size_allocate (gtk_frame_get_label_widget (frame), &title_allocation); mux_frame->bullet_allocation.x = allocation->x + xmargin + MUX_FRAME_BULLET_PADDING; mux_frame->bullet_allocation.y = allocation->y + ymargin + MUX_FRAME_BULLET_PADDING; mux_frame->bullet_allocation.width = title_allocation.height; mux_frame->bullet_allocation.height = title_allocation.height; } child_allocation.x = allocation->x + xmargin; child_allocation.y = allocation->y + ymargin + title_height; child_allocation.width = allocation->width - 2 * xmargin; child_allocation.height = allocation->height - 2 * ymargin - title_height; if (gtk_widget_get_mapped (widget)) { gdk_window_invalidate_rect (gtk_widget_get_window (widget), allocation, FALSE); } child = gtk_bin_get_child (bin); if (child && gtk_widget_get_visible (child)) { gtk_widget_size_allocate (child, &child_allocation); } } static void mux_frame_style_set (GtkWidget *widget, GtkStyle *previous) { MuxFrame *frame = MUX_FRAME (widget); mux_frame_update_style (frame); GTK_WIDGET_CLASS (mux_frame_parent_class)->style_set (widget, previous); } static void mux_frame_class_init (MuxFrameClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GParamSpec *pspec; object_class->dispose = mux_frame_dispose; object_class->finalize = mux_frame_finalize; widget_class->draw = mux_frame_draw; widget_class->get_preferred_width = mux_frame_get_preferred_width; widget_class->get_preferred_height = mux_frame_get_preferred_height; widget_class->size_allocate = mux_frame_size_allocate; widget_class->style_set = mux_frame_style_set; pspec = g_param_spec_boxed ("border-color", "Border color", "Color of the outside border", GDK_TYPE_COLOR, G_PARAM_READABLE); gtk_widget_class_install_style_property(widget_class, pspec); pspec = g_param_spec_boxed ("bullet-color", "Bullet color", "Color of the rounded rectangle before a title", GDK_TYPE_COLOR, G_PARAM_READABLE); gtk_widget_class_install_style_property(widget_class, pspec); pspec = g_param_spec_string ("title-font", "Title font", "Pango font description string for title text", "12", G_PARAM_READWRITE); gtk_widget_class_install_style_property(widget_class, pspec); } static void mux_frame_buildable_add_child (GtkBuildable *buildable, GtkBuilder *builder, GObject *child, const gchar *type) { if (!type) gtk_container_add (GTK_CONTAINER (buildable), GTK_WIDGET (child)); else GTK_BUILDER_WARN_INVALID_CHILD_TYPE (MUX_FRAME (buildable), type); } static void mux_frame_buildable_init (GtkBuildableIface *iface) { iface->add_child = mux_frame_buildable_add_child; } static void mux_frame_init (MuxFrame *self) { g_signal_connect (self, "notify::label-widget", G_CALLBACK (label_changed_cb), NULL); } GtkWidget* mux_frame_new (void) { return g_object_new (MUX_TYPE_FRAME, "border-width", 4, NULL); } syncevolution_1.4/src/gtk3-ui/mux-frame.h000066400000000000000000000032511230021373600205030ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef _MUX_FRAME #define _MUX_FRAME #include #include G_BEGIN_DECLS #define MUX_TYPE_FRAME mux_frame_get_type() #define MUX_FRAME(obj) \ (G_TYPE_CHECK_INSTANCE_CAST ((obj), MUX_TYPE_FRAME, MuxFrame)) #define MUX_FRAME_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST ((klass), MUX_TYPE_FRAME, MuxFrameClass)) #define MUX_IS_FRAME(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MUX_TYPE_FRAME)) #define MUX_IS_FRAME_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE ((klass), MUX_TYPE_FRAME)) #define MUX_FRAME_GET_CLASS(obj) \ (G_TYPE_INSTANCE_GET_CLASS ((obj), MUX_TYPE_FRAME, MuxFrameClass)) typedef struct { GtkFrame parent; GtkAllocation bullet_allocation; GdkColor bullet_color; GdkColor border_color; } MuxFrame; typedef struct { GtkFrameClass parent_class; } MuxFrameClass; GType mux_frame_get_type (void); GtkWidget* mux_frame_new (void); G_END_DECLS #endif syncevolution_1.4/src/gtk3-ui/sync-config-widget.c000066400000000000000000002241371230021373600223050ustar00rootroot00000000000000#include "config.h" #include #include #include #include #include "sync-ui.h" #include "sync-config-widget.h" #define INDICATOR_SIZE 16 #define CHILD_PADDING 3 G_DEFINE_TYPE (SyncConfigWidget, sync_config_widget, GTK_TYPE_CONTAINER) typedef struct source_widgets { char *name; GtkWidget *label; GtkWidget *entry; GtkWidget *check; GtkWidget *source_toggle_label; guint count; } source_widgets; enum { PROP_0, PROP_SERVER, PROP_NAME, PROP_CONFIG, PROP_CURRENT, PROP_HAS_TEMPLATE, PROP_CONFIGURED, PROP_CURRENT_SERVICE_NAME, PROP_EXPANDED, }; enum { SIGNAL_CHANGED, LAST_SIGNAL }; static guint32 signals[LAST_SIGNAL] = {0, }; typedef struct save_config_data { SyncConfigWidget *widget; gboolean delete; gboolean temporary; source_widgets *widgets; char *basename; } save_config_data; static void start_session_for_config_write_cb (SyncevoServer *server, char *path, GError *error, save_config_data *data); static void sync_config_widget_update_label (SyncConfigWidget *self); static void sync_config_widget_set_name (SyncConfigWidget *self, const char *name); static void remove_child (GtkWidget *widget, GtkContainer *container) { gtk_container_remove (container, widget); } const char* get_service_description (const char *service) { if (!service) return NULL; /* TRANSLATORS: Descriptions for specific services, shown in service * configuration form */ if (strcmp (service, "ScheduleWorld") == 0) { return _("ScheduleWorld enables you to keep your contacts, events, " "tasks, and notes in sync."); }else if (strcmp (service, "Google") == 0) { return _("Google Sync can back up and synchronize your contacts " "with your Gmail contacts."); }else if (strcmp (service, "Funambol") == 0) { /* TRANSLATORS: Please include the word "demo" (or the equivalent in your language): Funambol is going to be a 90 day demo service in the future */ return _("Back up your contacts and calendar. Sync with a single " "click, anytime, anywhere (DEMO)."); }else if (strcmp (service, "Mobical") == 0) { return _("Mobical Backup and Restore service allows you to securely " "back up your personal mobile data for free."); }else if (strcmp (service, "ZYB") == 0) { return _("ZYB is a simple way for people to store and share mobile " "information online."); }else if (strcmp (service, "Memotoo") == 0) { return _("Memotoo lets you access your personal data from any " "computer connected to the Internet."); } return NULL; } static void update_source_uri (char *name, GHashTable *source_configuration, SyncConfigWidget *self) { const char *uri; source_widgets *widgets; widgets = (source_widgets*)g_hash_table_lookup (self->sources, name); if (!widgets) { return; } uri = gtk_entry_get_text (GTK_ENTRY (widgets->entry)); g_hash_table_insert (source_configuration, g_strdup ("uri"), g_strdup (uri)); } static source_widgets * source_widgets_ref (source_widgets *widgets) { if (widgets) { widgets->count++; } return widgets; } static void source_widgets_unref (source_widgets *widgets) { if (widgets) { widgets->count--; if (widgets->count == 0) g_slice_free (source_widgets, widgets); } } static void check_source_cb (SyncevoSession *session, GError *error, source_widgets *widgets) { gboolean show = TRUE; if (error) { if(error->code == DBUS_GERROR_REMOTE_EXCEPTION && dbus_g_error_has_name (error, SYNCEVO_DBUS_ERROR_SOURCE_UNUSABLE)) { show = FALSE; } else { g_warning ("CheckSource failed: %s", error->message); /* non-fatal, ignore in UI */ } g_error_free (error); } if (widgets->count > 1) { if (show) { /* NOTE: with the new two sources per row layout not showing things * may look weird in some cases... the layout should really only be * done at this point */ gtk_widget_show (widgets->source_toggle_label); gtk_widget_show (widgets->label); gtk_widget_show (widgets->entry); gtk_widget_show (widgets->check); } else { /* next save should disable this source */ gtk_switch_set_active (GTK_SWITCH (widgets->check), FALSE); } } source_widgets_unref (widgets); g_object_unref (session); } static void set_config_cb (SyncevoSession *session, GError *error, save_config_data *data) { if (error) { g_warning ("Error in Session.SetConfig: %s", error->message); g_error_free (error); g_object_unref (session); show_error_dialog (GTK_WIDGET (data->widget), _("Sorry, failed to save the configuration")); return; } if (data->temporary) { syncevo_session_check_source (session, data->widgets->name, (SyncevoSessionGenericCb)check_source_cb, data->widgets); } else { data->widget->configured = TRUE; g_signal_emit (data->widget, signals[SIGNAL_CHANGED], 0); g_object_unref (session); } } static void get_config_for_overwrite_prevention_cb (SyncevoSession *session, SyncevoConfig *config, GError *error, save_config_data *data) { static int index = 0; char *name; if (error) { index = 0; if (error->code == DBUS_GERROR_REMOTE_EXCEPTION && dbus_g_error_has_name (error, SYNCEVO_DBUS_ERROR_NO_SUCH_CONFIG)) { /* Config does not exist (as expected), we can now save */ syncevo_session_set_config (session, data->temporary, data->temporary, data->widget->config, (SyncevoSessionGenericCb)set_config_cb, data); return; } g_warning ("Unexpected error in Session.GetConfig: %s", error->message); g_error_free (error); g_object_unref (session); return; } /* Config exists when we are trying to create a new config... * Need to start a new session with another name */ g_object_unref (session); name = g_strdup_printf ("%s__%d", data->basename, ++index); sync_config_widget_set_name (data->widget, name); g_free (name); syncevo_server_start_no_sync_session (data->widget->server, data->widget->config_name, (SyncevoServerStartSessionCb)start_session_for_config_write_cb, data); } static void save_config (save_config_data *data, SyncevoSession *session) { SyncConfigWidget *w = data->widget; if (data->delete) { syncevo_config_free (w->config); w->config = g_hash_table_new (g_str_hash, g_str_equal); } /* if this is a client peer (a device) and not configured, we * need to test that we aren't overwriting existing * configs */ /* TODO: This might be a good thing to do for any configurations.*/ if (peer_is_client (w->config) && !w->configured && !data->temporary) { syncevo_session_get_config (session, FALSE, (SyncevoSessionGetConfigCb)get_config_for_overwrite_prevention_cb, data); } else { syncevo_session_set_config (session, data->temporary, data->temporary, data->widget->config, (SyncevoSessionGenericCb)set_config_cb, data); } } static void status_changed_for_config_write_cb (SyncevoSession *session, SyncevoSessionStatus status, guint error_code, SyncevoSourceStatuses *source_statuses, save_config_data *data) { if (status == SYNCEVO_STATUS_IDLE) { save_config (data, session); } } static void get_status_for_config_write_cb (SyncevoSession *session, SyncevoSessionStatus status, guint error_code, SyncevoSourceStatuses *source_statuses, GError *error, save_config_data *data) { if (error) { g_warning ("Error in Session.GetStatus: %s", error->message); g_error_free (error); g_object_unref (session); /* TODO show in UI: save failed in service list */ return; } syncevo_source_statuses_free (source_statuses); if (status == SYNCEVO_STATUS_IDLE) { save_config (data, session); } } static void start_session_for_config_write_cb (SyncevoServer *server, char *path, GError *error, save_config_data *data) { SyncevoSession *session; if (error) { g_warning ("Error in Server.StartSession: %s", error->message); g_error_free (error); /* TODO show in UI: save failed in service list */ return; } session = syncevo_session_new (path); /* we want to know about status changes to our session */ g_signal_connect (session, "status-changed", G_CALLBACK (status_changed_for_config_write_cb), data); syncevo_session_get_status (session, (SyncevoSessionGetStatusCb)get_status_for_config_write_cb, data); } static void stop_clicked_cb (GtkButton *btn, SyncConfigWidget *self) { save_config_data *data; if (!self->config) { return; } syncevo_config_set_value (self->config, NULL, "defaultPeer", ""); sync_config_widget_set_current (self, FALSE); data = g_slice_new (save_config_data); data->widget = self; data->delete = FALSE; data->temporary = FALSE; syncevo_server_start_no_sync_session (self->server, self->config_name, (SyncevoServerStartSessionCb)start_session_for_config_write_cb, data); } static void use_clicked_cb (GtkButton *btn, SyncConfigWidget *self) { save_config_data *data; const char *username, *password, *sync_url, *pretty_name; char *real_url, *device; gboolean send, receive; SyncevoSyncMode mode; if (!self->config) { return; } if (!self->config_name || strlen (self->config_name) == 0) { g_free (self->config_name); self->config_name = g_strdup (gtk_entry_get_text (GTK_ENTRY (self->entry))); } if (self->mode_changed) { GHashTableIter iter; source_widgets *widgets; char *name; gboolean client = peer_is_client (self->config); send = gtk_switch_get_active (GTK_SWITCH (self->send_check)); receive = gtk_switch_get_active (GTK_SWITCH (self->receive_check)); if (send && receive) { mode = SYNCEVO_SYNC_TWO_WAY; } else if (send) { mode = client ? SYNCEVO_SYNC_ONE_WAY_FROM_SERVER : SYNCEVO_SYNC_ONE_WAY_FROM_CLIENT; } else if (receive) { mode = client ? SYNCEVO_SYNC_ONE_WAY_FROM_CLIENT : SYNCEVO_SYNC_ONE_WAY_FROM_SERVER; } else { mode = SYNCEVO_SYNC_NONE; } g_hash_table_iter_init (&iter, self->sources); while (g_hash_table_iter_next (&iter, (gpointer)&name, (gpointer)&widgets)) { const char *mode_str; gboolean active; active = gtk_switch_get_active (GTK_SWITCH (widgets->check)) && gtk_widget_get_sensitive (widgets->check); if (active) { mode_str = syncevo_sync_mode_to_string (mode); } else { mode_str = "none"; } syncevo_config_set_value (self->config, name, "sync", mode_str); } } username = gtk_entry_get_text (GTK_ENTRY (self->username_entry)); syncevo_config_set_value (self->config, NULL, "username", username); sync_url = gtk_entry_get_text (GTK_ENTRY (self->baseurl_entry)); /* make a wild guess if no scheme in url */ if (strstr (sync_url, "://") == NULL) { real_url = g_strdup_printf ("http://%s", sync_url); } else { real_url = g_strdup (sync_url); } syncevo_config_set_value (self->config, NULL, "syncURL", real_url); password = gtk_entry_get_text (GTK_ENTRY (self->password_entry)); syncevo_config_set_value (self->config, NULL, "password", password); syncevo_config_get_value (self->config, NULL, "deviceName", &device); if (!device || strlen (device) == 0) { if (!self->config_name || strlen (self->config_name) == 0 || !sync_url || strlen (sync_url) == 0) { show_error_dialog (GTK_WIDGET (self), _("Service must have a name and server URL")); return; } } syncevo_config_foreach_source (self->config, (ConfigFunc)update_source_uri, self); pretty_name = gtk_entry_get_text (GTK_ENTRY (self->entry)); syncevo_config_set_value (self->config, NULL, "PeerName", pretty_name); syncevo_config_get_value (self->config, NULL, "PeerName", &self->pretty_name); syncevo_config_set_value (self->config, NULL, "defaultPeer", self->config_name); sync_config_widget_set_current (self, TRUE); data = g_slice_new (save_config_data); data->widget = self; data->delete = FALSE; data->temporary = FALSE; data->basename = g_strdup (self->config_name); syncevo_server_start_no_sync_session (self->server, self->config_name, (SyncevoServerStartSessionCb)start_session_for_config_write_cb, data); g_free (real_url); } static void reset_delete_clicked_cb (GtkButton *btn, SyncConfigWidget *self) { char *msg, *yes, *no; save_config_data *data; if (!self->config) { return; } if (self->has_template) { /*TRANSLATORS: warning dialog text for resetting pre-defined services */ msg = g_strdup_printf (_("Do you want to reset the settings for %s? " "This will not remove any synced information on either end."), self->pretty_name); /*TRANSLATORS: buttons in reset-service warning dialog */ yes = _("Yes, reset"); no = _("No, keep settings"); } else { /*TRANSLATORS: warning dialog text for deleting user-defined services */ msg = g_strdup_printf (_("Do you want to delete the settings for %s? " "This will not remove any synced information on either " "end but it will remove these settings."), self->pretty_name); /*TRANSLATORS: buttons in delete-service warning dialog */ yes = _("Yes, delete"); no = _("No, keep settings"); } /*TRANSLATORS: decline button in "Reset/delete service" warning dialogs */ if (!show_confirmation (GTK_WIDGET (self), msg, yes, no)) { g_free (msg); return; } g_free (msg); if (self->current) { sync_config_widget_set_current (self, FALSE); } data = g_slice_new (save_config_data); data->widget = self; data->delete = TRUE; data->temporary = FALSE; syncevo_server_start_no_sync_session (self->server, self->config_name, (SyncevoServerStartSessionCb)start_session_for_config_write_cb, data); } static void update_buttons (SyncConfigWidget *self) { if (self->has_template) { /* TRANSLATORS: button labels in service configuration form */ gtk_button_set_label (GTK_BUTTON (self->reset_delete_button), _("Reset settings")); } else { gtk_button_set_label (GTK_BUTTON (self->reset_delete_button), _("Delete settings")); } if (self->configured) { gtk_widget_show (GTK_WIDGET (self->reset_delete_button)); } else { gtk_widget_hide (GTK_WIDGET (self->reset_delete_button)); } if (self->current || !self->current_service_name) { gtk_button_set_label (GTK_BUTTON (self->use_button), _("Save and use")); } else { gtk_button_set_label (GTK_BUTTON (self->use_button), _("Save and replace\ncurrent service")); } if (self->current && self->config) { if (peer_is_client (self->config)) { gtk_button_set_label (GTK_BUTTON (self->stop_button), _("Stop using device")); } else { gtk_button_set_label (GTK_BUTTON (self->stop_button), _("Stop using service")); } gtk_widget_show (self->stop_button); } else { gtk_widget_hide (self->stop_button); } } static void mode_widget_notify_active_cb (GtkWidget *widget, GParamSpec *pspec, SyncConfigWidget *self) { self->mode_changed = TRUE; } static void source_entry_notify_text_cb (GObject *gobject, GParamSpec *pspec, source_widgets *widgets) { gboolean new_editable, old_editable; const char *text; text = gtk_entry_get_text (GTK_ENTRY (widgets->entry)); new_editable = (strlen (text) > 0); old_editable = gtk_widget_get_sensitive (widgets->check); if (new_editable != old_editable) { gtk_widget_set_sensitive (widgets->check, new_editable); gtk_switch_set_active (GTK_SWITCH (widgets->check), new_editable); } } static GtkWidget* add_toggle_widget (SyncConfigWidget *self, const char *title, gboolean active, guint row, guint col) { GtkWidget *toggle; int padding; GtkWidget *label; padding = (col == 1) ? 0 : 32; col = col * 2; label = gtk_label_new (title); gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END); gtk_widget_set_size_request (label, 260, -1); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_table_attach (GTK_TABLE (self->mode_table), label, col, col + 1, row, row + 1, GTK_FILL, GTK_FILL, 0, 0); toggle = gtk_switch_new (); g_signal_connect_swapped (toggle, "hide", G_CALLBACK (gtk_widget_hide), label); g_signal_connect_swapped (toggle, "show", G_CALLBACK (gtk_widget_show), label); gtk_switch_set_active (GTK_SWITCH (toggle), active); g_signal_connect (toggle, "notify::active", G_CALLBACK (mode_widget_notify_active_cb), self); gtk_table_attach (GTK_TABLE (self->mode_table), toggle, col + 1, col + 2, row, row + 1, GTK_FILL, GTK_FILL, padding, 0); return toggle; } /* check if config includes a virtual source that covers the given * source */ static gboolean virtual_source_exists (SyncevoConfig *config, const char *name) { GHashTableIter iter; const char *source_string; GHashTable *source_config; g_hash_table_iter_init (&iter, config); while (g_hash_table_iter_next (&iter, (gpointer)&source_string, (gpointer)&source_config)) { char **strs; if (g_str_has_prefix (source_string, "source/")) { const char *uri, *type; type = g_hash_table_lookup (source_config, "backend"); uri = g_hash_table_lookup (source_config, "uri"); if (!uri || !type || !g_str_has_prefix (type, "virtual:")) { /* this source is not defined, or not virtual */ continue; } strs = g_strsplit (source_string + 7, "+", 0); if (g_strv_length (strs) > 1) { int i; for (i = 0; strs[i]; i++) { if (g_strcmp0 (name, strs[i]) == 0) { g_strfreev (strs); return TRUE; } } } g_strfreev (strs); } } return FALSE; } static void init_source (char *name, GHashTable *source_configuration, SyncConfigWidget *self) { char *str, *pretty_name; const char *uri, *type; guint rows; guint row; static guint col = 0; source_widgets *widgets; SyncevoSyncMode mode; save_config_data *data; type = g_hash_table_lookup (source_configuration, "backend"); uri = g_hash_table_lookup (source_configuration, "uri"); if (!type || strlen (type) == 0) { return; } if (g_str_has_prefix (type, "virtual:") && !uri) { /* undefined virtual source */ return; } if (virtual_source_exists (self->config, name)) { return; } g_object_get (self->mode_table, "n-rows", &rows, NULL); if (!self->no_source_toggles && col == 0) { col = 1; row = rows - 1; } else { col = 0; row = rows; } self->no_source_toggles = FALSE; widgets = g_slice_new0 (source_widgets); widgets->name = name; widgets->count = 1; g_hash_table_insert (self->sources, name, widgets); widgets->source_toggle_label = self->source_toggle_label; pretty_name = get_pretty_source_name (name); mode = syncevo_sync_mode_from_string (g_hash_table_lookup (source_configuration, "sync")); widgets->check = add_toggle_widget (self, pretty_name, (mode > SYNCEVO_SYNC_NONE), row, col); /* TRANSLATORS: label for an entry in service configuration form. * Placeholder is a source name. * Example: "Appointments URI" */ str = g_strdup_printf (_("%s URI"), pretty_name); widgets->label = gtk_label_new (str); g_free (str); g_free (pretty_name); g_object_get (self->server_settings_table, "n-rows", &row, NULL); gtk_misc_set_alignment (GTK_MISC (widgets->label), 0.0, 0.5); gtk_table_attach (GTK_TABLE (self->server_settings_table), widgets->label, 0, 1, row, row + 1, GTK_FILL, GTK_EXPAND, 0, 0); widgets->entry = gtk_entry_new (); gtk_entry_set_max_length (GTK_ENTRY (widgets->entry), 99); gtk_entry_set_width_chars (GTK_ENTRY (widgets->entry), 80); if (uri) { gtk_entry_set_text (GTK_ENTRY (widgets->entry), uri); } gtk_table_attach_defaults (GTK_TABLE (self->server_settings_table), widgets->entry, 1, 2, row, row + 1); g_signal_connect (widgets->entry, "notify::text", G_CALLBACK (source_entry_notify_text_cb), widgets); gtk_widget_set_sensitive (widgets->check, uri && strlen (uri) > 0); /* start a session so we save a temporary config so we can do * CheckSource, and show the source-related widgets if the * source is available */ data = g_slice_new (save_config_data); data->widget = self; data->delete = FALSE; data->temporary = TRUE; data->widgets = source_widgets_ref (widgets); syncevo_server_start_no_sync_session (self->server, self->config_name, (SyncevoServerStartSessionCb)start_session_for_config_write_cb, data); } static void get_common_mode (char *name, GHashTable *source_configuration, SyncevoSyncMode *common_mode) { SyncevoSyncMode mode; char *mode_str, *type; type = g_hash_table_lookup (source_configuration, "backend"); if (!type || strlen (type) == 0) { return; } mode_str = g_hash_table_lookup (source_configuration, "sync"); mode = syncevo_sync_mode_from_string (mode_str); if (mode == SYNCEVO_SYNC_NONE) { return; } if (*common_mode == SYNCEVO_SYNC_NONE) { *common_mode = mode; } else if (mode != *common_mode) { *common_mode = SYNCEVO_SYNC_UNKNOWN; } } void sync_config_widget_expand_id (SyncConfigWidget *self, const char *id) { if (id && self->config) { char *sync_url; if (syncevo_config_get_value (self->config, NULL, "syncURL", &sync_url) && strncmp (sync_url, id, strlen (id)) == 0) { sync_config_widget_set_expanded (self, TRUE); } else if (self->config_name && g_ascii_strcasecmp (self->config_name, id) == 0) { sync_config_widget_set_expanded (self, TRUE); } } } static void sync_config_widget_update_expander (SyncConfigWidget *self) { char *username = ""; char *password = ""; char *sync_url = ""; const char *descr; char *str; GtkWidget *label, *align; SyncevoSyncMode mode = SYNCEVO_SYNC_NONE; gboolean send, receive; gboolean client; gtk_container_foreach (GTK_CONTAINER (self->server_settings_table), (GtkCallback)remove_child, self->server_settings_table); gtk_table_resize (GTK_TABLE (self->server_settings_table), 2, 1); gtk_container_foreach (GTK_CONTAINER (self->mode_table), (GtkCallback)remove_child, self->mode_table); gtk_table_resize (GTK_TABLE (self->mode_table), 2, 1); client = peer_is_client (self->config); if (client) { if (!self->device_template_selected) { gtk_widget_hide (self->settings_box); gtk_widget_show (self->device_selector_box); /* temporary solution for device template selection: * show list of templates only */ } else { gtk_widget_show (self->settings_box); gtk_widget_hide (self->device_selector_box); gtk_widget_hide (self->userinfo_table); gtk_widget_hide (self->fake_expander); } } else { gtk_widget_show (self->settings_box); gtk_widget_hide (self->device_selector_box); gtk_widget_show (self->userinfo_table); gtk_widget_show (self->fake_expander); } syncevo_config_foreach_source (self->config, (ConfigFunc)get_common_mode, &mode); switch (mode) { case SYNCEVO_SYNC_TWO_WAY: send = receive = TRUE; break; case SYNCEVO_SYNC_ONE_WAY_FROM_CLIENT: if (client) { send = FALSE; receive = TRUE; } else { send = TRUE; receive = FALSE; } break; case SYNCEVO_SYNC_ONE_WAY_FROM_SERVER: if (client) { send = TRUE; receive = FALSE; } else { send = FALSE; receive = TRUE; } break; default: gtk_widget_show (self->complex_config_info_bar); send = FALSE; receive = FALSE; } self->mode_changed = FALSE; if (self->pretty_name) { gtk_entry_set_text (GTK_ENTRY (self->entry), self->pretty_name); } if (!self->config_name || strlen (self->config_name) == 0) { gtk_expander_set_expanded (GTK_EXPANDER (self->expander), TRUE); } descr = get_service_description (self->config_name); if (descr) { gtk_label_set_text (GTK_LABEL (self->description_label), get_service_description (self->config_name)); gtk_widget_show (self->description_label); } else { gtk_widget_hide (self->description_label); } update_buttons (self); /* TRANSLATORS: toggles in service configuration form, placeholder is service * or device name */ str = g_strdup_printf (_("Send changes to %s"), self->pretty_name); self->send_check = add_toggle_widget (self, str, send, 0, 0); gtk_widget_show (self->send_check); g_free (str); str = g_strdup_printf (_("Receive changes from %s"), self->pretty_name); self->receive_check = add_toggle_widget (self, str, receive, 0, 1); gtk_widget_show (self->receive_check); g_free (str); align = gtk_alignment_new (0.0, 1.0, 0.0, 0.0); gtk_alignment_set_padding (GTK_ALIGNMENT (align), 10, 0, 0, 0); gtk_widget_show (align); gtk_table_attach (GTK_TABLE (self->mode_table), align, 0, 1, 1, 2, GTK_FILL, GTK_FILL, 0, 0); self->source_toggle_label = gtk_label_new (""); /* TRANSLATORS: Label for the source toggles in configuration form. This is a verb, as in "Sync Calendar". */ gtk_label_set_markup (GTK_LABEL (self->source_toggle_label), _("Sync")); gtk_widget_show (self->source_toggle_label); gtk_container_add (GTK_CONTAINER (align), self->source_toggle_label); syncevo_config_get_value (self->config, NULL, "username", &username); syncevo_config_get_value (self->config, NULL, "password", &password); syncevo_config_get_value (self->config, NULL, "syncURL", &sync_url); if (username) { gtk_entry_set_text (GTK_ENTRY (self->username_entry), username); } if (password) { gtk_entry_set_text (GTK_ENTRY (self->password_entry), password); } // TRANSLATORS: label of a entry in service configuration label = gtk_label_new (_("Server address")); gtk_misc_set_alignment (GTK_MISC (label), 9.0, 0.5); gtk_widget_show (label); gtk_table_attach (GTK_TABLE (self->server_settings_table), label, 0, 1, 0, 1, GTK_FILL, GTK_EXPAND, 0, 0); self->baseurl_entry = gtk_entry_new (); gtk_entry_set_max_length (GTK_ENTRY (self->baseurl_entry), 99); gtk_entry_set_width_chars (GTK_ENTRY (self->baseurl_entry), 80); if (sync_url) { gtk_entry_set_text (GTK_ENTRY (self->baseurl_entry), sync_url); } gtk_widget_show (self->baseurl_entry); gtk_table_attach_defaults (GTK_TABLE (self->server_settings_table), self->baseurl_entry, 1, 2, 0, 1); /* update source widgets */ if (self->sources) { g_hash_table_destroy (self->sources); } self->sources = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)source_widgets_unref); self->no_source_toggles = TRUE; syncevo_config_foreach_source (self->config, (ConfigFunc)init_source, self); } /* only adds config to hashtable and combo */ static void sync_config_widget_add_config (SyncConfigWidget *self, const char *name, SyncevoConfig *config) { GtkListStore *store; GtkTreeIter iter; const char *guess_name; SyncevoConfig *guess_config; int score = 1; int guess_score, second_guess_score = -1; char *str; store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (self->combo))); if (syncevo_config_get_value (config, NULL, "score", &str)) { score = (int)strtol (str, NULL, 10); } gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, 0, name, 1, config, 2, score, -1); /* make an educated guess if possible */ gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter); gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, 0, &guess_name, 1, &guess_config, 2, &guess_score, -1); if (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter)) { gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, 2, &second_guess_score, -1); } if (guess_score > 1 && guess_score > second_guess_score) { gtk_combo_box_set_active (GTK_COMBO_BOX (self->combo), 0); /* TRANSLATORS: explanation before a device template combobox. * Placeholder is a device name like 'Nokia N85' or 'Syncevolution * Client' */ str = g_strdup_printf (_("This device looks like it might be a '%s'. " "If this is not correct, please take a look at " "the list of supported devices and pick yours " "if it is listed"), guess_name); } else { gtk_combo_box_set_active (GTK_COMBO_BOX (self->combo), -1); str = g_strdup (_("We don't know what this device is exactly. " "Please take a look at the list of " "supported devices and pick yours if it " "is listed")); } gtk_label_set_text (GTK_LABEL (self->device_text), str); g_free (str); } static void sync_config_widget_update_pretty_name (SyncConfigWidget *self) { self->pretty_name = NULL; if (self->config) { syncevo_config_get_value (self->config, NULL, "PeerName", &self->pretty_name); if (!self->pretty_name) { syncevo_config_get_value (self->config, NULL, "deviceName", &self->pretty_name); } } if (!self->pretty_name) { self->pretty_name = self->config_name; } } static void sync_config_widget_set_config (SyncConfigWidget *self, SyncevoConfig *config) { self->config = config; sync_config_widget_update_pretty_name (self); } static void setup_service_clicked (GtkButton *btn, SyncConfigWidget *self) { sync_config_widget_set_expanded (self, TRUE); } static void sync_config_widget_set_name (SyncConfigWidget *self, const char *name) { g_free (self->config_name); self->config_name = g_strdup (name); sync_config_widget_update_pretty_name (self); } static void device_selection_btn_clicked_cb (GtkButton *btn, SyncConfigWidget *self) { GtkTreeIter iter; if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (self->combo), &iter)) { const char *name; SyncevoConfig *config; GtkTreeModel *model; self->device_template_selected = TRUE; model = gtk_combo_box_get_model(GTK_COMBO_BOX (self->combo)); gtk_tree_model_get (model, &iter, 0, &name, -1 ); gtk_tree_model_get (model, &iter, 1, &config, -1 ); sync_config_widget_set_config (self, config); sync_config_widget_update_expander (self); } } static void server_settings_notify_expand_cb (GtkExpander *expander, GParamSpec *pspec, SyncConfigWidget *self) { /* NOTE: expander can be the fake or real one... */ if (gtk_expander_get_expanded (GTK_EXPANDER (self->fake_expander))) { g_signal_handlers_disconnect_by_func (self->fake_expander, server_settings_notify_expand_cb, self); gtk_widget_hide (self->fake_expander); gtk_expander_set_expanded (GTK_EXPANDER (self->fake_expander), FALSE); gtk_expander_set_expanded (GTK_EXPANDER (self->expander), TRUE); gtk_widget_show (self->expander); g_signal_connect (self->expander, "notify::expanded", G_CALLBACK (server_settings_notify_expand_cb), self); } else { g_signal_handlers_disconnect_by_func (self->expander, server_settings_notify_expand_cb, self); gtk_widget_hide (self->expander); gtk_widget_show (self->fake_expander); g_signal_connect (self->fake_expander, "notify::expanded", G_CALLBACK (server_settings_notify_expand_cb), self); } } static GdkPixbuf* load_icon (const char *uri, guint icon_size) { GError *error = NULL; GdkPixbuf *pixbuf; const char *filename; if (uri && strlen (uri) > 0) { if (g_str_has_prefix (uri, "file://")) { filename = uri+7; } else { g_warning ("only file:// icon uri is supported: %s", uri); filename = THEMEDIR "sync-generic.png"; } } else { filename = THEMEDIR "sync-generic.png"; } pixbuf = gdk_pixbuf_new_from_file_at_scale (filename, icon_size, icon_size, TRUE, &error); if (!pixbuf) { g_warning ("Failed to load service icon: %s", error->message); g_error_free (error); return NULL; } return pixbuf; } static void sync_config_widget_update_label (SyncConfigWidget *self) { if (self->config && self->pretty_name) { char *url, *sync_url; char *str; syncevo_config_get_value (self->config, NULL, "WebURL", &url); syncevo_config_get_value (self->config, NULL, "syncURL", &sync_url); if (self->current) { str = g_strdup_printf ("%s", self->pretty_name); } else { str = g_strdup_printf ("%s", self->pretty_name); } if (g_str_has_prefix (sync_url, "obex-bt://")) { char *tmp = g_strdup_printf (_("%s - Bluetooth device"), str); g_free (str); str = tmp; } else if (!self->has_template) { /* TRANSLATORS: service title for services that are not based on a * template in service list, the placeholder is the name of the service */ char *tmp = g_strdup_printf (_("%s - manually setup"), str); g_free (str); str = tmp; } else if (url && strlen (url) > 0) { char *tmp = g_strdup_printf ("%s -",str); g_free (str); str = tmp; } gtk_label_set_markup (GTK_LABEL (self->label), str); g_free (str); } } void sync_config_widget_set_current_service_name (SyncConfigWidget *self, const char *name) { g_free (self->current_service_name); self->current_service_name = g_strdup (name); update_buttons (self); } void sync_config_widget_set_current (SyncConfigWidget *self, gboolean current) { if (self->current != current) { self->current = current; sync_config_widget_update_label (self); } } static void set_session (SyncConfigWidget *self, const char *path) { g_free (self->running_session); self->running_session = g_strdup (path); gtk_widget_set_sensitive (GTK_WIDGET (self->reset_delete_button), !self->running_session); gtk_widget_set_sensitive (GTK_WIDGET (self->use_button), !self->running_session); /* TODO: maybe add a explanation text somewhere: * "Configuration changes are not possible while a sync is in progress" */ } static void session_changed_cb (SyncevoServer *server, char *path, gboolean started, SyncConfigWidget *self) { if (started) { set_session (self, path); } else if (g_strcmp0 (self->running_session, path) == 0 ) { set_session (self, NULL); } } static void get_sessions_cb (SyncevoServer *server, SyncevoSessions *sessions, GError *error, SyncConfigWidget *self) { if (error) { g_warning ("Server.GetSessions failed: %s", error->message); g_error_free (error); /* non-fatal, ignore in UI */ g_object_unref (self); return; } set_session (self, syncevo_sessions_index (sessions, 0)); syncevo_sessions_free (sessions); g_object_unref (self); } void sync_config_widget_set_server (SyncConfigWidget *self, SyncevoServer *server) { if (self->server) { g_signal_handlers_disconnect_by_func (self->server, session_changed_cb, self); g_object_unref (self->server); self->server = NULL; } if (!server && !self->server) { return; } self->server = g_object_ref (server); /* monitor sessions so we can set editing buttons insensitive */ g_signal_connect (self->server, "session-changed", G_CALLBACK (session_changed_cb), self); /* reference is released in callback */ g_object_ref (self); syncevo_server_get_sessions (self->server, (SyncevoServerGetSessionsCb)get_sessions_cb, self); } static void sync_config_widget_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { SyncConfigWidget *self = SYNC_CONFIG_WIDGET (object); switch (property_id) { case PROP_SERVER: sync_config_widget_set_server (self, g_value_get_pointer (value)); break; case PROP_NAME: sync_config_widget_set_name (self, g_value_get_string (value)); break; case PROP_CONFIG: sync_config_widget_set_config (self, g_value_get_pointer (value)); break; case PROP_CURRENT: sync_config_widget_set_current (self, g_value_get_boolean (value)); break; case PROP_HAS_TEMPLATE: sync_config_widget_set_has_template (self, g_value_get_boolean (value)); break; case PROP_CONFIGURED: sync_config_widget_set_configured (self, g_value_get_boolean (value)); break; case PROP_CURRENT_SERVICE_NAME: sync_config_widget_set_current_service_name (self, g_value_get_string (value)); break; case PROP_EXPANDED: sync_config_widget_set_expanded (self, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void sync_config_widget_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { SyncConfigWidget *self = SYNC_CONFIG_WIDGET (object); switch (property_id) { case PROP_SERVER: g_value_set_pointer (value, self->server); case PROP_NAME: g_value_set_string (value, self->config_name); case PROP_CONFIG: g_value_set_pointer (value, self->config); case PROP_CURRENT: g_value_set_boolean (value, self->current); case PROP_HAS_TEMPLATE: g_value_set_boolean (value, self->has_template); case PROP_CONFIGURED: g_value_set_boolean (value, self->configured); case PROP_CURRENT_SERVICE_NAME: g_value_set_string (value, self->current_service_name); case PROP_EXPANDED: g_value_set_boolean (value, self->expanded); default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void sync_config_widget_dispose (GObject *object) { SyncConfigWidget *self = SYNC_CONFIG_WIDGET (object); sync_config_widget_set_server (self, NULL); if (self->config) { syncevo_config_free (self->config); } self->config = NULL; g_free (self->config_name); self->config_name = NULL; g_free (self->current_service_name); self->current_service_name = NULL; g_free (self->running_session); self->running_session = NULL; if (self->sources) { g_hash_table_destroy (self->sources); self->sources = NULL; } G_OBJECT_CLASS (sync_config_widget_parent_class)->dispose (object); } static void init_default_config (SyncConfigWidget *self) { sync_config_widget_set_name (self, ""); self->has_template = FALSE; syncevo_config_set_value (self->config, NULL, "username", ""); syncevo_config_set_value (self->config, NULL, "password", ""); syncevo_config_set_value (self->config, NULL, "syncURL", ""); syncevo_config_set_value (self->config, NULL, "WebURL", ""); syncevo_config_set_value (self->config, "memo", "uri", ""); syncevo_config_set_value (self->config, "todo", "uri", ""); syncevo_config_set_value (self->config, "addressbook", "uri", ""); syncevo_config_set_value (self->config, "calendar", "uri", ""); } static gboolean label_button_draw_cb (GtkWidget *widget, cairo_t *cr, SyncConfigWidget *self) { GtkExpanderStyle style; gint indicator_x, indicator_y; GtkAllocation alloc; gtk_widget_get_allocation (widget, &alloc); indicator_x = gtk_widget_get_style (widget)->xthickness + INDICATOR_SIZE / 2; indicator_y = gtk_widget_get_style (widget)->ythickness + alloc.height / 2; if (self->expanded) { style = GTK_EXPANDER_EXPANDED; } else { style = GTK_EXPANDER_COLLAPSED; } gtk_paint_expander (gtk_widget_get_style (widget), cr, gtk_widget_get_state (widget), GTK_WIDGET (self), NULL, indicator_x, indicator_y, style); return FALSE; } static gboolean sync_config_widget_draw (GtkWidget *widget, cairo_t *cr) { SyncConfigWidget *self = SYNC_CONFIG_WIDGET (widget); GtkAllocation alloc; gtk_widget_get_allocation (widget, &alloc); /* should really use _render_frame() ... */ gtk_paint_box (gtk_widget_get_style (widget), cr, gtk_widget_get_state (widget), GTK_SHADOW_OUT, widget, NULL, 0, 0, alloc.width, alloc.height); gtk_container_propagate_draw (GTK_CONTAINER (self), self->label_box, cr); gtk_container_propagate_draw (GTK_CONTAINER (self), self->expando_box, cr); return FALSE; } static void sync_config_widget_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { GtkRequisition req; GtkAllocation alloc; SyncConfigWidget *self = SYNC_CONFIG_WIDGET (widget); GTK_WIDGET_CLASS (sync_config_widget_parent_class)->size_allocate (widget, allocation); gtk_widget_size_request (self->label_box, &req); alloc.x = allocation->x + gtk_widget_get_style (widget)->xthickness; alloc.y = allocation->y + gtk_widget_get_style (widget)->ythickness; alloc.width = allocation->width - 2 * gtk_widget_get_style (widget)->xthickness; alloc.height = req.height; gtk_widget_size_allocate (self->label_box, &alloc); if (self->expanded) { gtk_widget_size_request (self->expando_box, &req); alloc.x = allocation->x + 2 * gtk_widget_get_style (widget)->xthickness; alloc.y = allocation->y + gtk_widget_get_style (widget)->ythickness + alloc.height + CHILD_PADDING; alloc.width = allocation->width - 4 * gtk_widget_get_style (widget)->xthickness; alloc.height = req.height; gtk_widget_size_allocate (self->expando_box, &alloc); } } static void sync_config_widget_size_request (GtkWidget *widget, GtkRequisition *requisition) { SyncConfigWidget *self = SYNC_CONFIG_WIDGET (widget); GtkRequisition req; requisition->width = gtk_widget_get_style (widget)->xthickness * 2; requisition->height = gtk_widget_get_style (widget)->ythickness * 2; gtk_widget_size_request (self->label_box, &req); requisition->width += req.width; requisition->height = MAX (req.height, INDICATOR_SIZE) + gtk_widget_get_style (widget)->ythickness * 2; if (self->expanded) { gtk_widget_size_request (self->expando_box, &req); requisition->width = MAX (requisition->width, req.width + gtk_widget_get_style (widget)->xthickness * 4); requisition->height += req.height + 2 * gtk_widget_get_style (widget)->ythickness; } } static void sync_config_widget_get_preferred_width (GtkWidget *widget, gint *minimal_width, gint *natural_width) { GtkRequisition requisition; sync_config_widget_size_request (widget, &requisition); *minimal_width = *natural_width = requisition.width; } static void sync_config_widget_get_preferred_height (GtkWidget *widget, gint *minimal_height, gint *natural_height) { GtkRequisition requisition; sync_config_widget_size_request (widget, &requisition); *minimal_height = *natural_height = requisition.height; } static GObject * sync_config_widget_constructor (GType gtype, guint n_properties, GObjectConstructParam *properties) { SyncConfigWidget *self; GObjectClass *parent_class; char *url, *icon; GdkPixbuf *buf; parent_class = G_OBJECT_CLASS (sync_config_widget_parent_class); self = SYNC_CONFIG_WIDGET (parent_class->constructor (gtype, n_properties, properties)); if (!self->config || !self->server) { g_warning ("No SyncevoServer or Syncevoconfig set for SyncConfigWidget"); return G_OBJECT (self); } if (g_strcmp0 (self->config_name, "default") == 0) { init_default_config (self); gtk_widget_show (self->entry); gtk_widget_hide (self->label); } else { gtk_widget_hide (self->entry); gtk_widget_show (self->label); } syncevo_config_get_value (self->config, NULL, "WebURL", &url); syncevo_config_get_value (self->config, NULL, "IconURI", &icon); buf = load_icon (icon, SYNC_UI_LIST_ICON_SIZE); gtk_image_set_from_pixbuf (GTK_IMAGE (self->image), buf); g_object_unref (buf); if (url && strlen (url) > 0) { gtk_link_button_set_uri (GTK_LINK_BUTTON (self->link), url); gtk_widget_show (self->link); } else { gtk_widget_hide (self->link); } sync_config_widget_update_label (self); sync_config_widget_update_expander (self); /* hack to get focus in the right place on "Setup new service" */ if (gtk_widget_get_visible (self->entry)) { gtk_widget_grab_focus (self->entry); } return G_OBJECT (self); } static void sync_config_widget_map (GtkWidget *widget) { SyncConfigWidget *self = SYNC_CONFIG_WIDGET (widget); if (self->label_box && gtk_widget_get_visible (self->expando_box)) { gtk_widget_map (self->label_box); } if (self->expando_box && gtk_widget_get_visible (self->expando_box)) { gtk_widget_map (self->expando_box); } GTK_WIDGET_CLASS (sync_config_widget_parent_class)->map (widget); } static void sync_config_widget_unmap (GtkWidget *widget) { SyncConfigWidget *self = SYNC_CONFIG_WIDGET (widget); GTK_WIDGET_CLASS (sync_config_widget_parent_class)->unmap (widget); if (self->label_box) { gtk_widget_unmap (self->label_box); } if (self->expando_box) { gtk_widget_unmap (self->expando_box); } } static void sync_config_widget_add (GtkContainer *container, GtkWidget *widget) { g_warning ("Can't add widgets in to SyncConfigWidget!"); } static void sync_config_widget_remove (GtkContainer *container, GtkWidget *widget) { SyncConfigWidget *self = SYNC_CONFIG_WIDGET (container); if (self->label_box == widget) { gtk_widget_unparent (widget); self->label_box = NULL; } if (self->expando_box == widget) { gtk_widget_unparent (widget); self->expando_box = NULL; } } static void sync_config_widget_forall (GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data) { SyncConfigWidget *self = SYNC_CONFIG_WIDGET (container); if (self->label_box) { (* callback) (self->label_box, callback_data); } if (self->expando_box) { (* callback) (self->expando_box, callback_data); } } static void sync_config_widget_class_init (SyncConfigWidgetClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *w_class = GTK_WIDGET_CLASS (klass); GtkContainerClass *c_class = GTK_CONTAINER_CLASS (klass); GParamSpec *pspec; object_class->set_property = sync_config_widget_set_property; object_class->get_property = sync_config_widget_get_property; object_class->dispose = sync_config_widget_dispose; object_class->constructor = sync_config_widget_constructor; w_class->draw = sync_config_widget_draw; w_class->get_preferred_width = sync_config_widget_get_preferred_width; w_class->get_preferred_height = sync_config_widget_get_preferred_height; w_class->size_allocate = sync_config_widget_size_allocate; w_class->map = sync_config_widget_map; w_class->unmap = sync_config_widget_unmap; c_class->add = sync_config_widget_add; c_class->remove = sync_config_widget_remove; c_class->forall = sync_config_widget_forall; pspec = g_param_spec_pointer ("server", "SyncevoServer", "The SyncevoServer to use in Syncevolution DBus calls", G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_SERVER, pspec); pspec = g_param_spec_string ("name", "Configuration name", "The name of the Syncevolution service configuration", NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_NAME, pspec); pspec = g_param_spec_pointer ("config", "SyncevoConfig", "The SyncevoConfig struct this widget represents. Takes ownership.", G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_CONFIG, pspec); pspec = g_param_spec_boolean ("current", "Current", "Whether the service is currently used", FALSE, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_CURRENT, pspec); pspec = g_param_spec_boolean ("has-template", "has template", "Whether the service has a matching template", FALSE, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_HAS_TEMPLATE, pspec); pspec = g_param_spec_boolean ("configured", "Configured", "Whether the service has a configuration already", FALSE, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_CONFIGURED, pspec); pspec = g_param_spec_string ("current-service-name", "Current service name", "The name of the currently used service or NULL", NULL, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_CURRENT_SERVICE_NAME, pspec); pspec = g_param_spec_boolean ("expanded", "Expanded", "Whether the expander is open or closed", FALSE, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_EXPANDED, pspec); signals[SIGNAL_CHANGED] = g_signal_new ("changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE, G_STRUCT_OFFSET (SyncConfigWidgetClass, changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } static void label_enter_notify_cb (GtkWidget *widget, GdkEventCrossing *event, SyncConfigWidget *self) { if (!self->expanded) { gtk_widget_show (self->button); } gtk_widget_set_state (self->label_box, GTK_STATE_PRELIGHT); } static void label_leave_notify_cb (GtkWidget *widget, GdkEventCrossing *event, SyncConfigWidget *self) { if (event->detail != GDK_NOTIFY_INFERIOR) { gtk_widget_hide (self->button); gtk_widget_set_state (self->label_box, GTK_STATE_NORMAL); } } static void device_combo_changed (GtkComboBox *combo, SyncConfigWidget *self) { int active; active = gtk_combo_box_get_active (GTK_COMBO_BOX (combo)); gtk_widget_set_sensitive (self->device_select_btn, active > -1); } static void label_button_release_cb (GtkWidget *widget, GdkEventButton *event, SyncConfigWidget *self) { if (event->button == 1) { sync_config_widget_set_expanded (self, !sync_config_widget_get_expanded (self)); } } static gint compare_list_items (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, SyncConfigWidget *self) { int score_a, score_b; gtk_tree_model_get(model, a, 2, &score_a, -1); gtk_tree_model_get(model, b, 2, &score_b, -1); return score_a - score_b; } static void sync_config_widget_init (SyncConfigWidget *self) { GtkWidget *tmp_box, *hbox, *cont, *vbox, *label; GtkListStore *store; GtkCellRenderer *renderer; gtk_widget_set_has_window (GTK_WIDGET (self), FALSE); self->label_box = gtk_event_box_new (); gtk_widget_set_app_paintable (self->label_box, TRUE); gtk_widget_show (self->label_box); gtk_widget_set_parent (self->label_box, GTK_WIDGET (self)); gtk_widget_set_size_request (self->label_box, -1, SYNC_UI_LIST_ICON_SIZE + 6); g_signal_connect (self->label_box, "enter-notify-event", G_CALLBACK (label_enter_notify_cb), self); g_signal_connect (self->label_box, "leave-notify-event", G_CALLBACK (label_leave_notify_cb), self); g_signal_connect (self->label_box, "button-release-event", G_CALLBACK (label_button_release_cb), self); g_signal_connect (self->label_box, "draw", G_CALLBACK (label_button_draw_cb), self); hbox = gtk_hbox_new (FALSE, 0); gtk_widget_show (hbox); gtk_container_add (GTK_CONTAINER (self->label_box), hbox); self->image = gtk_image_new (); /* leave room for drawing the expander indicator in expose handler */ gtk_widget_set_size_request (self->image, SYNC_UI_LIST_ICON_SIZE + INDICATOR_SIZE, SYNC_UI_LIST_ICON_SIZE); gtk_misc_set_alignment (GTK_MISC (self->image), 1.0, 0.5); gtk_widget_show (self->image); gtk_box_pack_start (GTK_BOX (hbox), self->image, FALSE, FALSE, 8); tmp_box = gtk_hbox_new (FALSE, 0); gtk_widget_show (tmp_box); gtk_box_pack_start (GTK_BOX (hbox), tmp_box, FALSE, FALSE, 8); self->label = gtk_label_new (""); gtk_label_set_max_width_chars (GTK_LABEL (self->label), 60); gtk_label_set_ellipsize (GTK_LABEL (self->label), PANGO_ELLIPSIZE_END); gtk_misc_set_alignment (GTK_MISC (self->label), 0.0, 0.5); gtk_widget_show (self->label); gtk_box_pack_start (GTK_BOX (tmp_box), self->label, FALSE, FALSE, 0); self->entry = gtk_entry_new (); gtk_widget_set_no_show_all (self->entry, TRUE); gtk_box_pack_start (GTK_BOX (tmp_box), self->entry, FALSE, FALSE, 4); vbox = gtk_vbox_new (FALSE, 0); gtk_widget_show (vbox); gtk_box_pack_start (GTK_BOX (tmp_box), vbox, FALSE, FALSE, 0); /* TRANSLATORS: link button in service configuration form */ self->link = gtk_link_button_new_with_label ("", _("Launch website")); gtk_widget_set_no_show_all (self->link, TRUE); gtk_box_pack_start (GTK_BOX (vbox), self->link, TRUE, FALSE, 0); vbox = gtk_vbox_new (FALSE, 0); gtk_widget_show (vbox); gtk_box_pack_end (GTK_BOX (hbox), vbox, FALSE, FALSE, 32); /* TRANSLATORS: button in service configuration form */ self->button = gtk_button_new_with_label (_("Set up now")); gtk_widget_set_size_request (self->button, SYNC_UI_LIST_BTN_WIDTH, -1); g_signal_connect (self->button, "clicked", G_CALLBACK (setup_service_clicked), self); gtk_box_pack_start (GTK_BOX (vbox), self->button, TRUE, FALSE, 0); /* label_box built, now build expando_box */ self->expando_box = gtk_hbox_new (FALSE, 0); gtk_widget_set_no_show_all (self->expando_box, TRUE); gtk_widget_set_parent (self->expando_box, GTK_WIDGET (self)); self->device_selector_box = gtk_vbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (self->expando_box), self->device_selector_box, TRUE, TRUE, 16); hbox = gtk_hbox_new (FALSE, 8); gtk_widget_show (hbox); gtk_box_pack_start (GTK_BOX (self->device_selector_box), hbox, FALSE, TRUE, 8); self->device_text = gtk_label_new (_("We don't know what this device is exactly. " "Please take a look at the list of " "supported devices and pick yours if it " "is listed")); gtk_widget_show (self->device_text); gtk_label_set_line_wrap (GTK_LABEL (self->device_text), TRUE); gtk_widget_set_size_request (self->device_text, 600, -1); gtk_box_pack_start (GTK_BOX (hbox), self->device_text, FALSE, TRUE, 0); hbox = gtk_hbox_new (FALSE, 16); gtk_widget_show (hbox); gtk_box_pack_start (GTK_BOX (self->device_selector_box), hbox, FALSE, TRUE, 16); store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_INT); gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), 2, GTK_SORT_DESCENDING); gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store), 2, (GtkTreeIterCompareFunc)compare_list_items, NULL, NULL); self->combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store)); g_object_unref (G_OBJECT (store)); gtk_widget_set_size_request (self->combo, 200, -1); gtk_widget_show (self->combo); gtk_box_pack_start (GTK_BOX (hbox), self->combo, FALSE, TRUE, 0); renderer = gtk_cell_renderer_text_new (); gtk_cell_layout_pack_start (GTK_CELL_LAYOUT(self->combo), renderer, TRUE); gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT(self->combo), renderer, "text", 0, NULL); g_signal_connect (self->combo, "changed", G_CALLBACK (device_combo_changed), self); self->device_select_btn = gtk_button_new_with_label (_("Use these settings")); gtk_widget_set_sensitive (self->device_select_btn, FALSE); gtk_widget_show (self->device_select_btn); gtk_box_pack_start (GTK_BOX (hbox), self->device_select_btn, FALSE, TRUE, 0); g_signal_connect (self->device_select_btn, "clicked", G_CALLBACK (device_selection_btn_clicked_cb), self); /* settings_box has normal expander contents */ self->settings_box = gtk_vbox_new (FALSE, 0); gtk_widget_show (self->settings_box); gtk_box_pack_start (GTK_BOX (self->expando_box), self->settings_box, TRUE, TRUE, 16); vbox = gtk_vbox_new (FALSE, 8); gtk_widget_show (vbox); gtk_box_pack_start (GTK_BOX (self->settings_box), vbox, TRUE, TRUE, 0); tmp_box = gtk_vbox_new (FALSE, 0); gtk_widget_show (tmp_box); gtk_box_pack_start (GTK_BOX (vbox), tmp_box, FALSE, FALSE, 8); self->description_label = gtk_label_new (""); gtk_misc_set_alignment (GTK_MISC (self->description_label), 0.0, 0.5); gtk_widget_set_size_request (self->description_label, 700, -1); gtk_label_set_line_wrap (GTK_LABEL (self->description_label), TRUE); gtk_box_pack_start (GTK_BOX (tmp_box), self->description_label, FALSE, FALSE, 0); tmp_box = gtk_hbox_new (FALSE, 0); gtk_widget_show (tmp_box); gtk_box_pack_start (GTK_BOX (vbox), tmp_box, FALSE, FALSE, 0); self->userinfo_table = gtk_table_new (4, 2, FALSE); gtk_table_set_row_spacings (GTK_TABLE (self->userinfo_table), 2); gtk_table_set_col_spacings (GTK_TABLE (self->userinfo_table), 5); gtk_widget_show (self->userinfo_table); gtk_box_pack_start (GTK_BOX (tmp_box), self->userinfo_table, FALSE, FALSE, 0); /* TRANSLATORS: labels of entries in service configuration form */ label = gtk_label_new (_("Username")); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_widget_show (label); gtk_table_attach_defaults (GTK_TABLE (self->userinfo_table), label, 0, 1, 0, 1); self->username_entry = gtk_entry_new (); gtk_widget_show (self->username_entry); gtk_entry_set_width_chars (GTK_ENTRY (self->username_entry), 40); gtk_entry_set_max_length (GTK_ENTRY (self->username_entry), 99); gtk_table_attach_defaults (GTK_TABLE (self->userinfo_table), self->username_entry, 1, 2, 0, 1); label = gtk_label_new (_("Password")); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_widget_show (label); gtk_table_attach_defaults (GTK_TABLE (self->userinfo_table), label, 0, 1, 1, 2); self->password_entry = gtk_entry_new (); gtk_widget_show (self->password_entry); gtk_entry_set_width_chars (GTK_ENTRY (self->password_entry), 40); gtk_entry_set_visibility (GTK_ENTRY (self->password_entry), FALSE); gtk_entry_set_max_length (GTK_ENTRY (self->password_entry), 99); gtk_table_attach_defaults (GTK_TABLE (self->userinfo_table), self->password_entry, 1, 2, 1, 2); self->complex_config_info_bar = gtk_info_bar_new (); gtk_info_bar_set_message_type (GTK_INFO_BAR (self->complex_config_info_bar), GTK_MESSAGE_WARNING); gtk_box_pack_start (GTK_BOX (vbox), self->complex_config_info_bar, FALSE, FALSE, 0); /* TRANSLATORS: warning in service configuration form for people who have modified the configuration via other means. */ label = gtk_label_new (_("Current configuration is more complex " "than what can be shown here. Changes to sync " "mode or synced data types will overwrite that " "configuration.")); gtk_widget_set_size_request (label, 600, -1); gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); gtk_widget_show (label); cont = gtk_info_bar_get_content_area ( GTK_INFO_BAR (self->complex_config_info_bar)); gtk_container_add (GTK_CONTAINER (cont), label); self->mode_table = gtk_table_new (4, 1, FALSE); gtk_table_set_row_spacings (GTK_TABLE (self->mode_table), 2); gtk_table_set_col_spacings (GTK_TABLE (self->mode_table), 5); gtk_widget_show (self->mode_table); gtk_box_pack_start (GTK_BOX (vbox), self->mode_table, FALSE, FALSE, 0); /* TRANSLATORS: this is the epander label for server settings in service configuration form */ self->expander = gtk_expander_new (_("Hide server settings")); gtk_box_pack_start (GTK_BOX (vbox), self->expander, FALSE, FALSE, 8); tmp_box = gtk_hbox_new (FALSE, 0); gtk_widget_show (tmp_box); gtk_container_add (GTK_CONTAINER (self->expander), tmp_box); self->server_settings_table = gtk_table_new (1, 1, FALSE); gtk_table_set_row_spacings (GTK_TABLE (self->server_settings_table), 2); gtk_table_set_col_spacings (GTK_TABLE (self->server_settings_table), 5); gtk_widget_show (self->server_settings_table); gtk_box_pack_start (GTK_BOX (tmp_box), self->server_settings_table, FALSE, FALSE, 0); tmp_box = gtk_hbox_new (FALSE, 0); gtk_widget_show (tmp_box); gtk_box_pack_start (GTK_BOX (vbox), tmp_box, FALSE, FALSE, 8); /* TRANSLATORS: this is the epander label for server settings in service configuration form */ self->fake_expander = gtk_expander_new (_("Show server settings")); gtk_widget_show (self->fake_expander); gtk_box_pack_start (GTK_BOX (tmp_box), self->fake_expander, FALSE, FALSE, 0); g_signal_connect (self->fake_expander, "notify::expanded", G_CALLBACK (server_settings_notify_expand_cb), self); self->use_button = gtk_button_new (); gtk_widget_show (self->use_button); gtk_box_pack_end (GTK_BOX (tmp_box), self->use_button, FALSE, FALSE, 8); g_signal_connect (self->use_button, "clicked", G_CALLBACK (use_clicked_cb), self); self->stop_button = gtk_button_new (); gtk_box_pack_end (GTK_BOX (tmp_box), self->stop_button, FALSE, FALSE, 8); g_signal_connect (self->stop_button, "clicked", G_CALLBACK (stop_clicked_cb), self); self->reset_delete_button = gtk_button_new (); gtk_widget_show (self->reset_delete_button); gtk_box_pack_end (GTK_BOX (tmp_box), self->reset_delete_button, FALSE, FALSE, 8); g_signal_connect (self->reset_delete_button, "clicked", G_CALLBACK (reset_delete_clicked_cb), self); } GtkWidget* sync_config_widget_new (SyncevoServer *server, const char *name, SyncevoConfig *config, gboolean current, const char *current_service_name, gboolean configured, gboolean has_template) { return g_object_new (SYNC_TYPE_CONFIG_WIDGET, "server", server, "name", name, "config", config, "current", current, "current-service-name", current_service_name, "configured", configured, "has-template", has_template, NULL); } void sync_config_widget_set_expanded (SyncConfigWidget *self, gboolean expanded) { g_return_if_fail (SYNC_IS_CONFIG_WIDGET (self)); if (self->expanded != expanded) { self->expanded = expanded; if (self->expanded) { gtk_widget_hide (self->button); gtk_widget_show (self->expando_box); if (gtk_widget_get_visible (self->entry)) { gtk_widget_grab_focus (self->entry); } else { gtk_widget_grab_focus (self->username_entry); } } else { gtk_widget_show (self->button); gtk_widget_hide (self->expando_box); } g_object_notify (G_OBJECT (self), "expanded"); } } gboolean sync_config_widget_get_expanded (SyncConfigWidget *self) { return self->expanded; } void sync_config_widget_set_has_template (SyncConfigWidget *self, gboolean has_template) { if (self->has_template != has_template) { self->has_template = has_template; update_buttons (self); sync_config_widget_update_label (self); } } gboolean sync_config_widget_get_has_template (SyncConfigWidget *self) { return self->has_template; } void sync_config_widget_set_configured (SyncConfigWidget *self, gboolean configured) { if (self->configured != configured) { self->configured = configured; self->device_template_selected = configured; update_buttons (self); } } gboolean sync_config_widget_get_configured (SyncConfigWidget *self) { return self->configured; } gboolean sync_config_widget_get_current (SyncConfigWidget *widget) { return widget->current; } const char* sync_config_widget_get_name (SyncConfigWidget *widget) { return widget->config_name; } void sync_config_widget_add_alternative_config (SyncConfigWidget *self, const char *template_name, SyncevoConfig *config, gboolean configured) { sync_config_widget_add_config (self, template_name, config); if (configured) { sync_config_widget_set_config (self, config); sync_config_widget_set_configured (self, TRUE); } sync_config_widget_update_expander (self); } syncevolution_1.4/src/gtk3-ui/sync-config-widget.h000066400000000000000000000076341230021373600223130ustar00rootroot00000000000000#ifndef _SYNC_CONFIG_WIDGET #define _SYNC_CONFIG_WIDGET #include #include #include "syncevo-server.h" G_BEGIN_DECLS #define SYNC_TYPE_CONFIG_WIDGET sync_config_widget_get_type() #define SYNC_CONFIG_WIDGET(obj) \ (G_TYPE_CHECK_INSTANCE_CAST ((obj), SYNC_TYPE_CONFIG_WIDGET, SyncConfigWidget)) #define SYNC_CONFIG_WIDGET_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST ((klass), SYNC_TYPE_CONFIG_WIDGET, SyncConfigWidgetClass)) #define SYNC_IS_CONFIG_WIDGET(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SYNC_TYPE_CONFIG_WIDGET)) #define SYNC_IS_CONFIG_WIDGET_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE ((klass), SYNC_TYPE_CONFIG_WIDGET)) #define SYNC_CONFIG_WIDGET_GET_CLASS(obj) \ (G_TYPE_INSTANCE_GET_CLASS ((obj), SYNC_TYPE_CONFIG_WIDGET, SyncConfigWidgetClass)) typedef struct { GtkContainer parent; GtkWidget *expando_box; GtkWidget *label_box; GtkWidget *device_selector_box; GtkWidget *device_text; GtkWidget *combo; GtkWidget *device_select_btn; GtkWidget *settings_box; gboolean current; /* is this currently used config */ char *current_service_name; /* name of the current service */ gboolean configured; /* actual service configuration exists on server */ gboolean device_template_selected; gboolean has_template; /* this service configuration has a matching template */ gboolean expanded; SyncevoServer *server; SyncevoConfig *config; GHashTable *configs; /* possible configs. config above is one of these */ char *config_name; char *pretty_name; char *running_session; char *expand_id; /* label */ GtkWidget *image; GtkWidget *label; GtkWidget *entry; GtkWidget *link; GtkWidget *button; /* content */ GtkWidget *description_label; GtkWidget *userinfo_table; GtkWidget *name_label; GtkWidget *name_entry; GtkWidget *complex_config_info_bar; GtkWidget *mode_table; GtkWidget *send_check; GtkWidget *receive_check; GtkWidget *username_entry; GtkWidget *password_entry; GtkWidget *source_toggle_label; GtkWidget *baseurl_entry; GtkWidget *expander; GtkWidget *fake_expander; GtkWidget *server_settings_table; GtkWidget *reset_delete_button; GtkWidget *stop_button; GtkWidget *use_button; GHashTable *sources; /* key is source name, value is source_widgets */ gboolean mode_changed; gboolean no_source_toggles; } SyncConfigWidget; typedef struct { GtkContainerClass parent_class; void (*changed) (SyncConfigWidget *widget); } SyncConfigWidgetClass; GType sync_config_widget_get_type (void); GtkWidget *sync_config_widget_new (SyncevoServer *server, const char *name, SyncevoConfig *config, gboolean current, const char *current_service_name, gboolean configured, gboolean has_template); void sync_config_widget_set_expanded (SyncConfigWidget *widget, gboolean expanded); gboolean sync_config_widget_get_expanded (SyncConfigWidget *widget); gboolean sync_config_widget_get_current (SyncConfigWidget *widget); void sync_config_widget_set_current (SyncConfigWidget *self, gboolean current); void sync_config_widget_set_has_template (SyncConfigWidget *self, gboolean has_template); gboolean sync_config_widget_get_has_template (SyncConfigWidget *self); void sync_config_widget_set_configured (SyncConfigWidget *self, gboolean configured); gboolean sync_config_widget_get_configured (SyncConfigWidget *self); const char *sync_config_widget_get_name (SyncConfigWidget *widget); void sync_config_widget_expand_id (SyncConfigWidget *self, const char *id); void sync_config_widget_add_alternative_config (SyncConfigWidget *self, const char *name, SyncevoConfig *config, gboolean configured); G_END_DECLS #endif syncevolution_1.4/src/gtk3-ui/sync-generic.png000066400000000000000000000032051230021373600215240ustar00rootroot00000000000000PNG  IHDR@@iqsBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDATxkUUk9FY KBCR$FB B (/HB(PkrLKttV:sܹsιwafYk?>ҟ6q@{ uaU^&"QLJvCIm @ d'SF°;T@DF۟GRl/U=+oU5 [s08 LHag 'SՔDUPsAEM-@ޒ4d""2h[ ۀ`mYP-VUz\$4vsSZWlX p^Jۀu/eԒD@ҡukT vH+Ӂk T |+)5:no)NV?Z;EV("Sf`10Y+WJ+GUXN3^r\ĩ`~xY kfw..gQ`IvB~"МR,C !Y jԽDE9R -===kccE=ɌgX9,'V`U$ P ^3g;pr`<0 #b P`waBP3 =NPWh C]eV7kikxS n8.R`rq̷zmqpBph.gn(tv7Ŵqa^.9*ES2߮x;qЈKn~{طFJo?{~j +\vnAvw{ikicad>c 37"lOxFxbF]La&xL;=զD,'IBlFcR #^"2ODDv]ƴxWjǚ=`1&|j) | Qd<۽ĬJ@3 a(. b͸8x/ J̦ b^!1.r"(nN.DY @pkp(\O;Bq17p)$D lnmOm>0I&v`Fv(p7n;63{[Mwᶺ? xjt򸈲Ѧr  %F`c˞4$v0J]O2$ n nT$d/ l[MkHKBṕhUjI@9$dB9ݢOI@Z2#ћ63E@ Zy> +S$3E|b{U}X(+4Bnu3'sb  Y4rXU;jVJ@\ݪ9RKrfY!FB}#;j.n'7y; yy; yK.{+"|_#`7.[O NJ}*: ?+KďBO([MFdIENDB`syncevolution_1.4/src/gtk3-ui/sync-gtk.desktop.in000066400000000000000000000002711230021373600221670ustar00rootroot00000000000000[Desktop Entry] _Name=SyncEvolution (GTK) _Comment=Synchronize PIM data Version=1.0 Type=Application Exec=sync-ui Icon=sync Categories=Office;PDA;GTK; Terminal=false StartupNotify=true syncevolution_1.4/src/gtk3-ui/sync-spinner.gif000066400000000000000000000041461230021373600215540ustar00rootroot00000000000000GIF89a! ! NETSCAPE2.0,7/Eyo3gyb#uW)cj챾+{2L& C"ˤ%o̦3Q! ,HB5L3=a"رtVh!jԪ! ,N@. ^ r'NJ9c woChC!^ѐO쎣f5%~]]N ! ,H"IEκ ncVZ§0%K(h8)`˧'!Q:NE ! ,?H0JŒ5_^a5fnj:urՔ>,b2\ 0,!! ,6H0Jj犷qE`Ȣ,3mưX_1+k:! ,2 /[u RefhiȞSʙwEYL* ! ,5*naXYWAC䲪{ Yތ&%)A! ,7+ޡq|2"SxY%X린%ko@xPz̦P! ,JD*s~Stb^'b'.Rθl )!c)Ґ#=YJ~Kn! ,<Kޡq|`-)Tx lg̒"JTD! ,<<1`p%dԺ+|t^߭x\ JTC! ,:+|"y-i14go(ʠc3z̦ J! ,9H0J%ݍ%BH69+{VS➙j"DkXrl:Ш! ,H&@I@Jx@E)kG"ߢ%G0:27>a&p"ZR` ! ,K&"S #include #include "sync-ui-config.h" #include "sync-ui.h" void server_config_free (server_config *server) { if (!server) return; g_free (server->name); syncevo_config_free (server->config); g_slice_free (server_config, server); } void server_config_update_from_entry (server_config *server, GtkEntry *entry) { char **str; const char *new_str; /* all entries have a pointer to the correct string in server_config */ str = g_object_get_data (G_OBJECT (entry), "value"); g_assert (str); new_str = gtk_entry_get_text (entry); if ((*str == NULL && strlen (new_str) != 0) || (*str != NULL && strcmp (*str, new_str) != 0)) { server->changed = TRUE; g_free (*str); *str = g_strdup (new_str); } } static void add_source_config (char *name, GHashTable *syncevo_source_config, GHashTable *source_configs) { source_config *new_conf; new_conf = g_slice_new0 (source_config); new_conf->name = name; new_conf->supported_locally = TRUE; new_conf->stats_set = FALSE; new_conf->config = syncevo_source_config; g_hash_table_insert (source_configs, name, new_conf); } void server_config_init (server_config *server, SyncevoConfig *config) { server->config = config; /* build source_configs */ server->source_configs = g_hash_table_new (g_str_hash, g_str_equal); syncevo_config_foreach_source (config, (ConfigFunc)add_source_config, server->source_configs); if (!syncevo_config_get_value (config, NULL, "PeerName", &server->pretty_name)) { server->pretty_name = server->name; } } gboolean source_config_is_usable (source_config *source) { const char *source_uri; source_uri = g_hash_table_lookup (source->config, "uri"); if (!source_config_is_enabled (source) || !source_uri || strlen (source_uri) == 0 || !source->supported_locally) { return FALSE; } return TRUE; } gboolean source_config_is_enabled (source_config *source) { char *mode; mode = g_hash_table_lookup (source->config, "sync"); if (mode && (strcmp (mode, "none") == 0 || strcmp (mode, "disabled") == 0)) { return FALSE; } return TRUE; } server_data* server_data_new (const char *name, gpointer *data) { server_data *serv_data; serv_data = g_slice_new0 (server_data); serv_data->data = data; serv_data->config = g_slice_new0 (server_config); serv_data->config->name = g_strdup (name); return serv_data; } void server_data_free (server_data *data, gboolean free_config) { if (!data) return; if (free_config && data->config) { server_config_free (data->config); } if (data->options_override) { /* g_ptr_array_foreach (data->options_override, (GFunc)syncevo_option_free, NULL); */ g_ptr_array_free (data->options_override, TRUE); } g_slice_free (server_data, data); } gboolean peer_is_client (SyncevoConfig *config) { char *is_client; g_return_val_if_fail (config, FALSE); syncevo_config_get_value (config, NULL, "PeerIsClient", &is_client); return is_client && g_strcmp0 ("1", is_client) == 0; } syncevolution_1.4/src/gtk3-ui/sync-ui-config.h000066400000000000000000000060571230021373600214430ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef SYNC_UI_CONFIG_H #define SYNC_UI_CONFIG_H #include #include "syncevo-session.h" #include "syncevo-server.h" typedef struct source_config { char *name; gboolean supported_locally; SyncevoSourcePhase phase; gboolean stats_set; long status; long local_changes; long remote_changes; long local_rejections; long remote_rejections; GtkWidget *info_bar; /* info/error bar, after ui has been constructed */ GtkWidget *label; /* source report label, after ui has been constructed */ GtkWidget *box; /* source box, after ui has been constructed */ GHashTable *config; /* link to a "sub-hashtable" inside server_config->config */ } source_config; typedef struct server_config { char *name; char *pretty_name; char *password; /* any field in config has changed */ gboolean changed; /* a authentication detail (base_url/username/password) has changed */ gboolean auth_changed; gboolean password_changed; GHashTable *source_configs; /* source_config's*/ SyncevoConfig *config; } server_config; gboolean source_config_is_usable (source_config *source); gboolean source_config_is_enabled (source_config *source); void source_config_free (source_config *source); void server_config_init (server_config *server, SyncevoConfig *config); void server_config_free (server_config *server); void server_config_update_from_entry (server_config *server, GtkEntry *entry); GPtrArray* server_config_get_option_array (server_config *server); void server_config_disable_unsupported_sources (server_config *server); void server_config_ensure_default_sources_exist (server_config *server); /* data structure for syncevo_service_get_template_config_async and * syncevo_service_get_server_config_async. server is the server that * the method was called for. options_override are options that should * be overridden on the config we get. */ typedef struct server_data { server_config *config; GPtrArray *options_override; gpointer *data; } server_data; server_data* server_data_new (const char *name, gpointer *data); void server_data_free (server_data *data, gboolean free_config); /** * utility function: TRUE if the config belongs to a client (PeerIsClient) */ gboolean peer_is_client (SyncevoConfig *config); #endif syncevolution_1.4/src/gtk3-ui/sync-ui.c000066400000000000000000003631701230021373600201750ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include "syncevo-server.h" #include "syncevo-session.h" /* for return value definitions */ /* TODO: would be nice to have a non-synthesis-dependent API but for now it's like this... */ #include #include "config.h" #include "sync-ui-config.h" #include "sync-ui.h" #include "sync-config-widget.h" #ifdef USE_MOBLIN_UX #include "mux-frame.h" #endif static gboolean support_canceling = FALSE; #define REPORTS_PER_CALL 10 #define SYNC_UI_ICON_SIZE 48 #define STRING_VARIANT_HASHTABLE (dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE)) enum { PAGE_MAIN, PAGE_SETTINGS, PAGE_EMERGENCY, }; typedef enum bluetooth_type { SYNC_BLUETOOTH_NONE, SYNC_BLUETOOTH_GNOME, SYNC_BLUETOOTH_MOBLIN } bluetooth_type; typedef enum app_state { SYNC_UI_STATE_CURRENT_STATE, SYNC_UI_STATE_GETTING_SERVER, SYNC_UI_STATE_NO_SERVER, SYNC_UI_STATE_SERVER_OK, SYNC_UI_STATE_SERVER_FAILURE, SYNC_UI_STATE_SYNCING, } app_state; typedef enum ui_operation { OP_SYNC, /* use sync mode from config */ OP_SYNC_SLOW, OP_SYNC_REFRESH_FROM_CLIENT, OP_SYNC_REFRESH_FROM_SERVER, OP_SAVE, OP_RESTORE, } ui_operation; typedef struct operation_data { app_data *data; ui_operation operation; gboolean started; const char *dir; /* for OP_RESTORE */ } operation_data; struct _app_data { GtkWidget *sync_win; GtkWidget *services_win; /* will be NULL when USE_MOBLIN_UX is set*/ GtkWidget *emergency_win; /* will be NULL when USE_MOBLIN_UX is set*/ #ifdef USE_MOBLIN_UX GtkWidget *notebook; /* only in use with USE_MOBLIN_UX */ GtkWidget *back_btn; /* only in use with USE_MOBLIN_UX */ #endif GtkWidget *settings_btn; /* only in use with USE_MOBLIN_UX */ guint settings_id; GtkWidget *service_box; GtkWidget *info_bar; GtkWidget *no_connection_box; GtkWidget *main_frame; GtkWidget *log_frame; GtkWidget *server_icon_box; GtkWidget *offline_label; GtkWidget *progress; GtkWidget *sync_status_label; GtkWidget *spinner_image; GtkWidget *sync_btn; GtkWidget *change_service_btn; GtkWidget *emergency_btn; GtkWidget *server_label; GtkWidget *autosync_box; GtkWidget *autosync_toggle; GtkWidget *last_synced_label; GtkWidget *sources_box; GtkWidget *new_service_btn; GtkWidget *new_device_btn; GtkWidget *services_box; GtkWidget *devices_box; GtkWidget *scrolled_window; GtkWidget *expanded_config; GtkWidget *settings_close_btn; GtkWidget *emergency_label; GtkWidget *emergency_expander; GtkWidget *emergency_source_table; GtkWidget *refresh_from_server_btn_label; GtkWidget *refresh_from_client_btn_label; GtkWidget *emergency_backup_table; GtkWidget *emergency_close_btn; GtkWidget *password_dialog_entry; char *password_dialog_id; gboolean forced_emergency; GHashTable *emergency_sources; guint backup_count; gboolean online; gboolean syncing; gboolean synced_this_session; int last_sync; guint last_sync_src_id; ui_operation current_operation; server_config *current_service; app_state current_state; guint service_list_updates_left; gboolean open_current; /* should the service list open the current service when it populates next time*/ char *config_id_to_open; SyncevoServer *server; SyncevoSession *running_session; /* session that is currently active */ bluetooth_type bluetooth_wizard; }; static void set_sync_progress (app_data *data, float progress, char *status); static void set_app_state (app_data *data, app_state state); static void show_main_view (app_data *data); static void update_emergency_view (app_data *data); static void update_emergency_expander (app_data *data); static void show_emergency_view (app_data *data); static void show_services_list (app_data *data, const char *config_id_to_open); static void update_services_list (app_data *data); static void update_service_ui (app_data *data); static void setup_new_service_clicked (GtkButton *btn, app_data *data); static gboolean source_config_update_widget (source_config *source); static void get_presence_cb (SyncevoServer *server, char *status, char **transport, GError *error, app_data *data); static void get_reports_cb (SyncevoServer *server, SyncevoReports *reports, GError *error, app_data *data); static void start_session_cb (SyncevoServer *server, char *path, GError *error, operation_data *op_data); static void get_config_for_main_win_cb (SyncevoServer *server, SyncevoConfig *config, GError *error, app_data *data); void show_error_dialog (GtkWidget *widget, const char* message) { GtkWindow *window = GTK_WINDOW (gtk_widget_get_toplevel (widget)); GtkWidget *w; w = gtk_message_dialog_new (window, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", message); gtk_dialog_run (GTK_DIALOG (w)); gtk_widget_destroy (w); } static void remove_child (GtkWidget *widget, GtkContainer *container) { gtk_container_remove (container, widget); } static void change_service_clicked_cb (GtkButton *btn, app_data *data) { /* data->open_current = TRUE; */ show_services_list (data, NULL); } static void emergency_clicked_cb (GtkButton *btn, app_data *data) { show_emergency_view (data); } char* get_pretty_source_name (const char *source_name) { /* TRANSLATORS: There have been name changes to keep things in line with * the rest of the moblin UI. Please make sure the name you use matches * the ones in e.g. the panels. */ if (strcmp (source_name, "addressbook") == 0) { return g_strdup (_("Contacts")); } else if (strcmp (source_name, "calendar") == 0) { return g_strdup (_("Appointments")); } else if (strcmp (source_name, "todo") == 0) { return g_strdup (_("Tasks")); } else if (strcmp (source_name, "memo") == 0) { return g_strdup (_("Notes")); } else if (strcmp (source_name, "calendar+todo") == 0) { /* TRANSLATORS: This is a "combination source" for syncing with devices * that combine appointments and tasks. the name should match the ones * used for calendar and todo above */ return g_strdup (_("Appointments & Tasks")); } else { char *tmp; tmp = g_strdup (source_name); tmp[0] = g_ascii_toupper (tmp[0]); return tmp; } } char* get_pretty_source_name_markup (const char *source_name) { char *plain, *markup; plain = get_pretty_source_name (source_name); markup = g_markup_escape_text (plain, -1); g_free (plain); return markup; } static void reload_config (app_data *data, const char *server) { server_config_free (data->current_service); data->forced_emergency = FALSE; g_hash_table_remove_all (data->emergency_sources); if (!server || strlen (server) == 0) { data->current_service = NULL; update_service_ui (data); set_app_state (data, SYNC_UI_STATE_SERVER_OK); } else { data->synced_this_session = FALSE; data->current_service = g_slice_new0 (server_config); data->current_service->name = g_strdup (server); set_app_state (data, SYNC_UI_STATE_GETTING_SERVER); syncevo_server_get_config (data->server, data->current_service->name, FALSE, (SyncevoServerGetConfigCb)get_config_for_main_win_cb, data); } } static void abort_sync_cb (SyncevoSession *session, GError *error, app_data *data) { if (error) { /* TODO show in UI: failed to abort sync (while syncing) */ g_error_free (error); } /* status change handler takes care of updating UI */ } static void sync_cb (SyncevoSession *session, GError *error, app_data *data) { if (error) { /* TODO show in UI: sync failed (failed to even start) */ g_error_free (error); g_object_unref (session); return; } set_sync_progress (data, 0.0, _("Starting sync")); /* stop updates of "last synced" */ if (data->last_sync_src_id > 0) g_source_remove (data->last_sync_src_id); set_app_state (data, SYNC_UI_STATE_SYNCING); } gboolean show_confirmation (GtkWidget *widget, const char *message, const char *yes, const char *no) { GtkWidget *w; int ret; w = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (widget)), GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message); gtk_dialog_add_buttons (GTK_DIALOG (w), no, GTK_RESPONSE_NO, yes, GTK_RESPONSE_YES, NULL); ret = gtk_dialog_run (GTK_DIALOG (w)); gtk_widget_destroy (w); return (ret == GTK_RESPONSE_YES); } static void slow_sync (app_data *data) { operation_data *op_data; char *message; /* TRANSLATORS: slow sync confirmation dialog message. Placeholder * is service/device name */ message = g_strdup_printf (_("Do you want to slow sync with %s?"), data->current_service->pretty_name); /* TRANSLATORS: slow sync confirmation dialog buttons */ if (!show_confirmation (data->sync_win, message, _("Yes, do slow sync"), _("No, cancel sync"))) { g_free (message); return; } g_free (message); op_data = g_slice_new (operation_data); op_data->data = data; op_data->operation = OP_SYNC_SLOW; op_data->started = FALSE; syncevo_server_start_session (data->server, data->current_service->name, (SyncevoServerStartSessionCb)start_session_cb, op_data); show_main_view (data); } static void slow_sync_clicked_cb (GtkButton *btn, app_data *data) { slow_sync (data); } static void refresh_from_server_clicked_cb (GtkButton *btn, app_data *data) { operation_data *op_data; char *message; /* TRANSLATORS: confirmation dialog for "refresh from peer". Placeholder * is service/device name */ message = g_strdup_printf (_("Do you want to delete all local data and replace it with " "data from %s? This is not usually advised."), data->current_service->pretty_name); /* TRANSLATORS: "refresh from peer" confirmation dialog buttons */ if (!show_confirmation (data->sync_win, message, _("Yes, delete and replace"), _("No"))) { g_free (message); return; } g_free (message); op_data = g_slice_new (operation_data); op_data->data = data; op_data->operation = peer_is_client (data->current_service->config) ? OP_SYNC_REFRESH_FROM_CLIENT : OP_SYNC_REFRESH_FROM_SERVER; op_data->started = FALSE; syncevo_server_start_session (data->server, data->current_service->name, (SyncevoServerStartSessionCb)start_session_cb, op_data); show_main_view (data); } static void refresh_from_client_clicked_cb (GtkButton *btn, app_data *data) { operation_data *op_data; char *message; /* TRANSLATORS: confirmation dialog for "refresh from local side". Placeholder * is service/device name */ message = g_strdup_printf (_("Do you want to delete all data in %s and replace it with " "your local data? This is not usually advised."), data->current_service->pretty_name); /* TRANSLATORS: "refresh from local side" confirmation dialog buttons */ if (!show_confirmation (data->sync_win, message, _("Yes, delete and replace"), _("No"))) { g_free (message); return; } g_free (message); op_data = g_slice_new (operation_data); op_data->data = data; op_data->operation = peer_is_client (data->current_service->config) ? OP_SYNC_REFRESH_FROM_SERVER : OP_SYNC_REFRESH_FROM_CLIENT; op_data->started = FALSE; syncevo_server_start_session (data->server, data->current_service->name, (SyncevoServerStartSessionCb)start_session_cb, op_data); show_main_view (data); } static void start_sync (app_data *data) { operation_data *op_data; if (data->syncing) { syncevo_session_abort (data->running_session, (SyncevoSessionGenericCb)abort_sync_cb, data); set_sync_progress (data, -1.0, _("Trying to cancel sync")); } else { op_data = g_slice_new (operation_data); op_data->data = data; op_data->operation = OP_SYNC; op_data->started = FALSE; syncevo_server_start_session (data->server, data->current_service->name, (SyncevoServerStartSessionCb)start_session_cb, op_data); } } static void sync_clicked_cb (GtkButton *btn, app_data *data) { g_return_if_fail (data->current_service); start_sync (data); } #define DAY 60 * 60 * 24 #define HALF_DAY 60 * 60 * 12 #define HOUR 60 * 60 #define HALF_HOUR 60 * 30 #define MINUTE 60 #define HALF_MINUTE 30 static gboolean refresh_last_synced_label (app_data *data) { GTimeVal val; glong diff; char *msg; int delay; g_get_current_time (&val); diff = val.tv_sec - data->last_sync; if (!data->current_service) { msg = g_strdup (_("No service or device selected")); delay = -1; } else if (data->last_sync <= 0) { msg = g_strdup (data->current_service->pretty_name); /* we don't know */ delay = -1; } else if (diff < HALF_MINUTE) { /* TRANSLATORS: This is the title on main view. Placeholder is * the service name. Example: "Google - synced just now" */ msg = g_strdup_printf (_("%s - synced just now"), data->current_service->pretty_name); delay = 10; } else if (diff < MINUTE + HALF_MINUTE) { msg = g_strdup_printf (_("%s - synced a minute ago"), data->current_service->pretty_name); delay = MINUTE; } else if (diff < HOUR) { msg = g_strdup_printf (_("%s - synced %ld minutes ago"), data->current_service->pretty_name, (diff + HALF_MINUTE) / MINUTE); delay = MINUTE; } else if (diff < HOUR + HALF_HOUR) { msg = g_strdup_printf (_("%s - synced an hour ago"), data->current_service->pretty_name); delay = HOUR; } else if (diff < DAY) { msg = g_strdup_printf (_("%s - synced %ld hours ago"), data->current_service->pretty_name, (diff + HALF_HOUR) / (HOUR)); delay = HOUR; } else if (diff < DAY + HALF_DAY) { msg = g_strdup_printf (_("%s - synced a day ago"), data->current_service->pretty_name); delay = HOUR; } else { msg = g_strdup_printf (_("%s - synced %ld days ago"), data->current_service->pretty_name, (diff + HALF_DAY) / (DAY)); delay = HOUR; } gtk_label_set_text (GTK_LABEL (data->server_label), msg); g_free (msg); if (data->last_sync_src_id > 0) g_source_remove (data->last_sync_src_id); if (delay > 0) data->last_sync_src_id = g_timeout_add_seconds (delay, (GSourceFunc)refresh_last_synced_label, data); return FALSE; } static void set_sync_progress (app_data *data, float progress, char *status) { if (progress >= 0) gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (data->progress), progress); if (status) gtk_progress_bar_set_text (GTK_PROGRESS_BAR (data->progress), status); } static void set_info_bar (GtkWidget *widget, GtkMessageType type, SyncErrorResponse response_id, const char *message) { GtkWidget *container, *label; GtkInfoBar *bar = GTK_INFO_BAR (widget); if (!message) { gtk_widget_hide (widget); return; } container = gtk_info_bar_get_action_area (bar); gtk_container_foreach (GTK_CONTAINER (container), (GtkCallback)remove_child, container); switch (response_id) { case SYNC_ERROR_RESPONSE_SYNC: /* TRANSLATORS: Action button in info bar in main view. Shown with e.g. * "You've just restored a backup. The changes have not been " * "synced with %s yet" * Please make this text multi-line if your text is longer * than ~25 chars (example: "My very long\nbutton title") */ gtk_info_bar_add_button (bar, _("Sync now"), response_id); break; case SYNC_ERROR_RESPONSE_EMERGENCY: /* TRANSLATORS: Action button in info bar in main view. Shown with e.g. * "A normal sync is not possible at this time..." message. * "Other options" will open Emergency view * Please make this text multi-line if your text is longer * than ~25 chars (example: "My very long\nbutton title") */ gtk_info_bar_add_button (bar, _("Slow sync"), SYNC_ERROR_RESPONSE_EMERGENCY_SLOW_SYNC); gtk_info_bar_add_button (bar, _("Other options..."), response_id); break; case SYNC_ERROR_RESPONSE_SETTINGS_SELECT: /* TRANSLATORS: Action button in info bar in main view. Shown e.g. * when no service is selected. Will open configuration view * Please make this text multi-line if your text is longer * than ~25 chars (example: "My very long\nbutton title") */ gtk_info_bar_add_button (bar, _("Select sync service"), response_id); break; case SYNC_ERROR_RESPONSE_SETTINGS_OPEN: /* TRANSLATORS: Action button in info bar in main view. Shown e.g. * login to service fails. Will open configuration view for this service * (e.g. "Edit service\nsettings") * Please make this text multi-line if your text is longer * than ~25 chars (example: "My very long\nbutton title") */ gtk_info_bar_add_button (bar, _("Edit service settings"), response_id); break; case SYNC_ERROR_RESPONSE_NONE: break; default: g_warn_if_reached (); } gtk_info_bar_set_message_type (bar, type); container = gtk_info_bar_get_content_area (bar); gtk_container_foreach (GTK_CONTAINER (container), (GtkCallback)remove_child, container); label = gtk_label_new (message); gtk_widget_set_name (label, "info_label"); gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); gtk_widget_set_size_request (label, 250, -1); gtk_box_pack_start (GTK_BOX (container), label, FALSE, FALSE, 8); gtk_widget_show (label); gtk_widget_show (widget); } static void set_app_state (app_data *data, app_state state) { if (state != SYNC_UI_STATE_CURRENT_STATE) data->current_state = state; switch (data->current_state) { case SYNC_UI_STATE_GETTING_SERVER: gtk_widget_hide (data->service_box); gtk_widget_hide (data->info_bar); gtk_label_set_text (GTK_LABEL (data->sync_status_label), ""); refresh_last_synced_label (data); gtk_widget_set_sensitive (data->main_frame, TRUE); gtk_widget_set_sensitive (data->sync_btn, FALSE); gtk_widget_set_sensitive (data->change_service_btn, FALSE); gtk_widget_set_sensitive (data->emergency_btn, FALSE); if (data->settings_btn) gtk_widget_set_sensitive (data->settings_btn, FALSE); break; case SYNC_UI_STATE_SERVER_FAILURE: gtk_widget_hide (data->service_box); gtk_widget_hide (data->autosync_box); gtk_widget_hide (data->progress); refresh_last_synced_label (data); /* info bar content should be set earlier */ gtk_widget_show (data->info_bar); gtk_label_set_text (GTK_LABEL (data->sync_status_label), ""); gtk_widget_set_sensitive (data->main_frame, FALSE); gtk_widget_set_sensitive (data->sync_btn, FALSE); gtk_widget_set_sensitive (data->emergency_btn, FALSE); gtk_widget_set_sensitive (data->change_service_btn, FALSE); if (data->settings_btn) gtk_widget_set_sensitive (data->settings_btn, FALSE); break; case SYNC_UI_STATE_SERVER_OK: if (data->online) { gtk_widget_hide (data->no_connection_box); } else { gtk_widget_show (data->no_connection_box); } /* TRANSLATORS: These are for the button in main view, right side. Keep line length below ~20 characters, use two lines if needed */ gtk_button_set_label (GTK_BUTTON (data->sync_btn), _("Sync now")); if (!data->current_service) { gtk_widget_hide (data->service_box); gtk_widget_hide (data->autosync_box); gtk_widget_hide (data->progress); set_info_bar (data->info_bar, GTK_MESSAGE_INFO, SYNC_ERROR_RESPONSE_SETTINGS_SELECT, _("You haven't selected a sync service or device yet. " "Sync services let you synchronize your data " "between your netbook and a web service. You can " "also sync directly with some devices.")); refresh_last_synced_label (data); gtk_label_set_text (GTK_LABEL (data->sync_status_label), ""); gtk_widget_set_sensitive (data->sync_btn, FALSE); gtk_widget_set_sensitive (data->emergency_btn, FALSE); gtk_window_set_focus (GTK_WINDOW (data->sync_win), data->change_service_btn); } else { gtk_widget_hide (data->info_bar); gtk_widget_show (data->service_box); gtk_widget_show (data->autosync_box); gtk_widget_set_sensitive (data->sync_btn, data->online); gtk_widget_set_sensitive (data->emergency_btn, TRUE); if (data->synced_this_session && data->current_operation != OP_RESTORE) { gtk_button_set_label (GTK_BUTTON (data->sync_btn), _("Sync again")); } else { gtk_widget_hide (data->progress); } gtk_window_set_focus (GTK_WINDOW (data->sync_win), data->sync_btn); } gtk_widget_set_sensitive (data->main_frame, TRUE); gtk_widget_set_sensitive (data->change_service_btn, TRUE); if (data->settings_btn) gtk_widget_set_sensitive (data->settings_btn, TRUE); data->syncing = FALSE; break; case SYNC_UI_STATE_SYNCING: /* we have a active session, and a session is running (the running session may or may not be ours) */ gtk_widget_show (data->progress); if (data->current_operation == OP_RESTORE) { gtk_label_set_text (GTK_LABEL (data->sync_status_label), _("Restoring")); } else { gtk_label_set_text (GTK_LABEL (data->sync_status_label), _("Syncing")); } gtk_widget_set_sensitive (data->main_frame, FALSE); gtk_widget_set_sensitive (data->change_service_btn, FALSE); gtk_widget_set_sensitive (data->emergency_btn, FALSE); if (data->settings_btn) gtk_widget_set_sensitive (data->settings_btn, FALSE); gtk_widget_set_sensitive (data->sync_btn, support_canceling && data->current_operation != OP_RESTORE); if (support_canceling && support_canceling && data->current_operation != OP_RESTORE) { /* TRANSLATORS: This is for the button in main view, right side. Keep line length below ~20 characters, use two lines if needed */ gtk_button_set_label (GTK_BUTTON (data->sync_btn), _("Cancel sync")); } data->syncing = TRUE; break; default: g_assert_not_reached (); } } #ifdef USE_MOBLIN_UX static GtkWidget* switch_dummy_to_mux_frame (GtkWidget *dummy) { GtkWidget *frame, *parent; const char *title; g_assert (GTK_IS_BIN (dummy)); frame = mux_frame_new (); gtk_widget_set_name (frame, gtk_widget_get_name (dummy)); title = gtk_frame_get_label (GTK_FRAME(dummy)); if (title && strlen (title) > 0) gtk_frame_set_label (GTK_FRAME (frame), title); parent = gtk_widget_get_parent (dummy); g_assert (GTK_IS_BOX (parent)); gtk_widget_reparent (gtk_bin_get_child (GTK_BIN (dummy)), frame); gtk_container_remove (GTK_CONTAINER (parent), dummy); gtk_box_pack_start (GTK_BOX (parent), frame, TRUE, TRUE, 0); gtk_widget_show (frame); return frame; } static void set_page (app_data *data, int page) { int current = gtk_notebook_get_current_page (GTK_NOTEBOOK (data->notebook)); if (page != current) { gtk_notebook_set_current_page (GTK_NOTEBOOK (data->notebook), page); if (page != PAGE_MAIN) { gtk_widget_show (data->back_btn); } else { gtk_widget_hide (data->back_btn); } /* make sure the toggle is correct */ g_signal_handler_block (data->settings_btn, data->settings_id); if (page == PAGE_SETTINGS) { gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (data->settings_btn), TRUE); } else if (current == PAGE_SETTINGS) { gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (data->settings_btn), FALSE); } g_signal_handler_unblock (data->settings_btn, data->settings_id); } gtk_window_present (GTK_WINDOW (data->sync_win)); } static void settings_toggled (GtkToggleButton *button, app_data *data) { int page = gtk_notebook_get_current_page (GTK_NOTEBOOK (data->notebook)); if (page == PAGE_SETTINGS) { show_main_view (data); } else { show_services_list (data, NULL); } } static gboolean key_press_cb (GtkWidget *widget, GdkEventKey *event, app_data *data) { int page = gtk_notebook_get_current_page (GTK_NOTEBOOK (data->notebook)); if (event->keyval == GDK_KEY_Escape && page != PAGE_MAIN) { show_main_view (data); } return FALSE; } /* For some reason metacity sometimes won't maximize but will if asked * another time. For the record, I'm not proud of writing this */ static gboolean try_maximize (GtkWindow *win) { static int count = 0; count++; gtk_window_maximize (win); return (count < 10); } static void setup_windows (app_data *data, GtkWidget *main, GtkWidget *settings, GtkWidget *emergency) { GtkWidget *tmp, *toolbar, *close_btn; GtkToolItem *item; g_assert (GTK_IS_WINDOW (main)); g_assert (GTK_IS_WINDOW (settings)); g_assert (GTK_IS_WINDOW (emergency)); data->sync_win = main; data->services_win = NULL; data->emergency_win = NULL; /* populate the notebook with window contents */ data->notebook = gtk_notebook_new (); gtk_widget_show (data->notebook); gtk_notebook_set_show_tabs (GTK_NOTEBOOK (data->notebook), FALSE); gtk_notebook_set_show_border (GTK_NOTEBOOK (data->notebook), FALSE); gtk_window_maximize (GTK_WINDOW (data->sync_win)); g_timeout_add (10, (GSourceFunc)try_maximize, data->sync_win); gtk_window_set_decorated (GTK_WINDOW (data->sync_win), FALSE); gtk_widget_set_name (data->sync_win, "meego_win"); g_signal_connect (data->sync_win, "key-press-event", G_CALLBACK (key_press_cb), data); tmp = g_object_ref (gtk_bin_get_child (GTK_BIN (data->sync_win))); gtk_container_remove (GTK_CONTAINER (data->sync_win), tmp); gtk_notebook_append_page (GTK_NOTEBOOK (data->notebook), tmp, NULL); g_object_unref (tmp); tmp = g_object_ref (gtk_bin_get_child (GTK_BIN (settings))); gtk_container_remove (GTK_CONTAINER (settings), tmp); gtk_notebook_append_page (GTK_NOTEBOOK (data->notebook), tmp, NULL); g_object_unref (tmp); tmp = g_object_ref (gtk_bin_get_child (GTK_BIN (emergency))); gtk_container_remove (GTK_CONTAINER (emergency), tmp); gtk_notebook_append_page (GTK_NOTEBOOK (data->notebook), tmp, NULL); g_object_unref (tmp); tmp = gtk_vbox_new (FALSE, 0); gtk_widget_show (tmp); gtk_container_add (GTK_CONTAINER (data->sync_win), tmp); gtk_box_pack_end (GTK_BOX (tmp), data->notebook, TRUE, TRUE, 0); /* create the window toolbar */ toolbar = gtk_toolbar_new (); gtk_widget_set_name (toolbar, "moblin-toolbar"); gtk_box_pack_start (GTK_BOX (tmp), toolbar, FALSE, FALSE, 0); data->back_btn = gtk_button_new_with_label (_("Back to sync")); gtk_widget_set_name (data->back_btn, "moblin-toolbar-button"); gtk_widget_set_can_focus (data->back_btn, FALSE); gtk_widget_set_no_show_all (data->back_btn, TRUE); g_signal_connect_swapped (data->back_btn, "clicked", G_CALLBACK (show_main_view), data); item = gtk_tool_item_new (); gtk_container_add (GTK_CONTAINER (item), data->back_btn); gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, 0); item = gtk_tool_item_new (); gtk_tool_item_set_expand (item, TRUE); gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, 1); data->settings_btn = gtk_toggle_button_new (); gtk_widget_set_can_focus (data->settings_btn, FALSE); gtk_widget_set_name (data->settings_btn, "moblin-settings-button"); data->settings_id = g_signal_connect (data->settings_btn, "toggled", G_CALLBACK (settings_toggled), data); gtk_container_add (GTK_CONTAINER (data->settings_btn), gtk_image_new_from_icon_name ("preferences-other", GTK_ICON_SIZE_LARGE_TOOLBAR)); item = gtk_tool_item_new (); gtk_container_add (GTK_CONTAINER (item), data->settings_btn); gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1); close_btn = gtk_button_new (); gtk_widget_set_can_focus (close_btn, FALSE); gtk_widget_set_name (close_btn, "moblin-close-button"); g_signal_connect (close_btn, "clicked", G_CALLBACK (gtk_main_quit), NULL); gtk_container_add (GTK_CONTAINER (close_btn), gtk_image_new_from_icon_name ("window-close", GTK_ICON_SIZE_LARGE_TOOLBAR)); item = gtk_tool_item_new (); gtk_container_add (GTK_CONTAINER (item), close_btn); gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1); gtk_widget_show_all (toolbar); /* no need for close buttons */ gtk_widget_hide (data->settings_close_btn); gtk_widget_hide (data->emergency_close_btn); } static void show_emergency_view (app_data *data) { update_emergency_view (data); set_page (data, PAGE_EMERGENCY); } static void show_services_list (app_data *data, const char *config_id_to_open) { g_free (data->config_id_to_open); data->config_id_to_open = g_strdup (config_id_to_open); set_page (data, PAGE_SETTINGS); update_services_list (data); } static void show_main_view (app_data *data) { set_page (data, PAGE_MAIN); } #else /* return the placeholders themselves when not using Moblin UX */ static GtkWidget* switch_dummy_to_mux_frame (GtkWidget *dummy) { return dummy; } static void setup_windows (app_data *data, GtkWidget *main, GtkWidget *settings, GtkWidget *emergency) { data->sync_win = main; data->services_win = settings; data->emergency_win = emergency; gtk_window_set_transient_for (GTK_WINDOW (data->services_win), GTK_WINDOW (data->sync_win)); gtk_window_set_transient_for (GTK_WINDOW (data->emergency_win), GTK_WINDOW (data->sync_win)); g_signal_connect (data->services_win, "delete-event", G_CALLBACK (gtk_widget_hide_on_delete), NULL); g_signal_connect (data->emergency_win, "delete-event", G_CALLBACK (gtk_widget_hide_on_delete), NULL); } static void show_emergency_view (app_data *data) { update_emergency_view (data); gtk_widget_hide (data->services_win); gtk_window_present (GTK_WINDOW (data->emergency_win)); } static void show_services_list (app_data *data, const char *config_id_to_open) { g_free (data->config_id_to_open); data->config_id_to_open = g_strdup (config_id_to_open); gtk_widget_hide (data->emergency_win); gtk_window_present (GTK_WINDOW (data->services_win)); update_services_list (data); } static void show_main_view (app_data *data) { gtk_widget_hide (data->services_win); gtk_widget_hide (data->emergency_win); gtk_window_present (GTK_WINDOW (data->sync_win)); } #endif /* This is a hacky way to achieve autoscrolling when the expanders open/close */ static void services_box_allocate_cb (GtkWidget *widget, GtkAllocation *allocation, app_data *data) { if (GTK_IS_WIDGET (data->expanded_config)) { int y; GtkAdjustment *adj; GtkAllocation alloc; gtk_widget_translate_coordinates (data->expanded_config, data->services_box, 0, 0, NULL, &y); gtk_widget_get_allocation (data->expanded_config, &alloc); adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (data->scrolled_window)); gtk_adjustment_clamp_page (adj, y, y + alloc.height); data->expanded_config = NULL; } } static void info_bar_response_cb (GtkInfoBar *info_bar, SyncErrorResponse response_id, app_data *data) { switch (response_id) { case SYNC_ERROR_RESPONSE_SYNC: start_sync (data); break; case SYNC_ERROR_RESPONSE_EMERGENCY_SLOW_SYNC: slow_sync (data); break; case SYNC_ERROR_RESPONSE_EMERGENCY: show_emergency_view (data); break; case SYNC_ERROR_RESPONSE_SETTINGS_OPEN: data->open_current = TRUE; show_services_list (data, NULL); break; case SYNC_ERROR_RESPONSE_SETTINGS_SELECT: show_services_list (data, NULL); break; default: g_warn_if_reached (); } } static void new_device_clicked_cb (GtkButton *btn, app_data *data) { DBusGProxy *proxy; DBusGConnection *bus; GError *error = NULL; GAppInfo *info; GAppLaunchContext *context; switch (data->bluetooth_wizard) { case SYNC_BLUETOOTH_MOBLIN: bus = dbus_g_bus_get (DBUS_BUS_SESSION, NULL); if (bus) { proxy = dbus_g_proxy_new_for_name (bus, "org.moblin.UX.Shell.Toolbar", "/org/moblin/UX/Shell/Toolbar", "org.moblin.UX.Shell.Toolbar"); dbus_g_proxy_call_no_reply (proxy, "ShowPanel", G_TYPE_STRING, "bluetooth-panel", G_TYPE_INVALID, G_TYPE_INVALID); g_object_unref (proxy); } break; case SYNC_BLUETOOTH_GNOME: info = (GAppInfo*) g_desktop_app_info_new ("bluetooth-wizard.desktop"); context = (GAppLaunchContext*) gdk_display_get_app_launch_context ( gtk_widget_get_display (data->sync_win)); g_app_info_launch (info, NULL, context, &error); if (error) { g_warning ("Failed to spawn bluetooth-wizard: %s", error->message); g_error_free (error); return; } g_object_unref (info); g_object_unref (context); break; default: ; } } static void name_has_owner_cb (DBusGProxy *proxy, gboolean has_owner, GError *error, app_data *data) { if (has_owner) { gtk_widget_show (data->new_device_btn); data->bluetooth_wizard = SYNC_BLUETOOTH_MOBLIN; } g_object_unref (proxy); } static void init_bluetooth_ui (app_data *data) { char *bt_wizard; DBusGConnection *bus; DBusGProxy *proxy; data->bluetooth_wizard = SYNC_BLUETOOTH_NONE; /* look for gnome bluetooth wizard first */ bt_wizard = g_find_program_in_path ("bluetooth-wizard"); if (bt_wizard) { gtk_widget_show (data->new_device_btn); data->bluetooth_wizard = SYNC_BLUETOOTH_GNOME; g_free (bt_wizard); } else { /* try Moblin shell next (bluetooth panel integrates bt wizard) */ bus = dbus_g_bus_get (DBUS_BUS_SESSION, NULL); proxy = dbus_g_proxy_new_for_name (bus, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS); if (proxy) { org_freedesktop_DBus_name_has_owner_async (proxy, "org.moblin.UX.Shell.Toolbar", (org_freedesktop_DBus_name_has_owner_reply)name_has_owner_cb, data); } } } static void autosync_toggle_cb (GtkWidget *widget, gpointer x, app_data *data) { if (data->current_service && data->current_service->config) { gboolean new_active, old_active; char *autosync = NULL; new_active = gtk_switch_get_active (GTK_SWITCH (widget)); syncevo_config_get_value (data->current_service->config, NULL, "autoSync", &autosync); old_active = (g_strcmp0 (autosync, "1") == 0); if (old_active != new_active) { char *new_val; operation_data *op_data; new_val = new_active ? "1": "0"; syncevo_config_set_value (data->current_service->config, NULL, "autoSync", new_val); op_data = g_slice_new (operation_data); op_data->data = data; op_data->operation = OP_SAVE; op_data->started = FALSE; syncevo_server_start_no_sync_session (data->server, data->current_service->name, (SyncevoServerStartSessionCb)start_session_cb, op_data); } } } static void build_autosync_ui (app_data *data) { char *txt; GtkWidget *lbl; /* TRANSLATORS: label for checkbutton/toggle in main view. * Please stick to similar length strings or break the line with * "\n" if absolutely needed */ txt = _("Automatic sync"); lbl = gtk_label_new (txt); gtk_widget_show (lbl); gtk_box_pack_end (GTK_BOX (data->autosync_box), lbl, FALSE, FALSE, 0); data->autosync_toggle = gtk_switch_new (); gtk_widget_show (data->autosync_toggle); gtk_box_pack_end (GTK_BOX (data->autosync_box), data->autosync_toggle, FALSE, FALSE, 0); g_signal_connect (data->autosync_toggle, "notify::active", G_CALLBACK (autosync_toggle_cb), data); gtk_widget_show (data->autosync_toggle); } static void glade_name_workaround (GtkBuilder *builder, const char *name) { GtkWidget *w; w = GTK_WIDGET (gtk_builder_get_object (builder, name)); if (w) { gtk_widget_set_name (w, name); } } static gboolean init_ui (app_data *data) { GtkBuilder *builder; GError *error = NULL; GtkWidget /* *frame, */ * service_error_box, *btn; GtkAdjustment *adj; GtkCssProvider *provider; builder = gtk_builder_new (); gtk_builder_add_from_file (builder, GLADEDIR "ui.xml", &error); if (error) { g_printerr ("Failed to load user interface from %s: %s\n", GLADEDIR "ui.xml", error->message); g_error_free (error); g_object_unref (builder); return FALSE; } data->service_box = GTK_WIDGET (gtk_builder_get_object (builder, "service_box")); service_error_box = GTK_WIDGET (gtk_builder_get_object (builder, "service_error_box")); data->info_bar = gtk_info_bar_new (); gtk_widget_set_no_show_all (data->info_bar, TRUE); g_signal_connect (data->info_bar, "response", G_CALLBACK (info_bar_response_cb), data); gtk_box_pack_start (GTK_BOX (service_error_box), data->info_bar, TRUE, TRUE, 16); data->no_connection_box = GTK_WIDGET (gtk_builder_get_object (builder, "no_connection_box")); data->server_icon_box = GTK_WIDGET (gtk_builder_get_object (builder, "server_icon_box")); data->offline_label = GTK_WIDGET (gtk_builder_get_object (builder, "offline_label")); data->progress = GTK_WIDGET (gtk_builder_get_object (builder, "progressbar")); data->change_service_btn = GTK_WIDGET (gtk_builder_get_object (builder, "change_service_btn")); data->emergency_btn = GTK_WIDGET (gtk_builder_get_object (builder, "emergency_btn")); data->sync_btn = GTK_WIDGET (gtk_builder_get_object (builder, "sync_btn")); data->sync_status_label = GTK_WIDGET (gtk_builder_get_object (builder, "sync_status_label")); data->spinner_image = GTK_WIDGET (gtk_builder_get_object (builder, "spinner_image")); gtk_image_set_from_file (GTK_IMAGE (data->spinner_image), THEMEDIR "sync-spinner.gif"); gtk_widget_set_no_show_all (data->spinner_image, TRUE); gtk_widget_hide (data->spinner_image); data->autosync_box = GTK_WIDGET (gtk_builder_get_object (builder, "autosync_box")); build_autosync_ui (data); data->server_label = GTK_WIDGET (gtk_builder_get_object (builder, "sync_service_label")); data->last_synced_label = GTK_WIDGET (gtk_builder_get_object (builder, "last_synced_label")); data->sources_box = GTK_WIDGET (gtk_builder_get_object (builder, "sources_box")); data->new_service_btn = GTK_WIDGET (gtk_builder_get_object (builder, "new_service_btn")); gtk_widget_set_size_request (data->new_service_btn, SYNC_UI_LIST_BTN_WIDTH, SYNC_UI_LIST_ICON_SIZE); g_signal_connect (data->new_service_btn, "clicked", G_CALLBACK (setup_new_service_clicked), data); /* service list view */ data->scrolled_window = GTK_WIDGET (gtk_builder_get_object (builder, "scrolledwindow")); adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (data->scrolled_window)); data->services_box = GTK_WIDGET (gtk_builder_get_object (builder, "services_box")); gtk_container_set_focus_vadjustment (GTK_CONTAINER (data->services_box), adj); g_signal_connect(data->services_box, "size-allocate", G_CALLBACK (services_box_allocate_cb), data); data->devices_box = GTK_WIDGET (gtk_builder_get_object (builder, "devices_box")); data->settings_close_btn = GTK_WIDGET (gtk_builder_get_object (builder, "settings_close_btn")); /* emergency view */ btn = GTK_WIDGET (gtk_builder_get_object (builder, "slow_sync_btn")); g_signal_connect (btn, "clicked", G_CALLBACK (slow_sync_clicked_cb), data); data->refresh_from_server_btn_label = GTK_WIDGET (gtk_builder_get_object (builder, "refresh_from_server_btn_label")); g_signal_connect (GTK_WIDGET (gtk_builder_get_object (builder, "refresh_from_server_btn")), "clicked", G_CALLBACK (refresh_from_server_clicked_cb), data); data->refresh_from_client_btn_label = GTK_WIDGET (gtk_builder_get_object (builder, "refresh_from_client_btn_label")); g_signal_connect (GTK_WIDGET (gtk_builder_get_object (builder, "refresh_from_client_btn")), "clicked", G_CALLBACK (refresh_from_client_clicked_cb), data); data->emergency_label = GTK_WIDGET (gtk_builder_get_object (builder, "emergency_label")); data->emergency_expander = GTK_WIDGET (gtk_builder_get_object (builder, "emergency_expander")); data->emergency_source_table = GTK_WIDGET (gtk_builder_get_object (builder, "emergency_source_table")); data->emergency_backup_table = GTK_WIDGET (gtk_builder_get_object (builder, "emergency_backup_table")); data->emergency_close_btn = GTK_WIDGET (gtk_builder_get_object (builder, "emergency_close_btn")); /* No (documented) way to add own widgets to gtkbuilder it seems... swap the all dummy widgets with Muxwidgets */ setup_windows (data, GTK_WIDGET (gtk_builder_get_object (builder, "sync_win")), GTK_WIDGET (gtk_builder_get_object (builder, "services_win")), GTK_WIDGET (gtk_builder_get_object (builder, "emergency_win"))); data->main_frame = switch_dummy_to_mux_frame (GTK_WIDGET (gtk_builder_get_object (builder, "main_frame"))); data->log_frame = switch_dummy_to_mux_frame (GTK_WIDGET (gtk_builder_get_object (builder, "log_frame"))); /* frame = */ switch_dummy_to_mux_frame (GTK_WIDGET (gtk_builder_get_object (builder, "services_list_frame"))); /* frame = */ switch_dummy_to_mux_frame (GTK_WIDGET (gtk_builder_get_object (builder, "emergency_frame"))); g_signal_connect (data->sync_win, "destroy", G_CALLBACK (gtk_main_quit), NULL); g_signal_connect (data->change_service_btn, "clicked", G_CALLBACK (change_service_clicked_cb), data); g_signal_connect (data->emergency_btn, "clicked", G_CALLBACK (emergency_clicked_cb), data); g_signal_connect (data->sync_btn, "clicked", G_CALLBACK (sync_clicked_cb), data); g_signal_connect_swapped (data->emergency_close_btn, "clicked", G_CALLBACK (show_main_view), data); g_signal_connect_swapped (data->settings_close_btn, "clicked", G_CALLBACK (show_main_view), data); g_signal_connect (data->emergency_btn, "clicked", G_CALLBACK (emergency_clicked_cb), data); data->new_device_btn = GTK_WIDGET (gtk_builder_get_object (builder, "new_device_btn")); g_signal_connect (data->new_device_btn, "clicked", G_CALLBACK (new_device_clicked_cb), data); /* workarounds for glade not working with gtkbuilder >= 2.20: * widgets do not get names. */ glade_name_workaround (builder, "meego_win"); glade_name_workaround (builder, "sync_data_and_type_box"); glade_name_workaround (builder, "log_frame"); glade_name_workaround (builder, "backup_frame"); glade_name_workaround (builder, "services_frame"); glade_name_workaround (builder, "sync_service_label"); glade_name_workaround (builder, "sync_status_label"); glade_name_workaround (builder, "no_server_label"); glade_name_workaround (builder, "sync_failure_label"); glade_name_workaround (builder, "sync_btn"); init_bluetooth_ui (data); /* custom styling */ provider = gtk_css_provider_new (); if (gtk_css_provider_load_from_path (provider, THEMEDIR "sync-ui.css", &error)) { gtk_style_context_add_provider_for_screen (gdk_screen_get_default (), GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); g_object_unref (provider); } else { g_printerr ("Failed to load styles from %s: %s\n", THEMEDIR "sync-ui.css", error->message); g_error_free (error); error = NULL; } g_object_unref (builder); return TRUE; } static void load_icon (const char *uri, GtkBox *icon_box, guint icon_size) { GError *error = NULL; GdkPixbuf *pixbuf; GtkWidget *image; const char *filename; if (uri && strlen (uri) > 0) { if (g_str_has_prefix (uri, "file://")) { filename = uri+7; } else { g_warning ("only file:// icon uri is supported: %s", uri); filename = THEMEDIR "sync-generic.png"; } } else { filename = THEMEDIR "sync-generic.png"; } pixbuf = gdk_pixbuf_new_from_file_at_scale (filename, icon_size, icon_size, TRUE, &error); if (!pixbuf) { g_warning ("Failed to load service icon: %s", error->message); g_error_free (error); return; } image = gtk_image_new_from_pixbuf (pixbuf); gtk_widget_set_size_request (image, icon_size, icon_size); g_object_unref (pixbuf); gtk_container_foreach (GTK_CONTAINER(icon_box), (GtkCallback)remove_child, icon_box); gtk_box_pack_start (icon_box, image, FALSE, FALSE, 0); gtk_widget_show (image); } static void emergency_toggle_notify_active_cb (GtkWidget *widget, gpointer p, app_data *data) { char *source; source = g_object_get_data (G_OBJECT (widget), "source"); g_return_if_fail (source); if (gtk_switch_get_active (GTK_SWITCH (widget))) { g_hash_table_insert (data->emergency_sources, g_strdup (source), ""); } else { g_hash_table_remove (data->emergency_sources, source); } update_emergency_expander (data); } static GtkWidget* add_emergency_toggle_widget (app_data *data, const char *title, gboolean active, guint row, guint col) { GtkWidget *toggle; GtkWidget *label; col = col * 2; label = gtk_label_new (title); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_widget_show (label); gtk_table_attach (GTK_TABLE (data->emergency_source_table), label, col, col + 1, row, row + 1, GTK_FILL, GTK_FILL, 16, 0); toggle = gtk_switch_new (); gtk_switch_set_active (GTK_SWITCH (toggle), active); g_signal_connect (toggle, "notify::active", G_CALLBACK (emergency_toggle_notify_active_cb), data); gtk_widget_show (toggle); gtk_table_attach (GTK_TABLE (data->emergency_source_table), toggle, col + 1, col + 2, row, row + 1, GTK_FILL, GTK_FILL, 0, 0); return toggle; } static void update_emergency_expander (app_data *data) { char *text, *sources = NULL; GHashTableIter iter; char *name; g_hash_table_iter_init (&iter, data->emergency_sources); while (g_hash_table_iter_next (&iter, (gpointer)&name, NULL)) { char *pretty, *tmp; pretty = get_pretty_source_name (name); if (sources) { tmp = g_strdup_printf ("%s, %s", sources, pretty); g_free (sources); g_free (pretty); sources = tmp; } else { sources = pretty; } } if (sources) { /* This is the expander label in emergency view. It summarizes the * currently selected data sources. First placeholder is service/device * name, second a comma separeted list of sources. * E.g. "Affected data: Google Contacts, Appointments" */ text = g_strdup_printf (_("Affected data: %s %s"), data->current_service->pretty_name, sources); g_free (sources); } else { text = g_strdup_printf (_("Affected data: none")); } gtk_expander_set_label (GTK_EXPANDER (data->emergency_expander), text); g_free (text); } static void add_emergency_source (const char *name, GHashTable *source, app_data *data) { source_config *conf; GtkWidget *toggle; guint rows, cols; guint row; static guint col; gboolean active = TRUE; char *pretty_name; conf = g_hash_table_lookup (data->current_service->source_configs, name); g_object_get (data->emergency_source_table, "n-rows", &rows, "n-columns", &cols, NULL); if (cols != 1 && col == 0){ col = 1; row = rows - 1; } else { col = 0; row = rows; } active = (g_hash_table_lookup (data->emergency_sources, name) != NULL); pretty_name = get_pretty_source_name (name); toggle = add_emergency_toggle_widget (data, pretty_name, active, row, col); gtk_widget_set_sensitive (toggle, source_config_is_usable (conf)); g_object_set_data_full (G_OBJECT (toggle), "source", g_strdup (name), g_free); g_free (pretty_name); } static void update_backup_visibilities (app_data *data) { char *key; GHashTableIter iter; GList *l, *widgets; widgets = gtk_container_get_children ( GTK_CONTAINER (data->emergency_backup_table)); gtk_widget_show_all (data->emergency_backup_table); /* hide backup widgets that do not contain selected sources */ g_hash_table_iter_init (&iter, data->emergency_sources); while (g_hash_table_iter_next (&iter, (gpointer)&key, NULL)) { for (l = widgets; l; l = l->next) { if (!g_object_get_data (G_OBJECT (l->data), key)) { gtk_widget_hide (GTK_WIDGET (l->data)); } } } g_list_free (widgets); } static void restore_clicked_cb (GtkButton *btn, app_data *data) { const char *dir, *time_str; operation_data *op_data; char *message; dir = g_object_get_data (G_OBJECT (btn), "dir"); time_str = g_object_get_data (G_OBJECT (btn), "time"); g_return_if_fail (dir && time_str); /* TRANSLATORS: confirmation for restoring a backup. placeholder is the * backup time string defined below */ message = g_strdup_printf (_("Do you want to restore the backup from %s? " "All changes you have made since then will be lost."), time_str); if (!show_confirmation (data->sync_win, message, _("Yes, restore"), _("No"))) { g_free (message); return; } g_free (message); op_data = g_slice_new (operation_data); op_data->data = data; op_data->operation = OP_RESTORE; op_data->dir = dir; op_data->started = FALSE; syncevo_server_start_session (data->server, data->current_service->name, (SyncevoServerStartSessionCb)start_session_cb, op_data); show_main_view (data); } static void add_backup (app_data *data, const char *peername, const char *dir, long endtime, GList *sources) { GtkWidget *timelabel, *label, *blabel, *button, *box;; guint rows; char *text; char time_str[60]; struct tm *tim; tim = localtime (&endtime); /* TRANSLATORS: date/time for strftime(), used in emergency view backup * label. Any time format that shows date and time is good. */ strftime (time_str, sizeof (time_str), _("%x %X"), tim); g_object_get (data->emergency_backup_table, "n-rows", &rows, NULL); box = gtk_vbox_new (TRUE, 6); gtk_table_attach (GTK_TABLE (data->emergency_backup_table), box, 0, 1, rows, rows + 1, GTK_EXPAND|GTK_FILL, GTK_FILL, 16, 0); timelabel = gtk_label_new (time_str); gtk_misc_set_alignment (GTK_MISC (timelabel), 0.0, 0.5); gtk_label_set_line_wrap (GTK_LABEL (timelabel), TRUE); gtk_widget_set_size_request (timelabel, 600, -1); gtk_box_pack_start (GTK_BOX (box), timelabel, TRUE, TRUE, 0); /* TRANSLATORS: label for a backup in emergency view. Placeholder is * service or device name */ text = g_strdup_printf (_("Backed up before syncing with %s"), peername); label = gtk_label_new (text); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); gtk_widget_set_size_request (label, 600, -1); gtk_box_pack_start (GTK_BOX (box), label, TRUE, TRUE, 0); g_free (text); button = gtk_button_new (); gtk_table_attach (GTK_TABLE (data->emergency_backup_table), button, 1, 2, rows, rows + 1, GTK_FILL, GTK_SHRINK, 32, 0); g_object_set_data_full (G_OBJECT (button), "dir", g_strdup(dir), g_free); g_object_set_data_full (G_OBJECT (button), "time", g_strdup(time_str), g_free); g_signal_connect (button, "clicked", G_CALLBACK (restore_clicked_cb), data); blabel = gtk_label_new (_("Restore")); gtk_misc_set_padding (GTK_MISC (blabel), 32, 0); gtk_container_add (GTK_CONTAINER (button), blabel); for (; sources; sources = sources->next) { g_object_set_data (G_OBJECT (box), (char *)sources->data, ""); g_object_set_data (G_OBJECT (button), (char *)sources->data, ""); } } static void get_reports_for_backups_cb (SyncevoServer *server, SyncevoReports *reports, GError *error, app_data *data) { guint len, i; if (error) { g_warning ("Error in Session.GetReports: %s", error->message); g_error_free (error); /* non-fatal, unknown error */ return; } len = syncevo_reports_get_length (reports); for (i = 0; i < len; i++) { GHashTable *report = syncevo_reports_index (reports, i); GHashTableIter iter; char *key, *val; /* long status = -1; */ long endtime = -1; char *peername = NULL; char *dir = NULL; GList *backup_sources = NULL; g_hash_table_iter_init (&iter, report); while (g_hash_table_iter_next (&iter, (gpointer)&key, (gpointer)&val)) { char **strs; strs = g_strsplit (key, "-", 6); if (!strs) { continue; } if (g_strcmp0 (strs[0], "source") == 0 && g_strcmp0 (strs[2], "backup") == 0 && g_strcmp0 (strs[3], "before") == 0) { backup_sources = g_list_prepend (backup_sources, g_strdup (strs[1])); } else if (g_strcmp0 (strs[0], "end") == 0) { endtime = strtol (val, NULL, 10); } else if (g_strcmp0 (strs[0], "status") == 0) { /* status = strtol (val, NULL, 10); */ } else if (g_strcmp0 (strs[0], "peer") == 0) { peername = val; } else if (g_strcmp0 (strs[0], "dir") == 0) { dir = val; } g_strfreev (strs); } if (peername && dir && endtime > 0) { add_backup (data, peername, dir, endtime, backup_sources); } g_list_foreach (backup_sources, (GFunc)g_free, NULL); g_list_free (backup_sources); } data->backup_count += len; if (len == REPORTS_PER_CALL) { syncevo_server_get_reports (data->server, "", data->backup_count, REPORTS_PER_CALL, (SyncevoServerGetReportsCb)get_reports_for_backups_cb, data); } update_backup_visibilities (data); } static const char* get_syncevo_context (const char *config_name) { char *context; context = g_strrstr (config_name, "@"); if (!context) { context = ""; } return context; } static void update_emergency_view (app_data *data) { char *text; if (!data->current_service) { g_warning ("no service defined in Emergency view"); return; } if (data->forced_emergency) { text = g_strdup_printf ( /* TRANSLATORS: this is an explanation in Emergency view. * Placeholder is a service/device name */ _("A normal sync with %s is not possible at this time. " "You can do a slow two-way sync or start from scratch. You " "can also restore a backup, but a slow sync or starting from " "scratch will still be required before normal sync is " "possible."), data->current_service->pretty_name); } else { /* TRANSLATORS: this is an explanation in Emergency view. * Placeholder is a service/device name */ text = g_strdup_printf ( _("If something has gone horribly wrong, you can try a " "slow sync, start from scratch or restore from backup.")); } gtk_label_set_text (GTK_LABEL (data->emergency_label), text); g_free (text); /* TRANSLATORS: These are a buttons in Emergency view. Placeholder is a * service/device name. Please don't use too long lines, but feel free to * use several lines. */ text = g_strdup_printf (_("Delete all your local\n" "data and replace with\n" "data from %s"), data->current_service->pretty_name); gtk_label_set_text (GTK_LABEL (data->refresh_from_server_btn_label), text); g_free (text); text = g_strdup_printf (_("Delete all data on\n" "%s and replace\n" "with your local data"), data->current_service->pretty_name); gtk_label_set_text (GTK_LABEL (data->refresh_from_client_btn_label), text); g_free (text); gtk_container_foreach (GTK_CONTAINER (data->emergency_source_table), (GtkCallback)remove_child, data->emergency_source_table); gtk_table_resize (GTK_TABLE (data->emergency_source_table), 1, 1); /* using this instead of current_service->source_configs * to get the same order as the configuration has... */ syncevo_config_foreach_source (data->current_service->config, (ConfigFunc)add_emergency_source, data); update_emergency_expander (data); data->backup_count = 0; gtk_container_foreach (GTK_CONTAINER (data->emergency_backup_table), (GtkCallback)remove_child, data->emergency_backup_table); gtk_table_resize (GTK_TABLE (data->emergency_backup_table), 1, 1); syncevo_server_get_reports (data->server, get_syncevo_context (data->current_service->name), 0, REPORTS_PER_CALL, (SyncevoServerGetReportsCb)get_reports_for_backups_cb, data); } static void update_service_source_ui (const char *name, source_config *conf, app_data *data) { GtkWidget *lbl, *box; char *pretty_name, *title; if (!source_config_is_usable (conf)) { return; } conf->box = gtk_vbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (data->sources_box), conf->box, FALSE, FALSE, 8); pretty_name = get_pretty_source_name_markup (name); title = g_strdup_printf ("%s", pretty_name); lbl = gtk_label_new (NULL); gtk_label_set_markup (GTK_LABEL (lbl), title); g_free (pretty_name); g_free (title); gtk_misc_set_alignment (GTK_MISC (lbl), 0.0, 0.5); gtk_box_pack_start (GTK_BOX (conf->box), lbl, TRUE, TRUE, 0); box = gtk_hbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (conf->box), box, FALSE, FALSE, 0); conf->info_bar = gtk_info_bar_new (); gtk_box_pack_start (GTK_BOX (box), conf->info_bar, TRUE, TRUE, 16); gtk_widget_set_no_show_all (conf->info_bar, TRUE); g_signal_connect (conf->info_bar, "response", G_CALLBACK (info_bar_response_cb), data); conf->label = gtk_label_new (NULL); gtk_misc_set_alignment (GTK_MISC (conf->label), 0.0, 0.5); gtk_box_pack_start (GTK_BOX (conf->box), conf->label, TRUE, TRUE, 0); source_config_update_widget (conf); gtk_widget_show_all (conf->box); } static void check_source_cb (SyncevoSession *session, GError *error, source_config *source) { if (error) { if(error->code == DBUS_GERROR_REMOTE_EXCEPTION && dbus_g_error_has_name (error, SYNCEVO_DBUS_ERROR_SOURCE_UNUSABLE)) { /* source is not supported locally */ if (source) { source->supported_locally = FALSE; if (source->box) { /* widget is already on screen, hide it */ gtk_widget_hide (source->box); } } } else { g_warning ("CheckSource failed: %s", error->message); /* non-fatal, unknown error */ } g_error_free (error); return; } } static void update_service_ui (app_data *data) { char *icon_uri = NULL; char *autosync = NULL; gtk_container_foreach (GTK_CONTAINER (data->sources_box), (GtkCallback)remove_child, data->sources_box); if (data->current_service && data->current_service->config) { syncevo_config_get_value (data->current_service->config, NULL, "IconURI", &icon_uri); syncevo_config_get_value (data->current_service->config, NULL, "autoSync", &autosync); g_hash_table_foreach (data->current_service->source_configs, (GHFunc)update_service_source_ui, data); } load_icon (icon_uri, GTK_BOX (data->server_icon_box), SYNC_UI_ICON_SIZE); gtk_switch_set_active (GTK_SWITCH (data->autosync_toggle), g_strcmp0 (autosync, "1") == 0); refresh_last_synced_label (data); gtk_widget_show_all (data->sources_box); } static void unexpand_config_widget (GtkWidget *w, GtkWidget *exception) { if (SYNC_IS_CONFIG_WIDGET (w) && exception && exception != w) { sync_config_widget_set_expanded (SYNC_CONFIG_WIDGET (w), FALSE); } } static void config_widget_expanded_cb (GtkWidget *widget, GParamSpec *pspec, app_data *data) { if (sync_config_widget_get_expanded (SYNC_CONFIG_WIDGET (widget))) { data->expanded_config = widget; gtk_container_foreach (GTK_CONTAINER (data->services_box), (GtkCallback)unexpand_config_widget, widget); } } static void config_widget_changed_cb (GtkWidget *widget, app_data *data) { if (sync_config_widget_get_current (SYNC_CONFIG_WIDGET (widget))) { const char *name = NULL; name = sync_config_widget_get_name (SYNC_CONFIG_WIDGET (widget)); reload_config (data, name); show_main_view (data); } else { reload_config (data, NULL); update_services_list (data); } } static SyncConfigWidget* add_configuration_to_box (GtkBox *box, SyncevoConfig *config, const char *name, gboolean has_template, gboolean has_configuration, app_data *data) { GtkWidget *item = NULL; gboolean current = FALSE; const char *current_name = NULL; if (data->current_service) { current_name = data->current_service->pretty_name; if (data->current_service->name && name && g_ascii_strcasecmp (name, data->current_service->name) == 0) { current = TRUE; } } item = sync_config_widget_new (data->server, name, config, current, current_name, has_configuration, has_template); g_signal_connect (item, "changed", G_CALLBACK (config_widget_changed_cb), data); g_signal_connect (item, "notify::expanded", G_CALLBACK (config_widget_expanded_cb), data); gtk_widget_show (item); gtk_box_pack_start (box, item, FALSE, FALSE, 0); if (current) { sync_config_widget_set_expanded (SYNC_CONFIG_WIDGET (item), data->open_current); } if (g_strcmp0 (name, "default") == 0) { sync_config_widget_set_expanded (SYNC_CONFIG_WIDGET (item), TRUE); } if (data->config_id_to_open) { sync_config_widget_expand_id (SYNC_CONFIG_WIDGET (item), data->config_id_to_open); } return SYNC_CONFIG_WIDGET (item); } static void find_new_service_config (SyncConfigWidget *w, GtkWidget **found) { if (SYNC_IS_CONFIG_WIDGET (w)) { if (!sync_config_widget_get_configured (w) && !sync_config_widget_get_has_template (w)) { *found = GTK_WIDGET (w); } } } typedef struct config_data { app_data *data; char *name; gboolean has_configuration; gboolean has_template; GHashTable *device_templates; } config_data; #define LEGAL_CONFIG_NAME_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVXYZ1234567890-_" static void get_config_for_config_widget_cb (SyncevoServer *server, SyncevoConfig *config, GError *error, config_data *c_data) { char *ready, *is_peer, *url, *type; c_data->data->service_list_updates_left--; if (error) { /* show in UI? */ g_warning ("Server.GetConfig() failed: %s", error->message); g_error_free (error); return; } syncevo_config_get_value (config, NULL, "ConsumerReady", &ready); syncevo_config_get_value (config, NULL, "PeerIsClient", &is_peer); syncevo_config_get_value (config, NULL, "syncURL", &url); syncevo_config_get_value (config, NULL, "peerType", &type); if (g_strcmp0 ("1", ready) != 0 || (type && g_strcmp0 ("WebDAV", type) == 0) || (url && g_str_has_prefix (url, "local://@"))) { /* Ignore existing configs and templates unless they are explicitly marked as "ConsumerReady. Also ignore webdav (and the local syncs used for webdav) for now */ } else if (is_peer && g_strcmp0 ("1", is_peer) == 0) { if (url) { SyncConfigWidget *w; char *fp, *tmp, *template_name, *device_name = NULL; char **fpv = NULL; syncevo_config_get_value (config, NULL, "deviceName", &tmp); if (!tmp) { device_name = g_strdup (c_data->name); } else { device_name = g_strcanon (g_strdup (tmp), LEGAL_CONFIG_NAME_CHARS, '-'); } syncevo_config_get_value (config, NULL, "templateName", &template_name); if (!template_name) { syncevo_config_get_value (config, NULL, "fingerPrint", &fp); if (fp) { fpv = g_strsplit_set (fp, ",;", 2); if (g_strv_length (fpv) > 0) { template_name = fpv[0]; } } } /* keep a list of added devices */ w = g_hash_table_lookup (c_data->device_templates, url); if (!w) { w = add_configuration_to_box (GTK_BOX (c_data->data->devices_box), config, device_name, c_data->has_template, c_data->has_configuration, c_data->data); g_hash_table_insert (c_data->device_templates, url, w); sync_config_widget_add_alternative_config (w, template_name, config, c_data->has_configuration); } else { /* TODO: might want to add a new widget, if user has created more * configs for same device: this really requires us to look at * all configs / templates, then decide what to sho w*/ /* there is a widget for this device already, add this info there*/ sync_config_widget_add_alternative_config (w, template_name, config, c_data->has_configuration); } g_free (device_name); g_strfreev (fpv); } } else { add_configuration_to_box (GTK_BOX (c_data->data->services_box), config, c_data->name, c_data->has_template, c_data->has_configuration, c_data->data); } g_free (c_data->name); g_hash_table_unref (c_data->device_templates); g_slice_free (config_data, c_data); } static void get_config_for_config_widget (app_data *data, const char *config, gboolean has_template, gboolean has_configuration, GHashTable *device_templates) { config_data *c_data; data->service_list_updates_left++; c_data = g_slice_new0 (config_data); c_data->data = data; c_data->name = g_strdup (config); c_data->has_template = has_template; c_data->has_configuration = has_configuration; if (device_templates) { c_data->device_templates = g_hash_table_ref (device_templates); } syncevo_server_get_config (data->server, config, !has_configuration, (SyncevoServerGetConfigCb)get_config_for_config_widget_cb, c_data); } static void setup_new_service_clicked (GtkButton *btn, app_data *data) { GtkWidget *widget = NULL; gtk_container_foreach (GTK_CONTAINER (data->services_box), (GtkCallback)unexpand_config_widget, NULL); /* if a new service config has already been added, use that. * Otherwise add one. */ gtk_container_foreach (GTK_CONTAINER (data->services_box), (GtkCallback)find_new_service_config, &widget); if (!widget) { get_config_for_config_widget (data, "default", TRUE, FALSE, NULL); } else { sync_config_widget_set_expanded (SYNC_CONFIG_WIDGET (widget), TRUE); } } typedef struct templates_data { app_data *data; char **templates; } templates_data; static void get_configs_cb (SyncevoServer *server, char **configs, GError *error, templates_data *templ_data) { char **config_iter, **template_iter, **templates; app_data *data; GHashTable *device_templates; templ_data->data->service_list_updates_left = 0; templates = templ_data->templates; data = templ_data->data; g_slice_free (templates_data, templ_data); if (error) { show_main_view (data); g_warning ("Server.GetConfigs() failed: %s", error->message); g_strfreev (templates); g_error_free (error); return; } device_templates = g_hash_table_new (g_str_hash, g_str_equal); for (template_iter = templates; *template_iter; template_iter++){ gboolean found_config = FALSE; for (config_iter = configs; *config_iter; config_iter++) { if (*template_iter && *config_iter && g_ascii_strncasecmp (*template_iter, *config_iter, strlen (*config_iter)) == 0) { /* have template and config */ get_config_for_config_widget (data, *config_iter, TRUE, TRUE, device_templates); found_config = TRUE; break; } } if (!found_config) { /* have template, no config */ get_config_for_config_widget (data, *template_iter, TRUE, FALSE, device_templates); } } for (config_iter = configs; *config_iter; config_iter++) { gboolean found_template = FALSE; for (template_iter = templates; *template_iter; template_iter++) { if (*template_iter && *config_iter && g_ascii_strncasecmp (*template_iter, *config_iter, strlen (*config_iter)) == 0) { found_template = TRUE; break; } } if (!found_template) { /* have config, no template */ get_config_for_config_widget (data, *config_iter, FALSE, TRUE, device_templates); } } /* config initialization might ref/unref as well... */ g_hash_table_unref (device_templates); g_strfreev (configs); g_strfreev (templates); } static void get_template_configs_cb (SyncevoServer *server, char **templates, GError *error, app_data *data) { templates_data *templ_data; if (error) { data->service_list_updates_left = 0; show_main_view (data); show_error_dialog (data->sync_win, _("Failed to get list of supported services from SyncEvolution")); g_warning ("Server.GetConfigs() failed: %s", error->message); g_error_free (error); return; } templ_data = g_slice_new (templates_data); templ_data->data = data; templ_data->templates = templates; syncevo_server_get_configs (data->server, FALSE, (SyncevoServerGetConfigsCb)get_configs_cb, templ_data); } static void update_services_list (app_data *data) { if (data->service_list_updates_left > 0) { return; } gtk_container_foreach (GTK_CONTAINER (data->services_box), (GtkCallback)remove_child, data->services_box); gtk_container_foreach (GTK_CONTAINER (data->devices_box), (GtkCallback)remove_child, data->devices_box); /* set temp number before we know the real one */ data->service_list_updates_left = 1; syncevo_server_get_configs (data->server, TRUE, (SyncevoServerGetConfigsCb)get_template_configs_cb, data); } static void get_config_for_main_win_cb (SyncevoServer *server, SyncevoConfig *config, GError *error, app_data *data) { if (error) { if (error->code == DBUS_GERROR_REMOTE_EXCEPTION && dbus_g_error_has_name (error, SYNCEVO_DBUS_ERROR_NO_SUCH_CONFIG)) { /* another syncevolution client probably removed the config */ reload_config (data, NULL); } else { g_warning ("Error in Server.GetConfig: %s", error->message); /* TRANSLATORS: message in main view */ set_info_bar (data->info_bar, GTK_MESSAGE_ERROR, SYNC_ERROR_RESPONSE_NONE, _("There was a problem communicating with the " "sync process. Please try again later.")); set_app_state (data, SYNC_UI_STATE_SERVER_FAILURE); } g_error_free (error); return; } if (config) { GHashTableIter iter; char *name; source_config *source; server_config_init (data->current_service, config); set_app_state (data, SYNC_UI_STATE_SERVER_OK); /* get "locally supported" status for all sources */ g_hash_table_iter_init (&iter, data->current_service->source_configs); while (g_hash_table_iter_next (&iter, (gpointer)&name, (gpointer)&source)) { syncevo_server_check_source (data->server, data->current_service->name, name, (SyncevoServerGenericCb)check_source_cb, source); } syncevo_server_get_presence (server, data->current_service->name, (SyncevoServerGetPresenceCb)get_presence_cb, data); syncevo_server_get_reports (server, data->current_service->name, 0, 1, (SyncevoServerGetReportsCb)get_reports_cb, data); update_service_ui (data); } } static void set_running_session_status (app_data *data, SyncevoSessionStatus status, int error_code) { if (status & SYNCEVO_STATUS_QUEUEING) { g_warning ("Running session is queued, this shouldn't happen..."); } else if (status & SYNCEVO_STATUS_IDLE) { set_app_state (data, SYNC_UI_STATE_SERVER_OK); } else if (status & SYNCEVO_STATUS_DONE) { char *err; err = get_error_string_for_code (error_code, NULL); if (err) { if (data->current_operation == OP_RESTORE) { gtk_label_set_text (GTK_LABEL (data->sync_status_label), _("Restore failed")); } else { gtk_label_set_text (GTK_LABEL (data->sync_status_label), _("Sync failed")); } g_free (err); } else { if (data->current_operation == OP_RESTORE) { gtk_label_set_text (GTK_LABEL (data->sync_status_label), _("Restore complete")); } else { gtk_label_set_text (GTK_LABEL (data->sync_status_label), _("Sync complete")); } } set_app_state (data, SYNC_UI_STATE_SERVER_OK); set_sync_progress (data, 1.0, ""); } else if (status & SYNCEVO_STATUS_RUNNING || status & SYNCEVO_STATUS_SUSPENDING || status & SYNCEVO_STATUS_ABORTING) { set_app_state (data, SYNC_UI_STATE_SYNCING); } if (status & SYNCEVO_STATUS_WAITING) { gtk_widget_show (data->spinner_image); } else { gtk_widget_hide (data->spinner_image); } } static void running_session_status_changed_cb (SyncevoSession *session, SyncevoSessionStatus status, guint error_code, SyncevoSourceStatuses *source_statuses, app_data *data) { set_running_session_status (data, status, error_code); } static void get_running_session_status_cb (SyncevoSession *session, SyncevoSessionStatus status, guint error_code, SyncevoSourceStatuses *source_statuses, GError *error, app_data *data) { if (error) { g_warning ("Error in Session.GetStatus: %s", error->message); g_error_free (error); /* non-fatal, unknown error */ return; } set_running_session_status (data, status, error_code); } typedef struct source_progress_data { app_data *data; SyncevoSourcePhase phase; const char *source; } source_progress_data; static void find_updated_source_progress (const char *name, SyncevoSourcePhase phase, source_progress_data *prog_data) { GHashTable *configs = prog_data->data->current_service ? prog_data->data->current_service->source_configs : NULL; source_config *config; config = configs ? g_hash_table_lookup (configs, name) : NULL; if (config) { if (phase != config->phase) { config->phase = phase; prog_data->phase = config->phase; prog_data->source = name; } } } static void running_session_progress_changed_cb (SyncevoSession *session, int progress, SyncevoSourceProgresses *source_progresses, app_data *data) { source_progress_data *prog_data = g_slice_new0 (source_progress_data); prog_data->data = data; prog_data->phase = SYNCEVO_PHASE_NONE; prog_data->source = NULL; syncevo_source_progresses_foreach (source_progresses, (SourceProgressFunc)find_updated_source_progress, prog_data); if (!prog_data->source) { set_sync_progress (data, ((float)progress) / 100, NULL); } else { char *name; char *msg = NULL; name = get_pretty_source_name (prog_data->source); switch (prog_data->phase) { case SYNCEVO_PHASE_PREPARING: msg = g_strdup_printf (_("Preparing '%s'"), name); break; case SYNCEVO_PHASE_RECEIVING: msg = g_strdup_printf (_("Receiving '%s'"), name); break; case SYNCEVO_PHASE_SENDING: msg = g_strdup_printf (_("Sending '%s'"), name); break; default: ; } if (msg) { set_sync_progress (data, ((float)progress) / 100, msg); } g_free (msg); g_free (name); } g_slice_free (source_progress_data, prog_data); } typedef struct source_stats { long status; long mode; long local_changes; long remote_changes; long local_rejections; long remote_rejections; } source_stats; static void free_source_stats (source_stats *stats) { g_slice_free (source_stats, stats); } static gboolean handle_source_report_item (char **strs, const char *value, GHashTable *sources) { source_stats *stats; char **tmp; char *name; if (g_strv_length (strs) < 3) { return FALSE; } /* replace '__' with '_' and '_+' with '-' */ tmp = g_strsplit (strs[1], "__", 0); name = g_strjoinv ("_", tmp); g_strfreev (tmp); tmp = g_strsplit (name, "_+", 0); g_free (name); name = g_strjoinv ("-", tmp); g_strfreev (tmp); stats = g_hash_table_lookup (sources, name); if (!stats) { stats = g_slice_new0 (source_stats); g_hash_table_insert (sources, g_strdup (name), stats); } g_free (name); if (strcmp (strs[2], "stat") == 0) { if (g_strv_length (strs) != 6) { return FALSE; } if (strcmp (strs[3], "remote") == 0) { if (strcmp (strs[4], "added") == 0 || strcmp (strs[4], "updated") == 0 || strcmp (strs[4], "removed") == 0) { stats->remote_changes += strtol (value, NULL, 10); } else if (strcmp (strs[5], "reject") == 0) { stats->remote_rejections += strtol (value, NULL, 10); } } else if (strcmp (strs[3], "local") == 0) { if (strcmp (strs[4], "added") == 0 || strcmp (strs[4], "updated") == 0 || strcmp (strs[4], "removed") == 0) { stats->local_changes += strtol (value, NULL, 10); } else if (strcmp (strs[5], "reject") == 0) { stats->local_rejections += strtol (value, NULL, 10); } } else { return FALSE; } } else if (strcmp (strs[2], "mode") == 0) { stats->mode = strtol (value, NULL, 10); } else if (strcmp (strs[2], "status") == 0) { stats->status = strtol (value, NULL, 10); } else if (strcmp (strs[2], "resume") == 0) { } else if (strcmp (strs[2], "first") == 0) { } else if (strcmp (strs[2], "backup") == 0) { if (g_strv_length (strs) != 4) { return FALSE; } if (strcmp (strs[3], "before") == 0) { } else if (strcmp (strs[3], "after") == 0) { } else { return FALSE; } } else { return FALSE; } return TRUE; } static char* get_report_summary (source_config *source) { char *rejects = NULL; char *changes = NULL; char *msg = NULL; if (!source->stats_set) { return g_strdup (""); } if (source->local_rejections + source->remote_rejections == 0) { rejects = NULL; } else if (source->local_rejections == 0) { rejects = g_strdup_printf (ngettext ("There was one remote rejection.", "There were %ld remote rejections.", source->remote_rejections), source->remote_rejections); } else if (source->remote_rejections == 0) { rejects = g_strdup_printf (ngettext ("There was one local rejection.", "There were %ld local rejections.", source->local_rejections), source->local_rejections); } else { rejects = g_strdup_printf (_ ("There were %ld local rejections and %ld remote rejections."), source->local_rejections, source->remote_rejections); } if (source->local_changes + source->remote_changes == 0) { changes = g_strdup_printf (_("Last time: No changes.")); } else if (source->local_changes == 0) { changes = g_strdup_printf (ngettext ("Last time: Sent one change.", "Last time: Sent %ld changes.", source->remote_changes), source->remote_changes); } else if (source->remote_changes == 0) { // This is about changes made to the local data. Not all of these // changes were requested by the remote server, so "applied" // is a better word than "received" (bug #5185). changes = g_strdup_printf (ngettext ("Last time: Applied one change.", "Last time: Applied %ld changes.", source->local_changes), source->local_changes); } else { changes = g_strdup_printf (_("Last time: Applied %ld changes and sent %ld changes."), source->local_changes, source->remote_changes); } if (rejects) msg = g_strdup_printf ("%s\n%s", changes, rejects); else msg = g_strdup (changes); g_free (rejects); g_free (changes); return msg; } /* return TRUE if no errors are shown */ static gboolean source_config_update_widget (source_config *source) { char *msg; gboolean show_error; SyncErrorResponse response; if (!source->label) { return TRUE; } msg = get_error_string_for_code (source->status, &response); if (msg) { show_error = TRUE; set_info_bar (source->info_bar, GTK_MESSAGE_ERROR, response, msg); } else { show_error = FALSE; gtk_widget_hide (source->info_bar); msg = get_report_summary (source); gtk_label_set_text (GTK_LABEL (source->label), msg); } g_free (msg); return !show_error; } static void get_reports_cb (SyncevoServer *server, SyncevoReports *reports, GError *error, app_data *data) { long status = -1; long common_status = -1; source_stats *stats; GHashTable *sources; /* key is source name, value is a source_stats */ GHashTableIter iter; const char *key, *val; source_config *source_conf; char *error_msg; SyncErrorResponse response; gboolean have_source_errors; GHashTable *report = NULL; guint len; if (error) { g_warning ("Error in Session.GetReports: %s", error->message); g_error_free (error); /* non-fatal, unknown error */ return; } sources = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)free_source_stats); len = syncevo_reports_get_length (reports); if (len > 0) { report = syncevo_reports_index (reports, 0); val = g_hash_table_lookup (report, "dir"); if (!val || strlen (val) == 0) { /* dummy report for first time sync info*/ if (len > 1) { report = syncevo_reports_index (reports, 1); } else { report = NULL; } } } if (report) { g_hash_table_iter_init (&iter, report); while (g_hash_table_iter_next (&iter, (gpointer)&key, (gpointer)&val)) { char **strs; strs = g_strsplit (key, "-", 6); if (!strs) { continue; } if (strcmp (strs[0], "source") == 0) { if (!handle_source_report_item (strs, val, sources)) { g_warning ("Unidentified sync report item: %s=%s", key, val); } } else if (strcmp (strs[0], "start") == 0) { /* not used */ } else if (strcmp (strs[0], "end") == 0) { data->last_sync = strtol (val, NULL, 10); } else if (strcmp (strs[0], "status") == 0) { status = strtol (val, NULL, 10); } else if (strcmp (strs[0], "peer") == 0) { /* not used */ } else if (strcmp (strs[0], "error") == 0) { /* not used */ } else if (strcmp (strs[0], "dir") == 0) { /* not used */ } else { g_warning ("Unidentified sync report item: %s=%s", key, val); } g_strfreev (strs); } } else { common_status = 0; } /* sources now has all statistics we want */ /* ficure out if all sources have same status or if there's a slow sync */ data->forced_emergency = FALSE; g_hash_table_remove_all (data->emergency_sources); g_hash_table_iter_init (&iter, sources); while (g_hash_table_iter_next (&iter, (gpointer)&key, (gpointer)&stats)) { if (stats->status == 22001) { /* ignore abort because of another source slow syncing */ } else if (stats->status == 22000) { common_status = stats->status; data->forced_emergency = TRUE; g_hash_table_insert (data->emergency_sources, g_strdup (key), ""); } else if (common_status == -1) { common_status = stats->status; } else if (common_status != stats->status) { common_status = 0; } } if (status != 200) { /* don't want to show a sync time for failed syncs */ data->last_sync = -1; } if (!data->forced_emergency) { /* if user initiates a emergency sync wihtout forced_emergency, enable all sources by default*/ g_hash_table_iter_init (&iter, data->current_service->source_configs); while (g_hash_table_iter_next (&iter, (gpointer)&key, (gpointer)&source_conf)) { if (source_config_is_usable (source_conf)) { g_hash_table_insert (data->emergency_sources, g_strdup (key), ""); } } } /* get common error message */ error_msg = get_error_string_for_code (common_status, &response); have_source_errors = FALSE; g_hash_table_iter_init (&iter, sources); while (g_hash_table_iter_next (&iter, (gpointer)&key, (gpointer)&stats)) { /* store the statistics in source config */ source_conf = g_hash_table_lookup (data->current_service->source_configs, key); if (source_conf) { source_conf->stats_set = TRUE; source_conf->local_changes = stats->local_changes; source_conf->remote_changes = stats->remote_changes; source_conf->local_rejections = stats->local_rejections; source_conf->remote_rejections = stats->remote_rejections; if (error_msg) { /* there is a service-wide error, no need to show here */ source_conf->status = 0; } else { source_conf->status = stats->status; } /* if ui has been constructed already, update it */ if (!source_config_update_widget (source_conf)) { have_source_errors = TRUE; } } } if (!error_msg && !have_source_errors) { /* no common source errors or individual source errors: it's still possible that there are sync errors */ error_msg = get_error_string_for_code (status, &response); } /* update service UI */ refresh_last_synced_label (data); if (error_msg) { GtkMessageType type = GTK_MESSAGE_ERROR; if (response == SYNC_ERROR_RESPONSE_EMERGENCY) { type = GTK_MESSAGE_QUESTION; } if (!data->synced_this_session) { /* TRANSLATORS: the placeholder is a error message (hopefully) * explaining the problem */ char *msg = g_strdup_printf (_("There was a problem with last sync:\n%s"), error_msg); g_free (error_msg); error_msg = msg; } set_info_bar (data->info_bar, type, response, error_msg); g_free (error_msg); } else if (data->current_operation == OP_RESTORE) { /* special case for just after restoring */ error_msg = g_strdup_printf (_("You've just restored a backup. The changes have not been " "synced with %s yet"), data->current_service->pretty_name); set_info_bar (data->info_bar, GTK_MESSAGE_INFO, SYNC_ERROR_RESPONSE_SYNC, error_msg); } g_hash_table_destroy (sources); } static void set_config_cb (SyncevoSession *session, GError *error, app_data *data) { if (error) { g_warning ("Error in Session.SetConfig: %s", error->message); g_error_free (error); /* TODO show in UI: save failed */ } g_object_unref (session); } static void restore_cb (SyncevoSession *session, GError *error, app_data *data) { if (error) { g_warning ("Error in Session.Restore: %s", error->message); g_error_free (error); return; } } static void restore_backup (app_data *data, SyncevoSession *session, const char *dir) { char **sources; GHashTableIter iter; int i = 0; char *source; sources = g_malloc0 (sizeof (char*) * (g_hash_table_size (data->emergency_sources) + 1)); g_hash_table_iter_init (&iter, data->emergency_sources); while (g_hash_table_iter_next (&iter, (gpointer)&source, NULL)) { sources[i++] = g_strdup (source); } sources[i] = NULL; syncevo_session_restore (session, dir, TRUE, (const char**)sources, (SyncevoSessionGenericCb)restore_cb, data); g_strfreev (sources); } static void save_config (app_data *data, SyncevoSession *session) { syncevo_session_set_config (session, TRUE, FALSE, data->current_service->config, (SyncevoSessionGenericCb)set_config_cb, data); } static void do_sync (operation_data *op_data, SyncevoSession *session) { GHashTable *source_modes; GHashTableIter iter; source_config *source; SyncevoSyncMode mode = SYNCEVO_SYNC_NONE; app_data *data = op_data->data; source_modes = syncevo_source_modes_new (); if (op_data->operation != OP_SYNC) { /* in an emergency sync, set non-emergency sources to not sync*/ g_hash_table_iter_init (&iter, data->current_service->source_configs); while (g_hash_table_iter_next (&iter, NULL, (gpointer)&source)) { if (g_hash_table_lookup (data->emergency_sources, source->name) == NULL) { syncevo_source_modes_add (source_modes, source->name, SYNCEVO_SYNC_NONE); } } } /* override all non-supported with "none". */ g_hash_table_iter_init (&iter, data->current_service->source_configs); while (g_hash_table_iter_next (&iter, NULL, (gpointer)&source)) { if (!source->supported_locally) { syncevo_source_modes_add (source_modes, source->name, SYNCEVO_SYNC_NONE); } } /* use given mode or use default for normal syncs */ switch (op_data->operation) { case OP_SYNC: mode = SYNCEVO_SYNC_DEFAULT; break; case OP_SYNC_SLOW: mode = SYNCEVO_SYNC_SLOW; break; case OP_SYNC_REFRESH_FROM_CLIENT: mode = SYNCEVO_SYNC_REFRESH_FROM_CLIENT; break; case OP_SYNC_REFRESH_FROM_SERVER: mode = SYNCEVO_SYNC_REFRESH_FROM_SERVER; break; default: g_warn_if_reached(); } syncevo_session_sync (session, mode, source_modes, (SyncevoSessionGenericCb)sync_cb, data); syncevo_source_modes_free (source_modes); } static void set_config_for_sync_cb (SyncevoSession *session, GError *error, operation_data *op_data) { if (error) { g_warning ("Error in Session.SetConfig: %s", error->message); g_error_free (error); /* TODO show in UI: sync failed (failed to even start) */ return; } do_sync (op_data, session); } static void run_operation (operation_data *op_data, SyncevoSession *session) { SyncevoConfig *config; /* when we first get idle, start the operation */ if (op_data->started) { return; } op_data->started = TRUE; op_data->data->current_operation = op_data->operation; /* time for business */ switch (op_data->operation) { case OP_SYNC: case OP_SYNC_SLOW: case OP_SYNC_REFRESH_FROM_CLIENT: case OP_SYNC_REFRESH_FROM_SERVER: /* Make sure we don't get change diffs printed out, then sync */ config = g_hash_table_new (g_str_hash, g_str_equal); syncevo_config_set_value (config, NULL, "printChanges", "0"); syncevo_session_set_config (session, TRUE, TRUE, config, (SyncevoSessionGenericCb)set_config_for_sync_cb, op_data); syncevo_config_free (config); break; case OP_SAVE: save_config (op_data->data, session); break; case OP_RESTORE: restore_backup (op_data->data, session, op_data->dir); break; default: g_warn_if_reached (); } } /* Our sync session status */ static void status_changed_cb (SyncevoSession *session, SyncevoSessionStatus status, guint error_code, SyncevoSourceStatuses *source_statuses, operation_data *op_data) { switch (status) { case SYNCEVO_STATUS_IDLE: run_operation (op_data, session); break; case SYNCEVO_STATUS_DONE: op_data->data->synced_this_session = TRUE; /* no need for sync session anymore */ g_object_unref (session); /* refresh stats -- the service may no longer be the one syncing, * and we might have only saved config but what the heck... */ syncevo_server_get_reports (op_data->data->server, op_data->data->current_service->name, 0, 1, (SyncevoServerGetReportsCb)get_reports_cb, op_data->data); g_slice_free (operation_data, op_data); default: ; } } /* Our sync (or config-save) session status */ static void get_status_cb (SyncevoSession *session, SyncevoSessionStatus status, guint error_code, SyncevoSourceStatuses *source_statuses, GError *error, operation_data *op_data) { if (error) { g_warning ("Error in Session.GetStatus: %s", error->message); g_error_free (error); g_object_unref (session); switch (op_data->operation) { case OP_SYNC: /* TODO show in UI: sync failed (failed to even start) */ break; case OP_SAVE: /* TODO show in UI: save failed */ break; default: g_warn_if_reached (); } g_slice_free (operation_data, op_data); return; } if (status == SYNCEVO_STATUS_IDLE) { run_operation (op_data, session); } } static void start_session_cb (SyncevoServer *server, char *path, GError *error, operation_data *op_data) { SyncevoSession *session; app_data *data = op_data->data; if (error) { g_warning ("Error in Server.StartSession: %s", error->message); g_error_free (error); g_free (path); switch (op_data->operation) { case OP_SYNC: /* TODO show in UI: sync failed (failed to even start) */ break; case OP_SAVE: /* TODO show in UI: save failed */ break; default: g_warn_if_reached (); } g_slice_free (operation_data, op_data); return; } session = syncevo_session_new (path); if (data->running_session && strcmp (path, syncevo_session_get_path (data->running_session)) != 0) { /* This is a really unfortunate event: Someone got a session and we did not have time to set UI insensitive... */ gtk_label_set_markup (GTK_LABEL (data->server_label), _("Waiting for current operation to finish...")); gtk_widget_show_all (data->sources_box); } /* we want to know about status changes to our session */ g_signal_connect (session, "status-changed", G_CALLBACK (status_changed_cb), op_data); syncevo_session_get_status (session, (SyncevoSessionGetStatusCb)get_status_cb, op_data); g_free (path); } /* TODO: this function should accept source/peer name as param */ char* get_error_string_for_code (int error_code, SyncErrorResponse *response) { if (response) { *response = SYNC_ERROR_RESPONSE_NONE; } switch (error_code) { case -1: /* no errorcode */ case 0: case 200: case LOCERR_USERABORT: case LOCERR_USERSUSPEND: return NULL; case 22000: if (response) { *response = SYNC_ERROR_RESPONSE_EMERGENCY; } /* TRANSLATORS: next strings are error messages. */ return g_strdup (_("A normal sync is not possible at this time. The server " "suggests a slow sync, but this might not always be " "what you want if both ends already have data.")); case 22002: return g_strdup (_("The sync process died unexpectedly.")); case 22003: if (response) { *response = SYNC_ERROR_RESPONSE_SETTINGS_OPEN; } return g_strdup (_("Password request was not answered. You can save the " "password in the settings to prevent the request.")); case 506: /* TODO use the service device name here, this is a remote problem */ return g_strdup (_("There was a problem processing sync request. " "Trying again may help.")); case DB_Unauthorized: if (response) { *response = SYNC_ERROR_RESPONSE_SETTINGS_OPEN; } return g_strdup(_("Failed to login. Could there be a problem with " "your username or password?")); case DB_Forbidden: return g_strdup(_("Forbidden")); case DB_NotFound: if (response) { *response = SYNC_ERROR_RESPONSE_SETTINGS_OPEN; } /* TRANSLATORS: data source means e.g. calendar or addressbook */ return g_strdup(_("A data source could not be found. Could there be a " "problem with the settings?")); case DB_Fatal: case DB_Error: return g_strdup(_("Remote database error")); case LOCAL_STATUS_CODE + DB_Fatal: /* This can happen when EDS is borked, restart it may help... */ return g_strdup(_("There is a problem with the local database. " "Syncing again or rebooting may help.")); case DB_Full: return g_strdup(_("No space on disk")); case LOCERR_PROCESSMSG: return g_strdup(_("Failed to process SyncML")); case LOCERR_AUTHFAIL: return g_strdup(_("Server authorization failed")); case LOCERR_CFGPARSE: return g_strdup(_("Failed to parse configuration file")); case LOCERR_CFGREAD: return g_strdup(_("Failed to read configuration file")); case LOCERR_NOCFG: return g_strdup(_("No configuration found")); case LOCERR_NOCFGFILE: return g_strdup(_("No configuration file found")); case LOCERR_BADCONTENT: return g_strdup(_("Server sent bad content")); case LOCERR_CERT_EXPIRED: return g_strdup(_("Connection certificate has expired")); case LOCERR_CERT_INVALID: return g_strdup(_("Connection certificate is invalid")); case LOCERR_CONN: case LOCERR_NOCONN: case LOCERR_TRANSPFAIL: case LOCERR_TIMEOUT: if (response) { *response = SYNC_ERROR_RESPONSE_SETTINGS_OPEN; } return g_strdup(_("We were unable to connect to the server. The problem " "could be temporary or there could be something wrong " "with the settings.")); case LOCERR_BADURL: if (response) { *response = SYNC_ERROR_RESPONSE_SETTINGS_OPEN; } return g_strdup(_("The server URL is bad")); case LOCERR_SRVNOTFOUND: if (response) { *response = SYNC_ERROR_RESPONSE_SETTINGS_OPEN; } return g_strdup(_("The server was not found")); default: return g_strdup_printf (_("Error %d"), error_code); } } static void server_shutdown_cb (SyncevoServer *server, app_data *data) { if (data->syncing) { gtk_label_set_text (GTK_LABEL (data->sync_status_label), _("Sync failed")); set_sync_progress (data, 1.0 , ""); set_app_state (data, SYNC_UI_STATE_SERVER_OK); } /* re-init server here */ } static void set_running_session (app_data *data, const char *path) { if (data->running_session) { g_object_unref (data->running_session); } if (!path) { data->running_session = NULL; return; } data->running_session = syncevo_session_new (path); g_signal_connect (data->running_session, "progress-changed", G_CALLBACK (running_session_progress_changed_cb), data); g_signal_connect (data->running_session, "status-changed", G_CALLBACK (running_session_status_changed_cb), data); syncevo_session_get_status (data->running_session, (SyncevoSessionGetStatusCb)get_running_session_status_cb, data); } static void set_online_status (app_data *data, gboolean online) { if (online != data->online) { data->online = online; if (data->current_state == SYNC_UI_STATE_SERVER_OK) { if (data->online) { gtk_widget_hide (data->no_connection_box); } else { gtk_widget_show (data->no_connection_box); } } gtk_widget_set_sensitive (data->sync_btn, data->online); } } static void get_presence_cb (SyncevoServer *server, char *status, char **transports, GError *error, app_data *data) { if (error) { g_warning ("Server.GetSessions failed: %s", error->message); g_error_free (error); /* non-fatal, ignore in UI */ return; } if (data->current_service && status) { set_online_status (data, strcmp (status, "") == 0); } g_free (status); g_strfreev (transports); } static void password_dialog_response_cb (GtkWidget *dialog, int response, app_data *data) { const char *password; GHashTable *return_dict; return_dict = g_hash_table_new (g_str_hash, g_str_equal); if (response == GTK_RESPONSE_OK) { password = gtk_entry_get_text (GTK_ENTRY (data->password_dialog_entry)); g_hash_table_insert (return_dict, "password", (gpointer)password); } syncevo_server_info_response (data->server, data->password_dialog_id, "response", return_dict, NULL, NULL); g_hash_table_destroy (return_dict); g_free (data->password_dialog_id); data->password_dialog_id = NULL; gtk_widget_destroy (dialog); } static void info_request_cb (SyncevoServer *syncevo, char *id, char *session_path, char *state, char *handler_path, char *type, GHashTable *parameters, app_data *data) { GHashTable *t; GtkWidget *dialog, *content, *label, *align; char *msg; if (g_strcmp0 (state, "request") != 0 || g_strcmp0 (type, "password") != 0) { /* not handling other stuff */ return; } if (!data->running_session || g_strcmp0 (session_path, syncevo_session_get_path (data->running_session)) != 0) { /* not our problem */ return; } t = g_hash_table_new (g_str_hash, g_str_equal); syncevo_server_info_response (syncevo, id, "working", t, NULL, NULL); g_hash_table_destroy (t); data->password_dialog_id = g_strdup (id); /* TRANSLATORS: password request dialog contents: title, cancel button * and ok button */ dialog = gtk_dialog_new_with_buttons (_("Password is required for sync"), GTK_WINDOW (data->sync_win), GTK_DIALOG_DESTROY_WITH_PARENT, _("Cancel sync"), GTK_RESPONSE_CANCEL, _("Sync with password"), GTK_RESPONSE_OK, NULL); content = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); align = gtk_alignment_new (0.0, 0.5, 0.0, 0.0); gtk_alignment_set_padding (GTK_ALIGNMENT (align), 0, 0, 16, 16); gtk_widget_show (align); gtk_box_pack_start (GTK_BOX (content), align, FALSE, FALSE, 6); /* TRANSLATORS: password request dialog message, placeholder is service name */ msg = g_strdup_printf (_("Please enter password for syncing with %s:"), data->current_service->pretty_name); label = gtk_label_new (msg); gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END); gtk_widget_set_size_request (label, 500, -1); gtk_widget_show (label); gtk_container_add (GTK_CONTAINER (align), label); g_free (msg); align = gtk_alignment_new (0.0, 0.5, 0.0, 0.0); gtk_alignment_set_padding (GTK_ALIGNMENT (align), 0, 0, 16, 16); gtk_widget_show (align); gtk_box_pack_start (GTK_BOX (content), align, FALSE, FALSE, 6); data->password_dialog_entry = gtk_entry_new (); gtk_entry_set_max_length (GTK_ENTRY (data->password_dialog_entry), 99); gtk_entry_set_width_chars (GTK_ENTRY (data->password_dialog_entry), 30); gtk_entry_set_visibility (GTK_ENTRY (data->password_dialog_entry), FALSE); gtk_widget_show (data->password_dialog_entry); gtk_container_add (GTK_CONTAINER (align), data->password_dialog_entry); g_signal_connect (dialog, "response", G_CALLBACK (password_dialog_response_cb), data); gtk_window_present (GTK_WINDOW (dialog)); gtk_widget_grab_focus (data->password_dialog_entry); } static void server_presence_changed_cb (SyncevoServer *server, char *config_name, char *status, char *transport, app_data *data) { if (data->current_service && config_name && status && g_ascii_strcasecmp (data->current_service->name, config_name) == 0) { set_online_status (data, strcmp (status, "") == 0); } } static void server_templates_changed_cb (SyncevoServer *server, app_data *data) { if (gtk_widget_get_visible (data->services_box)) { update_services_list (data); } } static void server_session_changed_cb (SyncevoServer *server, char *path, gboolean started, app_data *data) { if (started) { set_running_session (data, path); } else if (data->running_session && strcmp (syncevo_session_get_path (data->running_session), path) == 0 ) { set_running_session (data, NULL); } } static void get_sessions_cb (SyncevoServer *server, SyncevoSessions *sessions, GError *error, app_data *data) { const char *path; if (error) { g_warning ("Server.GetSessions failed: %s", error->message); g_error_free (error); /* TODO show in UI: failed first syncevo call (unexpected, fatal?) */ return; } /* assume first one is active */ path = syncevo_sessions_index (sessions, 0); set_running_session (data, path); syncevo_sessions_free (sessions); } static void get_config_for_default_peer_cb (SyncevoServer *syncevo, SyncevoConfig *config, GError *error, app_data *data) { char *name; if (error) { g_warning ("Server.GetConfig failed: %s", error->message); g_error_free (error); /* TODO show in UI: failed first syncevo call (unexpected, fatal?) */ return; } syncevo_config_get_value (config, NULL, "defaultPeer", &name); reload_config (data, name); syncevo_config_free (config); } app_data* sync_ui_create () { app_data *data; data = g_slice_new0 (app_data); data->online = TRUE; data->current_state = SYNC_UI_STATE_GETTING_SERVER; data->forced_emergency = FALSE; data->emergency_sources = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); if (!init_ui (data)) { return NULL; } data->server = syncevo_server_get_default(); g_signal_connect (data->server, "shutdown", G_CALLBACK (server_shutdown_cb), data); g_signal_connect (data->server, "session-changed", G_CALLBACK (server_session_changed_cb), data); g_signal_connect (data->server, "presence-changed", G_CALLBACK (server_presence_changed_cb), data); g_signal_connect (data->server, "templates-changed", G_CALLBACK (server_templates_changed_cb), data); g_signal_connect (data->server, "info-request", G_CALLBACK (info_request_cb), data); syncevo_server_get_config (data->server, "", FALSE, (SyncevoServerGetConfigCb)get_config_for_default_peer_cb, data); syncevo_server_get_sessions (data->server, (SyncevoServerGetSessionsCb)get_sessions_cb, data); gtk_window_present (GTK_WINDOW (data->sync_win)); return data; } void sync_ui_show_settings (app_data *data, const char *id) { show_services_list (data, id); } GtkWindow* sync_ui_get_main_window (app_data *data) { return GTK_WINDOW(data->sync_win); } syncevolution_1.4/src/gtk3-ui/sync-ui.css000066400000000000000000000015211230021373600205300ustar00rootroot00000000000000# generic rules for MuxWidgets MuxFrame { background-color : #ffffff; -MuxFrame-border-color : #dee2e5; -MuxFrame-bullet-color : #aaaaaa; -MuxFrame-title-font : Bold 12; } MuxFrame:insensitive { background-color : #ffffff; } # sync-ui specific rules GtkWindow#meego_win { background-color : #4a535a; } GtkLabel#info_label { font : 12; } GtkEventBox#sync_data_and_type_box, GtkEventBox#sync_data_and_type_box:insensitive { background-color : #ececec; } GtkFrame#log_frame:insensitive, GtkFrame#backup_frame:insensitive, GtkFrame#services_frame:insensitive { background-color : #d2d6d9; } GtkLabel#sync_service_label, GtkLabel#sync_status_label, GtkLabel#no_server_label, GtkLabel#sync_failure_label { font : Bold; } GtkButton#sync_btn { -GtkButton-inner-border : 10 10 10 10; font : Bold; } syncevolution_1.4/src/gtk3-ui/sync-ui.h000066400000000000000000000035041230021373600201720ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef SYNC_UI_H #define SYNC_UI_H #include #include "config.h" #include "sync-ui-config.h" #include "sync-ui.h" #define SYNC_UI_LIST_ICON_SIZE 32 #define SYNC_UI_LIST_BTN_WIDTH 150 typedef struct _app_data app_data; typedef enum SyncErrorResponse { SYNC_ERROR_RESPONSE_NONE, SYNC_ERROR_RESPONSE_SYNC, SYNC_ERROR_RESPONSE_SETTINGS_SELECT, SYNC_ERROR_RESPONSE_SETTINGS_OPEN, SYNC_ERROR_RESPONSE_EMERGENCY, SYNC_ERROR_RESPONSE_EMERGENCY_SLOW_SYNC, } SyncErrorResponse; char* get_pretty_source_name (const char *source_name); char* get_error_string_for_code (int error_code, SyncErrorResponse *response); void show_error_dialog (GtkWidget *widget, const char* message); gboolean show_confirmation (GtkWidget *widget, const char *message, const char *yes, const char *no); app_data *sync_ui_create (); GtkWindow *sync_ui_get_main_window (app_data *data); void sync_ui_show_settings (app_data *data, const char *id); void toggle_set_active (GtkWidget *toggle, gboolean active); gboolean toggle_get_active (GtkWidget *toggle); #endif syncevolution_1.4/src/gtk3-ui/sync.desktop.in000066400000000000000000000002351230021373600214040ustar00rootroot00000000000000[Desktop Entry] _Name=Sync _Comment=Up to date Version=1.0 Type=Application Exec=sync-ui Icon=sync Categories=Network;GTK; Terminal=false StartupNotify=true syncevolution_1.4/src/gtk3-ui/sync.png000066400000000000000000000060271230021373600201170ustar00rootroot00000000000000PNG  IHDR00WtEXtSoftwareAdobe ImageReadyqe< IDATxY[l粻]z&M.pI 4*Q URՊB U[P>VRJTBT+B@b'k:~ٙxmCpϙs|?ѣGd7uSSS: VʭY$iǎt/Z5Mm8D/I>33s=,Ͻ{s{nx}OuuwڸiӍLFϟP(<dݱM`E398mM*cEӠ#*ݓPh;ںg)>.-~?4oC#e$(ԣ GXf6 _vko'Vc(ǿU$}\ձۡrѢr&== B!aؾRM4l!iTxrJe_=nth<",}>M}UFYd(gьI3W;^4eɚQT5Wd ^.-y%z*npmzV$6UzbLgf}ŒA[" Q=zUZ?3,1^jO0F}@#% q(dt[2@pKb_Uh"ydgi=\g!AaU9 }(Bšp*/0H 4*M-s`WL[]3voQ` <7[dhoŶ{2yծ s;0Z^ot 3eAK!n<7:+e4k HW̎s|.@E(2yJqq'Iݡ&YCGK>VF7EU!N/5F[;08ăZ)آm[P 8p(bZ*ZtF*s:ٖ@KtP~1"I_Op(n%N wE\wB7&8]a_8;~ӃthX3"Hf n vn ɦנƗ+rцf5דeZh{w~wSJ|%byI䂊ppa"(!7ڂr؂Y}~.uNzUZBxOd6'}}y=!zrkDlA׶ ,*W' o}[F@VSt٬YqY_*x@8QGG%=loh>3\a4KϏd0G1DKEz#UOΗ TaA&ZMCiʆe0uΦJԹ6꧈_}V1TZZ$""sdz`RSZ\zgXyH/`499YaG:Z"Z-7P/6Ssi Ls/<~> 3Nڷ"SemJrWU h ^JW–2`"0p˰?*]2Kh z?Jϝdӽ%X݋>Rpd(7۸@B(Y,"Ct[-|\;sۗ .]J48 ȕnaq㰩yoL穜7Q>W1oznYzim>],x]u*` ~0!MUۦێM,NEcS%|*---=bQ0I*n1 ?pY\]|_J E/Na"I'qJnRsRtZvł-Y./wOLon ^q O{Up8B'OC7\91kZ1^XB$~aArB屬vpd` !;}+J <9L Mv\f;;Q߷~6ïH1R:3,Kd)VNض9NϨ80lWWϧff}\%D&˲DhS%yLč̜Sm6M3mh[$nTO=\B7ZM P88>>>077BFV xlP88,l hFY{p GXC878Ds{kG GMgz6;:˝d2cr9utؔsYHR&!A߭\:J$j~mԲ]G Z%PQJXNv%3FL83ԩ)Aynx얫 6Y3pOҜ9OV2bU]–qNam,<&o!o٨Y[sUH,2&K\op0+O2Ox0$+z ̜ ﵁-"Uoob1g׮]H, B T4!/ɤ ̱$Ll6j*%T,6yo6kBl f" ͌0[lq 0-:1 IENDB`syncevolution_1.4/src/gtk3-ui/ui.xml000066400000000000000000003020371230021373600175740ustar00rootroot00000000000000 1024 False 5 Sync Emergency True 800 550 True sync_win True False 4 True False 0 none True True never True False queue True False True False 8 True False 8 800 True False 0 If something has gone horribly wrong you can try a slow sync, start from scratch or restore from backup. True False True 0 True True True False 0 20 True False 2 Calendar False True True False False True Tasks False True True False False True 1 2 True False Affected data: False True 1 False True 8 0 True False 5 True False 0 <big>Slow sync</big> True False True 0 True False 0 0 0 40 40 True False 6 False True True True False True False 16 Slow sync True True 0 True False 8 A slow sync compares items from both sides and tries to merge them. This may fail in some cases, leading to duplicates or lost information. True True 1 False True 1 False True 8 1 True False 5 True False 0 <big>Start from scratch</big> True False True 0 True False 0 0 0 40 40 True False False True True True False True False 16 Delete all your local information and replace with data from Zyb False True 0 True False 15 <b>or</b> True False False 1 False True True True False True False 16 Delete all data on Zyb and replace with your local information False True 2 False True 1 False True 8 2 True False 5 True False 0 <big>Restore from backup</big> True False True 0 True False queue 40 40 True False 800 True False 0 Backups are made before every time we Sync. Choose a backup to restore. Any changes you have made since then will be lost. True False False 0 True True True False queue True False 10 2 16 True False 0 5 Backup before syncing Dec 5th 2009 16:35 2 3 GTK_FILL GTK_FILL True False 0 5 Backup before syncing Dec 5th 2009 13:35 3 4 GTK_FILL GTK_FILL True False 0 5 Backup before syncing Dec 5th 2009 11:35 4 5 GTK_FILL GTK_FILL True False 0 5 Backup before syncing Dec 3rd 5 6 GTK_FILL GTK_FILL True False 0 5 Backup before syncing Dec 1st 6 7 GTK_FILL GTK_FILL True False 0 5 Backup before syncing yesterday 1 2 GTK_FILL GTK_FILL True False 0 5 Backup before syncing two hours ago GTK_FILL Restore False True True True False 1 2 GTK_FILL GTK_FILL 16 True True 1 True True 1 True True 8 3 False True 40 0 True False True True True 0 True False Close False True True True False False True 12 end 0 False True 1 1024 False 5 Settings True 800 550 True sync_win True False 4 True False 0 none True True never True False queue True False True False 12 36 12 True False 12 True False 5 True False 0 <big>Network sync</big> True False True 0 True False True False To sync you'll need a network connection and an account with a sync service. We support the following services: False True 0 False True 1 300 True True in True False queue True False True True 2 True False True False 0 If you don't see your service above but know that your sync provider uses SyncML you can setup a service manually. True True 0 Add new service False 150 True True True False False True 8 1 False False 3 True True 8 0 400 True False 5 True False 0 <big>Direct sync</big> True False True 0 True False True False Use Bluetooth to Sync your data from one device to another. False True 0 False True 1 300 True True in True False queue True False True True 2 True False True False 0 You will need to add Bluetooth devices before they can be synced. True True 0 Add new device False 150 True True False False True 8 1 False False 3 True True 1 True True 0 True False True True True 6 0 True False Close False True True True False False True 12 end 0 False True 1 1024 False Sync 800 550 True False True False 4 4 True False True False 0 none True False True False 48 48 True False True False gtk-missing-image 6 True True 0 False True 10 0 True False 0 0.20000000298023224 True end 42 False True 5 1 True False 6 4 False False 8 end 2 False True 16 0 True False True False True False True False True False True False False True 0 False True False 0 none True False 5 12 True False False False 0 False True 1 True True 55 0 False False 6 0 False True 0 False True 1 True False True True True 0 True True 0 250 True False 5 True False True False 0 in True False True False True False True False 0 False False 6 0 24 True False True False gtk-missing-image False True 0 False True 1 False True 0 False True 10 0 True False True False 10 True False 0 <b>Actions</b> True False True 0 Sync now False True True True False False False 1 False True True True False True False Change or edit sync service center False False 2 Fix a sync emergency False True True True False False False 3 True True 30 0 False False 20 1 True False True False False False True 0 False True 5 0 False False 5 2 25 True False False end True True 5 end 0 False False 3 True False True True True 0 True True 0 False False end 1 True True 0 syncevolution_1.4/src/shlibs.local000066400000000000000000000002561230021373600174500ustar00rootroot00000000000000# Evolution 2.8 libedataserver-1.2 5 libedataserver1.2-7 libecal-1.2 7 libecal1.2-7 libebook-1.2 9 libebook1.2-9 # Evolution 2.10/12 libedataserver-1.2 9 libedataserver1.2-9 syncevolution_1.4/src/src.am000066400000000000000000000454351230021373600162660ustar00rootroot00000000000000src_cppflags = -I$(top_srcdir)/src if ENABLE_GNOME_BLUETOOTH_PANEL include $(top_srcdir)/src/gnome-bluetooth/gnome-bluetooth.am src_cppflags += -I$(top_srcdir)/src/gnome-bluetooth endif if COND_GIO_GDBUS include $(top_srcdir)/src/gdbusxx/gdbusxx.am src_cppflags += -I$(top_srcdir)/src/gdbusxx else include $(top_srcdir)/src/gdbus/gdbus.am src_cppflags += -I$(top_srcdir)/src/gdbus endif if COND_CORE include $(top_srcdir)/src/syncevo/syncevo.am src_cppflags += -I$(top_srcdir)/src/syncevo include $(top_srcdir)/src/backends/backends.am src_cppflags += $(addprefix -I$(top_srcdir)/,$(BACKENDS)) -I$(SYNTHESIS_SUBDIR) bin_PROGRAMS += src/syncevolution bin_SCRIPTS += src/synccompare include $(top_srcdir)/src/templates/templates.am else src_cppflags += -I$(top_srcdir)/$(SYNTHESIS_SUBDIR_INCLUDES) endif include $(top_srcdir)/src/dbus/dbus.am src_cppflags += -I$(top_srcdir)/src/dbus include $(top_srcdir)/src/gtk-ui/gtk-ui.am include $(top_srcdir)/src/gtk3-ui/gtk-ui.am src_cppflags += -I$(top_srcdir)/test -I$(top_srcdir) $(BACKEND_CPPFLAGS) DISTCLEANFILES += src/synccompare CLEANFILES += src/libstdc++.a src/client-test $(CLIENT_LIB_TEST_FILES) if COND_DBUS nodist_bin_SCRIPTS += src/syncevo-http-server endif src/syncevo-http-server: $(top_srcdir)/test/syncevo-http-server.py $(AM_V_GEN)cp $< $@ CLEANFILES += src/syncevo-http-server nodist_bin_SCRIPTS += src/syncevo-phone-config src/syncevo-phone-config: $(top_srcdir)/test/syncevo-phone-config.py $(AM_V_GEN)cp $< $@ CLEANFILES += src/syncevo-phone-config SYNCEVOLUTION_DEP = if !ENABLE_MODULES # SYNCEVOLUTION_LDADD is defined in configure script. SYNCEVOLUTION_LDADD += @SYNCSOURCES@ SYNCEVOLUTION_DEP += @SYNCSOURCES@ endif dist_noinst_DATA += \ src/shlibs.local \ src/synthesis-includes/Makefile.am \ src/synthesis-includes/Makefile.in DISTCLEANFILES += src/synthesis-includes/Makefile # synccompare is created by replacing its 'import Algorithm::Diff;' # with a simplified copy of Diff.pm. src/synccompare : $(top_srcdir)/test/Algorithm/Diff.pm $(top_srcdir)/test/synccompare.pl $(AM_V_GEN)perl -e '$$diff = shift; open(DIFF, "<$$diff"); ($$_) = split(/__END__/, join("", )); s/\*import.*//m; s/require +Exporter;//; s/^#.*\n//mg; s/ +#.*\n//mg; $$diff = $$_;' -e 'while(<>) {' @MODIFY_SYNCCOMPARE@ -e 's/use +Algorithm::Diff;/"# embedded version of Algorithm::Diff follows, copyright by the original authors\n" . $$diff . "# end of embedded Algorithm::Diff\n"/e; print;}' $+ >$@ \ &&chmod u+x $@ # helper script for testing bin_SCRIPTS += src/synclog2html CLEANFILES += src/synclog2html src/synclog2html: $(top_srcdir)/test/log2html.py $(AM_V_GEN)cp $< $@ && chmod u+x $@ CORE_SOURCES = # The files which register backends have to be compiled into # "client-test" and "syncevolution" in order to pull in the # code from the libs which implement the backends. # # Unit testing also goes there. # # When using modules the registration is done inside the # module and the register file is unnecessary. However, they # still need to be included in "make dist". if ENABLE_MODULES dist_noinst_DATA += $(BACKEND_REGISTRIES) else CORE_SOURCES += $(BACKEND_REGISTRIES) endif CORE_CXXFLAGS = $(SYNTHESIS_CFLAGS) $(CPPUNIT_CXXFLAGS) CORE_LDADD = $(SYNCEVOLUTION_LDADD) src/syncevo/libsyncevolution.la $(GLIB_LIBS) $(GTHREAD_LIBS) $(GOBJECT_LIBS) $(LIBS) CORE_DEP = $(SYNCEVOLUTION_DEP) src/syncevo/libsyncevolution.la $(SYNTHESIS_DEP) CORE_LD_FLAGS = -Wl,-uSyncEvolution_Module_Version -Wl,--export-dynamic $(CPPUNIT_LDFLAGS) # put link to static c++ library into current directory, needed if compiling with --enable-static-c++ src/libstdc++.a : $(AM_V_GEN)path=`$(CXX) $(CORE_LDADD) $(LD_FLAGS) -print-file-name=src/libstdc++.a` && ln -s $$path . src_syncevolution_SOURCES = \ src/syncevolution.cpp \ $(CORE_SOURCES) if ENABLE_UNIT_TESTS nodist_src_syncevolution_SOURCES = test/test.cpp endif # SYNCEVOLUTION_LDADD will be replaced with libsyncebook.la/libsyncecal.la/libsyncsqlite.la # if linking statically against them, empty otherwise; # either way this does not lead to a dependency on those libs - done explicitly src_syncevolution_LDADD = $(CORE_LDADD) src_syncevolution_DEPENDENCIES = $(EXTRA_LTLIBRARIES) $(CORE_DEP) if COND_DBUS src_syncevolution_LDADD += $(gdbus_build_dir)/libgdbussyncevo.la src_syncevolution_DEPENDENCIES += $(gdbus_build_dir)/libgdbussyncevo.la endif src_syncevolution_LDFLAGS = $(PCRECPP_LIBS) $(CORE_LD_FLAGS) $(DBUS_LIBS) src_syncevolution_CXXFLAGS = $(PCRECPP_CFLAGS) $(SYNCEVOLUTION_CXXFLAGS) $(CORE_CXXFLAGS) $(DBUS_CFLAGS) $(SYNCEVO_WFLAGS) src_syncevolution_CPPFLAGS = $(src_cppflags) -I$(gdbus_dir) # include Synthesis in distribution: package only files in git if using a git checkout # # Need to run autogen.sh in $(distdir)-synthesis and not the final # $(distdir)/src/synthesis because recent autotools do not copy # files like config.sub when invoked in $(distdir)/src/synthesis # (automake 1.11.5, autoconf 2.69), probably because they are # found in a parent directory. However, these files are needed # later on during the recursive libsynthesis configure+make. all_dist_hooks += src_dist_hook src_dist_hook: @set -x; [ ! '$(SYNTHESIS_SUBDIR)' ] || \ rm -rf $(distdir)-synthesis && \ mkdir -p $(distdir)-synthesis && \ if test -d '$(SYNTHESIS_SRC)/.git'; \ then \ ( ( cd '$(SYNTHESIS_SRC)' && git archive HEAD ) | ( cd '$(distdir)-synthesis' && tar xf - && $$SHELL autogen.sh && rm -rf autom4te.cache && find . -name .gitignore -delete ) ) && \ ( printf 'Creating synthesis ChangeLog... ' && \ ( ( cd '$(SYNTHESIS_SRC)' && \ echo '# Generated by configure. Do not edit.' && \ githash=`git show-ref --head --hash | head -1` && \ echo "# git revision $$githash" && \ echo "# git tag `git describe --tags $$githash`" && \ echo && \ '$(top_srcdir)/missing' --run perl '$(top_srcdir)/build/gen-changelog.pl' ) >ChangeLog.tmp ) && \ ( mv -f ChangeLog.tmp '$(distdir)-synthesis/ChangeLog' && \ printf 'synthesis ChangeLog done\n' ) || \ ( rm -f ChangeLog.tmp ; \ printf 'synthesis ChangeLog failed\n'; \ echo 'Failed to generate synthesis ChangeLog.' >&2 ) \ ); \ elif test '$(SYNTHESIS_SRC)' != 'no-synthesis-source'; \ then \ cp -a '$(SYNTHESIS_SRC)/'* '$(distdir)-synthesis' && \ for i in _build autom4te.cache; do [ ! -d "$(SYNTHESIS_SRC)/$$i" ] || chmod -R u+rwx "$(SYNTHESIS_SRC)/$$i"; done && \ find '$(distdir)-synthesis' -name '.libs' -o -name '*~' -o -name '.*' -o -name '*.o' -o -name '*.lo' -o -name 'CVS' -o -name '.svn' -o -name '.git' -o -name .gitignore -o -name 'autom4te.cache' -print0 | xargs -0 rm -rf; \ fi && \ mv '$(distdir)-synthesis' '$(distdir)/src/synthesis' clean-local: testclean rm -rf src/testcases [ ! -L src/templates ] || rm src/templates # files created during testing testclean: rm -rf src/*.test.vcf src/*.log src/*.log.html src/*.tests src/*.diff src/*.dat src/*Client_Sync_*client.* src/*Client_Source* distclean-local: rm -rf $(SYNTHESIS_SUBDIR) rm -rf $(CLEAN_CLIENT_SRC) # Local sync helper executable. Built unconditionally at the moment, # thus creating a hard dependency on D-Bus. libexec_PROGRAMS += src/syncevo-local-sync src_syncevo_local_sync_SOURCES = \ src/syncevo-local-sync.cpp \ $(CORE_SOURCES) if ENABLE_UNIT_TESTS nodist_src_syncevo_local_sync_SOURCES = test/test.cpp endif src_syncevo_local_sync_LDADD = $(gdbus_build_dir)/libgdbussyncevo.la $(CORE_LDADD) $(DBUS_LIBS) src_syncevo_local_sync_CPPFLAGS = -DHAVE_CONFIG_H -I$(gdbus_dir) $(src_cppflags) src_syncevo_local_sync_CXXFLAGS = $(PCRECPP_CFLAGS) $(SYNCEVOLUTION_CXXFLAGS) $(CORE_CXXFLAGS) $(GLIB_CFLAGS) $(DBUS_CFLAGS) $(LIBSOUP_CFLAGS) $(SYNCEVO_WFLAGS) src_syncevo_local_sync_LDFLAGS = $(PCRECPP_LIBS) $(CORE_LD_FLAGS) $(LIBSOUP_LIBS) src_syncevo_local_sync_DEPENDENCIES = $(top_builddir)/$(gdbus_build_dir)/libgdbussyncevo.la $(EXTRA_LTLIBRARIES) $(CORE_DEP) $(SYNTHESIS_DEP) # Do the linking here, as with all SyncEvolution executables. # Sources are compiled in dbus/server. if COND_DBUS # DBus Server libexec_PROGRAMS += src/syncevo-dbus-server src_syncevo_dbus_server_SOURCES = \ $(CORE_SOURCES) if ENABLE_UNIT_TESTS nodist_src_syncevo_dbus_server_SOURCES = test/test.cpp endif src_syncevo_dbus_server_LDADD = $(builddir)/src/dbus/server/libsyncevodbusserver.la $(gdbus_build_dir)/libgdbussyncevo.la $(CORE_LDADD) $(LIBNOTIFY_LIBS) $(MLITE_LIBS) $(DBUS_LIBS) src_syncevo_dbus_server_CPPFLAGS = -DHAVE_CONFIG_H -I$(gdbus_dir) $(src_cppflags) -DSYNCEVOLUTION_LOCALEDIR=\"${SYNCEVOLUTION_LOCALEDIR}\" src_syncevo_dbus_server_CXXFLAGS = $(PCRECPP_CFLAGS) $(SYNCEVOLUTION_CXXFLAGS) $(CORE_CXXFLAGS) $(GLIB_CFLAGS) $(DBUS_CFLAGS) $(LIBNOTIFY_CFLAGS) $(MLITE_CFLAGS) $(SYNCEVO_WFLAGS) src_syncevo_dbus_server_LDFLAGS = $(PCRECPP_LIBS) $(CORE_LD_FLAGS) src_syncevo_dbus_server_DEPENDENCIES = $(builddir)/src/dbus/server/libsyncevodbusserver.la $(gdbus_build_dir)/libgdbussyncevo.la $(EXTRA_LTLIBRARIES) $(CORE_DEP) $(SYNTHESIS_DEP) # syncevo-dbus-server's helper binary libexec_PROGRAMS += src/syncevo-dbus-helper if ENABLE_UNIT_TESTS nodist_src_syncevo_dbus_helper_SOURCES = test/test.cpp endif src_syncevo_dbus_helper_SOURCES = \ $(CORE_SOURCES) src_syncevo_dbus_helper_LDADD = $(builddir)/src/dbus/server/libsyncevodbushelper.la $(gdbus_build_dir)/libgdbussyncevo.la $(CORE_LDADD) $(DBUS_LIBS) src_syncevo_dbus_helper_CPPFLAGS = -DHAVE_CONFIG_H -I$(gdbus_dir) $(src_cppflags) -DSYNCEVOLUTION_LOCALEDIR=\"${SYNCEVOLUTION_LOCALEDIR}\" src_syncevo_dbus_helper_CXXFLAGS = $(PCRECPP_CFLAGS) $(SYNCEVOLUTION_CXXFLAGS) $(CORE_CXXFLAGS) $(GLIB_CFLAGS) $(DBUS_CFLAGS) $(SYNCEVO_WFLAGS) src_syncevo_dbus_helper_LDFLAGS = $(PCRECPP_LIBS) $(CORE_LD_FLAGS) src_syncevo_dbus_helper_DEPENDENCIES = $(builddir)/src/dbus/server/libsyncevodbushelper.la $(gdbus_build_dir)/libgdbussyncevo.la $(EXTRA_LTLIBRARIES) $(CORE_DEP) $(SYNTHESIS_DEP) endif # COND_DBUS # With --disable-shared autotools links against libfunambol.a which does not # pull any of the test suites into the test binary, so they would not be # executed. The workaround is to explicitly set them as undefined on the # link line. src_client_test_SOURCES = \ src/client-test-app.cpp \ test/ClientTest.cpp \ test/ClientTest.h \ test/ClientTestAssert.h \ test/client-test-main.cpp \ $(CORE_SOURCES) nodist_src_client_test_SOURCES = test/test.cpp # Compiling ClientTest.cpp with autotool's default CXXFLAGS (usually # -g -O2) is expensive (due to many templates and the large file size) # and unnecessary - it is much better for debugging when optimization # is off. # # Therefore use GNU make's "Target-specific Variable Values" to # override CXXFLAGS for that special file. We assume that the compiler # knows the -g flag. # # We have to get the target name right. The worst that happens if we # don't is that it gets compiled with the normal CXXFLAGS. Because # we don't know if GNU make is used, use a configure check and only # enable this when found during configure. # # Note that src_client_test_CXXFLAGS cannot be used to remove # -O2, because CXXFLAGS comes later in the final compile command. @ifGNUmake@ $(foreach e, $(foreach i, $(src_client_test_SOURCES), $(dir $(i))src_client_test-$(basename $(notdir $i))), $(e).o $(e).lo $(e).obj) : CXXFLAGS = -g # list of test file base files # # Generated files (testcases/eds_event.ics.funambol.tem) are derived from # the original base file ($(srcdir)/test/testcases/eds_event.ics) by # applying a patch ($(srcdir)/test/testcases/eds_event.ics.funambol.tem.patch). CLIENT_LIB_TEST_FILES = \ src/testcases/lcs/file1.txt \ src/testcases/lcs/file2.txt \ src/testcases/local.png \ src/testcases/templates/clients/SyncEvolution.ini \ src/testcases/templates/clients/phone/nokia/S40/7210c.ini \ src/testcases/google_event.ics \ src/testcases/yahoo_contact.vcf \ src/testcases/eds_contact.vcf \ src/testcases/eds_event.ics \ src/testcases/eds_event.ics.local \ src/testcases/eds_memo.ics \ src/testcases/eds_task.ics # all patch files TEST_FILES_PATCHES = $(wildcard $(top_srcdir)/test/testcases/*.patch) # generated local files # converts from # $(top_srcdir)/test/testcases/eds_contact.vcf.apple.tem.patch # to # src/testcases/eds_contact.vcf.apple.tem TEST_FILES_GENERATED = $(subst .patch,,$(subst $(top_srcdir)/test/,src/,$(TEST_FILES_PATCHES))) # all patched files, regardless whether the patch already exists TEST_FILES_PATCHED = $(wildcard src/testcases/*.tem) # add files created via patches CLIENT_LIB_TEST_FILES += $(TEST_FILES_GENERATED) # client-test must link against all static utility libs which might contain # object files with SYNCEVOLUTION_TEST_SUITE_REGISTRATION() macros. # To pull in those object files, LDFLAGS must contain undef statements # for the C symbols exported by the macro. src_client_test_libs = src/syncevo/libsyncevolution.la if COND_DBUS src_client_test_libs += src/dbus/server/libsyncevodbushelper.la src/dbus/server/libsyncevodbusserver.la endif # src/syncevo/libsyncevolution.la -> src/syncevo/.libs/libsyncevolution.a -> -Wl,-u... src_client_test_undef = $(shell nm $(patsubst %.la,%.a,$(subst /lib,/.libs/lib,$(src_client_test_libs))) | grep funambolAutoRegisterRegistry | sed -e 's/.* /-Wl,-u/' ) src_client_test_CPPFLAGS = -DHAVE_CONFIG_H -DENABLE_INTEGRATION_TESTS -DENABLE_UNIT_TESTS $(src_cppflags) $(QT_CPPFLAGS) src_client_test_CXXFLAGS = $(filter-out -O2, @CPPUNIT_CXXFLAGS@ $(PCRECPP_CFLAGS) $(SYNCEVOLUTION_CXXFLAGS) $(CORE_CXXFLAGS) $(filter-out -O2 -g -W -Wall, $(QT_CXXFLAGS)) $(SYNCEVO_WFLAGS)) src_client_test_LDFLAGS = @CPPUNIT_LDFLAGS@ $(src_client_test_undef) $(CORE_LD_FLAGS) $(QT_LDFLAGS) src_client_test_LDADD = $(src_client_test_libs) $(CORE_LDADD) $(PCRECPP_LIBS) $(SYNTHESIS_ENGINE) $(QT_LIBS) # These dependencies are intentionally a bit too broad: # they ensure that all files are in place to *run* client-test. # rule to generate patched files from patches: # make cannot compute the dependencies completely, so run the commands # on each make invocation and do the time stamp test ourselves # # If we create the patched file anew, then set its time to the more # recent of the two input files. That way it won't be re-generated # (because it is not older), and it won't be used to refresh the patch # either in testcase2patch (because it is not newer either). # That is useful on platforms where diff produces different results # than the one in the source (possible because the "find shortest # patch" problem may have multiple solutions). all_phonies += $(TEST_FILES_GENERATED) $(TEST_FILES_GENERATED): @ set -e \ && mkdir -p 'src/testcases' \ && echo 'checking whether server specific test case $@ is up-to-date'; \ patchfile='$(top_srcdir)/test/$(subst src/,,$@).patch'; \ basefile='$(top_srcdir)/test/$(subst src/,,$(basename $(basename $@)))'; \ ( [ -e '$@' ] && [ ! '$@' -ot "$$patchfile" ] && [ ! $@ -ot "$$basefile" ] && echo ' $@ up-to-date' ) || \ ( [ ! -s "$$patchfile" ] && echo " copy $$basefile to $@ because patch file is empty" && cp "$$basefile" '$@' ) || \ ( echo " generating $@ by applying $$patchfile to $$basefile" && \ (echo '*** foo'; echo '--- bar'; cat "$$patchfile") | patch -s -o '$@' "$$basefile" && \ ( if [ "$$basefile" -ot "$$patchfile" ]; then \ touch -r "$$patchfile" '$@'; else \ touch -r "$$basefile" '$@'; fi ) \ ) # rule to regenerate patches: # like generating the patched files, this is run every time. # It must avoid making the patch file more recent than the # patched file, otherwise the rule above would needlessly recreate # it (not nice when having the file open in an editor). # # To avoid needlessly updating the content of the patch file, # the first two lines with changing information (paths, file dates) # are stripped from it. all_phonies += testcase2patch testcase2patch: $(TEST_FILES_GENERATED) @ set -e \ && echo 'checking whether test case patch files are up-to-date'; \ for i in src/testcases/*.tem; do \ temfile=`echo "$$i" | cut -d / -f 2-` \ patchfile="$(top_srcdir)/test/$$temfile.patch"; \ basefile="$(top_srcdir)/test/`echo $$temfile | cut -d . -f -2`"; \ if [ "$$patchfile" -ot "$$i" ] || [ "$$patchfile" -ot "$$basefile" ]; \ then \ diff -u "$$basefile" "$$i" | tail -n +3 > "$$patchfile" || true; \ touch -r "$$i" "$$patchfile"; \ echo " updated $$patchfile"; \ else \ echo " $$patchfile up-to-date"; \ fi; \ done # generate syntax-highlighted version of ClientTest.cpp for HTML # version of .log test results nodist_noinst_DATA += src/ClientTest.cpp.html CLEANFILES += src/ClientTest.cpp.html src/ClientTest.cpp.html: build/source2html.py test/ClientTest.cpp $(AM_V_GEN)python $+ >$@ # copy base test files $(filter-out %.tem, $(filter src/testcases/%, $(subst $(top_srcdir)/test/,src/,$(CLIENT_LIB_TEST_FILES)))) : src/% : $(top_srcdir)/test/% $(AM_V_at)mkdir -p '$(dir $@)'; \ cp '$<' '$@' # The binary does not really depend on the test cases, only running it does. # Listing the dependencies here is done to ensure that one doesn't accidentally # runs the binary with out-dated auxiliary files. src_client_test_DEPENDENCIES = $(EXTRA_LTLIBRARIES) $(src_client_test_libs) $(CORE_DEP) $(CLIENT_LIB_TEST_FILES) testcase2patch src/synccompare src/synclog2html src/templates # Copy template directory into current working directory, if not there # yet. -ef flag checks whether device and inode numbers of both files # are equal. If such check passes then it does mean that it is either # the same file or one of them is symlink to another. So if this is a # symlink, then it is fine - that is what we want. If this is the same # file then also it is fine - that means that we built the project in # the same directory as source. all_phonies += src/templates src/templates: $(AM_V_at)if test ! '$(top_srcdir)/src/templates' -ef '$(top_builddir)/src/templates'; \ then \ rm -rf src/templates; \ ln -s '$(abs_top_srcdir)/src/templates' 'src/templates'; \ fi # distribute test system? if ENABLE_TESTING # yes: install client-test and test files in testdir test_PROGRAMS += src/client-test include $(top_srcdir)/src/testcases.am else # The "all" dependency causes a rebuild even if the actual input files # haven't changed. If client-test is part of the regular targets built # by "all", then it must not depend on all! EXTRA_PROGRAMS += src/client-test nodist_src_client_test_SOURCES += $(CLIENT_LIB_TEST_FILES) src_client_test_DEPENDENCIES += all endif # test program for output redirection, has to be built # and run manually EXTRA_PROGRAMS += src/abort-redirect CLEANFILES += src/abort-redirect.log src_abort_redirect_SOURCES = test/abort-redirect.cpp src_abort_redirect_CPPFLAGS = -DHAVE_CONFIG_H $(src_cppflags) src_abort_redirect_CXXFLAGS = $(SYNCEVOLUTION_CXXFLAGS) $(CORE_CXXFLAGS) $(SYNCEVO_WFLAGS) src_abort_redirect_LDFLAGS = $(CORE_LD_FLAGS) src_abort_redirect_LDADD = $(CORE_LDADD) src_abort_redirect_DEPENDENCIES = all # special target for testing with valgrind valgrind : src/test valgrind --leak-check=yes --suppressions=valgrind.supp src/client-test # old-style name for test program(s) all_phonies += test valgrind src/test: src/client-test syncevolution_1.4/src/syncevo-local-sync.cpp000066400000000000000000000017501230021373600214040ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "config.h" #include #include SE_BEGIN_CXX extern "C" int main( int argc, char **argv ) { return LocalTransportMain(argc, argv); } SE_END_CXX syncevolution_1.4/src/syncevo/000077500000000000000000000000001230021373600166335ustar00rootroot00000000000000syncevolution_1.4/src/syncevo/BoostHelper.h000066400000000000000000000120661230021373600212370ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ /** * Including this header file allows to use boost::bind() with * a class member as first parameter and a boost::weak_ptr * as second parameter. * * When the functor is invoked, it will lock the instance * and only call the member if that succeeds. Otherwise it * will silently return to the caller. * * The member function must have "void" as return value. * * This behavior makes sense in particular with asynchronous method * calls where the result is only relevant while the caller still * exists. * * The code is inspired by * http://permalink.gmane.org/gmane.comp.lib.boost.user/70276 and was * adapted to the SyncEvolution namespace and coding style. In contrast * to that code, the shared_ptr is kept until the invoker gets freed. * Seems a bit safer that way. Instead of duplicating the ->* operator * in WeakPtrAdapter it uses the type of the member as template parameter * to cover all kinds of members. */ #ifndef INCL_SYNCEVOLUTION_BOOST_HELPER # define INCL_SYNCEVOLUTION_BOOST_HELPER #include #include #include SE_BEGIN_CXX template class WeakPtrInvoker { public: WeakPtrInvoker(const P &ptr, const M &member) : m_ptr(ptr), m_member(member) {} void operator()() const { if (m_ptr) { (boost::get_pointer(m_ptr)->*m_member)(); } } template void operator()(A1 a1) const { if (m_ptr) { (boost::get_pointer(m_ptr)->*m_member)(a1); } } template void operator()(A1 a1, A2 a2) const { if (m_ptr) { (boost::get_pointer(m_ptr)->*m_member)(a1, a2); } } template void operator()(A1 a1, A2 a2, A3 a3) const { if (m_ptr) { (boost::get_pointer(m_ptr)->*m_member)(a1, a2, a3); } } template void operator()(A1 a1, A2 a2, A3 a3, A4 a4) const { if (m_ptr) { (boost::get_pointer(m_ptr)->*m_member)(a1, a2, a3, a4); } } template void operator()(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) const { if (m_ptr) { (boost::get_pointer(m_ptr)->*m_member)(a1, a2, a3, a4, a5); } } template void operator()(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) const { if (m_ptr) { (boost::get_pointer(m_ptr)->*m_member)(a1, a2, a3, a4, a5, a6); } } template void operator()(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) const { if (m_ptr) { (boost::get_pointer(m_ptr)->*m_member)(a1, a2, a3, a4, a5, a6, a7); } } template void operator()(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) const { if (m_ptr) { (boost::get_pointer(m_ptr)->*m_member)(a1, a2, a3, a4, a5, a6, a7, a8); } } template void operator()(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9) const { if (m_ptr) { (boost::get_pointer(m_ptr)->*m_member)(a1, a2, a3, a4, a5, a6, a7, a8, a9); } } private: P m_ptr; M m_member; }; template class WeakPtrAdapter { public: WeakPtrAdapter(const boost::shared_ptr &ptr) : m_ptr(ptr) {} template WeakPtrInvoker, M> operator->*(M member) const { return WeakPtrInvoker, M>(m_ptr, member); } private: boost::shared_ptr m_ptr; }; SE_END_CXX namespace boost { template SyncEvo::WeakPtrAdapter get_pointer(const boost::weak_ptr &ptr) { return SyncEvo::WeakPtrAdapter(ptr.lock()); } } #endif // INCL_SYNCEVOLUTION_BOOST_HELPER syncevolution_1.4/src/syncevo/Cmdline.cpp000066400000000000000000006573631230021373600207360ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include "test.h" #include #include #include #include #include #include #include #include #include #include #include using namespace std; #include #include #include #include #include #include #include #include #include #include using namespace std; SE_BEGIN_CXX // synopsis and options char strings #include "CmdlineHelp.c" Cmdline::Cmdline(int argc, const char * const * argv) : m_argc(argc), m_argv(argv), m_validSyncProps(SyncConfig::getRegistry()), m_validSourceProps(SyncSourceConfig::getRegistry()) {} Cmdline::Cmdline(const vector &args) : m_args(args), m_validSyncProps(SyncConfig::getRegistry()), m_validSourceProps(SyncSourceConfig::getRegistry()) { m_argc = args.size(); m_argvArray.reset(new const char *[args.size()]); for(int i = 0; i < m_argc; i++) { m_argvArray[i] = m_args[i].c_str(); } m_argv = m_argvArray.get(); } Cmdline::Cmdline(const char *arg, ...) : m_validSyncProps(SyncConfig::getRegistry()), m_validSourceProps(SyncSourceConfig::getRegistry()) { va_list argList; va_start(argList, arg); for (const char *curr = arg; curr; curr = va_arg(argList, const char *)) { m_args.push_back(curr); } va_end(argList); m_argc = m_args.size(); m_argvArray.reset(new const char *[m_args.size()]); for (int i = 0; i < m_argc; i++) { m_argvArray[i] = m_args[i].c_str(); } m_argv = m_argvArray.get(); } bool Cmdline::parse() { vector parsed; return parse(parsed); } /** * Detects "--sync foo", "--sync=foo", "-s foo". */ static bool IsKeyword(const std::string &arg, const char *longWord, const char *shortWord) { return boost::istarts_with(arg, std::string(longWord) + "=") || boost::iequals(arg, longWord) || boost::iequals(arg, shortWord); } bool Cmdline::parse(vector &parsed) { parsed.clear(); if (m_argc) { parsed.push_back(m_argv[0]); } m_delimiter = "\n\n"; // All command line options which ask for a specific operation, // like --restore, --print-config, ... Used to detect conflicting // operations. vector operations; int opt = 1; bool ok; while (opt < m_argc) { parsed.push_back(m_argv[opt]); if (boost::iequals(m_argv[opt], "--")) { // separator between options and : // swallow it and leave option parsing opt++; break; } if (m_argv[opt][0] != '-') { if (strchr(m_argv[opt], '=')) { // property assignment if (!parseProp(UNKNOWN_PROPERTY_TYPE, NULL, m_argv[opt], NULL)) { return false; } else { opt++; continue; } } else { break; } } if (IsKeyword(m_argv[opt], "--sync", "-s")) { if (!parseAssignment(opt, parsed, SOURCE_PROPERTY_TYPE, "sync", NULL)) { return false; } // disable requirement to add --run explicitly in order to // be compatible with traditional command lines m_run = true; } else if (IsKeyword(m_argv[opt], "--keyring", "-k")) { if (!parseAssignment(opt, parsed, SYNC_PROPERTY_TYPE, "keyring", "true")) { return false; } } else if(boost::iequals(m_argv[opt], "--sync-property") || boost::iequals(m_argv[opt], "-y")) { opt++; if (!parseProp(SYNC_PROPERTY_TYPE, m_argv[opt - 1], opt == m_argc ? NULL : m_argv[opt])) { return false; } parsed.push_back(m_argv[opt]); } else if(boost::iequals(m_argv[opt], "--source-property") || boost::iequals(m_argv[opt], "-z")) { opt++; if (!parseProp(SOURCE_PROPERTY_TYPE, m_argv[opt - 1], opt == m_argc ? NULL : m_argv[opt])) { return false; } parsed.push_back(m_argv[opt]); }else if(boost::iequals(m_argv[opt], "--template") || boost::iequals(m_argv[opt], "-l")) { opt++; if (opt >= m_argc) { usage(false, string("missing parameter for ") + cmdOpt(m_argv[opt - 1])); return false; } parsed.push_back(m_argv[opt]); m_template = m_argv[opt]; m_configure = true; string temp = boost::trim_copy (m_template); if (temp.find ("?") == 0){ m_printTemplates = true; m_dontrun = true; m_template = temp.substr (1); } } else if(boost::iequals(m_argv[opt], "--print-databases")) { operations.push_back(m_argv[opt]); m_printDatabases = true; } else if(boost::iequals(m_argv[opt], "--create-database")) { operations.push_back(m_argv[opt]); m_createDatabase = true; } else if(boost::iequals(m_argv[opt], "--remove-database")) { operations.push_back(m_argv[opt]); m_removeDatabase = true; } else if(boost::iequals(m_argv[opt], "--print-servers") || boost::iequals(m_argv[opt], "--print-peers") || boost::iequals(m_argv[opt], "--print-configs")) { operations.push_back(m_argv[opt]); m_printServers = true; } else if(boost::iequals(m_argv[opt], "--print-config") || boost::iequals(m_argv[opt], "-p")) { operations.push_back(m_argv[opt]); m_printConfig = true; } else if(boost::iequals(m_argv[opt], "--print-sessions")) { operations.push_back(m_argv[opt]); m_printSessions = true; } else if(boost::iequals(m_argv[opt], "--configure") || boost::iequals(m_argv[opt], "-c")) { operations.push_back(m_argv[opt]); m_configure = true; } else if(boost::iequals(m_argv[opt], "--remove")) { operations.push_back(m_argv[opt]); m_remove = true; } else if(boost::iequals(m_argv[opt], "--run") || boost::iequals(m_argv[opt], "-r")) { operations.push_back(m_argv[opt]); m_run = true; } else if(boost::iequals(m_argv[opt], "--restore")) { operations.push_back(m_argv[opt]); opt++; if (opt >= m_argc) { usage(false, string("missing parameter for ") + cmdOpt(m_argv[opt - 1])); return false; } m_restore = m_argv[opt]; if (m_restore.empty()) { usage(false, string("missing parameter for ") + cmdOpt(m_argv[opt - 1])); return false; } //if can't convert it successfully, it's an invalid path if (!relToAbs(m_restore)) { usage(false, string("parameter '") + m_restore + "' for " + cmdOpt(m_argv[opt - 1]) + " must be log directory"); return false; } parsed.push_back(m_restore); } else if(boost::iequals(m_argv[opt], "--before")) { m_before = true; } else if(boost::iequals(m_argv[opt], "--after")) { m_after = true; } else if (boost::iequals(m_argv[opt], "--print-items")) { operations.push_back(m_argv[opt]); m_printItems = m_accessItems = true; } else if ((boost::iequals(m_argv[opt], "--export") && (m_export = true)) || (boost::iequals(m_argv[opt], "--import") && (m_import = true)) || (boost::iequals(m_argv[opt], "--update") && (m_update = true))) { operations.push_back(m_argv[opt]); m_accessItems = true; opt++; if (opt >= m_argc || !m_argv[opt][0]) { usage(false, string("missing parameter for ") + cmdOpt(m_argv[opt - 1])); return false; } m_itemPath = m_argv[opt]; if (m_itemPath != "-") { string dir, file; splitPath(m_itemPath, dir, file); if (dir.empty()) { dir = "."; } if (!relToAbs(dir)) { SyncContext::throwError(dir, errno); } m_itemPath = dir + "/" + file; } parsed.push_back(m_itemPath); } else if (boost::iequals(m_argv[opt], "--delimiter")) { opt++; if (opt >= m_argc) { usage(false, string("missing parameter for ") + cmdOpt(m_argv[opt - 1])); return false; } m_delimiter = m_argv[opt]; parsed.push_back(m_delimiter); } else if (boost::iequals(m_argv[opt], "--delete-items")) { operations.push_back(m_argv[opt]); m_deleteItems = m_accessItems = true; } else if(boost::iequals(m_argv[opt], "--dry-run")) { m_dryrun = true; } else if(boost::iequals(m_argv[opt], "--migrate")) { operations.push_back(m_argv[opt]); m_migrate = true; } else if(boost::iequals(m_argv[opt], "--status") || boost::iequals(m_argv[opt], "-t")) { operations.push_back(m_argv[opt]); m_status = true; } else if(boost::iequals(m_argv[opt], "--quiet") || boost::iequals(m_argv[opt], "-q")) { m_quiet = true; } else if(boost::iequals(m_argv[opt], "--help") || boost::iequals(m_argv[opt], "-h")) { m_usage = true; } else if(boost::iequals(m_argv[opt], "--version")) { operations.push_back(m_argv[opt]); m_version = true; } else if (parseBool(opt, "--daemon", NULL, true, m_useDaemon, ok)) { if (!ok) { return false; } } else if(boost::iequals(m_argv[opt], "--monitor")|| boost::iequals(m_argv[opt], "-m")) { operations.push_back(m_argv[opt]); m_monitor = true; } else if (boost::iequals(m_argv[opt], "--luids")) { // all following parameters are luids; can't be combined // with setting config and source name while (++opt < m_argc) { parsed.push_back(m_argv[opt]); m_luids.push_back(CmdlineLUID::toLUID(m_argv[opt])); } } else { usage(false, string(m_argv[opt]) + ": unknown parameter"); return false; } opt++; } if (opt < m_argc) { m_server = m_argv[opt++]; while (opt < m_argc) { parsed.push_back(m_argv[opt]); if (m_sources.empty() || !m_accessItems) { m_sources.insert(m_argv[opt++]); } else { // first additional parameter was source, rest are luids m_luids.push_back(CmdlineLUID::toLUID(m_argv[opt++])); } } } // check whether we have conflicting operations requested by user if (operations.size() > 1) { usage(false, boost::join(operations, " ") + ": mutually exclusive operations"); return false; } // common sanity checking for item listing/import/export/update if (m_accessItems) { if ((m_import || m_update) && m_dryrun) { usage(false, operations[0] + ": --dry-run not supported"); return false; } } return true; } bool Cmdline::parseBool(int opt, const char *longName, const char *shortName, bool def, Bool &value, bool &ok) { string option = m_argv[opt]; string param; size_t pos = option.find('='); if (pos != option.npos) { param = option.substr(pos + 1); option.resize(pos); } if ((longName && boost::iequals(option, longName)) || (shortName && boost::iequals(option, shortName))) { ok = true; if (param.empty()) { value = def; } else if (boost::iequals(param, "t") || boost::iequals(param, "1") || boost::iequals(param, "true") || boost::iequals(param, "yes")) { value = true; } else if (boost::iequals(param, "f") || boost::iequals(param, "0") || boost::iequals(param, "false") || boost::iequals(param, "no")) { value = false; } else { usage(false, string("parameter in '") + m_argv[opt] + "' must be 1/t/true/yes or 0/f/false/no"); ok = false; } // was our option return true; } else { // keep searching for match return false; } } bool Cmdline::isSync() { // make sure command line arguments really try to run sync if (m_usage || m_version || m_printServers || boost::trim_copy(m_server) == "?" || m_printTemplates || m_dontrun || m_argc == 1 || (m_useDaemon.wasSet() && m_argc == 2) || m_printDatabases || m_createDatabase || m_removeDatabase || m_printConfig || m_remove || (m_server == "" && m_argc > 1) || m_configure || m_migrate || m_status || m_printSessions || !m_restore.empty() || m_accessItems || m_dryrun || (!m_run && m_props.hasProperties(FullProps::IGNORE_GLOBAL_PROPS))) { return false; } else { return true; } } bool Cmdline::isRestore() const { return !m_restore.empty(); } bool Cmdline::dontRun() const { // this mimics the if() checks in run() if (m_usage || m_version || m_printServers || boost::trim_copy(m_server) == "?" || m_printTemplates) { return false; } else { return m_dontrun; } } void Cmdline::makeObsolete(boost::shared_ptr &from) { string oldname = from->getRootPath(); string newname, suffix; for (int counter = 0; true; counter++) { ostringstream newsuffix; newsuffix << ".old"; if (counter) { newsuffix << "." << counter; } suffix = newsuffix.str(); newname = oldname + suffix; if (from->hasPeerProperties()) { boost::shared_ptr renamed(new SyncConfig(from->getPeerName() + suffix)); if (renamed->exists()) { // don't pick a config name which has the same peer name // as some other, existing config continue; } } // now renaming should succeed, but let's check anyway if (!rename(oldname.c_str(), newname.c_str())) { break; } else if (errno != EEXIST && errno != ENOTEMPTY) { SE_THROW(StringPrintf("renaming %s to %s: %s", oldname.c_str(), newname.c_str(), strerror(errno))); } } string newConfigName; string oldContext = from->getContextName(); if (from->hasPeerProperties()) { newConfigName = from->getPeerName() + suffix + oldContext; } else { newConfigName = oldContext + suffix; } // Clear old pointer first. That frees cached data in SyncConfig, // which otherwise fails to notice that we moved files on disk // around. from.reset(); from.reset(new SyncConfig(newConfigName)); } void Cmdline::copyConfig(const boost::shared_ptr &from, const boost::shared_ptr &to, const set &selectedSources) { const set *sources = NULL; set allSources; if (!selectedSources.empty()) { // use explicitly selected sources sources = &selectedSources; } else { // need an explicit list of all sources which will be copied, // for the createFilters() call below BOOST_FOREACH(const std::string &source, from->getSyncSources()) { allSources.insert(source); } sources = &allSources; } // Apply config changes on-the-fly. Regardless what we do // (changing an existing config, migrating, creating from // a template), existing shared properties in the desired // context must be preserved unless explicitly overwritten. // Therefore read those, update with command line properties, // then set as filter. ConfigProps syncFilter; SourceProps sourceFilters; m_props.createFilters(to->getContextName(), to->getConfigName(), sources, syncFilter, sourceFilters); from->setConfigFilter(true, "", syncFilter); BOOST_FOREACH(const SourceProps::value_type &entry, sourceFilters) { from->setConfigFilter(false, entry.first, entry.second); } // Modify behavior of target UI before using it: set keyring // property separately. InitStateTri keyring = from->getKeyring(); if (keyring.wasSet()) { to->setKeyring(keyring); } // Write into the requested configuration, creating it if necessary. to->prepareConfigForWrite(); to->copy(*from, sources); } void Cmdline::finishCopy(const boost::shared_ptr &from, const boost::shared_ptr &to) { // give a change to do something before flushing configs to files to->preFlush(to->getUserInterfaceNonNull()); // done, now write it m_configModified = true; to->flush(); // migrating peer? if (m_migrate && from->hasPeerProperties()) { // also copy .synthesis dir string fromDir, toDir; fromDir = from->getRootPath() + "/.synthesis"; toDir = to->getRootPath() + "/.synthesis"; if (isDir(fromDir)) { cp_r(fromDir, toDir); } // Succeeded so far, remove "ConsumerReady" flag from migrated // config to hide that old config from normal UI users. Must // do this without going through SyncConfig, because that // would bump the version. BoolConfigProperty ready("ConsumerReady", "", "0"); // Also disable auto-syncing in the migrated config. StringConfigProperty autosync("autoSync", "", ""); { IniFileConfigNode node(from->getRootPath(), "config.ini", false); if (ready.getPropertyValue(node)) { ready.setProperty(node, false); } if (!autosync.getProperty(node).empty()) { autosync.setProperty(node, InitStateString("0", true)); } node.flush(); } // same for very old configs { IniFileConfigNode node(from->getRootPath() + "/spds/syncml", "config.txt", false); if (!autosync.getProperty(node).empty()) { autosync.setProperty(node, InitStateString("0", true)); } node.flush(); } // Set ConsumerReady for migrated SyncEvolution < 1.2 // configs, because in older releases all existing // configurations where shown. SyncEvolution 1.2 is more // strict and assumes that ConsumerReady must be set // explicitly. The sync-ui always has set the flag for // configs created or modified with it, but the command // line did not. Matches similar code in // syncevo-dbus-server. if (from->getConfigVersion(CONFIG_LEVEL_PEER, CONFIG_CUR_VERSION) == 0 /* SyncEvolution < 1.2 */) { to->setConsumerReady(true); to->flush(); } } } void Cmdline::migratePeer(const std::string &fromPeer, const std::string &toPeer) { boost::shared_ptr from(new SyncConfig(fromPeer)); makeObsolete(from); // hack: move to different target config for createSyncClient() m_server = toPeer; boost::shared_ptr to(createSyncClient()); // Special case for Memotoo: explicitly set preferred sync format // to vCard 3.0 as part of the SyncEvolution 1.1.x -> 1.2 migration, // because it works better. Template was also updated in 1.2, but // that alone wouldn't improve existing configs. if (from->getConfigVersion(CONFIG_LEVEL_PEER, CONFIG_CUR_VERSION) == 0) { vector urls = from->getSyncURL(); if (urls.size() == 1 && urls[0] == "http://sync.memotoo.com/syncML") { boost::shared_ptr to(createSyncClient()); m_props[to->getContextName()].m_sourceProps["addressbook"].insert(make_pair("syncFormat", "text/vcard")); } } copyConfig(from, to, set()); finishCopy(from, to); } /** * Finds first instance of delimiter string in other string. In * addition, it treats "\n\n" in a special way: that delimiter also * matches "\n\r\n". */ class FindDelimiter { const string m_delimiter; public: FindDelimiter(const string &delimiter) : m_delimiter(delimiter) {} boost::iterator_range operator()(string::iterator begin, string::iterator end) { if (m_delimiter == "\n\n") { // match both "\n\n" and "\n\r\n" while (end - begin >= 2) { if (*begin == '\n') { if (*(begin + 1) == '\n') { return boost::iterator_range(begin, begin + 2); } else if (end - begin >= 3 && *(begin + 1) == '\r' && *(begin + 2) == '\n') { return boost::iterator_range(begin, begin + 3); } } ++begin; } return boost::iterator_range(end, end); } else { boost::sub_range range(begin, end); return boost::find_first(range, m_delimiter); } } }; static void ShowLUID(SyncSourceLogging *logging, const std::string &luid) { string description; if (logging) { description = logging->getDescription(luid); } SE_LOG_SHOW(NULL, "%s%s%s", CmdlineLUID::fromLUID(luid).c_str(), description.empty() ? "" : ": ", description.c_str()); } static void ExportLUID(SyncSourceRaw *raw, ostream *out, const std::string &defDelimiter, const std::string &itemPath, bool &haveItem, bool &haveNewline, const std::string &luid) { string item; raw->readItemRaw(luid, item); if (!out) { // write into directory string fullPath = itemPath + "/" + luid; ofstream file((itemPath + "/" + luid).c_str()); file << item; file.close(); if (file.bad()) { SyncContext::throwError(fullPath, errno); } } else { std::string delimiter; if (haveItem) { if (defDelimiter.size() > 1 && haveNewline && defDelimiter[0] == '\n') { // already wrote initial newline, skip it delimiter = defDelimiter.substr(1); } else { delimiter = defDelimiter; } } if (out == &std::cout) { // special case, use logging infrastructure SE_LOG_SHOW(NULL, "%s%s", delimiter.c_str(), item.c_str()); // always prints newline haveNewline = true; } else { // write to file *out << delimiter << item; haveNewline = boost::ends_with(item, "\n"); } haveItem = true; } } bool Cmdline::run() { // --dry-run is only supported by some operations. // Be very strict about it and make sure it is off in all // potentially harmful operations, otherwise users might // expect it to have an effect when it doesn't. // TODO: check filter properties for invalid config and source // names if (m_usage) { usage(true); } else if (m_version) { SE_LOG_SHOW(NULL, "SyncEvolution %s%s\n%s%s", VERSION, SyncContext::isStableRelease() ? "" : " (pre-release)", EDSAbiWrapperInfo(), SyncSource::backendsInfo().c_str()); } else if (m_printServers || boost::trim_copy(m_server) == "?") { dumpConfigs("Configured servers:", SyncConfig::getConfigs()); } else if (m_printTemplates) { SyncConfig::DeviceList devices; if (m_template.empty()){ devices.push_back (SyncConfig::DeviceDescription("", "", SyncConfig::MATCH_FOR_CLIENT_MODE)); dumpConfigTemplates("Available configuration templates (servers):", SyncConfig::getPeerTemplates(devices), false); } else { //limiting at templates for syncml clients only. devices.push_back (SyncConfig::DeviceDescription("", m_template, SyncConfig::MATCH_FOR_SERVER_MODE)); dumpConfigTemplates("Available configuration templates (clients):", SyncConfig::matchPeerTemplates(devices), true); } } else if (m_dontrun) { // user asked for information } else if (m_printDatabases || m_createDatabase || m_removeDatabase) { // manipulate databases const SourceRegistry ®istry(SyncSource::getSourceRegistry()); boost::shared_ptr nodes; std::string header; boost::shared_ptr context; FilterConfigNode::ConfigFilter sourceFilter; std::string sourceName; FilterConfigNode::ConfigFilter::const_iterator backend; void (Cmdline::*operation)(SyncSource *, const std::string &) = m_printDatabases ? &Cmdline::listDatabases : m_createDatabase ? &Cmdline::createDatabase : &Cmdline::removeDatabase; if (!m_server.empty()) { // list for specific backend chosen via config if (m_sources.size() != 1) { SE_THROW(StringPrintf("must specify exactly one source after the config name '%s'", m_server.c_str())); } sourceName = *m_sources.begin(); sourceFilter = m_props.createSourceFilter(m_server, sourceName); backend = sourceFilter.find("backend"); context.reset(createSyncClient()); if (!context->exists()) { SE_THROW(StringPrintf("config '%s' does not exist", m_server.c_str())); } nodes.reset(new SyncSourceNodes(context->getSyncSourceNodesNoTracking(sourceName))); header = StringPrintf("%s/%s", m_server.c_str(), sourceName.c_str()); if (!nodes->dataConfigExists()) { SE_THROW(StringPrintf("%s does not exist", header.c_str())); } } else { sourceFilter = m_props.createSourceFilter(m_server, ""); backend = sourceFilter.find("backend"); context.reset(createSyncClient()); boost::shared_ptr sharedNode(new VolatileConfigNode()); boost::shared_ptr configNode(new VolatileConfigNode()); boost::shared_ptr hiddenNode(new VolatileConfigNode()); boost::shared_ptr trackingNode(new VolatileConfigNode()); boost::shared_ptr serverNode(new VolatileConfigNode()); nodes.reset(new SyncSourceNodes(true, sharedNode, configNode, hiddenNode, trackingNode, serverNode, "")); header = backend != sourceFilter.end() ? backend->second : "???"; } nodes->getProperties()->setFilter(sourceFilter); FilterConfigNode::ConfigFilter syncFilter = m_props.createSyncFilter(m_server); context->setConfigFilter(true, "", syncFilter); SyncSourceParams params("list", *nodes, context); if (!m_server.empty() || backend != sourceFilter.end()) { // list for specific backend params.m_name = sourceName; auto_ptr source(SyncSource::createSource(params, false, NULL)); if (source.get() != NULL) { if (!m_server.empty() && nodes) { // ensure that we have passwords for this config PasswordConfigProperty::checkPasswords(context->getUserInterfaceNonNull(), *context, PasswordConfigProperty::CHECK_PASSWORD_ALL, boost::assign::list_of(sourceName)); } (this->*operation)(source.get(), header); } else { SE_LOG_SHOW(NULL, "%s:\n cannot access databases", header.c_str()); } } else { // list for all backends BOOST_FOREACH(const RegisterSyncSource *source, registry) { BOOST_FOREACH(const Values::value_type &alias, source->m_typeValues) { if (!alias.empty() && source->m_enabled) { SourceType type(*alias.begin()); nodes->getProperties()->setProperty("backend", type.m_backend); std::string header = boost::join(alias, " = "); try { // The name is used in error messages. We // don't have a source name, so let's fall // back to the backend instead. params.m_name = type.m_backend; auto_ptr source(SyncSource::createSource(params, false)); (this->*operation)(source.get(), header); } catch (...) { SE_LOG_ERROR(NULL, "%s:\naccessing databases failed", header.c_str()); Exception::handle(); } } } } } } else if (m_printConfig) { boost::shared_ptr config; ConfigProps syncFilter; SourceProps sourceFilters; if (m_template.empty()) { if (m_server.empty()) { usage(false, "--print-config requires either a --template or a server name."); return false; } config.reset(new SyncConfig(m_server)); if (!config->exists()) { SE_LOG_ERROR(NULL, "Server '%s' has not been configured yet.", m_server.c_str()); return false; } // No need to include a context or additional sources, // because reading the m_server config already includes // the right information. m_props.createFilters("", m_server, NULL, syncFilter, sourceFilters); } else { string peer, context; SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(m_template, SyncConfig::NormalizeFlags(SyncConfig::NORMALIZE_SHORTHAND|SyncConfig::NORMALIZE_IS_NEW)), peer, context); config = SyncConfig::createPeerTemplate(peer); if (!config.get()) { SE_LOG_ERROR(NULL, "No configuration template for '%s' available.", m_template.c_str()); return false; } // When instantiating a template, include the properties // of the target context as filter to preserve shared // properties, the final name inside that context as // peer config name, and the sources defined in the template. list sourcelist = config->getSyncSources(); set sourceset(sourcelist.begin(), sourcelist.end()); m_props.createFilters(std::string("@") + context, "", &sourceset, syncFilter, sourceFilters); } // determine whether we dump a peer or a context int flags = DUMP_PROPS_NORMAL; string peer, context; SyncConfig::splitConfigString(config->getConfigName(), peer, context); if (peer.empty()) { flags |= HIDE_PER_PEER; checkForPeerProps(); } if (m_sources.empty() || m_sources.find("main") != m_sources.end()) { boost::shared_ptr syncProps(config->getProperties()); syncProps->setFilter(syncFilter); dumpProperties(*syncProps, config->getRegistry(), flags); } list sources = config->getSyncSources(); sources.sort(); BOOST_FOREACH(const string &name, sources) { if (m_sources.empty() || m_sources.find(name) != m_sources.end()) { SE_LOG_SHOW(NULL, "[%s]", name.c_str()); SyncSourceNodes nodes = config->getSyncSourceNodes(name); boost::shared_ptr sourceProps = nodes.getProperties(); sourceProps->setFilter(sourceFilters.createSourceFilter(name)); dumpProperties(*sourceProps, SyncSourceConfig::getRegistry(), flags | ((name != *(--sources.end())) ? HIDE_LEGEND : DUMP_PROPS_NORMAL)); } } } else if (m_configure || m_migrate) { if (!needConfigName()) { return false; } if (m_dryrun) { SyncContext::throwError("--dry-run not supported for configuration changes"); } // name of renamed config ("foo.old") after migration string newname; // True if the target configuration is a context like @default // or @foobar. Relevant in several places in the following // code. bool configureContext = false; bool fromScratch = false; string peer, context; SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(m_server), peer, context); if (peer.empty()) { configureContext = true; checkForPeerProps(); } // Make m_server a fully-qualified name. Useful in error // messages and essential for migrating "foo" where "foo" // happens to map to "foo@bar". Otherwise "foo" will be // mapped incorrectly to "foo@default" after renaming // "foo@bar" to "foo.old@bar". // // The inverse problem can occur for "foo@default": after // renaming, "foo" without "@default" would be mapped to // "foo@somewhere-else" if such a config exists. m_server = peer + "@" + context; // Both config changes and migration are implemented as copying from // another config (template resp. old one). Migration also moves // the old config. The target configuration is determined by m_server, // but the exact semantic of it depends on the operation. boost::shared_ptr from; boost::shared_ptr to; string origPeer; if (m_migrate) { if (!m_sources.empty()) { SE_LOG_ERROR(NULL, "cannot migrate individual sources"); return false; } string oldContext = context; from.reset(new SyncConfig(m_server)); if (!from->exists()) { // for migration into a different context, search for config without context oldContext = ""; from.reset(new SyncConfig(peer)); if (!from->exists()) { SE_LOG_ERROR(NULL, "Server '%s' has not been configured yet.", m_server.c_str()); return false; } } // Check if we are migrating an individual peer inside // a context which itself is too old. In that case, // the whole context and everything inside it needs to // be migrated. if (!configureContext) { bool obsoleteContext = false; if (from->getLayout() < SyncConfig::SHARED_LAYOUT) { // check whether @default context exists and is too old; // in that case migrate it first SyncConfig target("@default"); if (target.exists() && target.getConfigVersion(CONFIG_LEVEL_CONTEXT, CONFIG_CUR_VERSION) < CONFIG_CONTEXT_MIN_VERSION) { // migrate all peers inside @default *and* the one outside origPeer = m_server; m_server = "@default"; obsoleteContext = true; } } else { // config already is inside a context; need to check that context if (from->getConfigVersion(CONFIG_LEVEL_CONTEXT, CONFIG_CUR_VERSION) < CONFIG_CONTEXT_MIN_VERSION) { m_server = string("@") + context; obsoleteContext = true; } } if (obsoleteContext) { // hack: move to different config and back later from.reset(new SyncConfig(m_server)); peer = ""; configureContext = true; } } // rename on disk and point "from" to it makeObsolete(from); // modify the config referenced by the (possibly modified) m_server to.reset(createSyncClient()); } else { from.reset(new SyncConfig(m_server)); // m_server known, modify it to.reset(createSyncClient()); if (!from->exists()) { // creating from scratch, look for template fromScratch = true; string configTemplate; if (m_template.empty()) { if (configureContext) { // configuring a context, template doesn't matter => // use default "SyncEvolution" template configTemplate = peer = "SyncEvolution"; from = SyncConfig::createPeerTemplate(peer); } else if (peer == "target-config") { // Configuring the source context for local sync // => determine template based on context name. configTemplate = context; from = SyncConfig::createPeerTemplate(context); } else { // template is the peer name configTemplate = m_server; from = SyncConfig::createPeerTemplate(peer); } } else { // Template is specified explicitly. It must not contain a context, // because the context comes from the config name. configTemplate = m_template; if (SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(configTemplate, SyncConfig::NormalizeFlags(SyncConfig::NORMALIZE_SHORTHAND|SyncConfig::NORMALIZE_IS_NEW)), peer, context)) { SE_LOG_ERROR(NULL, "Template %s must not specify a context.", configTemplate.c_str()); return false; } string tmp; SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(m_server), tmp, context); from = SyncConfig::createPeerTemplate(peer); } list missing; if (!from.get()) { // check if all obligatory sync properties are specified; needed // for both the "is complete" check and the error message below ConfigProps syncProps = m_props.createSyncFilter(to->getContextName()); bool complete = true; BOOST_FOREACH(const ConfigProperty *prop, SyncConfig::getRegistry()) { if (prop->isObligatory() && syncProps.find(prop->getMainName()) == syncProps.end()) { missing.push_back(prop->getMainName()); complete = false; } } // if everything was specified and no invalid template name was given, allow user // to proceed with "none" template; if a template was specified, we skip // this and go directly to the code below which prints an error message if (complete && m_template.empty()) { from = SyncConfig::createPeerTemplate("none"); } } if (!from.get()) { SE_LOG_ERROR(NULL, "No configuration template for '%s' available.", configTemplate.c_str()); if (m_template.empty()) { SE_LOG_INFO(NULL, "Use '--template none' and/or specify relevant properties on the command line to create a configuration without a template. Need values for: %s", boost::join(missing, ", ").c_str()); } else if (missing.empty()) { SE_LOG_INFO(NULL, "All relevant properties seem to be set, omit the --template parameter to proceed."); } SE_LOG_INFO(NULL, "\n"); SyncConfig::DeviceList devices; devices.push_back(SyncConfig::DeviceDescription("", "", SyncConfig::MATCH_ALL)); dumpConfigTemplates("Available configuration templates (clients and servers):", SyncConfig::getPeerTemplates(devices), false, Logger::INFO); return false; } } } // Which sources are configured is determined as follows: // - all sources in the template by default (empty set), except when // - sources are listed explicitly, and either // - updating an existing config or // - configuring a context. // // This implies that when configuring a peer from scratch, all // sources in the template will be created, with command line // source properties applied to all of them. This might not be // what we want, but because this is how we have done it // traditionally, I keep this behavior for now. // // When migrating, m_sources is empty and thus the whole set of // sources will be migrated. Checking it here for clarity's sake. set sources; if (!m_migrate && !m_sources.empty() && (!fromScratch || configureContext)) { sources = m_sources; } // Also copy (aka create) sources listed on the command line if // creating from scratch and // - "--template none" enables the "do what I want" mode or // - source properties apply to it. // Creating from scratch with other sources is a possible typo // and will trigger an error below. if (fromScratch) { BOOST_FOREACH(const string &source, m_sources) { if (m_template == "none" || !m_props.createSourceFilter(to->getContextName(), source).empty()) { sources.insert(source); } } } // Special case for migration away from "type": older // SyncEvolution could cope with "type" only set correctly for // peers. Real-world case: Memotoo config, context had "type = // calendar" set for address book. // // Setting "backend" based on an incorrect "type" from the // context would lead to a broken, unusable config. Solution: // take "backend" and "databaseFormat" from a peer config when // migrating a context. // // Note that peers are assumed to be consistent. Not attempt is // made to detect a config which has inconsistent peer configs. if (m_migrate && configureContext && from->getConfigVersion(CONFIG_LEVEL_CONTEXT, CONFIG_CUR_VERSION) == 0) { list peers = from->getPeers(); peers.sort(); // make code below deterministic BOOST_FOREACH(const std::string source, from->getSyncSources()) { BOOST_FOREACH(const string &peer, peers) { IniFileConfigNode node(from->getRootPath() + "/peers/" + peer + "/sources/" + source, "config.ini", true); string sync = node.readProperty("sync"); if (sync.empty() || boost::iequals(sync, "none") || boost::iequals(sync, "disabled")) { // ignore this peer, it doesn't use the source continue; } SourceType type(node.readProperty("type")); if (!type.m_backend.empty()) { // found some "type": use "backend" and // "dataFormat" in filter, unless the user // already set a value there ConfigProps syncFilter; SourceProps sourceFilters; set set; set.insert(source); m_props.createFilters(to->getContextName(), "", &set, syncFilter, sourceFilters); const ConfigProps &sourceFilter = sourceFilters[source]; if (sourceFilter.find("backend") == sourceFilter.end()) { m_props[to->getContextName()].m_sourceProps[source]["backend"] = type.m_backend; } if (!type.m_localFormat.empty() && sourceFilter.find("databaseFormat") == sourceFilter.end()) { m_props[to->getContextName()].m_sourceProps[source]["databaseFormat"] = type.m_localFormat; } // use it without bothering to keep looking // (no consistenty check!) break; } } } } // TODO: update complete --configure output to be more informative. // This is a first step, but shouldn't be done in isolation. // SE_LOG_INFO(NULL, "%s configuration %s", // fromScratch ? "creating" : "updating", // to->getConfigName().c_str()); // copy and filter into the target config: createSyncClient() // creates a SyncContext for m_server, with propert // implementation of the password handling methods in derived // classes (D-Bus server, real command line) copyConfig(from, to, sources); // Sources are active now according to the server default. // Disable all sources not selected by user (if any selected) // and those which have no database. if (fromScratch) { list configuredSources = to->getSyncSources(); set sources = m_sources; SuspendFlags &s = SuspendFlags::getSuspendFlags(); BOOST_FOREACH(const string &source, configuredSources) { boost::shared_ptr sourceConfig(to->getSyncSourceConfig(source)); string disable = ""; set::iterator entry = sources.find(source); bool selected = entry != sources.end(); if (!m_sources.empty() && !selected) { disable = "not selected"; } else { if (entry != sources.end()) { // The command line parameter matched a valid source. // All entries left afterwards must have been typos. sources.erase(entry); } // check whether the sync source works; this can // take some time, so allow the user to abort SE_LOG_INFO(NULL, "%s: looking for databases...", source.c_str()); // Even if the peer config does not exist yet // (fromScratch == true), the source config itself // may already exist with a username/password // using the keyring. Need to retrieve that // password before using the source. // // We need to check for databases again here, // because otherwise we don't know whether the // source is usable. The "database" property can // be empty in a usable source, and the "sync" // property in some potential other peer config // is not accessible. PasswordConfigProperty::checkPasswords(to->getUserInterfaceNonNull(), *to, PasswordConfigProperty::CHECK_PASSWORD_SOURCE| PasswordConfigProperty::CHECK_PASSWORD_RESOLVE_PASSWORD| PasswordConfigProperty::CHECK_PASSWORD_RESOLVE_USERNAME, boost::assign::list_of(source)); SyncSourceParams params(source, to->getSyncSourceNodes(source), to); auto_ptr syncSource(SyncSource::createSource(params, false, to.get())); if (syncSource.get() == NULL) { disable = "no backend available"; } else { try { SyncSource::Databases databases = syncSource->getDatabases(); if (databases.empty()) { disable = "no database to synchronize"; } } catch (...) { std::string explanation; Exception::handle(explanation, HANDLE_EXCEPTION_NO_ERROR); disable = "backend failed: " + explanation; } } s.checkForNormal(); SE_LOG_INFO(NULL, "%s: %s\n", source.c_str(), disable.empty() ? "okay" : disable.c_str()); } // Do sanity checking of source (can it be enabled?), // but only set the sync mode if configuring a peer. // A context-only config doesn't have the "sync" // property. string syncMode; if (!disable.empty()) { // abort if the user explicitly asked for the sync source // and it cannot be enabled, otherwise disable it silently if (selected) { SyncContext::throwError(source + ": " + disable); } syncMode = "disabled"; } else if (selected) { // user absolutely wants it: enable even if off by default ConfigProps filter = m_props.createSourceFilter(m_server, source); ConfigProps::const_iterator sync = filter.find("sync"); syncMode = sync == filter.end() ? "two-way" : sync->second; } if (!syncMode.empty() && !configureContext) { sourceConfig->setSync(syncMode); } } if (!sources.empty()) { SyncContext::throwError(string("no such source(s): ") + boost::join(sources, " ")); } } // flush, move .synthesis dir, set ConsumerReady, ... finishCopy(from, to); // Now also migrate all peers inside context? if (configureContext && m_migrate) { BOOST_FOREACH(const string &peer, from->getPeers()) { migratePeer(peer + from->getContextName(), peer + to->getContextName()); } if (!origPeer.empty()) { migratePeer(origPeer, origPeer + to->getContextName()); } } } else if (m_remove) { if (!needConfigName()) { return false; } if (m_dryrun) { SyncContext::throwError("--dry-run not supported for removing configurations"); } // extra sanity check if (!m_sources.empty() || m_props.hasProperties(FullProps::IGNORE_GLOBAL_PROPS)) { usage(false, "too many parameters for --remove"); return false; } else { boost::shared_ptr config; config.reset(new SyncConfig(m_server)); if (!config->exists()) { SyncContext::throwError(string("no such configuration: ") + m_server); } config->remove(); m_configModified = true; return true; } } else if (m_accessItems) { // need access to specific source boost::shared_ptr context; context.reset(createSyncClient()); // operating on exactly one source (can be optional) string sourceName; bool haveSourceName = !m_sources.empty(); if (haveSourceName) { sourceName = *m_sources.begin(); } // apply filters context->setConfigFilter(true, "", m_props.createSyncFilter(m_server)); context->setConfigFilter(false, "", m_props.createSourceFilter(m_server, sourceName)); SyncSourceNodes sourceNodes = context->getSyncSourceNodesNoTracking(sourceName); SyncSourceParams params(sourceName, sourceNodes, context); cxxptr source; try { source.set(SyncSource::createSource(params, true)); } catch (const StatusException &ex) { // Creating the source failed. Detect some common reasons for this // and log those instead. None of these situations are fatal by themselves, // but in combination they are a problem. if (ex.syncMLStatus() == SyncMLStatus(sysync::LOCERR_CFGPARSE)) { std::list explanation; explanation.push_back(ex.what()); if (!m_server.empty() && !context->exists()) { explanation.push_back(StringPrintf("configuration '%s' does not exist", m_server.c_str())); } if (haveSourceName && !sourceNodes.exists()) { explanation.push_back(StringPrintf("source '%s' does not exist", sourceName.c_str())); } else if (!haveSourceName) { explanation.push_back("no source selected"); } SyncSourceConfig sourceConfig(sourceName, sourceNodes); if (!sourceConfig.getBackend().wasSet()) { explanation.push_back("backend property not set"); } SyncContext::throwError(SyncMLStatus(sysync::LOCERR_CFGPARSE), boost::join(explanation, "\n")); } else { throw; } } sysync::TSyError err; #define CHECK_ERROR(_op) if (err) { SE_THROW_EXCEPTION_STATUS(StatusException, string(source->getName()) + ": " + (_op), SyncMLStatus(err)); } PasswordConfigProperty::checkPasswords(context->getUserInterfaceNonNull(), *context, PasswordConfigProperty::CHECK_PASSWORD_ALL, boost::assign::list_of(source->getName())); source->setNeedChanges(false); source->open(); const SyncSource::Operations &ops = source->getOperations(); if (m_printItems) { SyncSourceLogging *logging = dynamic_cast(source.get()); if (!ops.m_startDataRead || !ops.m_readNextItem) { source->throwError("reading items not supported"); } err = ops.m_startDataRead(*source, "", ""); CHECK_ERROR("reading items"); source->setReadAheadOrder(SyncSourceBase::READ_ALL_ITEMS); processLUIDs(source, boost::bind(ShowLUID, logging, _1)); } else if (m_deleteItems) { if (!ops.m_deleteItem) { source->throwError("deleting items not supported"); } list luids; bool deleteAll = std::find(m_luids.begin(), m_luids.end(), "*") != m_luids.end(); err = ops.m_startDataRead(*source, "", ""); CHECK_ERROR("reading items"); if (deleteAll) { readLUIDs(source, luids); } else { luids = m_luids; } if (ops.m_endDataRead) { err = ops.m_endDataRead(*source); CHECK_ERROR("stop reading items"); } if (ops.m_startDataWrite) { err = ops.m_startDataWrite(*source); CHECK_ERROR("writing items"); } BOOST_FOREACH(const string &luid, luids) { sysync::ItemIDType id; id.item = (char *)luid.c_str(); err = ops.m_deleteItem(*source, &id); CHECK_ERROR("deleting item"); } char *token; err = ops.m_endDataWrite(*source, true, &token); if (token) { free(token); } CHECK_ERROR("stop writing items"); } else { SyncSourceRaw *raw = dynamic_cast(source.get()); if (!raw) { source->throwError("reading/writing items directly not supported"); } if (m_import || m_update) { err = ops.m_startDataRead(*source, "", ""); CHECK_ERROR("reading items"); if (ops.m_endDataRead) { err = ops.m_endDataRead(*source); CHECK_ERROR("stop reading items"); } if (ops.m_startDataWrite) { err = ops.m_startDataWrite(*source); CHECK_ERROR("writing items"); } cxxptr inFile; if (m_itemPath =="-" || !isDir(m_itemPath)) { string content; string luid; if (m_itemPath == "-") { context->getUserInterfaceNonNull().readStdin(content); } else if (!ReadFile(m_itemPath, content)) { SyncContext::throwError(m_itemPath, errno); } if (m_delimiter == "none") { if (m_update) { if (m_luids.size() != 1) { SyncContext::throwError("need exactly one LUID parameter"); } else { luid = *m_luids.begin(); } } SE_LOG_SHOW(NULL, "#0: %s", insertItem(raw, luid, content).getEncoded().c_str()); } else { typedef boost::split_iterator string_split_iterator; int count = 0; FindDelimiter finder(m_delimiter); // when updating, check number of luids in advance if (m_update) { unsigned long total = 0; for (string_split_iterator it = boost::make_split_iterator(content, finder); it != string_split_iterator(); ++it) { total++; } if (total != m_luids.size()) { SyncContext::throwError(StringPrintf("%lu items != %lu luids, must match => aborting", total, (unsigned long)m_luids.size())); } } list::const_iterator luidit = m_luids.begin(); for (string_split_iterator it = boost::make_split_iterator(content, finder); it != string_split_iterator(); ++it) { string luid; if (m_update) { if (luidit == m_luids.end()) { // was checked above SyncContext::throwError("internal error, not enough luids"); } luid = *luidit; ++luidit; } SE_LOG_SHOW(NULL, "#%d: %s", count, insertItem(raw, luid, string(it->begin(), it->end())).getEncoded().c_str()); count++; } } } else { ReadDir dir(m_itemPath); int count = 0; BOOST_FOREACH(const string &entry, dir) { string content; string path = m_itemPath + "/" + entry; if (!ReadFile(path, content)) { SyncContext::throwError(path, errno); } SE_LOG_SHOW(NULL, "#%d: %s: %s", count, entry.c_str(), insertItem(raw, "", content).getEncoded().c_str()); count++; } } char *token = NULL; err = ops.m_endDataWrite(*source, true, &token); if (token) { free(token); } CHECK_ERROR("stop writing items"); } else if (m_export) { err = ops.m_startDataRead(*source, "", ""); CHECK_ERROR("reading items"); ostream *out = NULL; cxxptr outFile; if (m_itemPath == "-") { // not actually used, falls back to SE_LOG_SHOW() out = &std::cout; } else if(!isDir(m_itemPath)) { outFile.set(new ofstream(m_itemPath.c_str())); out = outFile; } bool haveItem = false; // have written one item bool haveNewline = false; // that item had a newline at the end if (m_luids.empty()) { // Read all items. raw->setReadAheadOrder(SyncSourceBase::READ_ALL_ITEMS); processLUIDs(source, boost::bind(ExportLUID, raw, out, boost::ref(m_delimiter), boost::ref(m_itemPath), boost::ref(haveItem), boost::ref(haveNewline), _1)); } else { SyncSourceBase::ReadAheadItems luids; luids.reserve(m_luids.size()); luids.insert(luids.begin(), m_luids.begin(), m_luids.end()); raw->setReadAheadOrder(SyncSourceBase::READ_SELECTED_ITEMS, luids); BOOST_FOREACH(const string &luid, m_luids) { ExportLUID(raw, out, m_delimiter, m_itemPath, haveItem, haveNewline, luid); } } raw->setReadAheadOrder(SyncSourceBase::READ_NONE); if (outFile) { outFile->close(); if (outFile->bad()) { SyncContext::throwError(m_itemPath, errno); } } } } source->close(); } else { if (!needConfigName()) { return false; } std::set unmatchedSources; boost::shared_ptr context; context.reset(createSyncClient()); context->setConfigProps(m_props); context->setQuiet(m_quiet); context->setDryRun(m_dryrun); context->setConfigFilter(true, "", m_props.createSyncFilter(m_server)); if (m_sources.empty()) { // Special semantic of 'no source selected': apply // filter (if any exists) only to sources which are // *active*. Configuration of inactive sources is left // unchanged. This way we don't activate sync sources // accidentally when the sync mode is modified // temporarily. BOOST_FOREACH(const std::string &source, context->getSyncSources()) { boost::shared_ptr source_config = context->getSyncSourceConfig(source); if (!source_config->isDisabled()) { context->setConfigFilter(false, source, m_props.createSourceFilter(m_server, source)); } } } else { // apply (possibly empty) source filter to selected sources BOOST_FOREACH(const std::string &source, m_sources) { boost::shared_ptr source_config = context->getSyncSourceConfig(source); ConfigProps filter = m_props.createSourceFilter(m_server, source); if (!source_config || !source_config->exists()) { // invalid source name in m_sources, remember and // report this below unmatchedSources.insert(source); } else if (filter.find("sync") == filter.end()) { // Sync mode is not set, must override the // "sync=disabled" set below with the original // sync mode for the source or (if that is also // "disabled") with "two-way". The latter is part // of the command line semantic that listing a // source activates it. string sync = source_config->getSync(); filter["sync"] = sync == "disabled" ? "two-way" : sync; context->setConfigFilter(false, source, filter); } else { // sync mode is set, can use m_sourceProps // directly to apply it context->setConfigFilter(false, source, filter); } } // temporarily disable the rest FilterConfigNode::ConfigFilter disabled; disabled["sync"] = InitStateString("disabled", true); context->setConfigFilter(false, "", disabled); } // check whether there were any sources specified which do not exist if (!unmatchedSources.empty()) { context->throwError(string("no such source(s): ") + boost::join(unmatchedSources, " ")); } if (m_status) { context->status(); } else if (m_printSessions) { vector dirs; context->getSessions(dirs); bool first = true; BOOST_FOREACH(const string &dir, dirs) { if (first) { first = false; } else if(!m_quiet) { SE_LOG_SHOW(NULL, "\n"); } SE_LOG_SHOW(NULL, "%s", dir.c_str()); if (!m_quiet) { SyncReport report; context->readSessionInfo(dir, report); ostringstream out; out << report; SE_LOG_SHOW(NULL, "%s", out.str().c_str()); } } } else if (!m_restore.empty()) { // sanity checks: either --after or --before must be given, sources must be selected if ((!m_after && !m_before) || (m_after && m_before)) { usage(false, "--restore must be used with either --after (restore database as it was after that sync) or --before (restore data from before sync)"); return false; } if (m_sources.empty()) { usage(false, "Sources must be selected explicitly for --restore to prevent accidental restore."); return false; } context->restore(m_restore, m_after ? SyncContext::DATABASE_AFTER_SYNC : SyncContext::DATABASE_BEFORE_SYNC); } else { if (m_dryrun) { usage(false, "--dry-run not supported for running a synchronization"); return false; } // safety catch: if props are given, then --run // is required if (!m_run && (m_props.hasProperties(FullProps::IGNORE_GLOBAL_PROPS))) { usage(false, "Properties specified, but neither '--configure' nor '--run' - what did you want?"); return false; } return (context->sync(&m_report) == STATUS_OK); } } return true; } void Cmdline::readLUIDs(SyncSource *source, list &luids) { processLUIDs(source, boost::bind(&list::push_back, boost::ref(luids), _1)); } void Cmdline::processLUIDs(SyncSource *source, const boost::function &process) { const SyncSource::Operations &ops = source->getOperations(); sysync::ItemIDType id; sysync::sInt32 status; sysync::TSyError err = ops.m_readNextItem(*source, &id, &status, true); CHECK_ERROR("next item"); while (status != sysync::ReadNextItem_EOF) { process(id.item); StrDispose(id.item); StrDispose(id.parent); err = ops.m_readNextItem(*source, &id, &status, false); CHECK_ERROR("next item"); } } CmdlineLUID Cmdline::insertItem(SyncSourceRaw *source, const string &luid, const string &data) { SyncSourceRaw::InsertItemResult res = source->insertItemRaw(luid, data); CmdlineLUID cluid; cluid.setLUID(res.m_luid); return cluid; } string Cmdline::cmdOpt(const char *opt, const char *param) { string res = "'"; if (opt) { res += opt; } // parameter was provided as part of option bool included = opt && param && boost::ends_with(std::string(opt), std::string("=") + param); if (!included && opt && param) { res += " "; } if (!included && param) { res += param; } res += "'"; return res; } bool Cmdline::parseProp(PropertyType propertyType, const char *opt, const char *param, const char *propname) { std::string args = cmdOpt(opt, param); if (!param) { usage(false, string("missing parameter for ") + args); return false; } // determine property name and parameter for it string propstr; string paramstr; if (propname) { propstr = propname; paramstr = param; } else if (boost::trim_copy(string(param)) == "?") { paramstr = param; } else { const char *equal = strchr(param, '='); if (!equal) { usage(false, string("the '=' part is missing in: ") + args); return false; } propstr.assign(param, equal - param); paramstr.assign(equal + 1); } boost::trim(propstr); boost::trim_left(paramstr); // parse full property string PropertySpecifier spec = PropertySpecifier::StringToPropSpec(propstr); // determine property type and registry const ConfigPropertyRegistry *validProps = NULL; switch (propertyType) { case SYNC_PROPERTY_TYPE: validProps = &m_validSyncProps; break; case SOURCE_PROPERTY_TYPE: validProps = &m_validSourceProps; break; case UNKNOWN_PROPERTY_TYPE: // must guess based on both registries if (!propstr.empty()) { bool isSyncProp = m_validSyncProps.find(spec.m_property) != NULL; bool isSourceProp = m_validSourceProps.find(spec.m_property) != NULL; if (isSyncProp) { if (isSourceProp) { usage(false, StringPrintf("property '%s' in %s could be both a sync and a source property, use --sync-property or --source-property to disambiguate it", propname, args.c_str())); return false; } else { validProps = &m_validSyncProps; } } else if (isSourceProp || boost::iequals(spec.m_property, "type")) { validProps = &m_validSourceProps; } else { if (propname) { usage(false, StringPrintf("unrecognized property '%s' in %s", propname, args.c_str())); } else { usage(false, StringPrintf("unrecognized property in %s", args.c_str())); } return false; } } else { usage(false, StringPrintf("a property name must be given in %s", args.c_str())); return false; } } if (boost::trim_copy(string(param)) == "?") { m_dontrun = true; if (propname) { return listPropValues(*validProps, spec.m_property, opt ? opt : ""); } else { return listProperties(*validProps, opt ? opt : ""); } } else { if (boost::trim_copy(paramstr) == "?") { m_dontrun = true; return listPropValues(*validProps, spec.m_property, args); } else { const ConfigProperty *prop = validProps->find(spec.m_property); if (!prop && boost::iequals(spec.m_property, "type")) { // compatiblity mode for "type": map to the properties which // replaced it prop = validProps->find("backend"); if (!prop) { SE_LOG_ERROR(NULL, "backend: no such property"); return false; } SourceType sourceType(paramstr); string error; if (!prop->checkValue(sourceType.m_backend, error)) { SE_LOG_ERROR(NULL, "%s: %s", args.c_str(), error.c_str()); return false; } ContextProps &props = m_props[spec.m_config]; props.m_sourceProps[spec.m_source]["backend"] = InitStateString(sourceType.m_backend, !sourceType.m_backend.empty()); props.m_sourceProps[spec.m_source]["databaseFormat"] = InitStateString(sourceType.m_localFormat, !sourceType.m_localFormat.empty()); props.m_sourceProps[spec.m_source]["syncFormat"] = InitStateString(sourceType.m_format, !sourceType.m_format.empty()); props.m_sourceProps[spec.m_source]["forceSyncFormat"] = sourceType.m_forceFormat ? InitStateString("1", true) : InitStateString("0", false); return true; } else if (!prop) { SE_LOG_ERROR(NULL, "%s: no such property", args.c_str()); return false; } else { string error; if (!prop->checkValue(paramstr, error)) { SE_LOG_ERROR(NULL, "%s: %s", args.c_str(), error.c_str()); return false; } else { ContextProps &props = m_props[spec.m_config]; if (validProps == &m_validSyncProps) { // complain if sync property includes source prefix if (!spec.m_source.empty()) { SE_LOG_ERROR(NULL, "%s: source name '%s' not allowed in sync property", args.c_str(), spec.m_source.c_str()); return false; } props.m_syncProps[spec.m_property] = paramstr; } else { props.m_sourceProps[spec.m_source][spec.m_property] = paramstr; } return true; } } } } } bool Cmdline::parseAssignment(int &opt, vector &parsed, PropertyType propertyType, const char *propname, const char *def) { string param; bool haveParam = false; string cmdopt(m_argv[opt]); size_t off = cmdopt.find('='); if (off != cmdopt.npos) { // value embedded in option param = cmdopt.substr(off + 1); haveParam = true; } else if (!def && ++opt < m_argc) { // assume next entry is parameter param = m_argv[opt]; parsed.push_back(m_argv[opt]); haveParam = true; } else if (def) { // use default param = def; haveParam = true; } return parseProp(propertyType, cmdopt.c_str(), haveParam ? param.c_str() : NULL, propname); } bool Cmdline::listPropValues(const ConfigPropertyRegistry &validProps, const string &propName, const string &opt) { const ConfigProperty *prop = validProps.find(propName); if (!prop && boost::iequals(propName, "type")) { SE_LOG_SHOW(NULL, "%s\n" " [:[:getComment(); if (comment != "") { list commentLines; ConfigProperty::splitComment(comment, commentLines); BOOST_FOREACH(const string &line, commentLines) { out << " " << line << endl; } } else { out << " no documentation available" << endl; } SE_LOG_SHOW(NULL, "%s", out.str().c_str()); return true; } } bool Cmdline::listProperties(const ConfigPropertyRegistry &validProps, const string &opt) { // The first of several related properties has a comment. // Remember that comment and print it as late as possible, // that way related properties preceed their comment. string comment; bool needComma = false; ostringstream out; BOOST_FOREACH(const ConfigProperty *prop, validProps) { if (!prop->isHidden()) { string newComment = prop->getComment(); if (newComment != "") { if (!comment.empty()) { out << endl; dumpComment(out, " ", comment); out << endl; needComma = false; } comment = newComment; } std::string def = prop->getDefValue(); if (def.empty()) { def = "no default"; } ConfigProperty::Sharing sharing = prop->getSharing(); if (needComma) { out << ", "; } out << boost::join(prop->getNames(), " = ") << " (" << def << ", " << ConfigProperty::sharing2str(sharing) << (prop->isObligatory() ? ", required" : "") << ")"; needComma = true; } } out << endl; dumpComment(out, " ", comment); SE_LOG_SHOW(NULL, "%s", out.str().c_str()); return true; } static void findPeerProps(FilterConfigNode::ConfigFilter &filter, ConfigPropertyRegistry ®istry, set &peerProps) { BOOST_FOREACH(StringPair entry, filter) { const ConfigProperty *prop = registry.find(entry.first); if (prop && prop->getSharing() == ConfigProperty::NO_SHARING) { peerProps.insert(entry.first); } } } void Cmdline::checkForPeerProps() { set peerProps; BOOST_FOREACH(FullProps::value_type &entry, m_props) { ContextProps &props = entry.second; findPeerProps(props.m_syncProps, SyncConfig::getRegistry(), peerProps); BOOST_FOREACH(SourceProps::value_type &entry, props.m_sourceProps) { findPeerProps(entry.second, SyncSourceConfig::getRegistry(), peerProps); } } if (!peerProps.empty()) { string props = boost::join(peerProps, ", "); if (props == "forceSyncFormat, syncFormat") { // special case: these two properties might have been added by the // legacy "sync" property, which applies to both shared and unshared // properties => cannot determine that here anymore, so ignore it } else { SyncContext::throwError(string("per-peer (unshared) properties not allowed: ") + props); } } } void Cmdline::listDatabases(SyncSource *source, const string &header) { if (!source) { // silently skip backends like the "file" backend which do not support // listing databases and return NULL unless configured properly return; } ostringstream out; out << header << ":\n"; if (source->isInactive()) { out << source->getBackend() << ": not enabled during compilation or not usable in the current environment\n"; } else { SyncSource::Databases databases = source->getDatabases(); BOOST_FOREACH(const SyncSource::Database &database, databases) { out << " " << database.m_name << " (" << database.m_uri << ")"; if (database.m_isDefault) { out << " "; } out << endl; } } SE_LOG_SHOW(NULL, "%s", out.str().c_str()); SE_LOG_SHOW(NULL, "\n"); } void Cmdline::createDatabase(SyncSource *source, const string &header) { if (!source) { SE_THROW(StringPrintf("%s:\ncannot access databases", header.c_str())); return; } // Only the name can be set via the command line. URI is chosen by backend. InitStateString databaseID = source->getDatabaseID(); if (!databaseID.wasSet()) { SE_THROW("The 'database' property must be set to the name of the new database"); } SyncSource::Database database = source->createDatabase(SyncSource::Database(databaseID, "")); SE_LOG_SHOW(NULL, "%s: database '%s' (%s) was created.", header.c_str(), database.m_name.c_str(), database.m_uri.c_str()); } void Cmdline::removeDatabase(SyncSource *source, const string &header) { if (!source) { SE_THROW(StringPrintf("%s:\ncannot access databases", header.c_str())); return; } InitStateString databaseID = source->getDatabaseID(); if (!databaseID.wasSet()) { SE_THROW("The 'database' property was not set. Cowardly refusing to remove the default database. Set it to the empty string and try again if that was the intention."); } // determine URI source->open(); SyncSource::Database database = source->getDatabase(); if (database.m_uri.empty()) { SE_THROW(StringPrintf("Cannot determine database from 'database' property value '%s'.", databaseID.c_str())); } source->deleteDatabase(database.m_uri, SyncSource::REMOVE_DATA_DEFAULT); SE_LOG_SHOW(NULL, "%s: database '%s' (%s) was removed.", header.c_str(), database.m_name.c_str(), database.m_uri.c_str()); } void Cmdline::dumpConfigs(const string &preamble, const SyncConfig::ConfigList &servers) { ostringstream out; out << preamble << endl; BOOST_FOREACH(const SyncConfig::ConfigList::value_type &server,servers) { out << " " << server.first << " = " << server.second <m_templateId << " = " << server->m_description; if (printRank){ out << " " << server->m_rank *20 << "%"; } out << endl; } if (!templates.size()) { out << " none" << endl; } SE_LOG(NULL, level, "%s", out.str().c_str()); } void Cmdline::dumpProperties(const ConfigNode &configuredProps, const ConfigPropertyRegistry &allProps, int flags) { list perPeer, perContext, global; ostringstream out; BOOST_FOREACH(const ConfigProperty *prop, allProps) { if (prop->isHidden() || ((flags & HIDE_PER_PEER) && prop->getSharing() == ConfigProperty::NO_SHARING)) { continue; } if (!m_quiet) { string comment = prop->getComment(); if (!comment.empty()) { out << endl; dumpComment(out, "# ", comment); } } InitStateString value = prop->getProperty(configuredProps); if (!value.wasSet()) { out << "# "; } out << prop->getMainName() << " = " << value.get() << endl; list *type = NULL; switch (prop->getSharing()) { case ConfigProperty::GLOBAL_SHARING: type = &global; break; case ConfigProperty::SOURCE_SET_SHARING: type = &perContext; break; case ConfigProperty::NO_SHARING: type = &perPeer; break; } if (type) { type->push_back(prop->getMainName()); } } if (!m_quiet && !(flags & HIDE_LEGEND)) { if (!perPeer.empty() || !perContext.empty() || !global.empty()) { out << endl; } if (!perPeer.empty()) { out << "# per-peer (unshared) properties: " << boost::join(perPeer, ", ") << endl; } if (!perContext.empty()) { out << "# shared by peers in same context: " << boost::join(perContext, ", ") << endl; } if (!global.empty()) { out << "# global properties: " << boost::join(global, ", ") << endl; } } SE_LOG_SHOW(NULL, "%s", out.str().c_str()); } void Cmdline::dumpComment(ostream &stream, const string &prefix, const string &comment) { list commentLines; ConfigProperty::splitComment(comment, commentLines); BOOST_FOREACH(const string &line, commentLines) { stream << prefix << line << endl; } } void Cmdline::usage(bool full, const string &error, const string ¶m) { SE_LOG_SHOW(NULL, "%s", synopsis); if (full) { SE_LOG_SHOW(NULL, "\nOptions:\n%s", options); } if (error != "") { SE_LOG_SHOW(NULL, "\n"); SE_LOG_ERROR(NULL, "%s", error.c_str()); } if (param != "") { SE_LOG_INFO(NULL, "use '%s%s?' to get a list of valid parameters", param.c_str(), boost::ends_with(param, "=") ? "" : " "); } } bool Cmdline::needConfigName() { if (m_server.empty()) { usage(false, "No configuration name specified."); return false; } else { return true; } } SyncContext* Cmdline::createSyncClient() { return new SyncContext(m_server, true); } #ifdef ENABLE_UNIT_TESTS /** simple line-by-line diff */ static string diffStrings(const string &lhs, const string &rhs) { ostringstream res; typedef boost::split_iterator string_split_iterator; string_split_iterator lit = boost::make_split_iterator(lhs, boost::first_finder("\n", boost::is_iequal())); string_split_iterator rit = boost::make_split_iterator(rhs, boost::first_finder("\n", boost::is_iequal())); while (lit != string_split_iterator() && rit != string_split_iterator()) { if (*lit != *rit) { res << "< " << *lit << endl; res << "> " << *rit << endl; } ++lit; ++rit; } while (lit != string_split_iterator()) { res << "< " << *lit << endl; ++lit; } while (rit != string_split_iterator()) { res << "> " << *rit << endl; ++rit; } return res.str(); } # define CPPUNIT_ASSERT_EQUAL_DIFF( expected, actual ) \ do { \ string expected_ = (expected); \ string actual_ = (actual); \ if (expected_ != actual_) { \ CPPUNIT_NS::Message cpputMsg_(string("expected:\n") + \ expected_); \ cpputMsg_.addDetail(string("actual:\n") + \ actual_); \ cpputMsg_.addDetail(string("diff:\n") + \ diffStrings(expected_, actual_)); \ CPPUNIT_NS::Asserter::fail( cpputMsg_, \ CPPUNIT_SOURCELINE() ); \ } \ } while ( false ) // true if = static bool isPropAssignment(const string &buffer) { // ignore these comments (occur in type description) if (boost::starts_with(buffer, "KCalExtended = ") || boost::starts_with(buffer, "mkcal = ") || boost::starts_with(buffer, "QtContacts = ")) { return false; } size_t start = 0; while (start < buffer.size() && !isspace(buffer[start])) { start++; } if (start + 3 <= buffer.size() && buffer.substr(start, 3) == " = ") { return true; } else { return false; } } // remove pure comment lines from buffer, // also empty lines, // also defaultPeer and keyring (because reference properties do not include global props) static string filterConfig(const string &buffer) { ostringstream res; typedef boost::split_iterator string_split_iterator; for (string_split_iterator it = boost::make_split_iterator(buffer, boost::first_finder("\n", boost::is_iequal())); it != string_split_iterator(); ++it) { string line = boost::copy_range(*it); if (!line.empty() && line.find("defaultPeer =") == line.npos && line.find("keyring =") == line.npos && (!boost::starts_with(line, "# ") || isPropAssignment(line.substr(2)))) { res << line << endl; } } return res.str(); } static string removeComments(const string &buffer) { ostringstream res; typedef boost::split_iterator string_split_iterator; for (string_split_iterator it = boost::make_split_iterator(buffer, boost::first_finder("\n", boost::is_iequal())); it != string_split_iterator(); ++it) { string line = boost::copy_range(*it); if (!line.empty() && !boost::starts_with(line, "#")) { res << line << endl; } } return res.str(); } static string injectValues(const string &buffer) { string res = buffer; #if 0 // username/password not set in templates, only in configs created // via the command line - not anymore, but if it ever comes back, // here's the place for it boost::replace_first(res, "# username = ", "username = your SyncML server account name"); boost::replace_first(res, "# password = ", "password = your SyncML server password"); #endif return res; } // remove lines indented with spaces static string filterIndented(const string &buffer) { ostringstream res; bool first = true; typedef boost::split_iterator string_split_iterator; for (string_split_iterator it = boost::make_split_iterator(buffer, boost::first_finder("\n", boost::is_iequal())); it != string_split_iterator(); ++it) { if (!boost::starts_with(*it, " ")) { if (!first) { res << endl; } else { first = false; } res << *it; } } return res.str(); } // sort lines by file, preserving order inside each line static void sortConfig(string &config) { // file name, line number, property typedef pair > line_t; vector lines; typedef boost::split_iterator string_split_iterator; int linenr = 0; for (string_split_iterator it = boost::make_split_iterator(config, boost::first_finder("\n", boost::is_iequal())); it != string_split_iterator(); ++it, ++linenr) { string line(it->begin(), it->end()); if (line.empty()) { continue; } size_t colon = line.find(':'); string prefix = line.substr(0, colon); lines.push_back(make_pair(prefix, make_pair(linenr, line.substr(colon)))); } // stable sort because of line number sort(lines.begin(), lines.end()); size_t len = config.size(); config.resize(0); config.reserve(len); BOOST_FOREACH(const line_t &line, lines) { config += line.first; config += line.second.second; config += "\n"; } } // convert the internal config dump to .ini style (--print-config) static string internalToIni(const string &config) { ostringstream res; string section; typedef boost::split_iterator string_split_iterator; for (string_split_iterator it = boost::make_split_iterator(config, boost::first_finder("\n", boost::is_iequal())); it != string_split_iterator(); ++it) { string line(it->begin(), it->end()); if (line.empty()) { continue; } size_t colon = line.find(':'); string prefix = line.substr(0, colon); // internal values are not part of the --print-config output if (boost::contains(prefix, ".internal.ini") || boost::contains(line, "= internal value")) { continue; } // --print-config also doesn't duplicate the "type" property // => remove the shared property if (boost::contains(line, ":type = ") && boost::starts_with(line, "sources/")) { continue; } // sources//config.ini or // spds/sources//config.ini size_t endslash = prefix.rfind('/'); if (endslash != line.npos && endslash > 1) { size_t slash = prefix.rfind('/', endslash - 1); if (slash != line.npos) { string newsource = prefix.substr(slash + 1, endslash - slash - 1); if (newsource != section && prefix.find("/sources/") != prefix.npos && newsource != "syncml") { res << "[" << newsource << "]" << endl; section = newsource; } } } string assignment = line.substr(colon + 1); // substitude aliases with generic values boost::replace_first(assignment, "= syncml:auth-md5", "= md5"); boost::replace_first(assignment, "= syncml:auth-basix", "= basic"); res << assignment << endl; } return res.str(); } /** result of removeComments(filterRandomUUID(filterConfig())) for Google Calendar template/config */ static const std::string googlecaldav = "syncURL = https://www.google.com/calendar/dav/%u/user/?SyncEvolution=Google\n" "printChanges = 0\n" "dumpData = 0\n" "deviceId = fixed-devid\n" "IconURI = image://themedimage/icons/services/google-calendar\n" "ConsumerReady = 1\n" "peerType = WebDAV\n" "[calendar]\n" "sync = two-way\n" "backend = CalDAV\n"; /** result of removeComments(filterRandomUUID(filterConfig())) for Yahoo Calendar + Contacts */ static const std::string yahoo = "printChanges = 0\n" "dumpData = 0\n" "deviceId = fixed-devid\n" "IconURI = image://themedimage/icons/services/yahoo\n" "ConsumerReady = 1\n" "peerType = WebDAV\n" "[addressbook]\n" "sync = disabled\n" "backend = CardDAV\n" "[calendar]\n" "sync = two-way\n" "backend = CalDAV\n"; // Expected content of config.ini file depends on whether // a keyring is enabled or not. static const char DATABASE_PASSWORD_BAR[] = #ifdef HAVE_KEYRING // In keyring. "databasePassword = -" #else // In file. "databasePassword = bar" #endif ; /** * Testing is based on a text representation of a directory * hierarchy where each line is of the format * : * * The order of files is alphabetical, of lines in the file as * in the file. Lines in the file without line break cannot * be represented. * * The root of the hierarchy is not part of the representation * itself. */ class CmdlineTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(CmdlineTest); CPPUNIT_TEST(testFramework); CPPUNIT_TEST(testSetupScheduleWorld); CPPUNIT_TEST(testFutureConfig); CPPUNIT_TEST(testPeerConfigMigration); CPPUNIT_TEST(testContextConfigMigration); CPPUNIT_TEST(testSetupDefault); CPPUNIT_TEST(testSetupRenamed); CPPUNIT_TEST(testSetupFunambol); CPPUNIT_TEST(testSetupSynthesis); CPPUNIT_TEST(testPrintServers); CPPUNIT_TEST(testPrintFileTemplates); CPPUNIT_TEST(testPrintFileTemplatesConfig); CPPUNIT_TEST(testTemplate); CPPUNIT_TEST(testMatchTemplate); CPPUNIT_TEST(testAddSource); CPPUNIT_TEST(testSync); CPPUNIT_TEST(testKeyring); CPPUNIT_TEST(testWebDAV); CPPUNIT_TEST(testConfigure); CPPUNIT_TEST(testConfigureSources); CPPUNIT_TEST(testOldConfigure); CPPUNIT_TEST(testMigrate); CPPUNIT_TEST(testMigrateContext); CPPUNIT_TEST(testMigrateAutoSync); CPPUNIT_TEST_SUITE_END(); public: CmdlineTest() : m_testDir("CmdlineTest") { } void setUp() { rm_r(m_testDir); mkdir_p(m_testDir); } protected: /** verify that createFiles/scanFiles themselves work */ void testFramework() { const string root(m_testDir); const string content("baz:line\n" "caz/subdir:booh\n" "caz/subdir2/sub:# comment\n" "caz/subdir2/sub:# foo = bar\n" "caz/subdir2/sub:# empty = \n" "caz/subdir2/sub:# another comment\n" "foo:bar1\n" "foo:\n" "foo: \n" "foo:bar2\n"); const string filtered("baz:line\n" "caz/subdir:booh\n" "caz/subdir2/sub:# foo = bar\n" "caz/subdir2/sub:# empty = \n" "foo:bar1\n" "foo: \n" "foo:bar2\n"); createFiles(root, content); string res = scanFiles(root); CPPUNIT_ASSERT_EQUAL_DIFF(filtered, res); } void removeRandomUUID(string &buffer) { string uuidstr = "deviceId = syncevolution-"; size_t uuid = buffer.find(uuidstr); CPPUNIT_ASSERT(uuid != buffer.npos); size_t end = buffer.find("\n", uuid + uuidstr.size()); CPPUNIT_ASSERT(end != buffer.npos); buffer.replace(uuid, end - uuid, "deviceId = fixed-devid"); } string filterRandomUUID(const string &buffer) { string copy = buffer; removeRandomUUID(copy); return copy; } /** create new configurations */ void testSetupScheduleWorld() { doSetupScheduleWorld(false); } void doSetupScheduleWorld(bool shared) { string root; ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates"); ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir); ScopedEnvChange home("HOME", m_testDir); root = m_testDir; root += "/syncevolution/default"; string peer; if (shared) { peer = root + "/peers/scheduleworld"; } else { peer = root; } { rm_r(peer); TestCmdline cmdline("--configure", "--sync-property", "proxyHost = proxy", "scheduleworld", "addressbook", NULL); cmdline.doit(); string res = scanFiles(root); removeRandomUUID(res); string expected = ScheduleWorldConfig(); sortConfig(expected); boost::replace_first(expected, "# proxyHost = ", "proxyHost = proxy"); boost::replace_all(expected, "sync = two-way", "sync = disabled"); boost::replace_first(expected, "addressbook/config.ini:sync = disabled", "addressbook/config.ini:sync = two-way"); CPPUNIT_ASSERT_EQUAL_DIFF(expected, res); } { rm_r(peer); TestCmdline cmdline("--configure", "--sync-property", "deviceID = fixed-devid", "scheduleworld", NULL); cmdline.doit(); string res = scanFiles(root); string expected = ScheduleWorldConfig(); sortConfig(expected); CPPUNIT_ASSERT_EQUAL_DIFF(expected, res); } } void expectTooOld() { bool caught = false; try { SyncConfig config("scheduleworld"); } catch (const StatusException &ex) { caught = true; if (ex.syncMLStatus() != STATUS_RELEASE_TOO_OLD) { throw; } else { CPPUNIT_ASSERT_EQUAL(StringPrintf("SyncEvolution %s is too old to read configuration 'scheduleworld', please upgrade SyncEvolution.", VERSION), string(ex.what())); } } CPPUNIT_ASSERT(caught); } void testFutureConfig() { ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir); ScopedEnvChange home("HOME", m_testDir); doSetupScheduleWorld(false); // bump min/cur version to something not supported, then // try to read => should fail IniFileConfigNode root(m_testDir, "/syncevolution/.internal.ini", false); IniFileConfigNode context(m_testDir + "/syncevolution/default", ".internal.ini", false); IniFileConfigNode peer(m_testDir + "/syncevolution/default/peers/scheduleworld", ".internal.ini", false); root.setProperty("rootMinVersion", StringPrintf("%d", CONFIG_ROOT_MIN_VERSION + 1)); root.setProperty("rootCurVersion", StringPrintf("%d", CONFIG_ROOT_CUR_VERSION + 1)); root.flush(); context.setProperty("contextMinVersion", StringPrintf("%d", CONFIG_CONTEXT_MIN_VERSION + 1)); context.setProperty("contextCurVersion", StringPrintf("%d", CONFIG_CONTEXT_CUR_VERSION + 1)); context.flush(); peer.setProperty("peerMinVersion", StringPrintf("%d", CONFIG_PEER_MIN_VERSION + 1)); peer.setProperty("peerCurVersion", StringPrintf("%d", CONFIG_PEER_CUR_VERSION + 1)); peer.flush(); expectTooOld(); root.setProperty("rootMinVersion", StringPrintf("%d", CONFIG_ROOT_MIN_VERSION)); root.flush(); expectTooOld(); context.setProperty("contextMinVersion", StringPrintf("%d", CONFIG_CONTEXT_MIN_VERSION)); context.flush(); expectTooOld(); // okay now peer.setProperty("peerMinVersion", StringPrintf("%d", CONFIG_PEER_MIN_VERSION)); peer.flush(); SyncConfig config("scheduleworld"); } void expectMigration(const std::string &config) { bool caught = false; try { SyncConfig c(config); c.prepareConfigForWrite(); } catch (const StatusException &ex) { caught = true; if (ex.syncMLStatus() != STATUS_MIGRATION_NEEDED) { throw; } else { CPPUNIT_ASSERT_EQUAL(StringPrintf("Proceeding would modify config '%s' such that the " "previous SyncEvolution release will not be able to use it. " "Stopping now. Please explicitly acknowledge this step by " "running the following command on the command line: " "syncevolution --migrate '%s'", config.c_str(), config.c_str()), string(ex.what())); } } CPPUNIT_ASSERT(caught); } void testPeerConfigMigration() { ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir); ScopedEnvChange home("HOME", m_testDir); doSetupScheduleWorld(false); // decrease min/cur version to something no longer supported, // then try to write => should migrate in release mode and fail otherwise IniFileConfigNode peer(m_testDir + "/syncevolution/default/peers/scheduleworld", ".internal.ini", false); peer.setProperty("peerMinVersion", StringPrintf("%d", CONFIG_PEER_CUR_VERSION - 1)); peer.setProperty("peerCurVersion", StringPrintf("%d", CONFIG_PEER_CUR_VERSION - 1)); peer.flush(); SyncContext::setStableRelease(false); expectMigration("scheduleworld"); SyncContext::setStableRelease(true); { SyncConfig config("scheduleworld"); config.prepareConfigForWrite(); } { TestCmdline cmdline("--print-servers", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("Configured servers:\n" " scheduleworld = CmdlineTest/syncevolution/default/peers/scheduleworld\n" " scheduleworld.old = CmdlineTest/syncevolution/default/peers/scheduleworld.old\n", cmdline.m_out.str()); } // should be okay now SyncContext::setStableRelease(false); { SyncConfig config("scheduleworld"); config.prepareConfigForWrite(); } // do the same migration with command line SyncContext::setStableRelease(false); rm_r(m_testDir + "/syncevolution/default/peers/scheduleworld"); CPPUNIT_ASSERT_EQUAL(0, rename((m_testDir + "/syncevolution/default/peers/scheduleworld.old").c_str(), (m_testDir + "/syncevolution/default/peers/scheduleworld").c_str())); { TestCmdline cmdline("--migrate", "scheduleworld", NULL); cmdline.doit(); } { SyncConfig config("scheduleworld"); config.prepareConfigForWrite(); } { TestCmdline cmdline("--print-servers", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("Configured servers:\n" " scheduleworld = CmdlineTest/syncevolution/default/peers/scheduleworld\n" " scheduleworld.old = CmdlineTest/syncevolution/default/peers/scheduleworld.old\n", cmdline.m_out.str()); } } void testContextConfigMigration() { ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir); ScopedEnvChange home("HOME", m_testDir); doSetupScheduleWorld(false); // decrease min/cur version to something no longer supported, // then try to write => should migrate in release mode and fail otherwise IniFileConfigNode context(m_testDir + "/syncevolution/default", ".internal.ini", false); context.setProperty("contextMinVersion", StringPrintf("%d", CONFIG_CONTEXT_CUR_VERSION - 1)); context.setProperty("contextCurVersion", StringPrintf("%d", CONFIG_CONTEXT_CUR_VERSION - 1)); context.flush(); SyncContext::setStableRelease(false); expectMigration("@default"); SyncContext::setStableRelease(true); { SyncConfig config("@default"); config.prepareConfigForWrite(); } { TestCmdline cmdline("--print-servers", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("Configured servers:\n" " scheduleworld = CmdlineTest/syncevolution/default/peers/scheduleworld\n" " scheduleworld.old@default.old = CmdlineTest/syncevolution/default.old/peers/scheduleworld.old\n", cmdline.m_out.str()); } // should be okay now SyncContext::setStableRelease(false); { SyncConfig config("@default"); config.prepareConfigForWrite(); } // do the same migration with command line SyncContext::setStableRelease(false); rm_r(m_testDir + "/syncevolution/default"); CPPUNIT_ASSERT_EQUAL(0, rename((m_testDir + "/syncevolution/default.old/peers/scheduleworld.old").c_str(), (m_testDir + "/syncevolution/default.old/peers/scheduleworld").c_str())); CPPUNIT_ASSERT_EQUAL(0, rename((m_testDir + "/syncevolution/default.old").c_str(), (m_testDir + "/syncevolution/default").c_str())); { TestCmdline cmdline("--migrate", "@default", NULL); cmdline.doit(); } { SyncConfig config("@default"); config.prepareConfigForWrite(); } { TestCmdline cmdline("--print-servers", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("Configured servers:\n" " scheduleworld = CmdlineTest/syncevolution/default/peers/scheduleworld\n" " scheduleworld.old@default.old = CmdlineTest/syncevolution/default.old/peers/scheduleworld.old\n", cmdline.m_out.str()); } } void testSetupDefault() { string root; ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates"); ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir); ScopedEnvChange home("HOME", m_testDir); root = m_testDir; root += "/syncevolution/default"; TestCmdline cmdline("--configure", "--template", "default", "--sync-property", "deviceID = fixed-devid", "some-other-server", NULL); cmdline.doit(); string res = scanFiles(root, "some-other-server"); string expected = DefaultConfig(); sortConfig(expected); boost::replace_all(expected, "/syncevolution/", "/some-other-server/"); CPPUNIT_ASSERT_EQUAL_DIFF(expected, res); } void testSetupRenamed() { string root; ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates"); ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir); ScopedEnvChange home("HOME", m_testDir); root = m_testDir; root += "/syncevolution/default"; TestCmdline cmdline("--configure", "--template", "scheduleworld", "--sync-property", "deviceID = fixed-devid", "scheduleworld2", NULL); cmdline.doit(); string res = scanFiles(root, "scheduleworld2"); string expected = ScheduleWorldConfig(); sortConfig(expected); boost::replace_all(expected, "/scheduleworld/", "/scheduleworld2/"); CPPUNIT_ASSERT_EQUAL_DIFF(expected, res); } void testSetupFunambol() { doSetupFunambol(false); } void doSetupFunambol(bool shared) { string root; ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates"); ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir); ScopedEnvChange home("HOME", m_testDir); root = m_testDir; root += "/syncevolution/default"; string peer; if (shared) { peer = root + "/peers/funambol"; } else { peer = root; } rm_r(peer); const char * const argv_fixed[] = { "--configure", "--sync-property", "deviceID = fixed-devid", // templates are case-insensitive "FunamBOL", NULL }, * const argv_shared[] = { "--configure", "FunamBOL", NULL }; TestCmdline cmdline(shared ? argv_shared : argv_fixed); cmdline.doit(); string res = scanFiles(root, "funambol"); string expected = FunambolConfig(); sortConfig(expected); CPPUNIT_ASSERT_EQUAL_DIFF(expected, res); } void testSetupSynthesis() { doSetupSynthesis(false); } void doSetupSynthesis(bool shared) { string root; ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates"); ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir); ScopedEnvChange home("HOME", m_testDir); root = m_testDir; root += "/syncevolution/default"; string peer; if (shared) { peer = root + "/peers/synthesis"; } else { peer = root; } rm_r(peer); const char * const argv_fixed[] = { "--configure", "--sync-property", "deviceID = fixed-devid", "synthesis", NULL }, * const argv_shared[] = { "--configure", "synthesis", NULL }; TestCmdline cmdline(shared ? argv_shared : argv_fixed); cmdline.doit(); string res = scanFiles(root, "synthesis"); string expected = SynthesisConfig(); sortConfig(expected); CPPUNIT_ASSERT_EQUAL_DIFF(expected, res); } void testTemplate() { ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates"); ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir); ScopedEnvChange home("HOME", m_testDir); TestCmdline failure("--template", NULL); CPPUNIT_ASSERT(!failure.m_cmdline->parse()); CPPUNIT_ASSERT_NO_THROW(failure.expectUsageError("[ERROR] missing parameter for '--template'\n")); TestCmdline help("--template", "? ", NULL); help.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates (servers):\n" " template name = template description\n" " eGroupware = http://www.egroupware.org\n" " Funambol = http://my.funambol.com\n" " Google_Calendar = event sync via CalDAV, use for the 'target-config@google-calendar' config\n" " Google_Contacts = contact sync via SyncML, see http://www.google.com/support/mobile/bin/topic.py?topic=22181\n" " Goosync = http://www.goosync.com/\n" " Memotoo = http://www.memotoo.com\n" " Mobical = https://www.everdroid.com\n" " Oracle = http://www.oracle.com/technology/products/beehive/index.html\n" " Ovi = http://www.ovi.com\n" " ScheduleWorld = server no longer in operation\n" " SyncEvolution = http://www.syncevolution.org\n" " Synthesis = http://www.synthesis.ch\n" " WebDAV = contact and event sync using WebDAV, use for the 'target-config@' config\n" " Yahoo = contact and event sync using WebDAV, use for the 'target-config@yahoo' config\n", help.m_out.str()); CPPUNIT_ASSERT_EQUAL_DIFF("", help.m_err.str()); } void testMatchTemplate() { ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "testcases/templates"); ScopedEnvChange xdg("XDG_CONFIG_HOME", "/dev/null"); TestCmdline help1("--template", "?nokia 7210c", NULL); help1.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates (clients):\n" " template name = template description matching score in percent (100% = exact match)\n" " Nokia_7210c = Template for Nokia S40 series Phone 100%\n" " SyncEvolution_Client = SyncEvolution server side template 40%\n", help1.m_out.str()); CPPUNIT_ASSERT_EQUAL_DIFF("", help1.m_err.str()); TestCmdline help2("--template", "?nokia", NULL); help2.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates (clients):\n" " template name = template description matching score in percent (100% = exact match)\n" " Nokia_7210c = Template for Nokia S40 series Phone 100%\n" " SyncEvolution_Client = SyncEvolution server side template 40%\n", help2.m_out.str()); CPPUNIT_ASSERT_EQUAL_DIFF("", help2.m_err.str()); TestCmdline help3("--template", "?7210c", NULL); help3.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates (clients):\n" " template name = template description matching score in percent (100% = exact match)\n" " Nokia_7210c = Template for Nokia S40 series Phone 60%\n" " SyncEvolution_Client = SyncEvolution server side template 20%\n", help3.m_out.str()); CPPUNIT_ASSERT_EQUAL_DIFF("", help3.m_err.str()); TestCmdline help4("--template", "?syncevolution client", NULL); help4.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates (clients):\n" " template name = template description matching score in percent (100% = exact match)\n" " SyncEvolution_Client = SyncEvolution server side template 100%\n" " Nokia_7210c = Template for Nokia S40 series Phone 40%\n", help4.m_out.str()); CPPUNIT_ASSERT_EQUAL_DIFF("", help4.m_err.str()); } void testPrintServers() { ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates"); ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir); ScopedEnvChange home("HOME", m_testDir); doSetupScheduleWorld(false); doSetupSynthesis(true); doSetupFunambol(true); TestCmdline cmdline("--print-servers", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("Configured servers:\n" " funambol = CmdlineTest/syncevolution/default/peers/funambol\n" " scheduleworld = CmdlineTest/syncevolution/default/peers/scheduleworld\n" " synthesis = CmdlineTest/syncevolution/default/peers/synthesis\n", cmdline.m_out.str()); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str()); } void testPrintFileTemplates() { // use local copy of templates in build dir (no need to install) ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "./templates"); ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir); ScopedEnvChange home("HOME", m_testDir); doPrintFileTemplates(); } void testPrintFileTemplatesConfig() { // simulate reading templates from user's XDG HOME symlink("../templates", (m_testDir + "/syncevolution-templates").c_str()); ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null"); ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir); ScopedEnvChange home("HOME", m_testDir); doPrintFileTemplates(); } void doPrintFileTemplates() { // Compare only the properties which are really set. // // note that "backend" will be take from the @default context if one // exists, so run this before setting up Funambol below { TestCmdline cmdline("--print-config", "--template", "google calendar", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF(googlecaldav, removeComments(filterRandomUUID(filterConfig(cmdline.m_out.str())))); } { TestCmdline cmdline("--print-config", "--template", "yahoo", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF(yahoo, removeComments(filterRandomUUID(filterConfig(cmdline.m_out.str())))); } testSetupFunambol(); { TestCmdline cmdline("--print-config", "--template", "scheduleworld", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str()); string actual = cmdline.m_out.str(); // deviceId must be the one from Funambol CPPUNIT_ASSERT(boost::contains(actual, "deviceId = fixed-devid")); string filtered = injectValues(filterConfig(actual)); CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(ScheduleWorldConfig())), filtered); // there should have been comments CPPUNIT_ASSERT(actual.size() > filtered.size()); } { TestCmdline cmdline("--print-config", "funambol", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str()); CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(FunambolConfig())), injectValues(filterConfig(cmdline.m_out.str()))); } } void testAddSource() { string root; ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates"); ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir); ScopedEnvChange home("HOME", m_testDir); testSetupScheduleWorld(); root = m_testDir; root += "/syncevolution/default"; { TestCmdline cmdline("--configure", "--source-property", "uri = dummy", "scheduleworld", "xyz", NULL); cmdline.doit(); string res = scanFiles(root); string expected = ScheduleWorldConfig(); expected += "\n" "peers/scheduleworld/sources/xyz/.internal.ini:# adminData = \n" "peers/scheduleworld/sources/xyz/.internal.ini:# synthesisID = 0\n" "peers/scheduleworld/sources/xyz/config.ini:# sync = disabled\n" "peers/scheduleworld/sources/xyz/config.ini:uri = dummy\n" "peers/scheduleworld/sources/xyz/config.ini:# syncFormat = \n" "peers/scheduleworld/sources/xyz/config.ini:# forceSyncFormat = 0\n" "sources/xyz/config.ini:# backend = select backend\n" "sources/xyz/config.ini:# database = \n" "sources/xyz/config.ini:# databaseFormat = \n" "sources/xyz/config.ini:# databaseUser = \n" "sources/xyz/config.ini:# databasePassword = "; sortConfig(expected); CPPUNIT_ASSERT_EQUAL_DIFF(expected, res); } } void testSync() { TestCmdline failure("--sync", NULL); CPPUNIT_ASSERT(!failure.m_cmdline->parse()); CPPUNIT_ASSERT_NO_THROW(failure.expectUsageError("[ERROR] missing parameter for '--sync'\n")); TestCmdline failure2("--sync", "foo", NULL); CPPUNIT_ASSERT(!failure2.m_cmdline->parse()); CPPUNIT_ASSERT_EQUAL_DIFF("", failure2.m_out.str()); CPPUNIT_ASSERT_EQUAL_DIFF("[ERROR] '--sync foo': not one of the valid values (two-way, slow, refresh-from-local, refresh-from-remote = refresh, one-way-from-local, one-way-from-remote = one-way, refresh-from-client = refresh-client, refresh-from-server = refresh-server, one-way-from-client = one-way-client, one-way-from-server = one-way-server, local-cache-slow, local-cache-incremental = local-cache, disabled = none)\n", failure2.m_err.str()); TestCmdline failure3("--sync=foo", NULL); CPPUNIT_ASSERT(!failure3.m_cmdline->parse()); CPPUNIT_ASSERT_EQUAL_DIFF("", failure3.m_out.str()); CPPUNIT_ASSERT_EQUAL_DIFF("[ERROR] '--sync=foo': not one of the valid values (two-way, slow, refresh-from-local, refresh-from-remote = refresh, one-way-from-local, one-way-from-remote = one-way, refresh-from-client = refresh-client, refresh-from-server = refresh-server, one-way-from-client = one-way-client, one-way-from-server = one-way-server, local-cache-slow, local-cache-incremental = local-cache, disabled = none)\n", failure3.m_err.str()); TestCmdline help("--sync", " ?", NULL); help.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("--sync\n" " Requests a certain synchronization mode when initiating a sync:\n" " \n" " two-way\n" " only send/receive changes since last sync\n" " slow\n" " exchange all items\n" " refresh-from-remote\n" " discard all local items and replace with\n" " the items on the peer\n" " refresh-from-local\n" " discard all items on the peer and replace\n" " with the local items\n" " one-way-from-remote\n" " transmit changes from peer\n" " one-way-from-local\n" " transmit local changes\n" " local-cache-slow (server only)\n" " mirror remote data locally, transferring all data\n" " local-cache-incremental (server only)\n" " mirror remote data locally, transferring only changes;\n" " falls back to local-cache-slow automatically if necessary\n" " disabled (or none)\n" " synchronization disabled\n" " \n" " refresh/one-way-from-server/client are also supported. Their use is\n" " discouraged because the direction of the data transfer depends\n" " on the role of the local side (can be server or client), which is\n" " not always obvious.\n" " \n" " When accepting a sync session in a SyncML server (HTTP server), only\n" " sources with sync != disabled are made available to the client,\n" " which chooses the final sync mode based on its own configuration.\n" " When accepting a sync session in a SyncML client (local sync with\n" " the server contacting SyncEvolution on a device), the sync mode\n" " specified in the client is typically overriden by the server.\n", help.m_out.str()); CPPUNIT_ASSERT_EQUAL_DIFF("", help.m_err.str()); TestCmdline filter("--sync", "refresh-from-server", NULL); CPPUNIT_ASSERT(filter.m_cmdline->parse()); CPPUNIT_ASSERT(!filter.m_cmdline->run()); CPPUNIT_ASSERT_NO_THROW(filter.expectUsageError("[ERROR] No configuration name specified.\n")); CPPUNIT_ASSERT_EQUAL_DIFF("sync = refresh-from-server", string(filter.m_cmdline->m_props[""].m_sourceProps[""])); CPPUNIT_ASSERT_EQUAL_DIFF("", string(filter.m_cmdline->m_props[""].m_syncProps)); TestCmdline filter2("--source-property", "sync=refresh", NULL); CPPUNIT_ASSERT(filter2.m_cmdline->parse()); CPPUNIT_ASSERT(!filter2.m_cmdline->run()); CPPUNIT_ASSERT_NO_THROW(filter2.expectUsageError("[ERROR] No configuration name specified.\n")); CPPUNIT_ASSERT_EQUAL_DIFF("sync = refresh", string(filter2.m_cmdline->m_props[""].m_sourceProps[""])); CPPUNIT_ASSERT_EQUAL_DIFF("", string(filter2.m_cmdline->m_props[""].m_syncProps)); TestCmdline filter3("--source-property", "xyz=1", NULL); CPPUNIT_ASSERT(!filter3.m_cmdline->parse()); CPPUNIT_ASSERT_EQUAL(string(""), filter3.m_out.str()); CPPUNIT_ASSERT_EQUAL(string("[ERROR] '--source-property xyz=1': no such property\n"), filter3.m_err.str()); TestCmdline filter4("xyz=1", NULL); CPPUNIT_ASSERT(!filter4.m_cmdline->parse()); CPPUNIT_ASSERT_NO_THROW(filter4.expectUsageError("[ERROR] unrecognized property in 'xyz=1'\n")); TestCmdline filter5("=1", NULL); CPPUNIT_ASSERT(!filter5.m_cmdline->parse()); CPPUNIT_ASSERT_NO_THROW(filter5.expectUsageError("[ERROR] a property name must be given in '=1'\n")); } void testKeyring() { ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir); ScopedEnvChange home("HOME", m_testDir); rm_r(m_testDir); { TestCmdline cmdline(NULL, NULL); boost::shared_ptr context = cmdline.parse(); CPPUNIT_ASSERT(context); InitStateTri keyring = context->getKeyring(); CPPUNIT_ASSERT_EQUAL(false, keyring.wasSet()); CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_TRUE, keyring.getValue()); } { TestCmdline cmdline("--keyring", NULL); boost::shared_ptr context = cmdline.parse(); CPPUNIT_ASSERT(context); InitStateTri keyring = context->getKeyring(); CPPUNIT_ASSERT_EQUAL(true, keyring.wasSet()); CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_TRUE, keyring.getValue()); } { TestCmdline cmdline("--sync-property", "keyring=True", NULL); boost::shared_ptr context = cmdline.parse(); CPPUNIT_ASSERT(context); InitStateTri keyring = context->getKeyring(); CPPUNIT_ASSERT_EQUAL(true, keyring.wasSet()); CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_TRUE, keyring.getValue()); } { TestCmdline cmdline("keyring=True", NULL); boost::shared_ptr context = cmdline.parse(); CPPUNIT_ASSERT(context); InitStateTri keyring = context->getKeyring(); CPPUNIT_ASSERT_EQUAL(true, keyring.wasSet()); CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_TRUE, keyring.getValue()); } { TestCmdline cmdline("--keyring=true", NULL); boost::shared_ptr context = cmdline.parse(); CPPUNIT_ASSERT(context); InitStateTri keyring = context->getKeyring(); CPPUNIT_ASSERT_EQUAL(true, keyring.wasSet()); CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_TRUE, keyring.getValue()); } { TestCmdline cmdline("--keyring=1", NULL); boost::shared_ptr context = cmdline.parse(); CPPUNIT_ASSERT(context); InitStateTri keyring = context->getKeyring(); CPPUNIT_ASSERT_EQUAL(true, keyring.wasSet()); CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_TRUE, keyring.getValue()); } { TestCmdline cmdline("--keyring=Yes", NULL); boost::shared_ptr context = cmdline.parse(); CPPUNIT_ASSERT(context); InitStateTri keyring = context->getKeyring(); CPPUNIT_ASSERT_EQUAL(true, keyring.wasSet()); CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_TRUE, keyring.getValue()); } { TestCmdline cmdline("--keyring=false", NULL); boost::shared_ptr context = cmdline.parse(); CPPUNIT_ASSERT(context); InitStateTri keyring = context->getKeyring(); CPPUNIT_ASSERT_EQUAL(true, keyring.wasSet()); CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_FALSE, keyring.getValue()); } { TestCmdline cmdline("--keyring=0", NULL); boost::shared_ptr context = cmdline.parse(); CPPUNIT_ASSERT(context); InitStateTri keyring = context->getKeyring(); CPPUNIT_ASSERT_EQUAL(true, keyring.wasSet()); CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_FALSE, keyring.getValue()); } { TestCmdline cmdline("--keyring=NO", NULL); boost::shared_ptr context = cmdline.parse(); CPPUNIT_ASSERT(context); InitStateTri keyring = context->getKeyring(); CPPUNIT_ASSERT_EQUAL(true, keyring.wasSet()); CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_FALSE, keyring.getValue()); } { TestCmdline cmdline("--keyring=GNOME", NULL); boost::shared_ptr context = cmdline.parse(); CPPUNIT_ASSERT(context); InitStateTri keyring = context->getKeyring(); CPPUNIT_ASSERT_EQUAL(true, keyring.wasSet()); CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_STRING, keyring.getValue()); CPPUNIT_ASSERT_EQUAL(std::string("GNOME"), keyring.get()); } // Broken command line: treated like a sync, but config doesn't exist. { TestCmdline cmdline("keyring=KDE", "@foobar", NULL); cmdline.doit(false); CPPUNIT_ASSERT_EQUAL(std::string(""), cmdline.m_out.str()); CPPUNIT_ASSERT_EQUAL(std::string("[INFO] Configuration \"@foobar\" does not refer to a sync peer.\n[ERROR] Cannot proceed with sync without a configuration."), cmdline.m_err.str()); } { TestCmdline cmdline("keyring=KDE", "nosuchpeer@foobar", NULL); cmdline.doit(false); CPPUNIT_ASSERT_EQUAL(std::string(""), cmdline.m_out.str()); CPPUNIT_ASSERT_EQUAL(std::string("[INFO] Configuration \"nosuchpeer@foobar\" does not exist.\n[ERROR] Cannot proceed with sync without a configuration."), cmdline.m_err.str()); } // empty config prop { TestCmdline cmdline("--configure", "@default", NULL); cmdline.doit(); } // Try broken command line again. { TestCmdline cmdline("keyring=KDE", "@foobar", NULL); cmdline.doit(false); CPPUNIT_ASSERT_EQUAL(std::string(""), cmdline.m_out.str()); CPPUNIT_ASSERT_EQUAL(std::string("[INFO] Configuration \"@foobar\" does not refer to a sync peer.\n[ERROR] Cannot proceed with sync without a configuration."), cmdline.m_err.str()); } { TestCmdline cmdline("keyring=KDE", "nosuchpeer@foobar", NULL); cmdline.doit(false); CPPUNIT_ASSERT_EQUAL(std::string(""), cmdline.m_out.str()); CPPUNIT_ASSERT_EQUAL(std::string("[INFO] Configuration \"nosuchpeer@foobar\" does not exist.\n[ERROR] Cannot proceed with sync without a configuration."), cmdline.m_err.str()); } { TestCmdline cmdline("@foobar", NULL); boost::shared_ptr context = cmdline.parse(); CPPUNIT_ASSERT(context); InitStateTri keyring = context->getKeyring(); CPPUNIT_ASSERT_EQUAL(false, keyring.wasSet()); CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_TRUE, keyring.getValue()); } // now set the value permanently { TestCmdline cmdline("--keyring", "--configure", "@default", NULL); cmdline.doit(); boost::shared_ptr context = cmdline.parse(); CPPUNIT_ASSERT(context); InitStateTri keyring = context->getKeyring(); CPPUNIT_ASSERT_EQUAL(true, keyring.wasSet()); CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_TRUE, keyring.getValue()); } { TestCmdline cmdline("--keyring=KDE", "--configure", "@default", NULL); cmdline.doit(); boost::shared_ptr context = cmdline.parse(); CPPUNIT_ASSERT(context); InitStateTri keyring = context->getKeyring(); CPPUNIT_ASSERT_EQUAL(true, keyring.wasSet()); CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_STRING, keyring.getValue()); CPPUNIT_ASSERT_EQUAL(std::string("KDE"), keyring.get()); } // create by setting keyring in @default, then update; // @default not strictly needed rm_r(m_testDir); { TestCmdline cmdline("keyring=KDE", "--configure", "@default", NULL); cmdline.doit(); boost::shared_ptr context = cmdline.parse(); CPPUNIT_ASSERT(context); InitStateTri keyring = context->getKeyring(); CPPUNIT_ASSERT_EQUAL(true, keyring.wasSet()); CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_STRING, keyring.getValue()); } { TestCmdline cmdline("keyring=yes", "--configure", "@default", NULL); cmdline.doit(); boost::shared_ptr context = cmdline.parse(); CPPUNIT_ASSERT(context); InitStateTri keyring = context->getKeyring(); CPPUNIT_ASSERT_EQUAL(true, keyring.wasSet()); CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_TRUE, keyring.getValue()); } // allow sync operation although --keyring was set { TestCmdline cmdline("keyring=GNOME", "foobar@default", NULL); cmdline.doit(false); CPPUNIT_ASSERT_EQUAL(std::string(""), cmdline.m_out.str()); CPPUNIT_ASSERT_EQUAL(std::string("[INFO] Configuration \"foobar@default\" does not exist.\n[ERROR] Cannot proceed with sync without a configuration."), cmdline.m_err.str()); } // catch invalid "keyring" value { TestCmdline cmdline("--configure", "username=foo", "password=bar", "syncURL=http://no.such.server", "keyring=no-such-keyring", "foobar@default", NULL); cmdline.doit(false); CPPUNIT_ASSERT_EQUAL(std::string(""), cmdline.m_out.str()); CPPUNIT_ASSERT_EQUAL(std::string("[INFO] addressbook: looking for databases...\n" "[INFO] addressbook: okay\n" "[INFO] calendar: looking for databases...\n" "[INFO] calendar: okay\n" "[INFO] memo: looking for databases...\n" "[INFO] memo: okay\n" "[INFO] todo: looking for databases...\n" "[INFO] todo: okay\n" "[ERROR] Unsupported value for the \"keyring\" property, no such keyring found: no-such-keyring"), cmdline.m_err.str()); } } void testWebDAV() { #ifdef ENABLE_DAV ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates"); ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir); ScopedEnvChange home("HOME", m_testDir); // configure Yahoo under a different name, with explicit template selection { TestCmdline cmdline("--configure", "--template", "yahoo", "target-config@my-yahoo", NULL); cmdline.doit(); } { TestCmdline cmdline("--print-config", "target-config@my-yahoo", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF(yahoo, removeComments(filterRandomUUID(filterConfig(cmdline.m_out.str())))); } // configure Google Calendar with template derived from config name { TestCmdline cmdline("--configure", "target-config@google-calendar", NULL); cmdline.doit(); } { TestCmdline cmdline("--print-config", "target-config@google-calendar", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF(googlecaldav, removeComments(filterRandomUUID(filterConfig(cmdline.m_out.str())))); } // test "template not found" error cases { TestCmdline cmdline("--configure", "--template", "yahooxyz", "target-config@my-yahoo-xyz", NULL); CPPUNIT_ASSERT(cmdline.m_cmdline->parse()); CPPUNIT_ASSERT(!cmdline.m_cmdline->run()); static const char error[] = "[ERROR] No configuration template for 'yahooxyz' available.\n" "[INFO] \n" "[INFO] Available configuration templates (clients and servers):\n"; std::string out = cmdline.m_out.str(); std::string err = cmdline.m_err.str(); std::string all = cmdline.m_all.str(); CPPUNIT_ASSERT(boost::starts_with(err, error)); CPPUNIT_ASSERT(boost::ends_with(err, "\n")); CPPUNIT_ASSERT(!boost::ends_with(err, "\n\n")); CPPUNIT_ASSERT_EQUAL(string(""), out); CPPUNIT_ASSERT_EQUAL(all, err); } { TestCmdline cmdline("--configure", "target-config@foobar", NULL); CPPUNIT_ASSERT(cmdline.m_cmdline->parse()); CPPUNIT_ASSERT(!cmdline.m_cmdline->run()); static const char error[] = "[ERROR] No configuration template for 'foobar' available.\n" "[INFO] Use '--template none' and/or specify relevant properties on the command line to create a configuration without a template. Need values for: syncURL\n" "[INFO] \n" "[INFO] Available configuration templates (clients and servers):\n"; std::string out = cmdline.m_out.str(); std::string err = cmdline.m_err.str(); std::string all = cmdline.m_all.str(); CPPUNIT_ASSERT(boost::starts_with(err, error)); CPPUNIT_ASSERT(boost::ends_with(err, "\n")); CPPUNIT_ASSERT(!boost::ends_with(err, "\n\n")); CPPUNIT_ASSERT_EQUAL(string(""), out); CPPUNIT_ASSERT_EQUAL(err, all); } #endif } void testConfigure() { ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates"); ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir); ScopedEnvChange home("HOME", m_testDir); testSetupScheduleWorld(); string expected = doConfigure(ScheduleWorldConfig(), "sources/addressbook/config.ini:"); { // updating "type" for peer is mapped to updating "backend", // "databaseFormat", "syncFormat", "forceSyncFormat" TestCmdline cmdline("--configure", "--source-property", "addressbook/type=file:text/vcard:3.0", "scheduleworld", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str()); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str()); boost::replace_first(expected, "backend = addressbook", "backend = file"); boost::replace_first(expected, "# databaseFormat = ", "databaseFormat = text/vcard"); CPPUNIT_ASSERT_EQUAL_DIFF(expected, filterConfig(printConfig("scheduleworld"))); string shared = filterConfig(printConfig("@default")); CPPUNIT_ASSERT(shared.find("backend = file") != shared.npos); CPPUNIT_ASSERT(shared.find("databaseFormat = text/vcard") != shared.npos); } { // updating type for context must not affect peer TestCmdline cmdline("--configure", "--source-property", "type=file:text/x-vcard:2.1", "@default", "addressbook", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str()); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str()); boost::replace_first(expected, "databaseFormat = text/vcard", "databaseFormat = text/x-vcard"); CPPUNIT_ASSERT_EQUAL_DIFF(expected, filterConfig(printConfig("scheduleworld"))); string shared = filterConfig(printConfig("@default")); CPPUNIT_ASSERT(shared.find("backend = file") != shared.npos); CPPUNIT_ASSERT(shared.find("databaseFormat = text/x-vcard") != shared.npos); } string syncProperties("syncURL (no default, unshared, required)\n" "\n" "username (no default, unshared)\n" "\n" "password (no default, unshared)\n" "\n" "logdir (no default, shared)\n" "\n" "loglevel (0, unshared)\n" "\n" "notifyLevel (3, unshared)\n" "\n" "printChanges (TRUE, unshared)\n" "\n" "dumpData (TRUE, unshared)\n" "\n" "maxlogdirs (10, shared)\n" "\n" "autoSync (0, unshared)\n" "\n" "autoSyncInterval (30M, unshared)\n" "\n" "autoSyncDelay (5M, unshared)\n" "\n" "preventSlowSync (TRUE, unshared)\n" "\n" "useProxy (FALSE, unshared)\n" "\n" "proxyHost (no default, unshared)\n" "\n" "proxyUsername (no default, unshared)\n" "\n" "proxyPassword (no default, unshared)\n" "\n" "clientAuthType (md5, unshared)\n" "\n" "RetryDuration (5M, unshared)\n" "\n" "RetryInterval (2M, unshared)\n" "\n" "remoteIdentifier (no default, unshared)\n" "\n" "PeerIsClient (FALSE, unshared)\n" "\n" "SyncMLVersion (no default, unshared)\n" "\n" "PeerName (no default, unshared)\n" "\n" "deviceId (no default, shared)\n" "\n" "remoteDeviceId (no default, unshared)\n" "\n" "enableWBXML (TRUE, unshared)\n" "\n" "enableRefreshSync (FALSE, unshared)\n" "\n" "maxMsgSize (150000, unshared), maxObjSize (4000000, unshared)\n" "\n" "SSLServerCertificates (" SYNCEVOLUTION_SSL_SERVER_CERTIFICATES ", unshared)\n" "\n" "SSLVerifyServer (TRUE, unshared)\n" "\n" "SSLVerifyHost (TRUE, unshared)\n" "\n" "WebURL (no default, unshared)\n" "\n" "IconURI (no default, unshared)\n" "\n" "ConsumerReady (FALSE, unshared)\n" "\n" "peerType (no default, unshared)\n" "\n" "defaultPeer (no default, global)\n" "\n" "keyring (yes, global)\n"); string sourceProperties("sync (disabled, unshared, required)\n" "\n" "uri (no default, unshared)\n" "\n" "backend (select backend, shared)\n" "\n" "syncFormat (no default, unshared)\n" "\n" "forceSyncFormat (FALSE, unshared)\n" "\n" "database = evolutionsource (no default, shared)\n" "\n" "databaseFormat (no default, shared)\n" "\n" "databaseUser = evolutionuser (no default, shared), databasePassword = evolutionpassword (no default, shared)\n"); { TestCmdline cmdline("--sync-property", "?", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str()); CPPUNIT_ASSERT_EQUAL_DIFF(syncProperties, filterIndented(cmdline.m_out.str())); } { TestCmdline cmdline("--source-property", "?", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str()); CPPUNIT_ASSERT_EQUAL_DIFF(sourceProperties, filterIndented(cmdline.m_out.str())); } { TestCmdline cmdline("--source-property", "?", "--sync-property", "?", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str()); CPPUNIT_ASSERT_EQUAL_DIFF(sourceProperties + syncProperties, filterIndented(cmdline.m_out.str())); } { TestCmdline cmdline("--sync-property", "?", "--source-property", "?", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str()); CPPUNIT_ASSERT_EQUAL_DIFF(syncProperties + sourceProperties, filterIndented(cmdline.m_out.str())); } { TestCmdline cmdline("--source-property", "sync=?", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str()); CPPUNIT_ASSERT_EQUAL_DIFF("'--source-property sync=?'\n", filterIndented(cmdline.m_out.str())); } { TestCmdline cmdline("sync=?", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str()); CPPUNIT_ASSERT_EQUAL_DIFF("'sync=?'\n", filterIndented(cmdline.m_out.str())); } { TestCmdline cmdline("syncURL=?", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str()); CPPUNIT_ASSERT_EQUAL_DIFF("'syncURL=?'\n", filterIndented(cmdline.m_out.str())); } } void testConfigureSources() { ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates"); ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir); ScopedEnvChange home("HOME", m_testDir); // create from scratch with only addressbook configured { TestCmdline cmdline("--configure", "--source-property", "database = file://tmp/test", "--source-property", "type = file:text/x-vcard", "@foobar", "addressbook", NULL); cmdline.doit(); } string root = m_testDir; root += "/syncevolution/foobar"; string res = scanFiles(root); removeRandomUUID(res); string expected = StringPrintf(".internal.ini:contextMinVersion = %d\n" ".internal.ini:contextCurVersion = %d\n" "config.ini:# logdir = \n" "config.ini:# maxlogdirs = 10\n" "config.ini:deviceId = fixed-devid\n" "sources/addressbook/config.ini:backend = file\n" "sources/addressbook/config.ini:database = file://tmp/test\n" "sources/addressbook/config.ini:databaseFormat = text/x-vcard\n" "sources/addressbook/config.ini:# databaseUser = \n" "sources/addressbook/config.ini:# databasePassword = \n", CONFIG_CONTEXT_MIN_VERSION, CONFIG_CONTEXT_CUR_VERSION); CPPUNIT_ASSERT_EQUAL_DIFF(expected, res); // add calendar { TestCmdline cmdline("--configure", "--source-property", "database@foobar = file://tmp/test2", "--source-property", "backend = calendar", "@foobar", "calendar", NULL); cmdline.doit(); } res = scanFiles(root); removeRandomUUID(res); expected += "sources/calendar/config.ini:backend = calendar\n" "sources/calendar/config.ini:database = file://tmp/test2\n" "sources/calendar/config.ini:# databaseFormat = \n" "sources/calendar/config.ini:# databaseUser = \n" "sources/calendar/config.ini:# databasePassword = \n"; CPPUNIT_ASSERT_EQUAL_DIFF(expected, res); // add ScheduleWorld peer: must reuse existing backend settings { TestCmdline cmdline("--configure", "scheduleworld@foobar", NULL); cmdline.doit(); } res = scanFiles(root); removeRandomUUID(res); expected = ScheduleWorldConfig(); boost::replace_all(expected, "addressbook/config.ini:backend = addressbook", "addressbook/config.ini:backend = file"); boost::replace_all(expected, "addressbook/config.ini:# database = ", "addressbook/config.ini:database = file://tmp/test"); boost::replace_all(expected, "addressbook/config.ini:# databaseFormat = ", "addressbook/config.ini:databaseFormat = text/x-vcard"); boost::replace_all(expected, "calendar/config.ini:# database = ", "calendar/config.ini:database = file://tmp/test2"); sortConfig(expected); CPPUNIT_ASSERT_EQUAL_DIFF(expected, res); // disable all sources except for addressbook { TestCmdline cmdline("--configure", "--source-property", "addressbook/sync=two-way", "--source-property", "sync=none", "scheduleworld@foobar", NULL); cmdline.doit(); } res = scanFiles(root); removeRandomUUID(res); boost::replace_all(expected, "sync = two-way", "sync = disabled"); boost::replace_first(expected, "sync = disabled", "sync = two-way"); CPPUNIT_ASSERT_EQUAL_DIFF(expected, res); // override type in template while creating from scratch { TestCmdline cmdline("--configure", "--template", "SyncEvolution", "--source-property", "addressbook/type=file:text/vcard:3.0", "--source-property", "calendar/type=file:text/calendar:2.0", "syncevo@syncevo", NULL); cmdline.doit(); } string syncevoroot = m_testDir + "/syncevolution/syncevo"; res = scanFiles(syncevoroot + "/sources/addressbook"); CPPUNIT_ASSERT(res.find("backend = file\n") != res.npos); CPPUNIT_ASSERT(res.find("databaseFormat = text/vcard\n") != res.npos); res = scanFiles(syncevoroot + "/sources/calendar"); CPPUNIT_ASSERT(res.find("backend = file\n") != res.npos); CPPUNIT_ASSERT(res.find("databaseFormat = text/calendar\n") != res.npos); } void testOldConfigure() { ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates"); ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir); ScopedEnvChange home("HOME", m_testDir); string oldConfig = OldScheduleWorldConfig(); InitList props = InitList("serverNonce") + "clientNonce" + "devInfoHash" + "HashCode" + "ConfigDate" + "deviceData" + "adminData" + "synthesisID" + "rootMinVersion" + "rootCurVersion" + "contextMinVersion" + "contextCurVersion" + "peerMinVersion" + "peerCurVersion" + "lastNonce" + "last"; BOOST_FOREACH(string &prop, props) { boost::replace_all(oldConfig, prop + " = ", prop + " = internal value"); } rm_r(m_testDir); createFiles(m_testDir + "/.sync4j/evolution/scheduleworld", oldConfig); // Cannot read/and write old format anymore. SyncContext::setStableRelease(false); expectMigration("scheduleworld"); // Migrate explicitly. { TestCmdline cmdline("--migrate", "scheduleworld", NULL); cmdline.doit(); } // now test with new format string expected = ScheduleWorldConfig(); boost::replace_first(expected, "# ConsumerReady = 0", "ConsumerReady = 1"); boost::replace_first(expected, "# database = ", "database = xyz"); boost::replace_first(expected, "# databaseUser = ", "databaseUser = foo"); boost::replace_first(expected, "# databasePassword = ", DATABASE_PASSWORD_BAR); // migrating "type" sets forceSyncFormat if not the default, // and databaseFormat (if format was part of type, as for addressbook) boost::replace_first(expected, "# databaseFormat = ", "databaseFormat = text/vcard"); doConfigure(expected, "sources/addressbook/config.ini:"); } string doConfigure(const string &SWConfig, const string &addressbookPrefix) { string expected; { TestCmdline cmdline("--configure", "--source-property", "sync = disabled", "scheduleworld", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str()); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str()); expected = filterConfig(internalToIni(SWConfig)); boost::replace_all(expected, "sync = two-way", "sync = disabled"); CPPUNIT_ASSERT_EQUAL_DIFF(expected, filterConfig(printConfig("scheduleworld"))); } { TestCmdline cmdline("--configure", "--source-property", "sync = one-way-from-server", "scheduleworld", "addressbook", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str()); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str()); expected = SWConfig; boost::replace_all(expected, "sync = two-way", "sync = disabled"); boost::replace_first(expected, addressbookPrefix + "sync = disabled", addressbookPrefix + "sync = one-way-from-server"); expected = filterConfig(internalToIni(expected)); CPPUNIT_ASSERT_EQUAL_DIFF(expected, filterConfig(printConfig("scheduleworld"))); } { TestCmdline cmdline("--configure", "--sync", "two-way", "-z", "database=source", // note priority of suffix: most specific wins "--sync-property", "maxlogdirs@scheduleworld@default=20", "--sync-property", "maxlogdirs@default=10", "--sync-property", "maxlogdirs=5", "-y", "LOGDIR@default=logdir", "scheduleworld", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str()); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str()); boost::replace_all(expected, "sync = one-way-from-server", "sync = two-way"); boost::replace_all(expected, "sync = disabled", "sync = two-way"); boost::replace_all(expected, "# database = ", "database = source"); boost::replace_all(expected, "database = xyz", "database = source"); boost::replace_all(expected, "# maxlogdirs = 10", "maxlogdirs = 20"); boost::replace_all(expected, "# logdir = ", "logdir = logdir"); CPPUNIT_ASSERT_EQUAL_DIFF(expected, filterConfig(printConfig("scheduleworld"))); } return expected; } void testMigrate() { ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates"); ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir); ScopedEnvChange home("HOME", m_testDir); string oldRoot = m_testDir + "/.sync4j/evolution/scheduleworld"; string newRoot = m_testDir + "/syncevolution/default"; string oldConfig = OldScheduleWorldConfig(); { // migrate old config createFiles(oldRoot, oldConfig); string createdConfig = scanFiles(oldRoot); TestCmdline cmdline("--migrate", "scheduleworld", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str()); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str()); string migratedConfig = scanFiles(newRoot); string expected = ScheduleWorldConfig(); sortConfig(expected); // migrating SyncEvolution < 1.2 configs sets // ConsumerReady, to keep config visible in the updated // sync-ui boost::replace_all(expected, "# ConsumerReady = 0", "ConsumerReady = 1"); boost::replace_first(expected, "# database = ", "database = xyz"); boost::replace_first(expected, "# databaseUser = ", "databaseUser = foo"); boost::replace_first(expected, "# databasePassword = ", DATABASE_PASSWORD_BAR); // migrating "type" sets forceSyncFormat if different from the "false" default // and databaseFormat (if format was part of type, as for addressbook) boost::replace_first(expected, "# databaseFormat = ", "databaseFormat = text/vcard"); CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig); string renamedConfig = scanFiles(oldRoot + ".old"); CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig); } { // rewrite existing config with obsolete properties // => these properties should get removed // // There is one limitation: shared nodes are not rewritten. // This is acceptable. createFiles(newRoot + "/peers/scheduleworld", "config.ini:# obsolete comment\n" "config.ini:obsoleteprop = foo\n", true); string createdConfig = scanFiles(newRoot, "scheduleworld"); TestCmdline cmdline("--migrate", "scheduleworld", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str()); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str()); string migratedConfig = scanFiles(newRoot, "scheduleworld"); string expected = ScheduleWorldConfig(); sortConfig(expected); boost::replace_all(expected, "# ConsumerReady = 0", "ConsumerReady = 1"); boost::replace_first(expected, "# database = ", "database = xyz"); boost::replace_first(expected, "# databaseUser = ", "databaseUser = foo"); boost::replace_first(expected, "# databasePassword = ", DATABASE_PASSWORD_BAR); boost::replace_first(expected, "# databaseFormat = ", "databaseFormat = text/vcard"); CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig); string renamedConfig = scanFiles(newRoot, "scheduleworld.old.1"); boost::replace_first(createdConfig, "ConsumerReady = 1", "ConsumerReady = 0"); boost::replace_all(createdConfig, "/scheduleworld/", "/scheduleworld.old.1/"); CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig); } { // migrate old config with changes and .synthesis directory, a second time createFiles(oldRoot, oldConfig); createFiles(oldRoot, ".synthesis/dummy-file.bfi:dummy = foobar\n" "spds/sources/addressbook/changes/config.txt:foo = bar\n" "spds/sources/addressbook/changes/config.txt:foo2 = bar2\n", true); string createdConfig = scanFiles(oldRoot); rm_r(newRoot); TestCmdline cmdline("--migrate", "scheduleworld", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str()); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str()); string migratedConfig = scanFiles(newRoot); string expected = ScheduleWorldConfig(); sortConfig(expected); boost::replace_all(expected, "# ConsumerReady = 0", "ConsumerReady = 1"); boost::replace_first(expected, "# database = ", "database = xyz"); boost::replace_first(expected, "# databaseUser = ", "databaseUser = foo"); boost::replace_first(expected, "# databasePassword = ", DATABASE_PASSWORD_BAR); boost::replace_first(expected, "# databaseFormat = ", "databaseFormat = text/vcard"); boost::replace_first(expected, "peers/scheduleworld/sources/addressbook/config.ini", "peers/scheduleworld/sources/addressbook/.other.ini:foo = bar\n" "peers/scheduleworld/sources/addressbook/.other.ini:foo2 = bar2\n" "peers/scheduleworld/sources/addressbook/config.ini"); boost::replace_first(expected, "peers/scheduleworld/config.ini", "peers/scheduleworld/.synthesis/dummy-file.bfi:dummy = foobar\n" "peers/scheduleworld/config.ini"); CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig); string renamedConfig = scanFiles(oldRoot + ".old.1"); boost::replace_first(createdConfig, "ConsumerReady = 1", "ConsumerReady = 0"); CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig); } { string otherRoot = m_testDir + "/syncevolution/other"; rm_r(otherRoot); // migrate old config into non-default context createFiles(oldRoot, oldConfig); string createdConfig = scanFiles(oldRoot); { TestCmdline cmdline("--migrate", "scheduleworld@other", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str()); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str()); } string migratedConfig = scanFiles(otherRoot); string expected = ScheduleWorldConfig(); sortConfig(expected); boost::replace_all(expected, "# ConsumerReady = 0", "ConsumerReady = 1"); boost::replace_first(expected, "# database = ", "database = xyz"); boost::replace_first(expected, "# databaseUser = ", "databaseUser = foo"); boost::replace_first(expected, "# databasePassword = ", DATABASE_PASSWORD_BAR); boost::replace_first(expected, "# databaseFormat = ", "databaseFormat = text/vcard"); CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig); string renamedConfig = scanFiles(oldRoot + ".old"); CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig); // migrate the migrated config again inside the "other" context, // with no "default" context which might interfere with the tests // // ConsumerReady was set as part of previous migration, // must be removed during migration to hide the migrated // config from average users. rm_r(newRoot); { TestCmdline cmdline("--migrate", "scheduleworld@other", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str()); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str()); } migratedConfig = scanFiles(otherRoot, "scheduleworld"); expected = ScheduleWorldConfig(); sortConfig(expected); boost::replace_all(expected, "# ConsumerReady = 0", "ConsumerReady = 1"); boost::replace_first(expected, "# database = ", "database = xyz"); boost::replace_first(expected, "# databaseUser = ", "databaseUser = foo"); boost::replace_first(expected, "# databasePassword = ", DATABASE_PASSWORD_BAR); boost::replace_first(expected, "# databaseFormat = ", "databaseFormat = text/vcard"); CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig); renamedConfig = scanFiles(otherRoot, "scheduleworld.old.3"); boost::replace_all(expected, "/scheduleworld/", "/scheduleworld.old.3/"); boost::replace_all(expected, "ConsumerReady = 1", "ConsumerReady = 0"); CPPUNIT_ASSERT_EQUAL_DIFF(expected, renamedConfig); // migrate once more, this time without the explicit context in // the config name => must not change the context, need second .old dir { TestCmdline cmdline("--migrate", "scheduleworld", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str()); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str()); } migratedConfig = scanFiles(otherRoot, "scheduleworld"); boost::replace_all(expected, "/scheduleworld.old.3/", "/scheduleworld/"); boost::replace_all(expected, "ConsumerReady = 0", "ConsumerReady = 1"); CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig); renamedConfig = scanFiles(otherRoot, "scheduleworld.old.4"); boost::replace_all(expected, "/scheduleworld/", "/scheduleworld.old.4/"); boost::replace_all(expected, "ConsumerReady = 1", "ConsumerReady = 0"); CPPUNIT_ASSERT_EQUAL_DIFF(expected, renamedConfig); // remove ConsumerReady: must be remain unset when migrating // hidden SyncEvolution >= 1.2 configs { TestCmdline cmdline("--configure", "--sync-property", "ConsumerReady=0", "scheduleworld", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str()); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str()); } // migrate once more => keep ConsumerReady unset { TestCmdline cmdline("--migrate", "scheduleworld", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str()); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str()); } migratedConfig = scanFiles(otherRoot, "scheduleworld"); boost::replace_all(expected, "/scheduleworld.old.4/", "/scheduleworld/"); CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig); renamedConfig = scanFiles(otherRoot, "scheduleworld.old.5"); boost::replace_all(expected, "/scheduleworld/", "/scheduleworld.old.5/"); CPPUNIT_ASSERT_EQUAL_DIFF(expected, renamedConfig); } } void testMigrateContext() { // Migrate context containing a peer. Must also migrate peer. // Covers special case of inconsistent "type". ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates"); ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir); ScopedEnvChange home("HOME", m_testDir); string root = m_testDir + "/syncevolution/default"; string oldConfig = "config.ini:logDir = none\n" "peers/scheduleworld/config.ini:syncURL = http://sync.scheduleworld.com/funambol/ds\n" "peers/scheduleworld/config.ini:# username = \n" "peers/scheduleworld/config.ini:# password = \n" "peers/scheduleworld/sources/addressbook/config.ini:sync = two-way\n" "peers/scheduleworld/sources/addressbook/config.ini:uri = card3\n" "peers/scheduleworld/sources/addressbook/config.ini:type = addressbook:text/vcard\n" // correct! "sources/addressbook/config.ini:type = calendar\n" // wrong! "peers/funambol/config.ini:syncURL = http://sync.funambol.com/funambol/ds\n" "peers/funambol/config.ini:# username = \n" "peers/funambol/config.ini:# password = \n" "peers/funambol/sources/calendar/config.ini:sync = refresh-from-server\n" "peers/funambol/sources/calendar/config.ini:uri = cal\n" "peers/funambol/sources/calendar/config.ini:type = calendar\n" // correct! "peers/funambol/sources/addressbook/config.ini:# sync = disabled\n" "peers/funambol/sources/addressbook/config.ini:type = file\n" // not used for context because source disabled "sources/calendar/config.ini:type = memos\n" // wrong! "peers/memotoo/config.ini:syncURL = http://sync.memotoo.com/memotoo/ds\n" "peers/memotoo/config.ini:# username = \n" "peers/memotoo/config.ini:# password = \n" "peers/memotoo/sources/memo/config.ini:sync = refresh-from-client\n" "peers/memotoo/sources/memo/config.ini:uri = cal\n" "peers/memotoo/sources/memo/config.ini:type = memo:text/plain\n" // correct! "sources/memo/config.ini:type = todo\n" // wrong! ; { createFiles(root, oldConfig); TestCmdline cmdline("--migrate", "memo/backend=file", // override memo "backend" during migration "@default", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str()); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str()); string migratedConfig = scanFiles(root); CPPUNIT_ASSERT(migratedConfig.find("peers/scheduleworld/") != migratedConfig.npos); CPPUNIT_ASSERT(migratedConfig.find("sources/addressbook/config.ini:backend = addressbook") != migratedConfig.npos); CPPUNIT_ASSERT(migratedConfig.find("sources/addressbook/config.ini:databaseFormat = text/vcard") != migratedConfig.npos); CPPUNIT_ASSERT(migratedConfig.find("peers/scheduleworld/sources/addressbook/config.ini:syncFormat = text/vcard") != migratedConfig.npos); CPPUNIT_ASSERT(migratedConfig.find("peers/scheduleworld/sources/addressbook/config.ini:sync = two-way") != migratedConfig.npos); CPPUNIT_ASSERT(migratedConfig.find("peers/scheduleworld/sources/calendar/config.ini:# sync = disabled") != migratedConfig.npos); CPPUNIT_ASSERT(migratedConfig.find("peers/scheduleworld/sources/memo/config.ini:# sync = disabled") != migratedConfig.npos); CPPUNIT_ASSERT(migratedConfig.find("sources/calendar/config.ini:backend = calendar") != migratedConfig.npos); CPPUNIT_ASSERT(migratedConfig.find("sources/calendar/config.ini:# databaseFormat = ") != migratedConfig.npos); CPPUNIT_ASSERT(migratedConfig.find("peers/funambol/sources/calendar/config.ini:# syncFormat = ") != migratedConfig.npos); CPPUNIT_ASSERT(migratedConfig.find("peers/funambol/sources/addressbook/config.ini:# sync = disabled") != migratedConfig.npos); CPPUNIT_ASSERT(migratedConfig.find("peers/funambol/sources/calendar/config.ini:sync = refresh-from-server") != migratedConfig.npos); CPPUNIT_ASSERT(migratedConfig.find("peers/funambol/sources/memo/config.ini:# sync = disabled") != migratedConfig.npos); CPPUNIT_ASSERT(migratedConfig.find("sources/memo/config.ini:backend = file") != migratedConfig.npos); CPPUNIT_ASSERT(migratedConfig.find("sources/memo/config.ini:databaseFormat = text/plain") != migratedConfig.npos); CPPUNIT_ASSERT(migratedConfig.find("peers/memotoo/sources/memo/config.ini:syncFormat = text/plain") != migratedConfig.npos); CPPUNIT_ASSERT(migratedConfig.find("peers/memotoo/sources/addressbook/config.ini:# sync = disabled") != migratedConfig.npos); CPPUNIT_ASSERT(migratedConfig.find("peers/memotoo/sources/calendar/config.ini:# sync = disabled") != migratedConfig.npos); CPPUNIT_ASSERT(migratedConfig.find("peers/memotoo/sources/memo/config.ini:sync = refresh-from-client") != migratedConfig.npos); } } void testMigrateAutoSync() { ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates"); ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir); ScopedEnvChange home("HOME", m_testDir); string oldRoot = m_testDir + "/.sync4j/evolution/scheduleworld"; string newRoot = m_testDir + "/syncevolution/default"; string oldConfig = "spds/syncml/config.txt:autoSync = 1\n"; oldConfig += OldScheduleWorldConfig(); { // migrate old config createFiles(oldRoot, oldConfig); string createdConfig = scanFiles(oldRoot); TestCmdline cmdline("--migrate", "scheduleworld", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str()); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str()); string migratedConfig = scanFiles(newRoot); string expected = ScheduleWorldConfig(); boost::replace_first(expected, "# autoSync = 0", "autoSync = 1"); sortConfig(expected); // migrating SyncEvolution < 1.2 configs sets // ConsumerReady, to keep config visible in the updated // sync-ui boost::replace_all(expected, "# ConsumerReady = 0", "ConsumerReady = 1"); boost::replace_first(expected, "# database = ", "database = xyz"); boost::replace_first(expected, "# databaseUser = ", "databaseUser = foo"); boost::replace_first(expected, "# databasePassword = ", DATABASE_PASSWORD_BAR); // migrating "type" sets forceSyncFormat if not already the default, // and databaseFormat (if format was part of type, as for addressbook) boost::replace_first(expected, "# databaseFormat = ", "databaseFormat = text/vcard"); CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig); string renamedConfig = scanFiles(oldRoot + ".old"); // autoSync must have been unset boost::replace_first(createdConfig, ":autoSync = 1", ":autoSync = 0"); CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig); } { // rewrite existing config with autoSync set string createdConfig = scanFiles(newRoot, "scheduleworld"); TestCmdline cmdline("--migrate", "scheduleworld", NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str()); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str()); string migratedConfig = scanFiles(newRoot, "scheduleworld"); string expected = ScheduleWorldConfig(); boost::replace_first(expected, "# autoSync = 0", "autoSync = 1"); sortConfig(expected); boost::replace_all(expected, "# ConsumerReady = 0", "ConsumerReady = 1"); boost::replace_first(expected, "# database = ", "database = xyz"); boost::replace_first(expected, "# databaseUser = ", "databaseUser = foo"); boost::replace_first(expected, "# databasePassword = ", DATABASE_PASSWORD_BAR); boost::replace_first(expected, "# databaseFormat = ", "databaseFormat = text/vcard"); CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig); string renamedConfig = scanFiles(newRoot, "scheduleworld.old.1"); // autoSync must have been unset boost::replace_first(createdConfig, ":autoSync = 1", ":autoSync = 0"); // the scheduleworld config was consumer ready, the migrated one isn't boost::replace_all(createdConfig, "ConsumerReady = 1", "ConsumerReady = 0"); boost::replace_all(createdConfig, "/scheduleworld/", "/scheduleworld.old.1/"); CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig); } } const string m_testDir; private: /** * vararg constructor with NULL termination, * out and error stream into stringstream members */ class TestCmdline : public Logger { void init() { addLogger(boost::shared_ptr(this, NopDestructor())); m_argv.reset(new const char *[m_argvstr.size() + 1]); m_argv[0] = "client-test"; for (size_t index = 0; index < m_argvstr.size(); ++index) { m_argv[index + 1] = m_argvstr[index].c_str(); } m_cmdline.set(new KeyringSyncCmdline(m_argvstr.size() + 1, m_argv.get()), "cmdline"); } public: TestCmdline(const char *arg, ...) { va_list argList; va_start (argList, arg); for (const char *curr = arg; curr; curr = va_arg(argList, const char *)) { m_argvstr.push_back(curr); } va_end(argList); init(); } TestCmdline(const char * const argv[]) { for (int i = 0; argv[i]; i++) { m_argvstr.push_back(argv[i]); } init(); } ~TestCmdline() { removeLogger(this); } boost::shared_ptr parse() { if (!m_cmdline->parse()) { return boost::shared_ptr(); } boost::shared_ptr context(new SyncContext(m_cmdline->m_server)); context->setConfigFilter(true, "", m_cmdline->m_props.createSyncFilter(m_cmdline->m_server)); return context; } void doit(bool expectSuccess = true) { bool success = false; m_out.str(""); m_err.str(""); // emulates syncevolution.cpp exception handling try { success = m_cmdline->parse() && m_cmdline->run(); } catch (const std::exception &ex) { m_err << "[ERROR] " << ex.what(); } catch (...) { std::string explanation; Exception::handle(explanation); m_err << "[ERROR] " << explanation; } if (expectSuccess && m_err.str().size()) { m_out << endl << m_err.str(); } CPPUNIT_ASSERT_MESSAGE(m_out.str(), success == expectSuccess); } /** verify that Cmdline::usage() produced a short usage info followed by a specific error message */ void expectUsageError(const std::string &error) { // expect short usage info as normal output std::string out = m_out.str(); std::string err = m_err.str(); std::string all = m_all.str(); CPPUNIT_ASSERT(boost::starts_with(out, "List and manipulate databases:\n")); CPPUNIT_ASSERT(out.find("\nOptions:\n") == std::string::npos); CPPUNIT_ASSERT(boost::ends_with(out, "Remove item(s):\n" " syncevolution --delete-items [--] ( ... | '*')\n\n")); // exact error message CPPUNIT_ASSERT_EQUAL(error, err); // also check order CPPUNIT_ASSERT_EQUAL_DIFF(out + err, all); } // separate streams for normal messages and error messages ostringstream m_out, m_err; // combined stream with all messages ostringstream m_all; cxxptr m_cmdline; private: vector m_argvstr; boost::scoped_array m_argv; /** capture output produced while test ran */ void messagev(const MessageOptions &options, const char *format, va_list args) { Level level = options.m_level; if (level <= INFO) { ostringstream &out = level != SHOW ? m_err : m_out; std::string str = StringPrintfV(format, args); if (level != SHOW) { out << "[" << levelToStr(level) << "] "; m_all << "[" << levelToStr(level) << "] "; } out << str; m_all << str; if (!boost::ends_with(str, "\n")) { out << std::endl; m_all << std::endl; } } } }; string DefaultConfig() { string config = ScheduleWorldConfig(); boost::replace_first(config, "syncURL = http://sync.scheduleworld.com/funambol/ds", "syncURL = http://yourserver:port"); boost::replace_first(config, "http://www.scheduleworld.com", "http://www.syncevolution.org"); boost::replace_all(config, "ScheduleWorld", "SyncEvolution"); boost::replace_all(config, "scheduleworld", "syncevolution"); boost::replace_first(config, "PeerName = SyncEvolution", "# PeerName = "); boost::replace_first(config, "# ConsumerReady = 0", "ConsumerReady = 1"); boost::replace_first(config, "uri = card3", "uri = addressbook"); boost::replace_first(config, "uri = cal2", "uri = calendar"); boost::replace_first(config, "uri = task2", "uri = todo"); boost::replace_first(config, "uri = note", "uri = memo"); boost::replace_first(config, "syncFormat = text/vcard", "# syncFormat = "); return config; } string ScheduleWorldConfig(int contextMinVersion = CONFIG_CONTEXT_MIN_VERSION, int contextCurVersion = CONFIG_CONTEXT_CUR_VERSION, int peerMinVersion = CONFIG_PEER_MIN_VERSION, int peerCurVersion = CONFIG_PEER_CUR_VERSION) { // properties sorted by the order in which they are defined // in the sync and sync source property registry string config = StringPrintf("peers/scheduleworld/.internal.ini:peerMinVersion = %d\n" "peers/scheduleworld/.internal.ini:peerCurVersion = %d\n" "peers/scheduleworld/.internal.ini:# HashCode = 0\n" "peers/scheduleworld/.internal.ini:# ConfigDate = \n" "peers/scheduleworld/.internal.ini:# lastNonce = \n" "peers/scheduleworld/.internal.ini:# deviceData = \n" "peers/scheduleworld/.internal.ini:# webDAVCredentialsOkay = 0\n" "peers/scheduleworld/config.ini:syncURL = http://sync.scheduleworld.com/funambol/ds\n" "peers/scheduleworld/config.ini:# username = \n" "peers/scheduleworld/config.ini:# password = \n" ".internal.ini:contextMinVersion = %d\n" ".internal.ini:contextCurVersion = %d\n" "config.ini:# logdir = \n" "peers/scheduleworld/config.ini:# loglevel = 0\n" "peers/scheduleworld/config.ini:# notifyLevel = 3\n" "peers/scheduleworld/config.ini:# printChanges = 1\n" "peers/scheduleworld/config.ini:# dumpData = 1\n" "config.ini:# maxlogdirs = 10\n" "peers/scheduleworld/config.ini:# autoSync = 0\n" "peers/scheduleworld/config.ini:# autoSyncInterval = 30M\n" "peers/scheduleworld/config.ini:# autoSyncDelay = 5M\n" "peers/scheduleworld/config.ini:# preventSlowSync = 1\n" "peers/scheduleworld/config.ini:# useProxy = 0\n" "peers/scheduleworld/config.ini:# proxyHost = \n" "peers/scheduleworld/config.ini:# proxyUsername = \n" "peers/scheduleworld/config.ini:# proxyPassword = \n" "peers/scheduleworld/config.ini:# clientAuthType = md5\n" "peers/scheduleworld/config.ini:# RetryDuration = 5M\n" "peers/scheduleworld/config.ini:# RetryInterval = 2M\n" "peers/scheduleworld/config.ini:# remoteIdentifier = \n" "peers/scheduleworld/config.ini:# PeerIsClient = 0\n" "peers/scheduleworld/config.ini:# SyncMLVersion = \n" "peers/scheduleworld/config.ini:PeerName = ScheduleWorld\n" "config.ini:deviceId = fixed-devid\n" /* this is not the default! */ "peers/scheduleworld/config.ini:# remoteDeviceId = \n" "peers/scheduleworld/config.ini:# enableWBXML = 1\n" "peers/scheduleworld/config.ini:# enableRefreshSync = 0\n" "peers/scheduleworld/config.ini:# maxMsgSize = 150000\n" "peers/scheduleworld/config.ini:# maxObjSize = 4000000\n" "peers/scheduleworld/config.ini:# SSLServerCertificates = \n" "peers/scheduleworld/config.ini:# SSLVerifyServer = 1\n" "peers/scheduleworld/config.ini:# SSLVerifyHost = 1\n" "peers/scheduleworld/config.ini:WebURL = http://www.scheduleworld.com\n" "peers/scheduleworld/config.ini:IconURI = image://themedimage/icons/services/scheduleworld\n" "peers/scheduleworld/config.ini:# ConsumerReady = 0\n" "peers/scheduleworld/config.ini:# peerType = \n" "peers/scheduleworld/sources/addressbook/.internal.ini:# adminData = \n" "peers/scheduleworld/sources/addressbook/.internal.ini:# synthesisID = 0\n" "peers/scheduleworld/sources/addressbook/config.ini:sync = two-way\n" "peers/scheduleworld/sources/addressbook/config.ini:uri = card3\n" "sources/addressbook/config.ini:backend = addressbook\n" "peers/scheduleworld/sources/addressbook/config.ini:syncFormat = text/vcard\n" "peers/scheduleworld/sources/addressbook/config.ini:# forceSyncFormat = 0\n" "sources/addressbook/config.ini:# database = \n" "sources/addressbook/config.ini:# databaseFormat = \n" "sources/addressbook/config.ini:# databaseUser = \n" "sources/addressbook/config.ini:# databasePassword = \n" "peers/scheduleworld/sources/calendar/.internal.ini:# adminData = \n" "peers/scheduleworld/sources/calendar/.internal.ini:# synthesisID = 0\n" "peers/scheduleworld/sources/calendar/config.ini:sync = two-way\n" "peers/scheduleworld/sources/calendar/config.ini:uri = cal2\n" "sources/calendar/config.ini:backend = calendar\n" "peers/scheduleworld/sources/calendar/config.ini:# syncFormat = \n" "peers/scheduleworld/sources/calendar/config.ini:# forceSyncFormat = 0\n" "sources/calendar/config.ini:# database = \n" "sources/calendar/config.ini:# databaseFormat = \n" "sources/calendar/config.ini:# databaseUser = \n" "sources/calendar/config.ini:# databasePassword = \n" "peers/scheduleworld/sources/memo/.internal.ini:# adminData = \n" "peers/scheduleworld/sources/memo/.internal.ini:# synthesisID = 0\n" "peers/scheduleworld/sources/memo/config.ini:sync = two-way\n" "peers/scheduleworld/sources/memo/config.ini:uri = note\n" "sources/memo/config.ini:backend = memo\n" "peers/scheduleworld/sources/memo/config.ini:# syncFormat = \n" "peers/scheduleworld/sources/memo/config.ini:# forceSyncFormat = 0\n" "sources/memo/config.ini:# database = \n" "sources/memo/config.ini:# databaseFormat = \n" "sources/memo/config.ini:# databaseUser = \n" "sources/memo/config.ini:# databasePassword = \n" "peers/scheduleworld/sources/todo/.internal.ini:# adminData = \n" "peers/scheduleworld/sources/todo/.internal.ini:# synthesisID = 0\n" "peers/scheduleworld/sources/todo/config.ini:sync = two-way\n" "peers/scheduleworld/sources/todo/config.ini:uri = task2\n" "sources/todo/config.ini:backend = todo\n" "peers/scheduleworld/sources/todo/config.ini:# syncFormat = \n" "peers/scheduleworld/sources/todo/config.ini:# forceSyncFormat = 0\n" "sources/todo/config.ini:# database = \n" "sources/todo/config.ini:# databaseFormat = \n" "sources/todo/config.ini:# databaseUser = \n" "sources/todo/config.ini:# databasePassword = ", peerMinVersion, peerCurVersion, contextMinVersion, contextCurVersion); #ifdef ENABLE_LIBSOUP // path to SSL certificates has to be set only for libsoup boost::replace_first(config, "SSLServerCertificates = ", "SSLServerCertificates = /etc/ssl/certs/ca-certificates.crt:/etc/pki/tls/certs/ca-bundle.crt:/usr/share/ssl/certs/ca-bundle.crt"); #endif #if 0 // Currently we don't have an icon for ScheduleWorld. If we // had (MB #2062) one, then this code would ensure that the // reference config also has the right path for it. const char *templateDir = getenv("SYNCEVOLUTION_TEMPLATE_DIR"); if (!templateDir) { templateDir = TEMPLATE_DIR; } if (isDir(string(templateDir) + "/ScheduleWorld")) { boost::replace_all(config, "# IconURI = ", string("IconURI = file://") + templateDir + "/ScheduleWorld/icon.png"); } #endif return config; } string OldScheduleWorldConfig() { // old style paths string oldConfig = "spds/syncml/config.txt:syncURL = http://sync.scheduleworld.com/funambol/ds\n" "spds/syncml/config.txt:# username = \n" "spds/syncml/config.txt:# password = \n" "spds/syncml/config.txt:# logdir = \n" "spds/syncml/config.txt:# loglevel = 0\n" "spds/syncml/config.txt:# notifyLevel = 3\n" "spds/syncml/config.txt:# printChanges = 1\n" "spds/syncml/config.txt:# dumpData = 1\n" "spds/syncml/config.txt:# maxlogdirs = 10\n" "spds/syncml/config.txt:# autoSync = 0\n" "spds/syncml/config.txt:# autoSyncInterval = 30M\n" "spds/syncml/config.txt:# autoSyncDelay = 5M\n" "spds/syncml/config.txt:# preventSlowSync = 1\n" "spds/syncml/config.txt:# useProxy = 0\n" "spds/syncml/config.txt:# proxyHost = \n" "spds/syncml/config.txt:# proxyUsername = \n" "spds/syncml/config.txt:# proxyPassword = \n" "spds/syncml/config.txt:# clientAuthType = md5\n" "spds/syncml/config.txt:# RetryDuration = 5M\n" "spds/syncml/config.txt:# RetryInterval = 2M\n" "spds/syncml/config.txt:# remoteIdentifier = \n" "spds/syncml/config.txt:# PeerIsClient = 0\n" "spds/syncml/config.txt:# SyncMLVersion = \n" "spds/syncml/config.txt:PeerName = ScheduleWorld\n" "spds/syncml/config.txt:deviceId = fixed-devid\n" /* this is not the default! */ "spds/syncml/config.txt:# remoteDeviceId = \n" "spds/syncml/config.txt:# enableWBXML = 1\n" "spds/syncml/config.txt:# enableRefreshSync = 0\n" "spds/syncml/config.txt:# maxMsgSize = 150000\n" "spds/syncml/config.txt:# maxObjSize = 4000000\n" #ifdef ENABLE_LIBSOUP // path to SSL certificates is only set for libsoup "spds/syncml/config.txt:# SSLServerCertificates = /etc/ssl/certs/ca-certificates.crt:/etc/pki/tls/certs/ca-bundle.crt:/usr/share/ssl/certs/ca-bundle.crt\n" #else "spds/syncml/config.txt:# SSLServerCertificates = \n" #endif "spds/syncml/config.txt:# SSLVerifyServer = 1\n" "spds/syncml/config.txt:# SSLVerifyHost = 1\n" "spds/syncml/config.txt:WebURL = http://www.scheduleworld.com\n" "spds/syncml/config.txt:IconURI = image://themedimage/icons/services/scheduleworld\n" "spds/syncml/config.txt:# ConsumerReady = 0\n" "spds/sources/addressbook/config.txt:sync = two-way\n" "spds/sources/addressbook/config.txt:type = addressbook:text/vcard\n" "spds/sources/addressbook/config.txt:evolutionsource = xyz\n" "spds/sources/addressbook/config.txt:uri = card3\n" "spds/sources/addressbook/config.txt:evolutionuser = foo\n" "spds/sources/addressbook/config.txt:evolutionpassword = bar\n" "spds/sources/calendar/config.txt:sync = two-way\n" "spds/sources/calendar/config.txt:type = calendar\n" "spds/sources/calendar/config.txt:# database = \n" "spds/sources/calendar/config.txt:uri = cal2\n" "spds/sources/calendar/config.txt:# evolutionuser = \n" "spds/sources/calendar/config.txt:# evolutionpassword = \n" "spds/sources/memo/config.txt:sync = two-way\n" "spds/sources/memo/config.txt:type = memo\n" "spds/sources/memo/config.txt:# database = \n" "spds/sources/memo/config.txt:uri = note\n" "spds/sources/memo/config.txt:# evolutionuser = \n" "spds/sources/memo/config.txt:# evolutionpassword = \n" "spds/sources/todo/config.txt:sync = two-way\n" "spds/sources/todo/config.txt:type = todo\n" "spds/sources/todo/config.txt:# database = \n" "spds/sources/todo/config.txt:uri = task2\n" "spds/sources/todo/config.txt:# evolutionuser = \n" "spds/sources/todo/config.txt:# evolutionpassword = \n"; return oldConfig; } string FunambolConfig() { string config = ScheduleWorldConfig(); boost::replace_all(config, "/scheduleworld/", "/funambol/"); boost::replace_all(config, "PeerName = ScheduleWorld", "PeerName = Funambol"); boost::replace_first(config, "syncURL = http://sync.scheduleworld.com/funambol/ds", "syncURL = http://my.funambol.com/sync"); boost::replace_first(config, "WebURL = http://www.scheduleworld.com", "WebURL = http://my.funambol.com"); boost::replace_first(config, "IconURI = image://themedimage/icons/services/scheduleworld", "IconURI = image://themedimage/icons/services/funambol"); boost::replace_first(config, "# ConsumerReady = 0", "ConsumerReady = 1"); boost::replace_first(config, "# enableWBXML = 1", "enableWBXML = 0"); boost::replace_first(config, "# enableRefreshSync = 0", "enableRefreshSync = 1"); boost::replace_first(config, "# RetryInterval = 2M", "RetryInterval = 0"); boost::replace_first(config, "addressbook/config.ini:uri = card3", "addressbook/config.ini:uri = card"); boost::replace_all(config, "addressbook/config.ini:syncFormat = text/vcard", "addressbook/config.ini:# syncFormat = "); boost::replace_first(config, "calendar/config.ini:uri = cal2", "calendar/config.ini:uri = event"); boost::replace_all(config, "calendar/config.ini:# syncFormat = ", "calendar/config.ini:syncFormat = text/calendar"); boost::replace_all(config, "calendar/config.ini:# forceSyncFormat = 0", "calendar/config.ini:forceSyncFormat = 1"); boost::replace_first(config, "todo/config.ini:uri = task2", "todo/config.ini:uri = task"); boost::replace_all(config, "todo/config.ini:# syncFormat = ", "todo/config.ini:syncFormat = text/calendar"); boost::replace_all(config, "todo/config.ini:# forceSyncFormat = 0", "todo/config.ini:forceSyncFormat = 1"); return config; } string SynthesisConfig() { string config = ScheduleWorldConfig(); boost::replace_all(config, "/scheduleworld/", "/synthesis/"); boost::replace_all(config, "PeerName = ScheduleWorld", "PeerName = Synthesis"); boost::replace_first(config, "syncURL = http://sync.scheduleworld.com/funambol/ds", "syncURL = http://www.synthesis.ch/sync"); boost::replace_first(config, "WebURL = http://www.scheduleworld.com", "WebURL = http://www.synthesis.ch"); boost::replace_first(config, "IconURI = image://themedimage/icons/services/scheduleworld", "IconURI = image://themedimage/icons/services/synthesis"); boost::replace_first(config, "addressbook/config.ini:uri = card3", "addressbook/config.ini:uri = contacts"); boost::replace_all(config, "addressbook/config.ini:syncFormat = text/vcard", "addressbook/config.ini:# syncFormat = "); boost::replace_first(config, "calendar/config.ini:uri = cal2", "calendar/config.ini:uri = events"); boost::replace_first(config, "calendar/config.ini:sync = two-way", "calendar/config.ini:sync = disabled"); boost::replace_first(config, "memo/config.ini:uri = note", "memo/config.ini:uri = notes"); boost::replace_first(config, "todo/config.ini:uri = task2", "todo/config.ini:uri = tasks"); boost::replace_first(config, "todo/config.ini:sync = two-way", "todo/config.ini:sync = disabled"); return config; } /** create directory hierarchy, overwriting previous content */ void createFiles(const string &root, const string &content, bool append = false) { if (!append) { rm_r(root); } size_t start = 0; ofstream out; string outname; out.exceptions(ios_base::badbit|ios_base::failbit); while (start < content.size()) { size_t delim = content.find(':', start); size_t end = content.find('\n', start); if (delim == content.npos || end == content.npos) { // invalid content ?! break; } string newname = content.substr(start, delim - start); string line = content.substr(delim + 1, end - delim - 1); if (newname != outname) { if (out.is_open()) { out.close(); } string fullpath = root + "/" + newname; size_t fileoff = fullpath.rfind('/'); mkdir_p(fullpath.substr(0, fileoff)); out.open(fullpath.c_str(), append ? (ios_base::out|ios_base::ate|ios_base::app) : (ios_base::out|ios_base::trunc)); outname = newname; } out << line << endl; start = end + 1; } } /** turn directory hierarchy into string * * @param root root path in file system * @param peer if non-empty, then ignore all /peers/ directories * where != peer * @param onlyProps ignore lines which are comments */ string scanFiles(const string &root, const string &peer = "", bool onlyProps = true) { ostringstream out; scanFiles(root, "", peer, out, onlyProps); return out.str(); } void scanFiles(const string &root, const string &dir, const string &peer, ostringstream &out, bool onlyProps) { string newroot = root; newroot += "/"; newroot += dir; ReadDir readDir(newroot); sort(readDir.begin(), readDir.end()); BOOST_FOREACH(const string &entry, readDir) { if (isDir(newroot + "/" + entry)) { if (boost::ends_with(newroot, "/peers") && !peer.empty() && entry != peer) { // skip different peer directory continue; } else { scanFiles(root, dir + (dir.empty() ? "" : "/") + entry, peer, out, onlyProps); } } else { ifstream in; in.exceptions(ios_base::badbit /* failbit must not trigger exception because is set when reaching eof ?! */); in.open((newroot + "/" + entry).c_str()); string line; while (!in.eof()) { getline(in, line); if ((line.size() || !in.eof()) && (!onlyProps || (boost::starts_with(line, "# ") ? isPropAssignment(line.substr(2)) : !line.empty()))) { if (dir.size()) { out << dir << "/"; } out << entry << ":"; out << line << '\n'; } } } } } string printConfig(const string &server) { ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates"); ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir); ScopedEnvChange home("HOME", m_testDir); TestCmdline cmdline("--print-config", server.c_str(), NULL); cmdline.doit(); CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str()); return cmdline.m_out.str(); } }; SYNCEVOLUTION_TEST_SUITE_REGISTRATION(CmdlineTest); #endif // ENABLE_UNIT_TESTS SE_END_CXX syncevolution_1.4/src/syncevo/Cmdline.h000066400000000000000000000265031230021373600203650ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_SYNC_EVOLUTION_CMDLINE # define INCL_SYNC_EVOLUTION_CMDLINE #include #include #include #include #include #include #include SE_BEGIN_CXX class SyncSource; class SyncSourceRaw; class SyncContext; class CmdlineTest; /** * encodes a locally unique ID (LUID) in such a * way that it is treated as a plain word by shells */ class CmdlineLUID { std::string m_encodedLUID; public: /** fill with encoded LUID */ void setEncoded(const std::string &encodedLUID) { m_encodedLUID = encodedLUID; } /** return encoded LUID as string */ std::string getEncoded() const { return m_encodedLUID; } /** return original LUID */ std::string toLUID() const { return toLUID(m_encodedLUID); } static std::string toLUID(const std::string &encoded) { return StringEscape::unescape(encoded, '%'); } /** fill with unencoded LUID */ void setLUID(const std::string &luid) { m_encodedLUID = fromLUID(luid); } /** convert from unencoded LUID */ static std::string fromLUID(const std::string &luid) { return StringEscape::escape(luid, '%', StringEscape::STRICT); } }; class Cmdline { public: Cmdline(int argc, const char * const *argv); Cmdline(const std::vector &args); Cmdline(const char *arg, ...); virtual ~Cmdline() {} /** * parse the command line options * * @retval true if command line was okay */ bool parse(); /** * parse the command line options * relative paths in the arguments are converted to absolute paths * if it returns false, then the content of args is undefined. * * @retval true if command line was okay */ bool parse(std::vector &args); /** * @return false if run() still needs to be invoked, true when parse() already did * the job (like --sync-property ?) */ bool dontRun() const; bool run(); /** * sync report as owned by this instance, not filled in unless * run() executed a sync */ const SyncReport &getReport() const { return m_report; } /** the run() call modified configurations (added, updated, removed) */ bool configWasModified() const { return m_configModified; } Bool useDaemon() { return m_useDaemon; } /** whether '--monitor' is set */ bool monitor() { return m_monitor; } /** whether 'status' is set */ bool status() { return m_status; } /* server name */ std::string getConfigName() { return m_server; } /* check whether command line runs sync. It should be called after parsing. */ bool isSync(); /** same as isSync() for --restore */ bool isRestore() const; protected: // vector to store strings for arguments std::vector m_args; int m_argc; const char * const * m_argv; //array to store pointers of arguments boost::scoped_array m_argvArray; /** result of sync, if one was executed */ SyncReport m_report; Bool m_quiet; Bool m_dryrun; Bool m_status; Bool m_version; Bool m_usage; Bool m_configure; Bool m_remove; Bool m_run; Bool m_migrate; Bool m_printDatabases; Bool m_createDatabase; Bool m_removeDatabase; Bool m_printServers; Bool m_printTemplates; Bool m_printConfig; Bool m_printSessions; Bool m_dontrun; Bool m_monitor; Bool m_useDaemon; FullProps m_props; const ConfigPropertyRegistry &m_validSyncProps; const ConfigPropertyRegistry &m_validSourceProps; std::string m_restore; Bool m_before, m_after; Bool m_accessItems; std::string m_itemPath; std::string m_delimiter; std::list m_luids; Bool m_printItems, m_update, m_import, m_export, m_deleteItems; std::string m_server; std::string m_template; std::set m_sources; /** running the command line modified configuration settings (add, update, remove) */ Bool m_configModified; /** compose description of cmd line option with optional parameter */ static std::string cmdOpt(const char *opt, const char *param = NULL); /** * rename file or directory by appending .old or (if that already * exists) .old.x for x >= 1; updates config to point to the renamed directory */ void makeObsolete(boost::shared_ptr &from); /** * Copy from one config into another, with filters * applied for the target. All sources are copied * if selectedSources is empty, otherwise only * those. */ void copyConfig(const boost::shared_ptr &from, const boost::shared_ptr &to, const std::set &selectedSources); /** * flush, move .synthesis dir, set ConsumerReady, ... */ void finishCopy(const boost::shared_ptr &from, const boost::shared_ptr &to); /** * migrate peer config; target context must be ready */ void migratePeer(const std::string &fromPeer, const std::string &toPeer); /** * parse sync or source property * * @param propertyType sync, source, or unknown (in which case the property name must be given and must be unique) * @param opt command line option as it appeard in argv (e.g. --sync|--sync-property|-z) * @param param the parameter following the opt, may be NULL if none given (error!) * @param propname if given, then this is the property name and param contains the param value (--sync ) */ bool parseProp(PropertyType propertyType, const char *opt, const char *param, const char *propname = NULL); /** * parse keyword which sets a certain property, * like --sync=two-way, --sync two-way, ... for the "sync" property * * If a default value is give, then the format is like: * --keyring[=], --keyring=, ... * but not * --keyring */ bool parseAssignment(int &opt, std::vector &parsed, PropertyType propertyType, const char *propname, const char *def); bool listPropValues(const ConfigPropertyRegistry &validProps, const std::string &propName, const std::string &opt); bool listProperties(const ConfigPropertyRegistry &validProps, const std::string &opt); /** * check that m_props don't contain * properties which only apply to peers, throw error * if found */ void checkForPeerProps(); /** * list all known data sources of a certain type */ void listDatabases(SyncSource *source, const std::string &header); void createDatabase(SyncSource *source, const std::string &header); void removeDatabase(SyncSource *source, const std::string &header); void dumpConfigs(const std::string &preamble, const SyncConfig::ConfigList &servers); void dumpConfigTemplates(const std::string &preamble, const SyncConfig::TemplateList &templates, bool printRank = false, Logger::Level level = Logger::SHOW); enum DumpPropertiesFlags { DUMP_PROPS_NORMAL = 0, HIDE_LEGEND = 1<<0, /**< * do not show the explanation which properties are shared, * used while dumping any source which is not the last one */ HIDE_PER_PEER = 1<<1 /**< * config is for a context, not a peer, so do not show those * properties which are only per-peer */ }; void dumpProperties(const ConfigNode &configuredProps, const ConfigPropertyRegistry &allProps, int flags); void copyProperties(const ConfigNode &fromProps, ConfigNode &toProps, bool hidden, const ConfigPropertyRegistry &allProps); void dumpComment(std::ostream &stream, const std::string &prefix, const std::string &comment); /** ensure that m_server was set, false if error message was necessary */ bool needConfigName(); /** print usage information */ void usage(bool full, const std::string &error = std::string(""), const std::string ¶m = std::string("")); /** * This is a factory method used to delay sync client creation to its * subclass. The motivation is to let user implement their own * clients to avoid dependency. * @return the created sync client */ virtual SyncContext* createSyncClient(); friend class CmdlineTest; private: /** * Utility function to check m_argv[opt] against a specific boolean * parameter of the form "[=yes/1/t/true/no/0/f/false]. * * @param opt current index in m_argv * @param longName long form of the parameter, including --, may be NULL * @param shortName short form, including -, may be NULL * @param def default value if m_argv[opt] contains no explicit value * @retval value if and only if m_argv[opt] matches, then this is set to to true or false * @retval ok true if parsing succeeded, false if not and error message was printed */ bool parseBool(int opt, const char *longName, const char *shortName, bool def, Bool &value, bool &ok); /** * Fill list with all local IDs of the given source, as used by the source. */ void readLUIDs(SyncSource *source, std::list &luids); /** * Invoke a callback for each local ID. */ void processLUIDs(SyncSource *source, const boost::function &callback); /** * Add or update one item. * @param source SyncSource in write mode (startWriteData must have been called) * @param luid local ID, empty if item is to be added * @param data the item data to insert * @return encoded luid of inserted item */ CmdlineLUID insertItem(SyncSourceRaw *source, const std::string &luid, const std::string &data); }; SE_END_CXX #endif // INCL_SYNC_EVOLUTION_CMDLINE syncevolution_1.4/src/syncevo/CmdlineSyncClient.cpp000066400000000000000000000051141230021373600227070ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "CmdlineSyncClient.h" #include #include SE_BEGIN_CXX using namespace std; CmdlineSyncClient::CmdlineSyncClient(const string &server, bool doLogging): SyncContext(server, doLogging) { setUserInterface(this); } string CmdlineSyncClient::askPassword(const string &passwordName, const string &descr, const ConfigPasswordKey &key) { InitStateString password; // try to use keyring, if allowed if (GetLoadPasswordSignal()(getKeyring(), passwordName, descr, key, password) && password.wasSet()) { // succcess return password; } /** * if not built with secrets support or that support failed, * directly ask user to input password */ char buffer[256]; printf("Enter password for %s: ", descr.c_str()); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) && strcmp(buffer, "\n")) { size_t len = strlen(buffer); if (len && buffer[len - 1] == '\n') { buffer[len - 1] = 0; } password = std::string(buffer); } else { throwError(string("could not read password for ") + descr); } return password; } bool CmdlineSyncClient::savePassword(const string &passwordName, const string &password, const ConfigPasswordKey &key) { if (GetSavePasswordSignal()(getKeyring(), passwordName, password, key)) { // saved! return true; } // let config code store the password return false; } void CmdlineSyncClient::readStdin(string &content) { if (!ReadFile(cin, content)) { throwError("stdin", errno); } } SE_END_CXX syncevolution_1.4/src/syncevo/CmdlineSyncClient.h000066400000000000000000000043341230021373600223570ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_CMDLINESYNCCLIENT #define INCL_CMDLINESYNCCLIENT #include #include #include SE_BEGIN_CXX /** * a command line sync client for the purpose of * supporting a mechanism to save and retrieve password * in keyring. */ class CmdlineSyncClient : public SyncContext, private UserInterface { public: CmdlineSyncClient(const string &server, bool doLogging = false); /** * These 2 functions are from UserInterface and implement it * to use keyring to retrieve and save password in the keyring, * if enabled. */ virtual string askPassword(const string &passwordName, const string &descr, const ConfigPasswordKey &key); virtual bool savePassword(const string &passwordName, const string &password, const ConfigPasswordKey &key); /** read from real stdin */ virtual void readStdin(string &content); }; /** * This is a class derived from Cmdline. The purpose * is to implement the factory method 'createSyncClient' to create * new implemented 'CmdlineSyncClient' objects. */ class KeyringSyncCmdline : public Cmdline { public: KeyringSyncCmdline(int argc, const char * const * argv) : Cmdline(argc, argv) {} /** * create a user implemented sync client. */ SyncContext* createSyncClient() { return new CmdlineSyncClient(m_server, true); } }; SE_END_CXX #endif // INCL_CMDLINESYNCCLIENT syncevolution_1.4/src/syncevo/ConfigFilter.cpp000066400000000000000000000162001230021373600217110ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "config.h" #include #include using namespace std; SE_BEGIN_CXX void ConfigProps::add(const ConfigProps &other) { BOOST_FOREACH(const ConfigProps::value_type &entry, other) { std::pair res = insert(entry); if (!res.second) { res.first->second = entry.second; } } } InitStateString ConfigProps::get(const string &key, const string &def) const { const_iterator it = find(key); if (it == end()) { return InitStateString(def, false); } else { return it->second; } } ConfigProps::operator string () const { vector res; BOOST_FOREACH(const StringPair &filter, *this) { res.push_back(filter.first + " = " + filter.second); } sort(res.begin(), res.end()); return boost::join(res, "\n"); } ConfigProps SourceProps::createSourceFilter(const std::string &source) const { const_iterator it = find(""); ConfigProps filter; if (it != end()) { filter = it->second; } if (!source.empty()) { it = find(source); if (it != end()) { filter.add(it->second); } } return filter; } ConfigProps FullProps::createSyncFilter(const std::string &config) const { const_iterator it = find(""); ConfigProps filter; if (it != end()) { // first unset context filter = it->second.m_syncProps; } if (!config.empty()) { std::string normal = SyncConfig::normalizeConfigString(config, SyncConfig::NORMALIZE_LONG_FORMAT); std::string peer, context; SyncConfig::splitConfigString(normal, peer, context); // then overwrite with context config it = find(std::string("@") + context); if (it != end()) { filter.add(it->second.m_syncProps); } // finally peer config, if we have one if (!peer.empty()) { it = find(normal); if (it != end()) { filter.add(it->second.m_syncProps); } } } return filter; } ConfigProps FullProps::createSourceFilter(const std::string &config, const std::string &source) const { const_iterator it = find(""); ConfigProps filter; if (it != end()) { // first unset context filter = it->second.m_sourceProps.createSourceFilter(source); } if (!config.empty()) { std::string normal = SyncConfig::normalizeConfigString(config, SyncConfig::NORMALIZE_LONG_FORMAT); std::string peer, context; SyncConfig::splitConfigString(normal, peer, context); // then overwrite with context config it = find(std::string("@") + context); if (it != end()) { filter.add(it->second.m_sourceProps.createSourceFilter(source)); } // finally peer config, if we have one if (!peer.empty()) { it = find(normal); if (it != end()) { filter.add(it->second.m_sourceProps.createSourceFilter(source)); } } } return filter; } bool FullProps::hasProperties(PropCheckMode mode) const { BOOST_FOREACH(const value_type &context, *this) { if (mode == CHECK_ALL && !context.second.m_syncProps.empty()) { return true; } if (mode == IGNORE_GLOBAL_PROPS) { const ConfigPropertyRegistry ®istry = SyncConfig::getRegistry(); BOOST_FOREACH(const StringPair &entry, context.second.m_syncProps) { const ConfigProperty *prop = registry.find(entry.first); if (!prop || prop->getSharing() != ConfigProperty::GLOBAL_SHARING) { return true; } } } BOOST_FOREACH(const SourceProps::value_type &source, context.second.m_sourceProps) { if (!source.second.empty()) { return true; } } } return false; } void FullProps::createFilters(const string &context, const string &config, const set *sources, ConfigProps &syncFilter, SourceProps &sourceFilters) { boost::shared_ptr shared; if (!context.empty()) { // Read from context. If it does not exist, we simply set no properties // as filter. Previously there was a check for existance, but that was // flawed because it ignored the global property "defaultPeer". shared.reset(new SyncConfig(context)); shared->getProperties()->readProperties(syncFilter); } // add command line filters for context or config? if (!context.empty()) { syncFilter.add(createSyncFilter(context)); // default for (so far) unknown sources which might be created sourceFilters[""].add(createSourceFilter(context, "")); } if (!config.empty()) { syncFilter.add(createSyncFilter(config)); sourceFilters[""].add(createSourceFilter(config, "")); } // build full set of all sources set allSources; if (sources) { allSources = *sources; } if (shared) { std::list tmp = shared->getSyncSources(); allSources.insert(tmp.begin(), tmp.end()); } if (!config.empty()) { std::list tmp = SyncConfig(config).getSyncSources(); allSources.insert(tmp.begin(), tmp.end()); } // explicit filter for all known sources BOOST_FOREACH(std::string source, allSources) { ConfigProps &props = sourceFilters[source]; if (shared) { // combine existing properties from context and command line // filter SyncSourceNodes nodes = shared->getSyncSourceNodes(source, ""); nodes.getProperties()->readProperties(props); // Special case "type" property: the value in the context // is not preserved. Every new peer must ensure that // its own value is compatible (= same backend) with // the other peers. props.erase("type"); props.add(createSourceFilter(context, source)); } if (!config.empty()) { props.add(createSourceFilter(config, source)); } } } SE_END_CXX syncevolution_1.4/src/syncevo/ConfigFilter.h000066400000000000000000000114031230021373600213560ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_SYNC_EVOLUTION_CONFIG_FILTER # define INCL_SYNC_EVOLUTION_CONFIG_FILTER #include #include #include #include #include SE_BEGIN_CXX /** a case-insensitive string to InitStateString mapping */ class ConfigProps : public std::map > { public: /** format as = lines */ operator std::string () const; /** * Add all entries from the second set of properties, * overwriting existing ones (in contrast to map::insert(), * which does not overwrite). */ void add(const ConfigProps &other); /** * Return value in map or the given default, marked as unset. */ InitStateString get(const std::string &key, const std::string &def = "") const; }; /** * Properties for different sources. * * Source and property names are case-insensitive. */ class SourceProps : public std::map > { public: /** * Combine per-source property filters with filter for * all sources: per-source filter values always win. */ ConfigProps createSourceFilter(const std::string &source) const; }; /** * A pair of sync and source properties. Source properties are * reached via "" for "all sources", and "" for a * specific source. */ struct ContextProps { ConfigProps m_syncProps; SourceProps m_sourceProps; }; /** * A collection of sync and source settings, including different contexts. * * Primary index is by configuration: * "" for unset, "@" for explicit context, "foo@bar" for peer config * * Index is case-insensitive. */ class FullProps : public std::map > { public: enum PropCheckMode { CHECK_ALL, IGNORE_GLOBAL_PROPS }; /** any of the contained ConfigProps has entries */ bool hasProperties(PropCheckMode mode) const; /** * Combines sync properties into one filter, giving "config" * priority over "context of config" and over "no specific context". * Contexts which do not apply to the config are silently ignored. * Error checking for invalid contexts in the FullProps instance * must be done separately. * * @param config empty string (unknown config) or valid peer or context name */ ConfigProps createSyncFilter(const std::string &config) const; /** * Combines source properties into one filter. Same priority rules * as for sync properties apply. Priorities inside each context * are resolved via SourceProps::createSourceFilter(). The context * is checked first, so "sync@foo@default" overrides "addressbook/sync". * * @param config valid peer or context name * @param source empty string (only pick properties applying to all sources) or source name */ ConfigProps createSourceFilter(const std::string &config, const std::string &source) const; /** * read properties from context, then update with command line * properties for a) that context and b) the given config * * @param context context name, including @ sign, empty if not needed * @param config possibly non-normalized configuration name which determines * additional filters, can be empty * @param sources additional sources for which sourceFilters need to be set * @retval syncFilter global sync properties * @retval sourceFilters entries for sources known in either context, config, or * listed explicitly, * key "" as fallback for unknown sources */ void createFilters(const std::string &context, const std::string &config, const std::set *sources, ConfigProps &syncFilter, SourceProps &sourceFilters); }; SE_END_CXX #endif syncevolution_1.4/src/syncevo/ConfigNode.cpp000066400000000000000000000034201230021373600213510ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include using namespace std; SE_BEGIN_CXX boost::shared_ptr ConfigNode::createFileNode(const string &filename) { string::size_type off = filename.rfind('/'); boost::shared_ptr filenode; if (off != filename.npos) { filenode.reset(new IniFileConfigNode(filename.substr(0, off), filename.substr(off + 1), false)); } else { filenode.reset(new IniFileConfigNode(".", filename, false)); } boost::shared_ptr savenode(new SafeConfigNode(filenode)); savenode->setMode(false); return savenode; } void ConfigNode::writeProperties(const ConfigProps &props) { BOOST_FOREACH(const ConfigProps::value_type &entry, props) { setProperty(entry.first, entry.second); } } SE_END_CXX syncevolution_1.4/src/syncevo/ConfigNode.h000066400000000000000000000174771230021373600210370ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_EVOLUTION_ABSTRACT_CONFIG_NODE # define INCL_EVOLUTION_ABSTRACT_CONFIG_NODE #include #include #include #include #include #include #include #include #include #include SE_BEGIN_CXX // see ConfigFilter.h class ConfigProps; /** * This class corresponds to the Funambol C++ client * DeviceManagementNode, but offers a slightly different API. See * ConfigTree for details. */ class ConfigNode { public: /** free resources without saving */ virtual ~ConfigNode() {} /** creates a file-backed config node which accepts arbitrary key/value pairs */ static boost::shared_ptr createFileNode(const std::string &filename); /** a name for the node that the user can understand */ virtual std::string getName() const = 0; /** * True if the config node has no persistent storage behind it. * In that case, flush() and setProperty() may as well be skipped. */ virtual bool isVolatile() const = 0; /** * save all changes persistently */ virtual void flush() = 0; /** reload from background storage, discarding in-memory changes */ virtual void reload() {} /** * Returns the value of the given property * * @param property - the property name * @return value of the property or empty string if not set; * also includes whether the property was set */ virtual InitStateString readProperty(const std::string &property) const = 0; /** * Sets a property value. Overloaded, with variations providing * convenience wrappers around it. * * @param property the property name * @param value the property value and whether it is considered * "explicitly set"; if it is not, then the property * shall be removed from the list of properties * @param comment a comment explaining what the property is about, with * \n separating lines; might be used by the backend * when adding a new property * @param defValue Default value in case that value isn't set. * Can be be used by an implementation to annotate * unset properties. */ void setProperty(const std::string &property, const InitStateString &value, const std::string &comment = std::string("")) { writeProperty(property, value, comment); } void setProperty(const std::string &property, const char *value) { setProperty(property, InitStateString(value, true)); } void setProperty(const std::string &property, char *value) { setProperty(property, InitStateString(value, true)); } /** * Sets a boolean property, using "true/false". */ void setProperty(const std::string &property, const InitState &value) { setProperty(property, InitStateString(value ? "true" : "false", value.wasSet())); } void setProperty(const std::string &property, bool value) { setProperty(property, InitState(value, true)); } /** * Sets a property value with automatic conversion to the underlying string, * using stream formatting. */ template void setProperty(const std::string &property, const InitState &value) { std::stringstream strval; strval << value.get(); setProperty(property, InitStateString(strval.str(), value.wasSet())); } template void setProperty(const std::string &property, const T &value) { setProperty(property, InitState(value, true)); } bool getProperty(const std::string &property, std::string &value) const { InitStateString str = readProperty(property); if (str.wasSet()) { value = str; return true; } else { return false; } } bool getProperty(const std::string &property, bool &value) const { InitStateString str = readProperty(property); if (!str.wasSet() || str.empty()) { return false; } /* accept keywords */ if (boost::iequals(str, "true") || boost::iequals(str, "yes") || boost::iequals(str, "on")) { value = true; return true; } if (boost::iequals(str, "false") || boost::iequals(str, "no") || boost::iequals(str, "off")) { value = false; return true; } /* zero means false */ double number; if (getProperty(property, number)) { value = number != 0; return true; } return false; } template bool getProperty(const std::string &property, T &value) const { InitStateString str = readProperty(property); if (!str.wasSet() || str.empty()) { return false; } else { std::stringstream strval(str); strval >> value; return !strval.bad(); } } // defined here for source code backwards compatibility typedef ConfigProps PropsType; /** * Actual implementation of setProperty(). Uses different * different name, to avoid shadowing the setProperty() * variations in derived classes. */ virtual void writeProperty(const std::string &property, const InitStateString &value, const std::string &comment = std::string("")) = 0; /** * Extract all list of all currently defined properties * and their values. Does not include values which were * initialized with their defaults, if the implementation * remembers that. * * @retval props to be filled with key/value pairs; guaranteed * to be empty before the call */ virtual void readProperties(ConfigProps &props) const = 0; /** * Add the given properties. To replace the content of the * node, call clear() first. */ virtual void writeProperties(const ConfigProps &props); /** * Remove a certain property. * * @param property the name of the property which is to be removed */ virtual void removeProperty(const std::string &property) = 0; /** * Remove all properties. */ virtual void clear() = 0; /** * Node exists in backend storage. */ virtual bool exists() const = 0; /** * Node is read-only. Otherwise read-write. */ virtual bool isReadOnly() const = 0; }; SE_END_CXX #endif syncevolution_1.4/src/syncevo/ConfigTree.h000066400000000000000000000125321230021373600210340ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_EVOLUTION_CONFIG_TREE # define INCL_EVOLUTION_CONFIG_TREE #include #include #include #include #include SE_BEGIN_CXX class ConfigNode; /** * This class organizes the access to config nodes in a tree. Nodes * are identified by a relative path name, using a slash / as * separator between levels. Each node can have user-visible and * hidden properties. The two sets might be stored in the same * ConfigNode, i.e. properties should have unique names per node. For * each path there's also a second, separate namespace of key/value * pairs. The intented use for that is saving state by sync sources * close to, but without interfering with their configuration and the * state maintained by the client library itself. * * A ConfigNode can list all its properties while the tree lists nodes * at a specific level and creates nodes. * * This model is similar to the Funambol C++ DeviceManagementTree. * Besides being implemented differently, it also provides additional * functionality: * - the same node can be opened more than once; in the client library * the content of multiple instances is not synchronized and changes * can get lost * - nodes and the whole tree can be explicitly flushed * - it distinguishes between user visible configuration options and * hidden read/write properties attached to the same path * - in addition to these visible or hidden properties under well-known * names there can be nodes attached to each path which can * be used for arbitrary key/value pairs; different "other" nodes can * be selected via an additional string * - temporarily override values without saving them (see FilterConfigNode * decorator) * - improved access to properties inside nodes (iterating, deleting) */ class ConfigTree { public: /** frees all resources *without* flushing changed nodes */ virtual ~ConfigTree() {} /** ensure that all changes are saved persistently */ virtual void flush() = 0; /** tell all nodes to reload from background storage, discarding in-memory changes */ virtual void reload() = 0; /** * Remove all configuration nodes below and including a certain * path and (if based on files) directories created for them, if * empty after file removal. * * The nodes must not be in use for this to work. */ virtual void remove(const std::string &path) = 0; /** * Selects which node attached to a path name is to be used. * This is similar in concept to multiple data forks in a file. */ enum PropertyType { visible, /**< visible configuration properties */ hidden, /**< hidden read/write properties */ other, /**< additional node selected via otherID */ server, /**< yet another additional node, similar to other */ }; /** * Open the specified node. Opening it multiple * times will return the same instance, so the content * is always synchronized. * * @param path a relative path with / as separator * @param type selects which fork of that path is to be opened * (visible, hidden, change tracking, server) * @param otherId an additional string to be attached to the 'other' or 'server' * node's name (allows having multiple different such * nodes); an empty string is allowed */ virtual boost::shared_ptr open(const std::string &path, PropertyType type, const std::string &otherId = std::string("")) = 0; /** * Use the specified node, with type determined * by caller. The reason for adding the instance is * twofold: * - ensure that flush() is called on the node * as part of flushing the tree * - an existing instance is reused and shared between * different users of the tree * * @param path a relative or absolute path, may be outside of normal tree * @param node default instance if not opened before, discarded if a * node was registered or opened under the given path before */ virtual boost::shared_ptr add(const std::string &path, const boost::shared_ptr &node) = 0; /** * returns names of all existing nodes beneath the given path */ virtual std::list getChildren(const std::string &path) = 0; }; SE_END_CXX #endif syncevolution_1.4/src/syncevo/CurlTransportAgent.cpp000066400000000000000000000225101230021373600231400ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #ifdef ENABLE_LIBCURL #include #include #include #include SE_BEGIN_CXX CurlTransportAgent::CurlTransportAgent() : m_easyHandle(easyInit()), m_slist(NULL), m_status(INACTIVE), m_timeoutSeconds(0), m_reply(NULL), m_replyLen(0), m_replySize(0) { #ifdef ENABLE_MAEMO /* hack because Maemo doesn't support IPv6 yet */ curl_easy_setopt(m_easyHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); #endif /* * set up for post where message is pushed into curl via * its read callback and reply is stored in write callback */ CURLcode code; if ((code = curl_easy_setopt(m_easyHandle, CURLOPT_NOPROGRESS, false)) || (code = curl_easy_setopt(m_easyHandle, CURLOPT_PROGRESSFUNCTION, progressCallback)) || (code = curl_easy_setopt(m_easyHandle, CURLOPT_WRITEFUNCTION, writeDataCallback)) || (code = curl_easy_setopt(m_easyHandle, CURLOPT_WRITEDATA, (void *)this)) || (code = curl_easy_setopt(m_easyHandle, CURLOPT_READFUNCTION, readDataCallback)) || (code = curl_easy_setopt(m_easyHandle, CURLOPT_READDATA, (void *)this)) || (code = curl_easy_setopt(m_easyHandle, CURLOPT_ERRORBUFFER, this->m_curlErrorText )) || (code = curl_easy_setopt(m_easyHandle, CURLOPT_AUTOREFERER, true)) || (code = curl_easy_setopt(m_easyHandle, CURLOPT_POST, true)) || (code = curl_easy_setopt(m_easyHandle, CURLOPT_FOLLOWLOCATION, true))) { /* error encountered, throw exception */ curl_easy_cleanup(m_easyHandle); checkCurl(code); } } CURL *CurlTransportAgent::easyInit() { static bool initialized = false; static CURLcode initres; if (!initialized) { initres = curl_global_init(CURL_GLOBAL_ALL); initialized = true; } if (initres) { SE_THROW_EXCEPTION(TransportException, "global curl initialization failed"); } CURL *handle = curl_easy_init(); if (!handle) { SE_THROW_EXCEPTION(TransportException, "no curl handle"); } return handle; } CurlTransportAgent::~CurlTransportAgent() { if (m_reply) { free(m_reply); } curl_easy_cleanup(m_easyHandle); curl_slist_free_all(m_slist); } void CurlTransportAgent::setURL(const std::string &url) { m_url = url; CURLcode code = curl_easy_setopt(m_easyHandle, CURLOPT_URL, m_url.c_str()); checkCurl(code); } void CurlTransportAgent::setProxy(const std::string &proxy) { m_proxy = proxy; CURLcode code = curl_easy_setopt(m_easyHandle, CURLOPT_PROXY, m_proxy.c_str()); checkCurl(code); } void CurlTransportAgent::setProxyAuth(const std::string &user, const std::string &password) { m_auth = user + ":" + password; CURLcode code = curl_easy_setopt(m_easyHandle, CURLOPT_PROXYUSERPWD, m_auth.c_str()); checkCurl(code); } void CurlTransportAgent::setContentType(const std::string &type) { m_contentType = type; } void CurlTransportAgent::setUserAgent(const std::string &agent) { m_agent = agent; CURLcode code = curl_easy_setopt(m_easyHandle, CURLOPT_USERAGENT, m_agent.c_str()); checkCurl(code); } void CurlTransportAgent::setSSL(const std::string &cacerts, bool verifyServer, bool verifyHost) { m_cacerts = cacerts; CURLcode code = CURLE_OK; if (!m_cacerts.empty()) { if (isDir(m_cacerts)) { // libcurl + OpenSSL does not work with a directory set in CURLOPT_CAINFO. // Must set the directory name as CURLOPT_CAPATH. // // Hopefully libcurl NSS also finds the directory name // here ("NSS-powered libcurl provides the option only for // backward compatibility. "). code = curl_easy_setopt(m_easyHandle, CURLOPT_CAPATH, m_cacerts.c_str()); } else { code = curl_easy_setopt(m_easyHandle, CURLOPT_CAINFO, m_cacerts.c_str()); } } if (!code) { code = curl_easy_setopt(m_easyHandle, CURLOPT_SSL_VERIFYPEER, (long)verifyServer); } if (!code) { code = curl_easy_setopt(m_easyHandle, CURLOPT_SSL_VERIFYHOST, (long)(verifyHost ? 2 : 0)); } checkCurl(code); } void CurlTransportAgent::setTimeout(int seconds) { m_timeoutSeconds = seconds; } void CurlTransportAgent::shutdown() { } void CurlTransportAgent::send(const char *data, size_t len) { CURLcode code; m_replyLen = 0; m_message = data; m_messageSent = 0; m_messageLen = len; curl_slist_free_all(m_slist); m_slist = NULL; // Setting Expect explicitly prevents problems with certain // proxies: if curl is allowed to depend on Expect, then it will // send the POST header and wait for the servers reply that it is // allowed to continue. This will always be the case with a correctly // configured SyncML and because some proxies reject unknown Expect // requests, it is better not used. m_slist = curl_slist_append(m_slist, "Expect:"); std::string contentHeader("Content-Type: "); contentHeader += m_contentType; m_slist = curl_slist_append(m_slist, contentHeader.c_str()); m_status = ACTIVE; if (m_timeoutSeconds) { m_sendStartTime = Timespec::monotonic(); } m_aborting = false; if ((code = curl_easy_setopt(m_easyHandle, CURLOPT_PROGRESSDATA, static_cast (this)))|| (code = curl_easy_setopt(m_easyHandle, CURLOPT_HTTPHEADER, m_slist)) || (code = curl_easy_setopt(m_easyHandle, CURLOPT_POSTFIELDSIZE, len)) ){ m_status = CANCELED; checkCurl(code); } if ((code = curl_easy_perform(m_easyHandle))) { m_status = FAILED; checkCurl(code, false); } else { m_status = GOT_REPLY; } } void CurlTransportAgent::cancel() { /* nothing to do */ } TransportAgent::Status CurlTransportAgent::wait(bool noReply) { return m_status; } void CurlTransportAgent::getReply(const char *&data, size_t &len, std::string &contentType) { data = m_reply; len = m_replyLen; const char *curlContentType; if (!curl_easy_getinfo(m_easyHandle, CURLINFO_CONTENT_TYPE, &curlContentType) && curlContentType) { contentType = curlContentType; } else { // unknown contentType = ""; } } size_t CurlTransportAgent::writeDataCallback(void *buffer, size_t size, size_t nmemb, void *stream) throw() { return static_cast(stream)->writeData(buffer, size * nmemb); } size_t CurlTransportAgent::writeData(void *buffer, size_t size) throw() { bool increase = false; while (m_replyLen + size > m_replySize) { m_replySize = m_replySize ? m_replySize * 2 : 64 * 1024; increase = true; } if (increase) { m_reply = (char *)realloc(m_reply, m_replySize); if (!m_reply) { m_replySize = 0; m_replyLen = 0; return 0; } } memcpy(m_reply + m_replyLen, buffer, size); m_replyLen += size; return size; } size_t CurlTransportAgent::readDataCallback(void *buffer, size_t size, size_t nmemb, void *stream) throw() { return static_cast(stream)->readData(buffer, size * nmemb); } size_t CurlTransportAgent::readData(void *buffer, size_t size) throw() { size_t curr = std::min(size, m_messageLen - m_messageSent); memcpy(buffer, m_message + m_messageSent, curr); m_messageSent += curr; return curr; } void CurlTransportAgent::checkCurl(CURLcode code, bool exception) { if (code) { if(exception){ SE_THROW_EXCEPTION(TransportException, m_curlErrorText); }else { SE_LOG_INFO(NULL, "CurlTransport Failure: %s", m_curlErrorText); } } } int CurlTransportAgent::progressCallback(void* transport, double, double, double, double) { CurlTransportAgent *agent = static_cast (transport); SuspendFlags &flags = SuspendFlags::getSuspendFlags(); // check signals and abort transfer? flags.printSignals(); if (flags.getState() == SuspendFlags::ABORT) { agent->setAborting (true); return -1; } return agent->processCallback(); } int CurlTransportAgent::processCallback() { if (m_timeoutSeconds) { Timespec curTime = Timespec::monotonic(); if (curTime > m_sendStartTime + m_timeoutSeconds) { m_status = TIME_OUT; return -1; } } return 0; } SE_END_CXX #endif // ENABLE_LIBCURL syncevolution_1.4/src/syncevo/CurlTransportAgent.h000066400000000000000000000075301230021373600226120ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_CURLTRANSPORTAGENT #define INCL_CURLTRANSPORTAGENT #include "config.h" #ifdef ENABLE_LIBCURL #include #include #include SE_BEGIN_CXX /** * message send/receive with curl * * The simple curl API is used, so sending blocks until the * reply is ready. */ class CurlTransportAgent : public HTTPTransportAgent { public: CurlTransportAgent(); ~CurlTransportAgent(); virtual void setURL(const std::string &url); virtual void setProxy(const std::string &proxy); virtual void setProxyAuth(const std::string &user, const std::string &password); virtual void setSSL(const std::string &cacerts, bool verifyServer, bool verifyHost); virtual void setContentType(const std::string &type); virtual void setUserAgent(const std::string &agent); virtual void shutdown(); virtual void send(const char *data, size_t len); virtual void cancel(); virtual Status wait(bool noReply = false); virtual void getReply(const char *&data, size_t &len, std::string &contentType); virtual void setTimeout(int seconds); int processCallback(); void setAborting(bool aborting) {m_aborting = aborting;} private: CURL *m_easyHandle; curl_slist *m_slist; std::string m_contentType; Status m_status; bool m_aborting; Timespec m_sendStartTime; int m_timeoutSeconds; /** * libcurl < 7.17.0 does not copy strings passed into curl_easy_setopt(). * These are local copies that remain valid as long as needed. */ std::string m_url, m_proxy, m_auth, m_agent, m_cacerts; /** message buffer (owned by caller) */ const char *m_message; /** number of valid bytes in m_message */ size_t m_messageLen; /** number of sent bytes in m_message */ size_t m_messageSent; /** reply buffer */ char *m_reply; /** number of valid bytes in m_reply */ size_t m_replyLen; /** total buffer size */ size_t m_replySize; /** error text from curl, set via CURLOPT_ERRORBUFFER */ char m_curlErrorText[CURL_ERROR_SIZE]; /** CURLOPT_READFUNCTION, stream == CurlTransportAgent */ static size_t readDataCallback(void *buffer, size_t size, size_t nmemb, void *stream) throw(); size_t readData(void *buffer, size_t size) throw(); /** CURLOPT_WRITEFUNCTION, stream == CurlTransportAgent */ static size_t writeDataCallback(void *ptr, size_t size, size_t nmemb, void *stream) throw(); size_t writeData(void *buffer, size_t size) throw(); /** CURLOPT_PROGRESS callback, use this function to detect user abort */ static int progressCallback (void *ptr, double dltotal, double dlnow, double uptotal, double upnow); /** check curl error code and turn into exception */ void checkCurl(CURLcode code, bool exception = true); /** * initialize curl if necessary, return new handle * * Never returns NULL, instead throws exceptions. */ static CURL *easyInit(); }; SE_END_CXX #endif // ENABLE_LIBCURL #endif // INCL_TRANSPORTAGENT syncevolution_1.4/src/syncevo/DBusTraits.h000066400000000000000000000133511230021373600210330ustar00rootroot00000000000000/* * Copyright (C) 2011-12 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ /** * GDBus traits for sending some common structs over D-Bus. * Needed by by syncevo-local-sync and syncevo-dbus-helper. */ #ifndef INCL_SYNCEVO_DBUS_TRAITS # define INCL_SYNCEVO_DBUS_TRAITS #include #include #include #include #include #include namespace GDBusCXX { template <> struct dbus_traits : dbus_enum_traits {}; template <> struct dbus_traits : dbus_enum_traits {}; /** like a pair of two values, but with different storage class on the host */ template struct dbus_traits< SyncEvo::InitState > : public dbus_traits< std::pair > { typedef dbus_traits< std::pair > base_traits; typedef SyncEvo::InitState host_type; typedef const host_type &arg_type; #ifdef GDBUS_CXX_GIO static void get(ExtractArgs &context, reader_type &reader, host_type &value) { typename base_traits::host_type tmp; base_traits::get(context, reader, tmp); value = host_type(tmp.first, tmp.second); } #else static void get(connection_type *conn, message_type *msg, reader_type &reader, host_type &value) { typename base_traits::host_type tmp; base_traits::get(conn, msg, reader, tmp); value = host_type(tmp.first, tmp.second); } #endif static void append(builder_type &builder, arg_type value) { base_traits::append(builder, typename base_traits::host_type(value.get(), value.wasSet())); } }; /** * Actual content is a std::map, so serialization can be done using that. * We only have to ensure that instances and parameters use FullProps. */ template <> struct dbus_traits : public dbus_traits < std::map > > { typedef SyncEvo::FullProps host_type; typedef const SyncEvo::FullProps &arg_type; }; /** * Similar to SyncEvo::FullProps. */ template <> struct dbus_traits : public dbus_traits < std::map > > { typedef SyncEvo::SourceProps host_type; typedef const SyncEvo::SourceProps &arg_type; }; template <> struct dbus_traits : public dbus_traits < std::map > > { typedef SyncEvo::ConfigProps host_type; typedef const SyncEvo::ConfigProps &arg_type; }; /** * a struct containing ConfigProps + SourceProps */ template <> struct dbus_traits : public dbus_struct_traits > > {}; /** * a struct containing various strings and an integer */ template <> struct dbus_traits : public dbus_struct_traits > > > > > > > {}; template <> struct dbus_traits : public dbus_struct_traits > > {}; } #endif // INCL_SYNCEVO_DBUS_TRAITS syncevolution_1.4/src/syncevo/DataBlob.h000066400000000000000000000034101230021373600204520ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_EVOLUTION_DATA_BLOB # define INCL_EVOLUTION_DATA_BLOB #include #include #include SE_BEGIN_CXX /** * Abstract base class for a chunk of data. * Can be opened for reading and writing. * Meant to be used for plain files and * for sections inside a larger file. */ class DataBlob { public: virtual ~DataBlob() {} /** * Create stream for writing data. * Always overwrites old data. */ virtual boost::shared_ptr write() = 0; /** * Create stream for reading data. */ virtual boost::shared_ptr read() = 0; /** some kind of user visible name for the data */ virtual std::string getName() const = 0; /** true if the data exists already */ virtual bool exists() const = 0; /** true if the data is read-only and write() will fail */ virtual bool isReadonly() const = 0; }; SE_END_CXX #endif // INCL_EVOLUTION_DATA_BLOB syncevolution_1.4/src/syncevo/DevNullConfigNode.h000066400000000000000000000042671230021373600223220ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_SYNCEVO_DEVNULL_CONFIG_NODE # define INCL_SYNCEVO_DEVNULL_CONFIG_NODE #include #include SE_BEGIN_CXX /** * A read-only node which raises an exception when someone tries to * write into it. */ class DevNullConfigNode : public ConfigNode { string m_name; public: DevNullConfigNode(const string &name) : m_name() {} virtual string getName() const { return m_name; } virtual bool isVolatile() const { return true; } virtual void flush() {} virtual InitStateString readProperty(const string &property) const { return ""; } virtual void writeProperty(const string &property, const InitStateString &value, const string &comment = string("")) { SE_THROW(m_name + ": virtual read-only configuration node, cannot write property " + property + " = " + value); } virtual void readProperties(PropsType &props) const {} virtual void writeProperties(const PropsType &props) { if (!props.empty()) { SE_THROW(m_name + ": virtual read-only configuration node, cannot write properties"); } } virtual void removeProperty(const string &property) {} virtual void clear() {} virtual bool exists() const { return false; } virtual bool isReadOnly() const { return true; } }; SE_END_CXX #endif // SYNCEVO_DEVNULL_CONFIG_NODE syncevolution_1.4/src/syncevo/EDSClient.cpp000066400000000000000000000021641230021373600211140ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include SE_BEGIN_CXX EDSRegistryLoader &EDSRegistryLoaderSingleton(const boost::shared_ptr &loader) { static boost::shared_ptr singleton; if (!singleton) { singleton = loader; } return *singleton; } SE_END_CXX syncevolution_1.4/src/syncevo/EDSClient.h000066400000000000000000000120401230021373600205530ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_SYNCEVO_EDS_CLIENT #define INCL_SYNCEVO_EDS_CLIENT #include #include #if defined(HAVE_EDS) && defined(USE_EDS_CLIENT) #include #include #include #include #include #include #include typedef SyncEvo::GListCXX ESourceListCXX; SE_GOBJECT_TYPE(ESourceRegistry) SE_GOBJECT_TYPE(ESource) SE_GOBJECT_TYPE(EClient) #endif // HAVE_EDS && USE_EDS_CLIENT #include SE_BEGIN_CXX // This code must always be compiled into libsyncevolution. // It may get used by backends which were compiled against // EDS >= 3.6 even when the libsyncevolution itself wasn't. class EDSRegistryLoader; EDSRegistryLoader &EDSRegistryLoaderSingleton(const boost::shared_ptr &loader); // The following code implements EDSRegistryLoader. // For the sake of simplicity, its all in the header file, // so all users of it end up with a copy of the code and // then they can activate that code when instantiating the object // and passing it to EDSRegistryLoaderSingleton(). #if defined(HAVE_EDS) && defined(USE_EDS_CLIENT) /** * Creates ESourceRegistry on demand and shares it inside * SyncEvolution. It's never freed once used. */ class EDSRegistryLoader : private boost::noncopyable { public: typedef boost::function Callback_t; /** * Callback gets invoked exactly once. If the registry pointer is empty, * then the error will explain why. */ static void getESourceRegistryAsync(const Callback_t &cb) { EDSRegistryLoaderSingleton(boost::shared_ptr(new EDSRegistryLoader)).async(cb); } /** * Returns shared ESourceRegistry, throws error if creation failed. */ static ESourceRegistryCXX getESourceRegistry() { return EDSRegistryLoaderSingleton(boost::shared_ptr(new EDSRegistryLoader)).sync(); } private: Bool m_loading; ESourceRegistryCXX m_registry; GErrorCXX m_gerror; std::list m_pending; void async(const Callback_t &cb) { if (m_registry || m_gerror) { cb(m_registry, m_gerror); } else { m_pending.push_back(cb); #if 0 m_loading = true; SYNCEVO_GLIB_CALL_ASYNC(e_source_registry_new, boost::bind(&EDSRegistryLoader::created, this, _1, _2), NULL); #else ESourceRegistry *registry; GErrorCXX gerror; registry = e_source_registry_new_sync(NULL, gerror); created(registry, gerror); #endif } } ESourceRegistryCXX sync() { #if 0 if (!m_loading) { m_loading = true; SYNCEVO_GLIB_CALL_ASYNC(e_source_registry_new, boost::bind(&EDSRegistryLoader::created, this, _1, _2), NULL); } GRunWhile(!boost::lambda::var(m_registry) && !boost::lambda::var(m_gerror)); #else if (!m_registry) { ESourceRegistry *registry; GErrorCXX gerror; registry = e_source_registry_new_sync(NULL, gerror); created(registry, gerror); } #endif if (m_registry) { return m_registry; } if (m_gerror) { m_gerror.throwError("creating source registry"); } return m_registry; } void created(ESourceRegistry *registry, const GError *gerror) throw () { try { m_registry = ESourceRegistryCXX::steal(registry); m_gerror = gerror; BOOST_FOREACH (const Callback_t &cb, m_pending) { cb(m_registry, m_gerror); } } catch (...) { Exception::handle(HANDLE_EXCEPTION_FATAL); } } }; #endif // HAVE_EDS && USE_EDS_CLIENT SE_END_CXX #endif // INCL_SYNCEVO_EDS_CLIENT syncevolution_1.4/src/syncevo/FileConfigTree.cpp000066400000000000000000000173231230021373600221720ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; SE_BEGIN_CXX FileConfigTree::FileConfigTree(const string &root, SyncConfig::Layout layout) : m_root(root), m_layout(layout), m_readonly(false) { } void FileConfigTree::flush() { BOOST_FOREACH(const NodeCache_t::value_type &node, m_nodes) { node.second->flush(); } } void FileConfigTree::reload() { BOOST_FOREACH(const NodeCache_t::value_type &node, m_nodes) { node.second->reload(); } } /** * remove config files, backup files of config files (with ~ at * the end) and empty directories */ static bool rm_filter(const string &path, bool isDir) { if (isDir) { // skip non-empty directories ReadDir dir(path); return dir.begin() == dir.end(); } else { // only delete well-known files return boost::ends_with(path, "/config.ini") || boost::ends_with(path, "/config.ini~") || boost::ends_with(path, "/config.txt") || boost::ends_with(path, "/config.txt~") || boost::ends_with(path, "/.other.ini") || boost::ends_with(path, "/.other.ini~") || boost::ends_with(path, "/.server.ini") || boost::ends_with(path, "/.server.ini~") || boost::ends_with(path, "/.internal.ini") || boost::ends_with(path, "/.internal.ini~") || path.find("/.synthesis/") != path.npos; } } void FileConfigTree::remove(const string &path) { string fullpath = m_root + "/" + path; clearNodes(fullpath); rm_r(fullpath, rm_filter); } void FileConfigTree::reset() { for (NodeCache_t::iterator it = m_nodes.begin(); it != m_nodes.end(); ++it) { if (it->second.use_count() > 1) { // If the use count is larger than 1, then someone besides // the cache is referencing the node. We cannot force that // other entity to drop the reference, so bail out here. SE_THROW(it->second->getName() + ": cannot be removed while in use"); } } m_nodes.clear(); } void FileConfigTree::clearNodes(const string &fullpath) { NodeCache_t::iterator it; it = m_nodes.begin(); while (it != m_nodes.end()) { const string &key = it->first; if (boost::starts_with(key, fullpath)){ /* 'it = m_nodes.erase(it);' doesn't make sense * because 'map::erase' returns 'void' in gcc. But other * containers like list, vector could work! :( * Below is STL recommended usage. */ NodeCache_t::iterator erased = it++; if (erased->second.use_count() > 1) { // same check as in reset() SE_THROW(erased->second->getName() + ": cannot be removed while in use"); } m_nodes.erase(erased); } else { ++it; } } } boost::shared_ptr FileConfigTree::open(const string &path, ConfigTree::PropertyType type, const string &otherId) { string fullpath; string filename; fullpath = normalizePath(m_root + "/" + path + "/"); if (type == other) { if (m_layout == SyncConfig::SYNC4J_LAYOUT) { fullpath += "/changes"; if (!otherId.empty()) { fullpath += "_"; fullpath += otherId; } filename = "config.txt"; } else { filename += ".other"; if (!otherId.empty()) { filename += "_"; filename += otherId; } filename += ".ini"; } } else { filename = type == server ? ".server.ini" : m_layout == SyncConfig::SYNC4J_LAYOUT ? "config.txt" : type == hidden ? ".internal.ini" : "config.ini"; } string fullname = normalizePath(fullpath + "/" + filename); NodeCache_t::iterator found = m_nodes.find(fullname); if (found != m_nodes.end()) { return found->second; } else if(type != other && type != server) { boost::shared_ptr node(new IniFileConfigNode(fullpath, filename, m_readonly)); return m_nodes[fullname] = node; } else { boost::shared_ptr node(new IniHashConfigNode(fullpath, filename, m_readonly)); return m_nodes[fullname] = node; } } boost::shared_ptr FileConfigTree::add(const string &path, const boost::shared_ptr &node) { NodeCache_t::iterator found = m_nodes.find(path); if (found != m_nodes.end()) { return found->second; } else { m_nodes[path] = node; return node; } } static inline bool isNode(const string &dir, const string &name) { struct stat buf; string fullpath = dir + "/" + name; return !stat(fullpath.c_str(), &buf) && S_ISDIR(buf.st_mode); } list FileConfigTree::getChildren(const string &path) { list res; string fullpath; fullpath = normalizePath(m_root + "/" + path); // first look at existing files if (!access(fullpath.c_str(), F_OK)) { ReadDir dir(fullpath); BOOST_FOREACH(const string entry, dir) { if (isNode(fullpath, entry)) { res.push_back(entry); } } } // Now also add those which have been created, // but not saved yet. The full path must be // //. fullpath += "/"; BOOST_FOREACH(const NodeCache_t::value_type &node, m_nodes) { string currpath = node.first; if (currpath.size() > fullpath.size() && currpath.substr(0, fullpath.size()) == fullpath) { // path prefix matches, now check whether we have // a real sibling, i.e. another full path below // the prefix size_t start = fullpath.size(); size_t end = currpath.find('/', start); if (currpath.npos != end) { // Okay, another path separator found. // Now make sure we don't have yet another // directory level. if (currpath.npos == currpath.find('/', end + 1)) { // Insert it if not there yet. string name = currpath.substr(start, end - start); if (res.end() == find(res.begin(), res.end(), name)) { res.push_back(name); } } } } } return res; } SE_END_CXX syncevolution_1.4/src/syncevo/FileConfigTree.h000066400000000000000000000057471230021373600216460ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_EVOLUTION_FILE_CONFIG_TREE # define INCL_EVOLUTION_FILE_CONFIG_TREE #include #include #include #include #include SE_BEGIN_CXX /** * This implementation maps nodes to plain .ini style files below an * absolute directory of the filesystem. The caller is responsible for * choosing that directory and how hidden and user-visible files are * to be named. */ class FileConfigTree : public ConfigTree { public: /** * @param root absolute filesystem path for * .syncj4/evolution or .config/syncevolution * @param layout determines file names to be used; * HTTP_SERVER_LAYOUT and SHARED_LAYOUT are the same, * whereas SYNC4J_LAYOUT activates some backward-compatibility code */ FileConfigTree(const std::string &root, SyncConfig::Layout layout); void setReadOnly(bool readonly) { m_readonly = readonly; } bool getReadOnly() const { return m_readonly; } std::string getRoot() const { return m_root; } /* ConfigTree API */ virtual void flush(); virtual void reload(); virtual void remove(const std::string &path); virtual void reset(); virtual boost::shared_ptr open(const std::string &path, PropertyType type, const std::string &otherId = std::string("")); virtual boost::shared_ptr add(const std::string &path, const boost::shared_ptr &node); std::list getChildren(const std::string &path); private: /** * remove all nodes from the node cache which are located at 'fullpath' * or are contained inside it */ void clearNodes(const std::string &fullpath); private: const std::string m_root; SyncConfig::Layout m_layout; bool m_readonly; typedef std::map< std::string, boost::shared_ptr > NodeCache_t; /** cache of all nodes ever accessed */ NodeCache_t m_nodes; }; SE_END_CXX #endif syncevolution_1.4/src/syncevo/FileDataBlob.cpp000066400000000000000000000036371230021373600216200ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include #include SE_BEGIN_CXX FileDataBlob::FileDataBlob(const std::string &path, const std::string &fileName, bool readonly) : m_path(path), m_fileName(fileName), m_readonly(readonly) { } FileDataBlob::FileDataBlob(const std::string &fullpath, bool readonly) : m_readonly(readonly) { splitPath(fullpath, m_path, m_fileName); } boost::shared_ptr FileDataBlob::write() { if (m_readonly) { SE_THROW(getName() + ": internal error: attempt to write read-only FileDataBlob"); } mkdir_p(m_path); boost::shared_ptr file(new SafeOstream(getName())); return file; } boost::shared_ptr FileDataBlob::read() { boost::shared_ptr file(new std::ifstream(getName().c_str())); return file; } std::string FileDataBlob::getName() const { return m_path + "/" + m_fileName; } bool FileDataBlob::exists() const { std::string fullname = getName(); return !access(fullname.c_str(), F_OK); } SE_END_CXX syncevolution_1.4/src/syncevo/FileDataBlob.h000066400000000000000000000036301230021373600212560ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_EVOLUTION_FILE_DATA_BLOB # define INCL_EVOLUTION_FILE_DATA_BLOB #include #include #include SE_BEGIN_CXX /** * Abstract base class for a chunk of data. * Can be opened for reading and writing. * Meant to be used for plain files and * for sections inside a larger file. */ class FileDataBlob : public DataBlob { std::string m_path; std::string m_fileName; bool m_readonly; public: /** * @param path directory name * @param fileName name of file inside that directory * @param readonly do not create or write file, it must exist; * write() will throw an exception */ FileDataBlob(const std::string &path, const std::string &fileName, bool readonly); FileDataBlob(const std::string &fullpath, bool readonly); boost::shared_ptr write(); boost::shared_ptr read(); virtual std::string getName() const; virtual bool exists() const; virtual bool isReadonly() const { return m_readonly; } }; SE_END_CXX #endif // INCL_EVOLUTION_FILE_DATA_BLOB syncevolution_1.4/src/syncevo/FilterConfigNode.cpp000066400000000000000000000063711230021373600225270ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include #include SE_BEGIN_CXX FilterConfigNode::FilterConfigNode(const boost::shared_ptr &node, const ConfigFilter &filter) : m_filter(filter), m_node(node), m_readOnlyNode(node) { } FilterConfigNode::FilterConfigNode(const boost::shared_ptr &node, const ConfigFilter &filter) : m_filter(filter), m_readOnlyNode(node) { } void FilterConfigNode::addFilter(const string &property, const InitStateString &value) { m_filter[property] = value; } void FilterConfigNode::setFilter(const ConfigFilter &filter) { m_filter = filter; } InitStateString FilterConfigNode::readProperty(const string &property) const { ConfigFilter::const_iterator it = m_filter.find(property); if (it != m_filter.end()) { return it->second; } else { return m_readOnlyNode->readProperty(property); } } void FilterConfigNode::writeProperty(const string &property, const InitStateString &value, const string &comment) { ConfigFilter::iterator it = m_filter.find(property); if (!m_node.get()) { SyncContext::throwError(getName() + ": read-only, setting properties not allowed"); } if (it != m_filter.end()) { m_filter.erase(it); } m_node->writeProperty(property, value, comment); } void FilterConfigNode::readProperties(ConfigProps &props) const { m_readOnlyNode->readProperties(props); BOOST_FOREACH(const StringPair &filter, m_filter) { // overwrite existing values or add new ones props[filter.first] = filter.second; } } void FilterConfigNode::removeProperty(const string &property) { ConfigFilter::iterator it = m_filter.find(property); if (!m_node.get()) { SyncContext::throwError(getName() + ": read-only, removing properties not allowed"); } if (it != m_filter.end()) { m_filter.erase(it); } m_node->removeProperty(property); } void FilterConfigNode::clear() { m_filter.clear(); m_node->clear(); } void FilterConfigNode::flush() { if (!m_node.get()) { SyncContext::throwError(getName() + ": read-only, flushing not allowed"); } m_node->flush(); } SE_END_CXX syncevolution_1.4/src/syncevo/FilterConfigNode.h000066400000000000000000000065251230021373600221750ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_EVOLUTION_FILTER_CONFIG_NODE # define INCL_EVOLUTION_FILTER_CONFIG_NODE #include #include #include #include #include #include #include #include #include SE_BEGIN_CXX /** * This class acts as filter between a real config node and its user: * reads which match properties which are set in the filter will * return the value set in the filter. Writes will go to the underlying * node and future reads will return the written value. * * The purpose of this class is temporarily overriding saved values * during one run without having to modify the saved values. */ class FilterConfigNode : public ConfigNode { public: /** config filters are the same case-insensitive string to string mapping as property sets */ typedef ConfigProps ConfigFilter; /** read-write access to underlying node */ FilterConfigNode(const boost::shared_ptr &node, const ConfigFilter &filter = ConfigFilter()); /** read-only access to underlying node */ FilterConfigNode(const boost::shared_ptr &node, const ConfigFilter &filter = ConfigFilter()); virtual std::string getName() const { return m_readOnlyNode->getName(); } virtual bool isVolatile() const { return m_readOnlyNode->isVolatile(); } /** add another entry to the list of filter properties */ virtual void addFilter(const std::string &property, const InitStateString &value); /** replace current filter list with new one */ virtual void setFilter(const ConfigFilter &filter); virtual const ConfigFilter &getFilter() const { return m_filter; } /* ConfigNode API */ virtual void flush(); virtual InitStateString readProperty(const std::string &property) const; virtual void writeProperty(const std::string &property, const InitStateString &value, const std::string &comment = ""); virtual void readProperties(ConfigProps &props) const; virtual void removeProperty(const std::string &property); virtual bool exists() const { return m_readOnlyNode->exists(); } virtual bool isReadOnly() const { return !m_node || m_readOnlyNode->isReadOnly(); } virtual void clear(); private: ConfigFilter m_filter; boost::shared_ptr m_node; boost::shared_ptr m_readOnlyNode; }; SE_END_CXX #endif syncevolution_1.4/src/syncevo/ForkExec.cpp000066400000000000000000000636741230021373600210650ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "ForkExec.h" #include #include #if defined(HAVE_GLIB) #include #include #include "test.h" SE_BEGIN_CXX static const std::string ForkExecEnvVar("SYNCEVOLUTION_FORK_EXEC="); static const std::string ForkExecInstanceEnvVar("SYNCEVOLUTION_FORK_EXEC_INSTANCE="); #ifndef GDBUS_CXX_HAVE_DISCONNECT // internal D-Bus API: only used to monitor parent by having one method call pending static const std::string FORKEXEC_PARENT_PATH("/org/syncevolution/forkexec/parent"); static const std::string FORKEXEC_PARENT_IFACE("org.syncevolution.forkexec.parent"); static const std::string FORKEXEC_PARENT_DESTINATION = "direct.peer"; // doesn't matter, routing is off /** * The only purpose is to accept method calls and never reply. * When the parent destructs or gets killed, the caller (= child) * will notice because the method call fails, which ForkExecChild * translates into a "parent died" signal. */ class ForkExecParentDBusAPI : public GDBusCXX::DBusObjectHelper { public: /** * @param instance a unique string to distinguish multiple different ForkExecParent * instances; necessary because otherwise GIO GDBus may route messages from * one connection to older instances on other connections */ ForkExecParentDBusAPI(const GDBusCXX::DBusConnectionPtr &conn, const std::string &instance) : GDBusCXX::DBusObjectHelper(conn, FORKEXEC_PARENT_PATH + "/" + instance, FORKEXEC_PARENT_IFACE) { add(this, &ForkExecParentDBusAPI::watch, "Watch"); activate(); } ~ForkExecParentDBusAPI() { SE_LOG_DEBUG(NULL, "ForkExecParentDBusAPI %s: destroying with %ld active watches", getPath(), (long)m_watches.size()); } bool hasWatches() const { return !m_watches.empty(); } private: void watch(const boost::shared_ptr< GDBusCXX::Result0> &result) { SE_LOG_DEBUG(NULL, "ForkExecParentDBusAPI %s: received 'Watch' method call from child", getPath()); m_watches.push_back(result); } std::list< boost::shared_ptr< GDBusCXX::Result0> > m_watches; }; #endif // GDBUS_CXX_HAVE_DISCONNECT ForkExec::ForkExec() { } static Mutex ForkExecMutex; static unsigned int ForkExecCount; ForkExecParent::ForkExecParent(const std::string &helper, const std::vector &args) : m_helper(helper), m_args(args), m_childPid(0), m_hasConnected(false), m_hasQuit(false), m_status(0), m_sigIntSent(false), m_sigTermSent(false), m_mergedStdoutStderr(false), m_out(NULL), m_err(NULL), m_outID(0), m_errID(0), m_watchChild(NULL) { Mutex::Guard guard = ForkExecMutex.lock(); ForkExecCount++; m_instance = StringPrintf("forkexec%u", ForkExecCount); } boost::shared_ptr ForkExecParent::create(const std::string &helper, const std::vector &args) { boost::shared_ptr forkexec(new ForkExecParent(helper, args)); return forkexec; } ForkExecParent::~ForkExecParent() { if (m_outID) { g_source_remove(m_outID); } if (m_errID) { g_source_remove(m_errID); } if (m_out) { g_io_channel_unref(m_out); } if (m_err) { g_io_channel_unref(m_err); } if (m_watchChild) { // stop watching g_source_destroy(m_watchChild); g_source_unref(m_watchChild); } if (m_childPid) { g_spawn_close_pid(m_childPid); } #ifndef GDBUS_CXX_HAVE_DISCONNECT if (m_api) { SE_LOG_DEBUG(NULL, "ForkExecParent: shutting down, telling %s %ld that it lost the connection, it %s", m_helper.c_str(), (long)m_childPid, m_api->hasWatches() ? "is watching" : "is not watching"); m_api.reset(); } #endif } /** * Redirect stdout to stderr. * * Child setup function, called insided forked process before exec(). * only async-signal-safe functions allowed according to http://developer.gnome.org/glib/2.30/glib-Spawning-Processes.html#GSpawnChildSetupFunc */ void ForkExecParent::forked(gpointer data) throw() { ForkExecParent *me = static_cast(data); // When debugging, undo the LogRedirect output redirection that // we inherited from the parent process. That ensures that // any output is printed directly, instead of going through // the parent's output processing in LogRedirect. if (getenv("SYNCEVOLUTION_DEBUG")) { LogRedirect::removeRedirect(); } if (me->m_mergedStdoutStderr) { dup2(STDERR_FILENO, STDOUT_FILENO); } } void ForkExecParent::start() { if (m_watchChild) { SE_THROW("child already started"); } // boost::shared_ptr me = ...; GDBusCXX::DBusErrorCXX dbusError; SE_LOG_DEBUG(NULL, "ForkExecParent: preparing for child process %s", m_helper.c_str()); m_server = GDBusCXX::DBusServerCXX::listen("", &dbusError); if (!m_server) { dbusError.throwFailure("starting server"); } m_server->setNewConnectionCallback(boost::bind(&ForkExecParent::newClientConnection, this, _2)); // look for helper binary std::string helper; GSpawnFlags flags = G_SPAWN_DO_NOT_REAP_CHILD; if (m_helper.find('/') == m_helper.npos) { helper = getEnv("SYNCEVOLUTION_LIBEXEC_DIR", ""); if (helper.empty()) { // env variable not set, look in libexec dir helper = SYNCEVO_LIBEXEC; helper += "/"; helper += m_helper; if (access(helper.c_str(), R_OK)) { // some error, try PATH flags = (GSpawnFlags)(flags | G_SPAWN_SEARCH_PATH); helper = m_helper; } } else { // use env variable without further checks, must work helper += "/"; helper += m_helper; } } else { // absolute path, use it helper = m_helper; } m_argvStrings.push_back(helper); m_argvStrings.insert(m_argvStrings.end(), m_args.begin(), m_args.end()); m_argv.reset(AllocStringArray(m_argvStrings)); for (char **env = environ; *env; env++) { if (!boost::starts_with(*env, ForkExecEnvVar) && !boost::starts_with(*env, ForkExecInstanceEnvVar)) { m_envStrings.push_back(*env); } } // pass D-Bus address via env variable m_envStrings.push_back(ForkExecEnvVar + m_server->getAddress()); m_envStrings.push_back(ForkExecInstanceEnvVar + getInstance()); m_env.reset(AllocStringArray(m_envStrings)); SE_LOG_DEBUG(NULL, "ForkExecParent: running %s with D-Bus address %s", helper.c_str(), m_server->getAddress().c_str()); // Check which kind of output redirection is wanted. m_mergedStdoutStderr = !m_onOutput.empty(); if (!m_onOutput.empty()) { m_mergedStdoutStderr = true; } GErrorCXX gerror; int err = -1, out = -1; if (!g_spawn_async_with_pipes(NULL, // working directory static_cast(m_argv.get()), static_cast(m_env.get()), flags, // child setup function: redirect stdout to stderr, undo LogRedirect forked, this, &m_childPid, NULL, // set stdin to /dev/null (m_mergedStdoutStderr || m_onStdout.empty()) ? NULL : &out, (m_mergedStdoutStderr || !m_onStderr.empty()) ? &err : NULL, gerror)) { m_childPid = 0; gerror.throwError("spawning child"); } // set up output redirection, ignoring failures setupPipe(m_err, m_errID, err); setupPipe(m_out, m_outID, out); SE_LOG_DEBUG(NULL, "ForkExecParent: child process for %s has pid %ld", helper.c_str(), (long)m_childPid); // TODO: introduce C++ wrapper around GSource m_watchChild = g_child_watch_source_new(m_childPid); g_source_set_callback(m_watchChild, (GSourceFunc)watchChildCallback, this, NULL); g_source_attach(m_watchChild, NULL); } void ForkExecParent::setupPipe(GIOChannel *&channel, guint &sourceID, int fd) { if (fd == -1) { // nop return; } channel = g_io_channel_unix_new(fd); if (!channel) { // failure SE_LOG_DEBUG(NULL, "g_io_channel_unix_new() returned NULL"); close(fd); return; } // Close fd when freeing the channel (done by caller). g_io_channel_set_close_on_unref(channel, true); // Don't block in outputReady(). GErrorCXX error; g_io_channel_set_flags(channel, G_IO_FLAG_NONBLOCK, error); // We assume that the helper is writing data in the same encoding // and thus avoid any kind of conversion. Necessary to avoid // buffering. error.clear(); g_io_channel_set_encoding(channel, NULL, error); g_io_channel_set_buffered(channel, true); sourceID = g_io_add_watch(channel, (GIOCondition)(G_IO_IN|G_IO_ERR|G_IO_HUP), outputReady, this); } gboolean ForkExecParent::outputReady(GIOChannel *source, GIOCondition condition, gpointer data) throw () { bool cont = true; try { ForkExecParent *me = static_cast(data); gchar *buffer = NULL; gsize length = 0; GErrorCXX error; // Try reading, even if the condition wasn't G_IO_IN. GIOStatus status = g_io_channel_read_to_end(source, &buffer, &length, error); if (buffer && length) { if (source == me->m_out) { me->m_onStdout(buffer, length); } else if (me->m_mergedStdoutStderr) { me->m_onOutput(buffer, length); } else { me->m_onStderr(buffer, length); } } if (status == G_IO_STATUS_EOF || (condition & (G_IO_HUP|G_IO_ERR)) || error) { SE_LOG_DEBUG(NULL, "reading helper %s %ld done: %s", source == me->m_out ? "stdout" : me->m_mergedStdoutStderr ? "combined stdout/stderr" : "stderr", (long)me->m_childPid, (const char *)error); // Will remove event source from main loop. cont = false; // Free channel and forget source tag (source will be freed // by caller when we return false). if (source == me->m_out) { me->m_out = NULL; me->m_outID = 0; } else { me->m_err = NULL; me->m_errID = 0; } g_io_channel_unref(source); // Send delayed OnQuit signal now? me->checkCompletion(); } // If an exception skips this, we are going to die, in // which case we don't care about the leak. g_free(buffer); } catch (...) { Exception::handle(HANDLE_EXCEPTION_FATAL); } return cont; } void ForkExecParent::watchChildCallback(GPid pid, gint status, gpointer data) throw() { ForkExecParent *me = static_cast(data); me->m_hasQuit = true; me->m_status = status; me->checkCompletion(); } void ForkExecParent::checkCompletion() throw () { if (m_hasQuit && !m_out && !m_err) { try { m_onQuit(m_status); if (!m_hasConnected || m_status != 0) { SE_LOG_DEBUG(NULL, "ForkExecParent: child %ld was signaled %s, signal %d (SIGINT=%d, SIGTERM=%d), int sent %s, term sent %s", (long)m_childPid, WIFSIGNALED(m_status) ? "yes" : "no", WTERMSIG(m_status), SIGINT, SIGTERM, m_sigIntSent ? "yes" : "no", m_sigTermSent ? "yes" : "no"); if (WIFSIGNALED(m_status) && ((WTERMSIG(m_status) == SIGINT && m_sigIntSent) || (WTERMSIG(m_status) == SIGTERM && m_sigTermSent))) { // not an error when the child dies because we killed it return; } if (WIFSIGNALED(m_status) && WTERMSIG(m_status) == SIGKILL && m_sigTermSent) { // This started to happen on Debian Testing after the Wheezy release: // everything seems to shut down normally, and yet the exit status // of the helper shows SIGKILL instead of SIGTERM as the reason for // quitting. valgrind is involved, too. Not sure where this new (?) // behavior comes from. It seems to be harmless, so accept that // additional exit code without complaining (which would break unit // testing). SE_LOG_DEBUG(NULL, "ForkExecParent: ignoring unexpected exit signal SIGKILL of child %ld", (long)m_childPid); return; } std::string error = "child process quit"; if (!m_hasConnected) { error += " unexpectedly"; } if (WIFEXITED(m_status)) { error += StringPrintf(" with return code %d", WEXITSTATUS(m_status)); } else if (WIFSIGNALED(m_status)) { error += StringPrintf(" because of signal %d", WTERMSIG(m_status)); } else { error += " for unknown reasons"; } SE_LOG_ERROR(NULL, "%s", error.c_str()); m_onFailure(STATUS_FATAL, error); } } catch (...) { std::string explanation; SyncMLStatus status = Exception::handle(explanation); try { m_onFailure(status, explanation); } catch (...) { Exception::handle(); } } } } void ForkExecParent::newClientConnection(GDBusCXX::DBusConnectionPtr &conn) throw() { try { SE_LOG_DEBUG(NULL, "ForkExecParent: child %s %ld has connected", m_helper.c_str(), (long)m_childPid); m_hasConnected = true; #ifndef GDBUS_CXX_HAVE_DISCONNECT m_api.reset(new ForkExecParentDBusAPI(conn, getInstance())); #endif m_onConnect(conn); } catch (...) { std::string explanation; SyncMLStatus status = Exception::handle(explanation); try { m_onFailure(status, explanation); } catch (...) { Exception::handle(); } } } void ForkExecParent::addEnvVar(const std::string &name, const std::string &value) { if(!name.empty()) { m_envStrings.push_back(name + "=" + value); } } void ForkExecParent::stop(int signal) { if (!m_childPid || m_hasQuit) { // not running, nop return; } SE_LOG_DEBUG(NULL, "ForkExecParent: killing %s %ld with signal %d (%s %s)", m_helper.c_str(), (long)m_childPid, signal, (!signal || signal == SIGINT) ? "SIGINT" : "", (!signal || signal == SIGTERM) ? "SIGTERM" : ""); if (!signal || signal == SIGINT) { ::kill(m_childPid, SIGINT); m_sigIntSent = true; } if (!signal || signal == SIGTERM) { ::kill(m_childPid, SIGTERM); m_sigTermSent = true; } if (signal && signal != SIGINT && signal != SIGTERM) { ::kill(m_childPid, signal); } } void ForkExecParent::kill() { if (!m_childPid || m_hasQuit) { // not running, nop return; } SE_LOG_DEBUG(NULL, "ForkExecParent: killing %s %ld with SIGKILL", m_helper.c_str(), (long)m_childPid); ::kill(m_childPid, SIGKILL); #ifndef GDBUS_CXX_HAVE_DISCONNECT // Cancel the pending method call from the child to us. This will // send an error reply to the child, which it'll treat as // "connection lost". if (m_api) { SE_LOG_DEBUG(NULL, "ForkExecParent: telling %s %ld that it lost the connection, it %s", m_helper.c_str(), (long)m_childPid, m_api->hasWatches() ? "is watching" : "is not watching"); m_api.reset(); } #endif } ForkExecChild::ForkExecChild() : m_state(IDLE) { m_instance = getEnv(ForkExecInstanceEnvVar.substr(0, ForkExecInstanceEnvVar.size() - 1).c_str(), ""); } boost::shared_ptr ForkExecChild::create() { boost::shared_ptr forkexec(new ForkExecChild); return forkexec; } void ForkExecChild::connect() { // set error state, clear it later m_state = DISCONNECTED; const char *address = getParentDBusAddress(); if (!address) { SE_THROW("cannot connect to parent, was not forked"); } SE_LOG_DEBUG(NULL, "ForkExecChild: connecting to parent with D-Bus address %s", address); GDBusCXX::DBusErrorCXX dbusError; GDBusCXX::DBusConnectionPtr conn = dbus_get_bus_connection(address, &dbusError, true /* always delay message processing */); if (!conn) { dbusError.throwFailure("connecting to server"); } m_state = CONNECTED; // start watching connection #ifdef GDBUS_CXX_HAVE_DISCONNECT conn.setDisconnect(boost::bind(&ForkExecChild::connectionLost, this)); #else // emulate disconnect with a pending method call class Parent : public GDBusCXX::DBusRemoteObject { public: Parent(const GDBusCXX::DBusConnectionPtr &conn, const std::string &instance) : GDBusCXX::DBusRemoteObject(conn, FORKEXEC_PARENT_PATH + "/" + instance, FORKEXEC_PARENT_IFACE, FORKEXEC_PARENT_DESTINATION), m_watch(*this, "Watch") {} GDBusCXX::DBusClientCall0 m_watch; } parent(conn, getInstance()); parent.m_watch.start(boost::bind(&ForkExecChild::connectionLost, this)); #endif m_onConnect(conn); dbus_bus_connection_undelay(conn); } void ForkExecChild::connectionLost() { SE_LOG_DEBUG(NULL, "lost connection to parent"); m_state = DISCONNECTED; m_onQuit(); } bool ForkExecChild::wasForked() { return getParentDBusAddress() != NULL; } const char *ForkExecChild::getParentDBusAddress() { return getenv(ForkExecEnvVar.substr(0, ForkExecEnvVar.size() - 1).c_str()); } #ifdef ENABLE_UNIT_TESTS /** * Assumes that /bin/[false/true/echo] exist and that "env" is in the * path. Currently this does not cover actual D-Bus connection * handling and usage. */ class ForkExecTest : public CppUnit::TestFixture { public: void setUp() { m_statusValid = false; m_status = 0; } private: CPPUNIT_TEST_SUITE(ForkExecTest); CPPUNIT_TEST(testTrue); CPPUNIT_TEST(testFalse); CPPUNIT_TEST(testPath); CPPUNIT_TEST(testNotFound); CPPUNIT_TEST(testEnv1); CPPUNIT_TEST(testEnv2); CPPUNIT_TEST(testOutErr); CPPUNIT_TEST(testMerged); CPPUNIT_TEST_SUITE_END(); bool m_statusValid; int m_status; void hasQuit(int status) { m_status = status; m_statusValid = true; } static void append(const char *buffer, size_t length, std::string &all) { all.append(buffer, length); } boost::shared_ptr create(const std::string &helper) { boost::shared_ptr parent(ForkExecParent::create(helper)); parent->m_onQuit.connect(boost::bind(&ForkExecTest::hasQuit, this, _1)); return parent; } void testTrue() { boost::shared_ptr parent(create("/bin/true")); parent->start(); while (!m_statusValid) { g_main_context_iteration(NULL, true); } CPPUNIT_ASSERT(WIFEXITED(m_status)); CPPUNIT_ASSERT_EQUAL(0, WEXITSTATUS(m_status)); } void testFalse() { boost::shared_ptr parent(create("/bin/false")); parent->start(); while (!m_statusValid) { g_main_context_iteration(NULL, true); } CPPUNIT_ASSERT(WIFEXITED(m_status)); CPPUNIT_ASSERT_EQUAL(1, WEXITSTATUS(m_status)); } void testPath() { boost::shared_ptr parent(create("true")); parent->start(); while (!m_statusValid) { g_main_context_iteration(NULL, true); } CPPUNIT_ASSERT(WIFEXITED(m_status)); CPPUNIT_ASSERT_EQUAL(0, WEXITSTATUS(m_status)); } void testNotFound() { boost::shared_ptr parent(create("no-such-binary")); std::string out; std::string err; parent->m_onStdout.connect(boost::bind(append, _1, _2, boost::ref(out))); parent->m_onStderr.connect(boost::bind(append, _1, _2, boost::ref(err))); try { parent->start(); } catch (const SyncEvo::Exception &ex) { if (strstr(ex.what(), "spawning child: ")) { // glib itself detected that binary wasn't found. This // is what normally happens, but there's no guarantee, // thus the code below... return; } throw; } while (!m_statusValid) { g_main_context_iteration(NULL, true); } CPPUNIT_ASSERT(WIFEXITED(m_status)); CPPUNIT_ASSERT_EQUAL(1, WEXITSTATUS(m_status)); CPPUNIT_ASSERT_EQUAL(std::string(""), out); CPPUNIT_ASSERT_MESSAGE(err, err.find("no-such-binary") != err.npos); } void testEnv1() { boost::shared_ptr parent(create("env")); parent->addEnvVar("FORK_EXEC_TEST_ENV", "foobar"); std::string out; parent->m_onStdout.connect(boost::bind(append, _1, _2, boost::ref(out))); parent->start(); while (!m_statusValid) { g_main_context_iteration(NULL, true); } CPPUNIT_ASSERT(WIFEXITED(m_status)); CPPUNIT_ASSERT_EQUAL(0, WEXITSTATUS(m_status)); CPPUNIT_ASSERT_MESSAGE(out, out.find("FORK_EXEC_TEST_ENV=foobar\n") != out.npos); } void testEnv2() { boost::shared_ptr parent(create("env")); parent->addEnvVar("FORK_EXEC_TEST_ENV1", "foo"); parent->addEnvVar("FORK_EXEC_TEST_ENV2", "bar"); std::string out; parent->m_onStdout.connect(boost::bind(append, _1, _2, boost::ref(out))); parent->start(); while (!m_statusValid) { g_main_context_iteration(NULL, true); } CPPUNIT_ASSERT(WIFEXITED(m_status)); CPPUNIT_ASSERT_EQUAL(0, WEXITSTATUS(m_status)); CPPUNIT_ASSERT_MESSAGE(out, out.find("FORK_EXEC_TEST_ENV1=foo\n") != out.npos); CPPUNIT_ASSERT_MESSAGE(out, out.find("FORK_EXEC_TEST_ENV2=bar\n") != out.npos); } void testOutErr() { // This tests uses a trick to get output via stdout (normal // env output) and stderr (from ld.so). boost::shared_ptr parent(create("env")); parent->addEnvVar("FORK_EXEC_TEST_ENV", "foobar"); parent->addEnvVar("LD_DEBUG", "files"); std::string out; std::string err; parent->m_onStdout.connect(boost::bind(append, _1, _2, boost::ref(out))); parent->m_onStderr.connect(boost::bind(append, _1, _2, boost::ref(err))); parent->start(); while (!m_statusValid) { g_main_context_iteration(NULL, true); } CPPUNIT_ASSERT(WIFEXITED(m_status)); CPPUNIT_ASSERT_EQUAL(0, WEXITSTATUS(m_status)); CPPUNIT_ASSERT_MESSAGE(out, out.find("FORK_EXEC_TEST_ENV=foobar\n") != out.npos); CPPUNIT_ASSERT_MESSAGE(err, err.find("transferring control: ") != err.npos); } void testMerged() { // This tests uses a trick to get output via stdout (normal // env output) and stderr (from ld.so). boost::shared_ptr parent(create("env")); parent->addEnvVar("FORK_EXEC_TEST_ENV", "foobar"); parent->addEnvVar("LD_DEBUG", "files"); std::string output; parent->m_onOutput.connect(boost::bind(append, _1, _2, boost::ref(output))); parent->start(); while (!m_statusValid) { g_main_context_iteration(NULL, true); } CPPUNIT_ASSERT(WIFEXITED(m_status)); CPPUNIT_ASSERT_EQUAL(0, WEXITSTATUS(m_status)); // output from ld.so directly followed by env output CPPUNIT_ASSERT_MESSAGE(output, pcrecpp::RE("transferring control:.*\\n(\\s+\\d+:.*\\n)*[A-Za-z0-9_]+=.*\\n").PartialMatch(output)); } }; SYNCEVOLUTION_TEST_SUITE_REGISTRATION(ForkExecTest); #endif SE_END_CXX #endif // HAVE_GLIB syncevolution_1.4/src/syncevo/ForkExec.h000066400000000000000000000236401230021373600205170ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_FORK_EXEC # define INCL_FORK_EXEC #ifdef HAVE_CONFIG_H # include "config.h" #endif #if defined(HAVE_GLIB) #include #include #include #include "gdbus-cxx-bridge.h" #include SE_BEGIN_CXX #ifndef GDBUS_CXX_HAVE_DISCONNECT class ForkExecParentDBusAPI; #endif /** * Utility class which starts a specific helper binary in a second * process. The helper binary is identified via its base name like * "syncevo-dbus-helper", exact location is then determined * automatically, or via an absolute path. * * Direct D-Bus communication is set up automatically. For this to * work, the helper must use ForkExecChild::connect(). To debug this * when using GIO DBus, set G_DBUS_DEBUG=message. * * There are more options mentioned here: * http://developer.gnome.org/gio/unstable/ch03.html * * Progress (like "client connected") and failures ("client disconnected") * are reported via boost::signal2 signals. To make progess, the user of * this class must run a glib event loop in the default context. * * Note that failures encountered inside the class methods themselves * will be reported via exceptions. Only asynchronous errors encountered * inside the event loop are reported via the failure signal. * * Slots connected to the signals may throw exceptions. They will be * propagated up to the caller when ForkExec methods were called directly. * When thrown inside the event loop, the exception will be logged and * then reported via the onFailure signal. */ class ForkExec : private boost::noncopyable { public: /** * Called when the D-Bus connection is up and running. It is ready * to register objects that the peer might need. It is * guaranteed that any objects registered now will be ready before * the helper gets a chance to make D-Bus calls. */ typedef boost::signals2::signal OnConnect; OnConnect m_onConnect; /** * Called when an exception cannot be propagated up to the caller * because it was thrown inside the event loop, or when some other * kind of failure is encountered which cannot be reported via some * other means. The original problem is already logged when * onFailure is invoked, so further logging should not be done unless * it adds new information. * * Bad results of asynchronous method calls are reported via the * result callback of the method, not via onFailure. * * When a failure occurs, the peer should be considered dead and * the connection to it should be shut down (if any had been * established at all). * * When the child quits before establishing a connection or quits * with a non-zero return code, onFailure will be called. That way * a user of ForkExecParent doesn't have to connect to onQuit. */ typedef boost::signals2::signal OnFailure; OnFailure m_onFailure; /** * A unique string for the ForkExecParent/Child pair which can be used * as D-Bus path component. */ std::string getInstance() const { return m_instance; } protected: ForkExec(); std::string m_instance; }; /** * The parent side of a fork/exec. */ class ForkExecParent : public ForkExec { public: ~ForkExecParent(); /** * A ForkExecParent instance must be created via this factory * method and then be tracked in a shared pointer. This method * will not start the helper yet: first connect your slots, then * call start(). */ static boost::shared_ptr create(const std::string &helper, const std::vector &args = std::vector()); /** * the helper string passed to create() */ std::string getHelper() const { return m_helper; } /** * run the helper executable in the parent process */ void start(); /** * request that the child process terminates by sending it a * SIGINT and/or SIGTERM * @param signal if zero (default), send both signals, otherwise * the specified one */ void stop(int signal = 0); /** * kill the child process without giving it a chance to shut down * by sending it a SIGKILL */ void kill(); /** * Called when the helper has quit. The parameter of the signal is * the return status of the helper (see waitpid()). If output * redirection is active, then this signal will only be invoked * after processing all output. */ typedef boost::signals2::signal OnQuit; OnQuit m_onQuit; /** * Called when output from the helper is available. The buffer is * guaranteed to be nul-terminated with a byte that is not * included in the size. * * Register slots *before* calling start(), because output * redirection of the helper will only be done if someone is * waiting for it. If m_onOutput has a slot, then both stderr and * stdout are redirected into the same stream and only m_onOutput * will be invoked. */ typedef boost::signals2::signal OnOutput; OnOutput m_onOutput; OnOutput m_onStdout; OnOutput m_onStderr; enum State { IDLE, /**< instance constructed, but start() not called yet */ STARTING, /**< start() called */ CONNECTED, /**< child has connected, D-Bus connection established */ TERMINATED /**< child has quit */ }; State getState() { return m_hasQuit ? TERMINATED : m_hasConnected ? CONNECTED : m_watchChild ? STARTING : IDLE; } /** * Get the childs pid. This can be used as a unique id common to * both parent and child. */ int getChildPid() { return static_cast(m_childPid); } /** * Simply pushes a new environment variable onto m_envStrings. */ void addEnvVar(const std::string &name, const std::string &value); private: ForkExecParent(const std::string &helper, const std::vector &args); std::string m_helper; std::vector m_args; boost::shared_ptr m_server; boost::scoped_array m_argv; std::list m_argvStrings; boost::scoped_array m_env; std::list m_envStrings; GPid m_childPid; bool m_hasConnected; bool m_hasQuit; gint m_status; bool m_sigIntSent; bool m_sigTermSent; #ifndef GDBUS_CXX_HAVE_DISCONNECT boost::scoped_ptr m_api; #endif /** invoke m_onOutput while reading from a single stream */ bool m_mergedStdoutStderr; GIOChannel *m_out, *m_err; guint m_outID, m_errID; GSource *m_watchChild; static void watchChildCallback(GPid pid, gint status, gpointer data) throw(); void newClientConnection(GDBusCXX::DBusConnectionPtr &conn) throw(); void setupPipe(GIOChannel *&channel, guint &sourceID, int fd); static gboolean outputReady(GIOChannel *source, GIOCondition condition, gpointer data) throw (); void checkCompletion() throw (); static void forked(gpointer me) throw(); }; /** * The child side of a fork/exec. * * At the moment, the child cannot monitor the parent or kill it. * Might be added (if needed), in which case the corresponding * ForkExecParent members should be moved to the common ForkExec. */ class ForkExecChild : public ForkExec { public: /** * A ForkExecChild instance must be created via this factory * method and then be tracked in a shared pointer. The process * must have been started by ForkExecParent (directly or indirectly) * and any environment variables set by ForkExecParent must still * be set. */ static boost::shared_ptr create(); /** * Initiates connection to parent, connect to ForkExec::m_onConnect * before calling this function to be notified of success and * ForkExec::m_onFailure for failures. * * m_onConnect is guaranteed to be called before message processing * starts. It's the right place to add objects to the bus that are * expected by the parent. */ void connect(); /** * Called when the parent has quit. */ typedef boost::signals2::signal OnQuit; OnQuit m_onQuit; enum State { IDLE, /**< created, connect() not called yet */ CONNECTING, /**< connect() called but no connection yet */ CONNECTED, /**< connection established */ DISCONNECTED /**< lost connection or failed to establish it */ }; State getState() const { return m_state; } /** * true if the current process was created by ForkExecParent */ static bool wasForked(); private: ForkExecChild(); static const char *getParentDBusAddress(); void connectionLost(); State m_state; }; SE_END_CXX #endif // HAVE_GLIB #endif // INCL_FORK_EXEC syncevolution_1.4/src/syncevo/GLibSupport.cpp000066400000000000000000000242611230021373600215560ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #ifdef ENABLE_UNIT_TESTS #include "test.h" #endif #include #include #include #ifdef HAVE_GLIB #include #include #endif using namespace std; SE_BEGIN_CXX #ifdef HAVE_GLIB class Select { GMainLoop *m_loop; GMainContext *m_context; struct FDSource; FDSource *m_source; Timespec m_deadline; GPollFD m_pollfd; GLibSelectResult m_result; struct FDSource { GSource m_source; Select *m_select; static gboolean prepare(GSource *source, gint *timeout) { FDSource *fdsource = (FDSource *)source; if (!fdsource->m_select->m_deadline) { *timeout = -1; return FALSE; } Timespec now = Timespec::monotonic(); if (now < fdsource->m_select->m_deadline) { Timespec delta = fdsource->m_select->m_deadline - now; *timeout = delta.tv_sec * 1000 + delta.tv_nsec / 1000000; return FALSE; } else { fdsource->m_select->m_result = GLIB_SELECT_TIMEOUT; *timeout = 0; return TRUE; } } static gboolean check(GSource *source) { FDSource *fdsource = (FDSource *)source; if (fdsource->m_select->m_pollfd.revents) { fdsource->m_select->m_result = GLIB_SELECT_READY; return TRUE; } else { return FALSE; } } static gboolean dispatch(GSource *source, GSourceFunc callback, gpointer user_data) { FDSource *fdsource = (FDSource *)source; g_main_loop_quit(fdsource->m_select->m_loop); return FALSE; } static GSourceFuncs m_funcs; }; public: Select(GMainLoop *loop, int fd, int direction, Timespec *timeout) : m_loop(loop), m_context(g_main_loop_get_context(loop)), m_result(GLIB_SELECT_QUIT) { if (timeout) { m_deadline = Timespec::monotonic() + *timeout; } memset(&m_pollfd, 0, sizeof(m_pollfd)); m_source = (FDSource *)g_source_new(&FDSource::m_funcs, sizeof(FDSource)); if (!m_source) { SE_THROW("no FDSource"); } m_source->m_select = this; m_pollfd.fd = fd; if (fd >= 0 && direction != GLIB_SELECT_NONE) { if (direction & GLIB_SELECT_READ) { m_pollfd.events |= G_IO_IN | G_IO_HUP | G_IO_ERR; } if (direction & GLIB_SELECT_WRITE) { m_pollfd.events |= G_IO_OUT | G_IO_ERR; } g_source_add_poll(&m_source->m_source, &m_pollfd); } g_source_attach(&m_source->m_source, m_context); } ~Select() { if (m_source) { g_source_destroy(&m_source->m_source); } } GLibSelectResult run() { g_main_loop_run(m_loop); return m_result; } }; GSourceFuncs Select::FDSource::m_funcs = { Select::FDSource::prepare, Select::FDSource::check, Select::FDSource::dispatch }; GLibSelectResult GLibSelect(GMainLoop *loop, int fd, int direction, Timespec *timeout) { Select instance(loop, fd, direction, timeout); return instance.run(); } void GErrorCXX::throwError(const string &action) { throwError(action, m_gerror); } void GErrorCXX::throwError(const string &action, const GError *err) { string gerrorstr = action; if (!gerrorstr.empty()) { gerrorstr += ": "; } if (err) { gerrorstr += err->message; // No need to clear m_error! Will be done as part of // destructing the GErrorCCXX. } else { gerrorstr = "failure"; } SE_THROW(gerrorstr); } static void changed(GFileMonitor *monitor, GFile *file1, GFile *file2, GFileMonitorEvent event, gpointer userdata) { GLibNotify::callback_t *callback = static_cast(userdata); if (!callback->empty()) { (*callback)(file1, file2, event); } } GLibNotify::GLibNotify(const char *file, const callback_t &callback) : m_callback(callback) { GFileCXX filecxx(g_file_new_for_path(file), TRANSFER_REF); GErrorCXX gerror; GFileMonitorCXX monitor(g_file_monitor_file(filecxx.get(), G_FILE_MONITOR_NONE, NULL, gerror), TRANSFER_REF); m_monitor.swap(monitor); if (!m_monitor) { gerror.throwError(std::string("monitoring ") + file); } g_signal_connect_after(m_monitor.get(), "changed", G_CALLBACK(changed), (void *)&m_callback); } class PendingChecks { typedef std::set *> Checks; Checks m_checks; DynMutex m_mutex; Cond m_cond; public: /** * Called by main thread before and after sleeping. * Runs all registered checks and removes the ones * which are done. */ void runChecks(); /** * Called by additional threads. Returns when check() * returned false. */ void blockOnCheck(const boost::function &check); }; void PendingChecks::runChecks() { DynMutex::Guard guard = m_mutex.lock(); Checks::iterator it = m_checks.begin(); bool removed = false; while (it != m_checks.end()) { bool cont; try { cont = (**it)(); } catch (...) { Exception::handle(HANDLE_EXCEPTION_FATAL); // keep compiler happy cont = false; } if (!cont) { // Done with this check Checks::iterator next = it; ++next; m_checks.erase(it); it = next; removed = true; } else { ++it; } } // Tell blockOnCheck() calls that they may have completed. if (removed) { m_cond.signal(); } } void PendingChecks::blockOnCheck(const boost::function &check) { DynMutex::Guard guard = m_mutex.lock(); // When we get here, the conditions for returning may already have // been met. Check before sleeping. If we need to continue, then // holding the mutex ensures that the main thread will run the // check on the next iteration. if (check()) { m_checks.insert(&check); do { m_cond.wait(m_mutex); } while (m_checks.find(&check) != m_checks.end()); } } void GRunWhile(const boost::function &check) { static PendingChecks checks; if (g_main_context_is_owner(g_main_context_default())) { // Check once before sleeping, conditions may already be met // for some checks. checks.runChecks(); // Drive event loop. while (check()) { g_main_context_iteration(NULL, true); checks.runChecks(); } } else { // Transfer check into main thread. checks.blockOnCheck(check); } } #ifdef ENABLE_UNIT_TESTS class GLibTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(GLibTest); CPPUNIT_TEST(notify); CPPUNIT_TEST_SUITE_END(); struct Event { GFileCXX m_file1; GFileCXX m_file2; GFileMonitorEvent m_event; }; static void notifyCallback(list &events, GFile *file1, GFile *file2, GFileMonitorEvent event) { Event tmp; tmp.m_file1.reset(file1); tmp.m_file2.reset(file2); tmp.m_event = event; events.push_back(tmp); } static gboolean timeout(gpointer data) { g_main_loop_quit(static_cast(data)); return false; } void notify() { list events; static const char *name = "GLibTest.out"; unlink(name); GMainLoopCXX loop(g_main_loop_new(NULL, FALSE), TRANSFER_REF); if (!loop) { SE_THROW("could not allocate main loop"); } GLibNotify notify(name, boost::bind(notifyCallback, boost::ref(events), _1, _2, _3)); { events.clear(); GLibEvent id(g_timeout_add_seconds(5, timeout, loop.get()), "timeout"); ofstream out(name); out << "hello"; out.close(); g_main_loop_run(loop.get()); CPPUNIT_ASSERT(!events.empty()); } { events.clear(); ofstream out(name); out.close(); GLibEvent id(g_timeout_add_seconds(5, timeout, loop.get()), "timeout"); g_main_loop_run(loop.get()); CPPUNIT_ASSERT(!events.empty()); } { events.clear(); unlink(name); GLibEvent id(g_timeout_add_seconds(5, timeout, loop.get()), "timeout"); g_main_loop_run(loop.get()); CPPUNIT_ASSERT(!events.empty()); } } }; SYNCEVOLUTION_TEST_SUITE_REGISTRATION(GLibTest); #endif // ENABLE_UNIT_TESTS #else // HAVE_GLIB GLibSelectResult GLibSelect(GMainLoop *loop, int fd, int direction, Timespec *timeout) { SE_THROW("GLibSelect() not implemented without glib support"); return GLIB_SELECT_QUIT; } #endif // HAVE_GLIB SE_END_CXX syncevolution_1.4/src/syncevo/GLibSupport.h000066400000000000000000001203101230021373600212130ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_GLIB_SUPPORT # define INCL_GLIB_SUPPORT #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #ifdef HAVE_GLIB # include # include #else typedef void *GMainLoop; #endif #include #include #include #include #include #include #include #include #include #include #include #include #include SE_BEGIN_CXX enum { GLIB_SELECT_NONE = 0, GLIB_SELECT_READ = 1, GLIB_SELECT_WRITE = 2 }; enum GLibSelectResult { GLIB_SELECT_TIMEOUT, /**< returned because not ready after given amount of time */ GLIB_SELECT_READY, /**< fd is ready */ GLIB_SELECT_QUIT /**< something else caused the loop to quit, return to caller immediately */ }; /** * Waits for one particular file descriptor to become ready for reading * and/or writing. Keeps the given loop running while waiting. * * @param loop loop to keep running; must not be NULL * @param fd file descriptor to watch, -1 for none * @param direction read, write, both, or none (then fd is ignored) * @param timeout timeout in seconds + nanoseconds from now, NULL for no timeout, empty value for immediate return * @return see GLibSelectResult */ GLibSelectResult GLibSelect(GMainLoop *loop, int fd, int direction, Timespec *timeout); #ifdef HAVE_GLIB // Signal callback. Specializations will handle varying number of parameters. template struct GObjectSignalHandler { // static void handler(); // No specialization defined for the requested function prototype. }; template<> struct GObjectSignalHandler { static void handler(gpointer data) throw () { try { (*reinterpret_cast< boost::function *>(data))(); } catch (...) { Exception::handle(HANDLE_EXCEPTION_FATAL); } } }; template struct GObjectSignalHandler { static void handler(A1 a1, gpointer data) throw () { try { (*reinterpret_cast< boost::function *>(data))(a1); } catch (...) { Exception::handle(HANDLE_EXCEPTION_FATAL); } } }; template struct GObjectSignalHandler { static void handler(A1 a1, A2 a2, gpointer data) throw () { try { (*reinterpret_cast< boost::function *>(data))(a1, a2); } catch (...) { Exception::handle(HANDLE_EXCEPTION_FATAL); } } }; template struct GObjectSignalHandler { static void handler(A1 a1, A2 a2, A3 a3, gpointer data) throw () { try { (*reinterpret_cast< boost::function *>(data))(a1, a2, a3); } catch (...) { Exception::handle(HANDLE_EXCEPTION_FATAL); } } }; template struct GObjectSignalHandler { static void handler(A1 a1, A2 a2, A3 a3, A4 a4, gpointer data) throw () { try { (*reinterpret_cast< boost::function *>(data))(a1, a2, a3, a4); } catch (...) { Exception::handle(HANDLE_EXCEPTION_FATAL); } } }; template struct GObjectSignalHandler { static void handler(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, gpointer data) throw () { try { (*reinterpret_cast< boost::function *>(data))(a1, a2, a3, a4, a5); } catch (...) { Exception::handle(HANDLE_EXCEPTION_FATAL); } } }; template struct GObjectSignalHandler { static void handler(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, gpointer data) throw () { try { (*reinterpret_cast< boost::function *>(data))(a1, a2, a3, a4, a5, a6); } catch (...) { Exception::handle(HANDLE_EXCEPTION_FATAL); } } }; template struct GObjectSignalHandler { static void handler(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, gpointer data) throw () { try { (*reinterpret_cast< boost::function *>(data))(a1, a2, a3, a4, a5, a6, a7); } catch (...) { Exception::handle(HANDLE_EXCEPTION_FATAL); } } }; template struct GObjectSignalHandler { static void handler(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, gpointer data) throw () { try { (*reinterpret_cast< boost::function *>(data))(a1, a2, a3, a4, a5, a6, a7, a8); } catch (...) { Exception::handle(HANDLE_EXCEPTION_FATAL); } } }; template struct GObjectSignalHandler { static void handler(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, gpointer data) throw () { try { (*reinterpret_cast< boost::function *>(data))(a1, a2, a3, a4, a5, a6, a7, a8, a9); } catch (...) { Exception::handle(HANDLE_EXCEPTION_FATAL); } } }; enum RefOwnership { TRANSFER_REF = false, /**< * Create new smart pointer which steals an existing reference without * increasing the reference count of the object. */ ADD_REF = true /**< * Create new smart pointer which increases the reference count when * storing the pointer to the object. */ }; template class TrackGObject : public boost::intrusive_ptr { typedef boost::intrusive_ptr Base_t; // Frees the instance of boost::function which was allocated // by connectSignal. template static void signalDestroy(gpointer data, GClosure *closure) throw () { try { delete reinterpret_cast< boost::function *>(data); } catch (...) { Exception::handle(HANDLE_EXCEPTION_FATAL); } } public: TrackGObject(C *ptr, RefOwnership ownership) : Base_t(ptr, (bool)ownership) {} TrackGObject() {} TrackGObject(const TrackGObject &other) : Base_t(other) {} operator C * () const { return Base_t::get(); } operator bool () const { return Base_t::get() != NULL; } C * ref() const { return static_cast(g_object_ref(Base_t::get())); } static TrackGObject steal(C *ptr) { return TrackGObject(ptr, TRANSFER_REF); } template guint connectSignal(const char *signal, const boost::function &callback) { return g_signal_connect_data(Base_t::get(), signal, G_CALLBACK(&GObjectSignalHandler::handler), new boost::function(callback), &signalDestroy, GConnectFlags(0)); } void disconnectSignal(guint handlerID) { g_signal_handler_disconnect(static_cast(Base_t::get()), handlerID); } }; template class StealGObject : public TrackGObject { public: StealGObject(C *ptr) : TrackGObject(ptr, TRANSFER_REF) {} StealGObject() {} StealGObject(const StealGObject &other) : TrackGObject(other) {} }; template class TrackGLib : public boost::intrusive_ptr { typedef boost::intrusive_ptr Base_t; public: TrackGLib(C *ptr, RefOwnership ownership) : Base_t(ptr, (bool)ownership) {} TrackGLib() {} TrackGLib(const TrackGLib &other) : Base_t(other) {} operator C * () const { return Base_t::get(); } operator bool () const { return Base_t::get() != NULL; } C * ref() const { return static_cast(g_object_ref(Base_t::get())); } static TrackGLib steal(C *ptr) { return TrackGLib(ptr, TRANSFER_REF); } }; template class StealGLib : public TrackGLib { public: StealGLib(C *ptr) : TrackGLib(ptr, TRANSFER_REF) {} StealGLib() {} StealGLib(const StealGLib &other) : TrackGLib(other) {} }; /** * Defines a shared pointer for a GObject-based type, with intrusive * reference counting. Use *outside* of SyncEvolution namespace * (i.e. outside of SE_BEGIN/END_CXX. This is necessary because some * functions must be put into the boost namespace. The type itself is * *inside* the SyncEvolution namespace. * * connectSignal() connects a GObject signal to a boost::function with * the function signature S. Returns the handler ID, which can be * passed to g_signal_handler_disconnect() to remove the connection. * * Example: * SE_GOBJECT_TYPE(GFile) * SE_GOBJECT_TYPE(GObject) * SE_BEGIN_CXX * { * // reference normally increased during construction, * // steal() avoids that * GFileCXX filecxx = GFileCXX::steal(g_file_new_for_path("foo")); * GFile *filec = filecxx.get(); // does not increase reference count * // file freed here as filecxx gets destroyed * } * * GObjectCXX object(...); * // Define signature explicitly because it cannot be guessed from * // boost::bind() result. * object.connectSignal("notify", * boost::bind(...)); * // Signature is taken from boost::function parameter. * guint handlerID = * object.connectSignal("notify", * boost::function(boost::bind(...))); * object.disconnectSignal(handlerID); * SE_END_CXX */ #define SE_GOBJECT_TYPE(_x) \ void inline intrusive_ptr_add_ref(_x *ptr) { g_object_ref(ptr); } \ void inline intrusive_ptr_release(_x *ptr) { g_object_unref(ptr); } \ SE_BEGIN_CXX \ typedef TrackGObject<_x> _x ## CXX; \ typedef StealGObject<_x> _x ## StealCXX; \ SE_END_CXX \ /** * Defines a CXX smart pointer similar to SE_GOBJECT_TYPE, * but for types which have their own _ref and _unref * calls. * * Example: * SE_GLIB_TYPE(GMainLoop, g_main_loop) */ #define SE_GLIB_TYPE(_x, _func_prefix) \ void inline intrusive_ptr_add_ref(_x *ptr) { _func_prefix ## _ref(ptr); } \ void inline intrusive_ptr_release(_x *ptr) { _func_prefix ## _unref(ptr); } \ SE_BEGIN_CXX \ typedef TrackGLib<_x> _x ## CXX; \ typedef StealGLib<_x> _x ## StealCXX; \ SE_END_CXX SE_END_CXX SE_GOBJECT_TYPE(GFile) SE_GOBJECT_TYPE(GFileMonitor) SE_GLIB_TYPE(GMainLoop, g_main_loop) SE_GLIB_TYPE(GAsyncQueue, g_async_queue) SE_BEGIN_CXX /** * Wrapper around g_file_monitor_file(). * Not copyable because monitor is tied to specific callback * via memory address. */ class GLibNotify : public boost::noncopyable { public: typedef boost::function callback_t; GLibNotify(const char *file, const callback_t &callback); private: GFileMonitorCXX m_monitor; callback_t m_callback; }; /** * Wraps GError. Where a GError** is expected, simply pass * a GErrorCXX instance. */ struct GErrorCXX { GError *m_gerror; /** empty error, NULL pointer */ GErrorCXX() : m_gerror(NULL) {} /** copies error content */ GErrorCXX(const GErrorCXX &other) : m_gerror(g_error_copy(other.m_gerror)) {} GErrorCXX &operator =(const GErrorCXX &other) { if (m_gerror != other.m_gerror) { if (m_gerror) { g_clear_error(&m_gerror); } if (other.m_gerror) { m_gerror = g_error_copy(other.m_gerror); } } return *this; } GErrorCXX &operator =(const GError* err) { if (err != m_gerror) { if (m_gerror) { g_clear_error(&m_gerror); } if (err) { m_gerror = g_error_copy(err); } } return *this; } /** takes over ownership */ void take (GError *err) { if (err != m_gerror) { if (m_gerror) { g_clear_error(&m_gerror); } m_gerror = err; } } /** For convenient access to GError members (message, domain, ...) */ const GError * operator-> () const { return m_gerror; } /** * For passing to C functions. They must not free the GError, * because GErrorCXX retains ownership. */ operator const GError * () const { return m_gerror; } /** error description, with fallback if not set (not expected, so not localized) */ operator const char * () { return m_gerror ? m_gerror->message : "<>"; } /** clear error */ ~GErrorCXX() { g_clear_error(&m_gerror); } /** clear error if any is set */ void clear() { g_clear_error(&m_gerror); } /** transfer ownership of error back to caller */ GError *release() { GError *gerror = m_gerror; m_gerror = NULL; return gerror; } /** checks whether the current error is the one passed as parameters */ bool matches(GQuark domain, gint code) { return g_error_matches(m_gerror, domain, code); } /** * Use this when passing GErrorCXX instance to C functions which need to set it. * Make sure the pointer isn't set yet (new GErrorCXX instance, reset if * an error was encountered before) or the GNOME functions will complain * when overwriting the existing error. */ operator GError ** () { return &m_gerror; } /** true if error set */ operator bool () { return m_gerror != NULL; } /** * always throws an exception, including information from GError if available: * : |failure */ void throwError(const std::string &action); static void throwError(const std::string &action, const GError *err); }; template void NoopDestructor(T *) {} template void GObjectDestructor(T *ptr) { g_object_unref(ptr); } template void GFreeDestructor(T *ptr) { g_free(static_cast(ptr)); } /** * Copies strings from a collection into a newly allocated, NULL * terminated array. Copying the strings is optional. Suggested * usage is: * * C collection; * collection.push_back(...); * boost::scoped_array array(AllocStringArray(collection)); * */ template char **AllocStringArray(const T &strings, const char **(*allocArray)(size_t) = NULL, void (*freeArray)(const char **) = NULL, const char *(*copyString)(const char *) = NULL, const void (*freeString)(char *) = NULL) { size_t arraySize = strings.size() + 1; const char **array = NULL; array = allocArray ? allocArray(arraySize) : new const char *[arraySize]; if (!array) { throw std::bad_alloc(); } try { memset(array, 0, sizeof(*array) * arraySize); size_t i = 0; BOOST_FOREACH(const std::string &str, strings) { array[i] = copyString ? copyString(str.c_str()) : str.c_str(); if (!array[i]) { throw std::bad_alloc(); } i++; } } catch (...) { if (freeString) { for (const char **ptr = array; *ptr; ptr++) { freeString(const_cast(*ptr)); } } if (freeArray) { freeArray(array); } throw; } return const_cast(array); } /** * Wraps a G[S]List of pointers to a specific type. * Can be used with boost::FOREACH and provides forward iterators * (two-way iterators and reverse iterators also possible, but not implemented). * Frees the list and optionally (not turned on by default) also frees * the data contained in it, using the provided destructor class. * Use GObjectDestructor for GObject instances. * * @param T the type of the instances pointed to inside the list * @param L GList or GSList * @param D destructor function freeing a T instance */ template< class T, class L, void (*D)(T*) = NoopDestructor > struct GListCXX : boost::noncopyable { L *m_list; static void listFree(GSList *l) { g_slist_free(l); } static void listFree(GList *l) { g_list_free(l); } static GSList *listPrepend(GSList *list, T *entry) { return g_slist_prepend(list, (gpointer)entry); } static GList *listPrepend(GList *list, T *entry) { return g_list_prepend(list, (gpointer)entry); } static GSList *listAppend(GSList *list, T *entry) { return g_slist_append(list, (gpointer)entry); } static GList *listAppend(GList *list, T *entry) { return g_list_append(list, (gpointer)entry); } public: typedef T * value_type; /** by default initialize an empty list; if parameter is not NULL, owership is transferred to the new instance of GListCXX */ GListCXX(L *list = NULL) : m_list(list) {} /** free list */ ~GListCXX() { clear(); } /** free old content, take owership of new one */ void reset(L *list = NULL) { clear(); m_list = list; } bool empty() { return m_list == NULL; } /** clear error if any is set */ void clear() { #if 1 BOOST_FOREACH(T *entry, *this) { D(entry); } #else for (iterator it = begin(); it != end(); ++it) { D(*it); } #endif listFree(m_list); m_list = NULL; } /** * Use this when passing GListCXX instance to C functions which need to set it. * Make sure the pointer isn't set yet (new GListCXX instance or cleared). */ operator L ** () { return &m_list; } /** * Cast to plain G[S]List, for use in functions which do not modify the list. */ operator L * () { return m_list; } class iterator : public std::iterator { L *m_entry; public: iterator(L *list) : m_entry(list) {} iterator(const iterator &other) : m_entry(other.m_entry) {} /** * boost::foreach needs a reference as return code here, * which forces us to do type casting on the address of the void * pointer, * then dereference the pointer. The reason is that typecasting the * pointer value directly yields an rvalue, which can't be used to initialize * the reference return value. */ T * &operator -> () const { return *getEntryPtr(); } T * &operator * () const { return *getEntryPtr(); } iterator & operator ++ () { m_entry = m_entry->next; return *this; } iterator operator ++ (int) { return iterator(m_entry->next); } bool operator == (const iterator &other) { return m_entry == other.m_entry; } bool operator != (const iterator &other) { return m_entry != other.m_entry; } private: /** * Used above, necessary to hide the fact that we do type * casting tricks. Otherwise the compiler will complain about * *(T **)&m_entry->data with "dereferencing type-punned * pointer will break strict-aliasing rules". * * That warning is about breaking assumptions that the compiler * uses for optimizations. The hope is that those optimzations * aren't done here, and/or are disabled by using a function. */ T** getEntryPtr() const { return (T **)&m_entry->data; } }; iterator begin() { return iterator(m_list); } iterator end() { return iterator(NULL); } class const_iterator : public std::iterator { L *m_entry; T *m_value; public: const_iterator(L *list) : m_entry(list) {} const_iterator(const const_iterator &other) : m_entry(other.m_entry) {} T * &operator -> () const { return *getEntryPtr(); } T * &operator * () const { return *getEntryPtr(); } const_iterator & operator ++ () { m_entry = m_entry->next; return *this; } const_iterator operator ++ (int) { return iterator(m_entry->next); } bool operator == (const const_iterator &other) { return m_entry == other.m_entry; } bool operator != (const const_iterator &other) { return m_entry != other.m_entry; } private: T** getEntryPtr() const { return (T **)&m_entry->data; } }; const_iterator begin() const { return const_iterator(m_list); } const_iterator end() const { return const_iterator(NULL); } void push_back(T *entry) { m_list = listAppend(m_list, entry); } void push_front(T *entry) { m_list = listPrepend(m_list, entry); } }; /** use this for a list which owns the strings it points to */ typedef GListCXX > GStringListFreeCXX; /** use this for a list which does not own the strings it points to */ typedef GListCXX GStringListNoFreeCXX; /** * Wraps a C gchar array and takes care of freeing the memory. */ class PlainGStr : public boost::shared_ptr { public: PlainGStr() {} PlainGStr(gchar *str) : boost::shared_ptr(str, g_free) {} PlainGStr(const PlainGStr &other) : boost::shared_ptr(other) {} operator const gchar *() const { return &**this; } const gchar *c_str() const { return &**this; } }; /** * Wraps a glib string array, frees with g_strfreev(). */ class PlainGStrArray : public boost::shared_ptr { public: PlainGStrArray() {} PlainGStrArray(gchar **array) : boost::shared_ptr(array, g_strfreev) {} PlainGStrArray(const PlainGStrArray &other) : boost::shared_ptr(other) {} operator gchar * const *() const { return &**this; } gchar * &at(size_t index) { return get()[index]; } private: // Hide this operator because boost::shared_ptr has problems with it, // probably because of missing traits for a pointer type. Instead use // at(). gchar * operator[] (size_t index); }; // empty template, need specialization based on parameter and return types template struct GAsyncReady5 {}; template struct GAsyncReady4 {}; template struct GAsyncReady3 {}; template struct GAsyncReady2 {}; template struct GAsyncReady1 {}; // empty template, need specializations based on arity template struct GAsyncReadyCXX {}; // five parameters of finish function template struct GAsyncReadyCXX : public GAsyncReady5::result_type, F, finish, typename boost::function_traits::arg1_type, typename boost::function_traits::arg2_type, typename boost::function_traits::arg3_type, typename boost::function_traits::arg4_type, typename boost::function_traits::arg5_type> { }; // four parameters template struct GAsyncReadyCXX : public GAsyncReady4::result_type, F, finish, typename boost::function_traits::arg1_type, typename boost::function_traits::arg2_type, typename boost::function_traits::arg3_type, typename boost::function_traits::arg4_type> { }; // three parameters template struct GAsyncReadyCXX : public GAsyncReady3::result_type, F, finish, typename boost::function_traits::arg1_type, typename boost::function_traits::arg2_type, typename boost::function_traits::arg3_type> { }; // two parameters template struct GAsyncReadyCXX : public GAsyncReady2::result_type, F, finish, typename boost::function_traits::arg1_type, typename boost::function_traits::arg2_type> { }; // one parameter template struct GAsyncReadyCXX : public GAsyncReady1::result_type, F, finish, typename boost::function_traits::arg1_type> { }; // For finish functions with return parameters the assumption is that // they have a non-void return value. Otherwise there would be no need // for the return parameters. // // result = GObject, GAsyncResult, A3, A4, A5 template struct GAsyncReady5 { typedef typename boost::remove_pointer::type A3_t; typedef typename boost::remove_pointer::type A4_t; typedef typename boost::remove_pointer::type A5_t; typedef boost::function CXXFunctionCB_t; static void handleGLibResult(GObject *sourceObject, GAsyncResult *result, gpointer userData) throw () { try { A3_t retval1 = boost::value_initialized(); A4_t retval2 = boost::value_initialized(); A5_t retval3 = boost::value_initialized(); T t = finish(reinterpret_cast(sourceObject), result, &retval1, &retval2, &retval3); std::auto_ptr cb(static_cast(userData)); (*cb)(t, retval1, retval2, retval3); } catch (...) { // called from C, must not let exception escape Exception::handle(HANDLE_EXCEPTION_FATAL); } } }; // result = GObject, GAsyncResult, A3, A4, GError template struct GAsyncReady5 { typedef typename boost::remove_pointer::type A3_t; typedef typename boost::remove_pointer::type A4_t; typedef boost::function CXXFunctionCB_t; static void handleGLibResult(GObject *sourceObject, GAsyncResult *result, gpointer userData) throw () { try { GErrorCXX gerror; A3_t retval1 = boost::value_initialized(); A4_t retval2 = boost::value_initialized(); T t = finish(reinterpret_cast(sourceObject), result, &retval1, &retval2, gerror); std::auto_ptr cb(static_cast(userData)); (*cb)(t, retval1, retval2, gerror); } catch (...) { // called from C, must not let exception escape Exception::handle(HANDLE_EXCEPTION_FATAL); } } }; // result = GObject, GAsyncResult, A3, A4 template struct GAsyncReady4 { typedef typename boost::remove_pointer::type A3_t; typedef typename boost::remove_pointer::type A4_t; typedef boost::function CXXFunctionCB_t; static void handleGLibResult(GObject *sourceObject, GAsyncResult *result, gpointer userData) throw () { try { A3_t retval1 = boost::value_initialized(); A4_t retval2 = boost::value_initialized(); T t = finish(reinterpret_cast(sourceObject), result, &retval1, &retval2); std::auto_ptr cb(static_cast(userData)); (*cb)(t, retval1, retval2); } catch (...) { // called from C, must not let exception escape Exception::handle(HANDLE_EXCEPTION_FATAL); } } }; // result = GObject, GAsyncResult, A3, GError template struct GAsyncReady4 { typedef typename boost::remove_pointer::type A3_t; typedef boost::function CXXFunctionCB_t; static void handleGLibResult(GObject *sourceObject, GAsyncResult *result, gpointer userData) throw () { try { GErrorCXX gerror; A3_t retval = boost::value_initialized(); T t = finish(reinterpret_cast(sourceObject), result, &retval, gerror); std::auto_ptr cb(static_cast(userData)); (*cb)(t, retval, gerror); } catch (...) { // called from C, must not let exception escape Exception::handle(HANDLE_EXCEPTION_FATAL); } } }; // res = GObject, GAsyncResult, GError template struct GAsyncReady3{ typedef boost::function CXXFunctionCB_t; static void handleGLibResult(GObject *sourceObject, GAsyncResult *result, gpointer userData) throw () { try { GErrorCXX gerror; T t = finish(reinterpret_cast(sourceObject), result, gerror); std::auto_ptr cb(static_cast(userData)); (*cb)(t, gerror); } catch (...) { // called from C, must not let exception escape Exception::handle(HANDLE_EXCEPTION_FATAL); } } }; // void = GObject, GAsyncResult, GError template struct GAsyncReady3 { typedef boost::function CXXFunctionCB_t; static void handleGLibResult(GObject *sourceObject, GAsyncResult *result, gpointer userData) throw () { try { GErrorCXX gerror; finish(reinterpret_cast(sourceObject), result, gerror); std::auto_ptr cb(static_cast(userData)); (*cb)(gerror); } catch (...) { // called from C, must not let exception escape Exception::handle(HANDLE_EXCEPTION_FATAL); } } }; // result = GObject, GAsyncResult template struct GAsyncReady2 { typedef boost::function CXXFunctionCB_t; static void handleGLibResult(GObject *sourceObject, GAsyncResult *result, gpointer userData) throw () { try { T t = finish(reinterpret_cast(sourceObject), result); std::auto_ptr cb(static_cast(userData)); (*cb)(t); } catch (...) { // called from C, must not let exception escape Exception::handle(HANDLE_EXCEPTION_FATAL); } } }; // result = GAsyncResult, GError template struct GAsyncReady2 { public: typedef boost::function CXXFunctionCB_t; static void handleGLibResult(GObject *sourceObject, GAsyncResult *result, gpointer userData) throw () { try { GErrorCXX gerror; T t = finish(result, gerror); std::auto_ptr cb(static_cast(userData)); (*cb)(t, gerror); } catch (...) { // called from C, must not let exception escape Exception::handle(HANDLE_EXCEPTION_FATAL); } } }; // void = GObject, GAsyncResult template struct GAsyncReady2 { typedef boost::function CXXFunctionCB_t; static void handleGLibResult(GObject *sourceObject, GAsyncResult *result, gpointer userData) throw () { try { finish(reinterpret_cast(sourceObject), result); std::auto_ptr cb(static_cast(userData)); (*cb)(); } catch (...) { // called from C, must not let exception escape Exception::handle(HANDLE_EXCEPTION_FATAL); } } }; // void = GAsyncResult, GError template struct GAsyncReady2 { typedef boost::function CXXFunctionCB_t; static void handleGLibResult(GObject *sourceObject, GAsyncResult *result, gpointer userData) throw () { try { GErrorCXX gerror; finish(result, gerror); std::auto_ptr cb(static_cast(userData)); (*cb)(gerror); } catch (...) { // called from C, must not let exception escape Exception::handle(HANDLE_EXCEPTION_FATAL); } } }; /** * convenience macro for picking the GAsyncReadyCXX that matches the _prepare call: * first switch based on arity of the finish function, then on its type */ #define SYNCEVO_GLIB_CALL_ASYNC_CXX(_prepare) \ GAsyncReadyCXX< boost::remove_pointer::type, \ & _prepare ## _finish, \ boost::function_traits::type>::arity > /** * Macro for asynchronous methods which use a GAsyncReadyCallback to * indicate completion. The assumption is that there is a matching * _finish function for the function which starts the operation. * * The boost::function callback will be called exactly once, with the * following parameters: * - return value of the _finish call, if non-void * - all return parameters of the _finish call, in the order * in which they appear there * - a GError is passed as "const GError *", with NULL if the * _finish function did not set an error; it does not have to * be freed. * * Other parameters must be freed if required by the _finish semantic. * * Use boost::bind() with a boost::weak_ptr as second * parameter when the callback belongs to an instance which is * not guaranteed to be around when the operation completes. * * Example: * * static void asyncCB(const GError *gerror, const char *func, bool &failed, bool &done) { * done = true; * if (gerror) { * failed = true; * // log gerror->message or store in GErrorCXX * } * } * * bool done = false, failed = false; * SYNCEVO_GLIB_CALL_ASYNC(folks_individual_aggregator_prepare, * boost::bind(asyncCB, _1, * "folks_individual_aggregator_prepare", * boost::ref(failed), boost::ref(done)), * aggregator); * * // Don't continue unless finished, because the callback will write * // into "done" and possibly "failed". * while (!done) { * g_main_context_iteration(NULL, true); * } * * @param _prepare name of the function which starts the operation * @param _cb boost::function with GError pointer and optional result value; * exceptions are considered fatal * @param _args parameters of _prepare, without the final GAsyncReadyCallback + user_data pair; * usually at least a GCancellable pointer is part of the arguments */ #define SYNCEVO_GLIB_CALL_ASYNC(_prepare, _cb, _args...) \ _prepare(_args, \ SYNCEVO_GLIB_CALL_ASYNC_CXX(_prepare)::handleGLibResult, \ new SYNCEVO_GLIB_CALL_ASYNC_CXX(_prepare)::CXXFunctionCB_t(_cb)) // helper class for finish method with some kind of result other than void template class GAsyncReadyDoneCXX { public: template static void storeResult(GErrorCXX &gerrorStorage, R &resultStorage, bool &done, T result, const GError *gerror) { done = true; gerrorStorage = gerror; resultStorage = result; } template static boost::function createCB(R &result, GErrorCXX &gerror, bool &done) { return boost::bind(storeResult, boost::ref(gerror), boost::ref(result), boost::ref(done), _1, _2); } }; // helper class for finish method with void result template<> class GAsyncReadyDoneCXX { public: static void storeResult(GErrorCXX &gerrorStorage, bool &done, const GError *gerror) { done = true; gerrorStorage = gerror; } static boost::function createCB(const int *dummy, GErrorCXX &gerror, bool &done) { return boost::bind(storeResult, boost::ref(gerror), boost::ref(done), _1); } }; /** * Like SYNCEVO_GLIB_CALL_ASYNC, but blocks until the operation * has finished. * * @param _res an instance which will hold the result when done, NULL when result is void * @param _gerror a GErrorCXX instance which will hold an error * pointer afterwards in case of a failure */ #define SYNCEVO_GLIB_CALL_SYNC(_res, _gerror, _prepare, _args...) \ do { \ bool done = false; \ SYNCEVO_GLIB_CALL_ASYNC(_prepare, \ GAsyncReadyDoneCXX::result_type>::createCB(_res, _gerror, done), \ _args); \ GRunWhile(! boost::lambda::var(done)); \ } while (false); \ /** * Process events in the default context while the callback returns * true. * * This must be used instead of g_main_context_iterate() by code which * may get called in other threads. In that case the check is * transferred to the main thread which does the actual event * processing. g_main_context_iterate() would just block because we * register the main thread as permanent owner of the default context, * or would suffer from race conditions if we didn't. * * The main thread must also be running GRunWhile(). * * Exceptions in the check code are fatal and should be avoided. */ void GRunWhile(const boost::function &check); #endif // HAVE_GLIB SE_END_CXX #endif // INCL_GLIB_SUPPORT syncevolution_1.4/src/syncevo/GValueSupport.h000066400000000000000000000205111230021373600215630ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_GVALUE_SUPPORT #define INCL_GVALUE_SUPPORT #include #include #include SE_BEGIN_CXX /** * Base C++ wrapper for GValue. Owns the data stored in it. * init() must be called before using it. */ class GValueCXX : public GValue { public: GValueCXX() { memset(static_cast(this), 0, sizeof(GValue)); } GValueCXX(const GValue &other) { memset(static_cast(this), 0, sizeof(GValue)); *this = other; } ~GValueCXX() { g_value_unset(this); } void init(GType gType) { g_value_init(this, gType); } GValueCXX &operator = (const GValue &other) { if (&other != this) { g_value_copy(&other, this); } return *this; } /** text representation, for debugging */ PlainGStr toString() const { PlainGStr str(g_strdup_value_contents(this)); return str; } /** * A GDestroyNotify for dynamically allocated GValueCXX instances, * for use in containers like GHashTable. */ static void destroy(gpointer gvaluecxx) { delete static_cast(gvaluecxx); } }; template void dummyTake(GValue *, C); template void dummySetStatic(GValue *, const C); /** * Declares a C++ wrapper for a GValue containing a specific * GType. */ template< class nativeType, class constNativeType, GType gType, void (*setGValue)(GValue *, constNativeType), constNativeType (*getFromGValue)(const GValue *), void (*takeIntoGValue)(GValue *, nativeType) = dummyTake, void (*setStaticInGValue)(GValue *, constNativeType) = dummySetStatic > class GValueTypedCXX : public GValueCXX { public: typedef GValueTypedCXX value_type; /** * prepare value, without setting it (isSet() will return false) */ GValueTypedCXX() { init(gType); } /** * copy other value, referencing (GObject) or duplicating (string) it */ GValueTypedCXX(const value_type &other) { init(gType); *this = other; } /** * copy value, referencing (GObject) or duplicating (string) it */ GValueTypedCXX(constNativeType value) { init(gType); set(value); } /** * copy (addRef = true) or take ownership of value during construction */ GValueTypedCXX(nativeType value, bool addRef) { init(gType); if (addRef) { set(value); } else { take(value); } } /** copy other value */ GValueCXX &operator = (const value_type &other) { if (&other != this) { g_value_copy(&other, this); } return *this; } /** copy other value */ GValueCXX &operator = (constNativeType other) { set(other); return *this; } /** * set value, copying (string) or referencing (GObject) it if necessary */ void set(constNativeType other) { setGValue(this, other); } /** * store pointer to static instance which does not have to be copied or freed * (like a const char * string) */ void setStatic(constNativeType other) { setStaticInGValue(this, other); } /** transfer ownership of complex object (string, GObject) to GValue */ void take(nativeType other) { takeIntoGValue(this, other); } /** access content without transfering ownership */ constNativeType get() const { return getFromGValue(this); } }; /** * Declares a C++ wrapper for a GValue containing a dynamically * created GType. Uses g_value_set/get/take_boxed() with the necessary * type casting. * * Example: * typedef GValueDynTypedCXX GValueDateTimeCXX; */ template< class nativeType, GType (*gTypeFactory)() > class GValueDynTypedCXX : public GValueCXX { public: typedef GValueDynTypedCXX value_type; /** * prepare value, without setting it (isSet() will return false) */ GValueDynTypedCXX() { init(gTypeFactory()); } /** * copy other value */ GValueDynTypedCXX(const value_type &other) { init(gTypeFactory()); *this = other; } /** * copy value, referencing (GObject) or duplicating (string) it */ GValueDynTypedCXX(const nativeType value) { init(gTypeFactory()); set(value); } /** * copy (addRef = true) or take ownership of value during construction */ GValueDynTypedCXX(nativeType value, bool addRef) { init(gTypeFactory()); if (addRef) { set(value); } else { take(value); } } /** copy other value */ value_type &operator = (const value_type &other) { if (&other != this) { g_value_copy(&other, this); } return *this; } /** copy other value, referencing (GObject) or duplicating (string) it */ value_type &operator = (const nativeType other) { set(other); return *this; } /** * set value, copying (string) or referencing (GObject) it if necessary */ void set(const nativeType other) { g_value_set_boxed(this, other); } /** * store pointer to static instance which does not have to be copied or freed * (like a const char * string) */ void setStatic(const nativeType other) { g_value_set_static_boxed(this, other); } /** transfer ownership of complex object (string, GObject) to GValue */ void take(nativeType other) { g_value_take_boxed(this, other); } /** access content without transfering ownership */ const nativeType get() const { return static_cast(g_value_get_boxed(this)); } }; typedef GValueTypedCXX GValueBooleanCXX; typedef GValueTypedCXX GValueCharCXX; typedef GValueTypedCXX GValueUCharCXX; typedef GValueTypedCXX GValueIntCXX; typedef GValueTypedCXX GValueUIntCXX; typedef GValueTypedCXX GValueLongCXX; typedef GValueTypedCXX GValueULongCXX; typedef GValueTypedCXX GValueInt64CXX; typedef GValueTypedCXX GValueUInt64CXX; typedef GValueTypedCXX GValueFloatCXX; typedef GValueTypedCXX GValueDoubleCXX; typedef GValueTypedCXX GValueEnumCXX; typedef GValueTypedCXX GValueBooleanCXX; typedef GValueTypedCXX GValueStringCXX; typedef GValueTypedCXX GValueObjectCXX; SE_END_CXX #endif // INCL_GVALUE_SUPPORT syncevolution_1.4/src/syncevo/GeeSupport.h000066400000000000000000000155241230021373600211100ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_GEE_SUPPORT #define INCL_GEE_SUPPORT #include #include #include SE_GOBJECT_TYPE(GeeMap) SE_GOBJECT_TYPE(GeeMapEntry) SE_GOBJECT_TYPE(GeeMapIterator) SE_GOBJECT_TYPE(GeeIterable) SE_GOBJECT_TYPE(GeeIterator) SE_GOBJECT_TYPE(GeeMultiMap) SE_GOBJECT_TYPE(GeeCollection) #include SE_BEGIN_CXX namespace GeeSupport { /** Used for GeeMapEntryWrapper. */ template struct traits { typedef E Wrapper_t; typedef typename E::Cast_t Cast_t; static E get(Wrapper_t &wrapper) { return wrapper; } }; /** Default is for types which have a corresponding SE_GOBJECT_TYPE. */ template struct traits { typedef StealGObject Wrapper_t; typedef E * Cast_t; static E * get(Wrapper_t &wrapper) { return wrapper.get(); } }; /** Dynamically allocated plain C strings also work. */ template<> struct traits { typedef PlainGStr Wrapper_t; typedef gchar * Cast_t; static const gchar * get(Wrapper_t &wrapper) { return wrapper; } }; } /** * A wrapper class for some kind of Gee collection (like List or Map) * which provides standard const forward iterators. Main use case * is read-only access via BOOST_FOREACH. * * Example: * GeeMap *individuals = folks_individual_aggregator_get_individuals(aggregator); * typedef GeeCollCXX< GeeMapEntryWrapper > Coll; * BOOST_FOREACH (Coll::value_type &entry, Coll(individuals)) { * const gchar *id = entry.key(); * FolksIndividual *individual(entry.value()); * GeeSet *emails = folks_email_details_get_email_addresses(FOLKS_EMAIL_DETAILS(individual)); * typedef GeeCollCXX EmailColl; * BOOST_FOREACH (FolksEmailFieldDetails *email, EmailColl(emails)) { * const gchar *value = * reinterpret_cast(folks_abstract_field_details_get_value(FOLKS_ABSTRACT_FIELD_DETAILS(email))); * } * } * * @param Entry The C++ type that corresponds to the entries in the collection, * must be copyable and constructable from a gpointer (default) or * intermediate type Cast (when given). Must own the content * pointed to by the gpointer. Plain pointers are not good enough, * they lead to memory leaks! * @param Cast Used to cast gpointer into something that Entry's constructor accepts. */ template class GeeCollCXX { GeeIterableCXX m_collection; public: template GeeCollCXX(Collection *collection, RefOwnership ownership) : m_collection(GEE_ITERABLE(collection), ownership) {} GeeCollCXX(GeeCollectionCXX &collection) : m_collection(GEE_ITERABLE(collection.get()), ADD_REF) {} GeeIterable *get() const { return m_collection.get(); } class Iterator { /** Defines how to handle the gpointer result of gee_iterator_get(). */ typedef GeeSupport::traits Traits_t; mutable GeeIteratorCXX m_it; bool m_valid; /** A smart pointer which owns the value returned by gee_iterator_get(). */ typename Traits_t::Wrapper_t m_wrapper; /** A copy of the wrapped value, needed because the * operator must return a reference to it. */ Entry m_entry; public: /** * Takes ownership of iterator, which may be NULL for the end Iterator. */ Iterator(GeeIterator *iterator) : m_it(iterator, TRANSFER_REF), m_valid(false) {} Iterator & operator ++ () { m_valid = gee_iterator_next(m_it); if (m_valid) { // First cast gpointer into something which is accepted by the wrapper. */ m_wrapper = typename Traits_t::Wrapper_t(static_cast((gee_iterator_get(m_it)))); m_entry = Traits_t::get(m_wrapper); } else { m_wrapper = typename Traits_t::Wrapper_t(NULL); m_entry = NULL; } return *this; } bool operator == (const Iterator &other) { if (other.m_it.get() == NULL) { // Comparison against end Iterator. return !m_valid; } else { // Cannot check for "point to same element"; // at least detect when compared against ourselves. return this == &other; } } bool operator != (const Iterator &other) { return !(*this == other); } Entry & operator * () { return m_entry; } typedef Entry value_type; typedef ptrdiff_t difference_type; typedef std::forward_iterator_tag iterator_category; typedef Entry *pointer; typedef Entry &reference; }; Iterator begin() const { Iterator it(gee_iterable_iterator(m_collection.get())); // advance to first element, if any ++it; return it; } Iterator end() const { return Iterator(NULL); } typedef Iterator const_iterator; typedef Iterator iterator; typedef Entry value_type; }; /** A collection of gchar * strings. */ typedef GeeCollCXX GeeStringCollection; template std::forward_iterator_tag iterator_category(const typename GeeCollCXX::Iterator &) { return std::forward_iterator_tag(); } template class GeeMapEntryWrapper { mutable GeeMapEntryCXX m_entry; public: typedef GeeMapEntry *Cast_t; /** take ownership of entry instance */ GeeMapEntryWrapper(GeeMapEntry *entry = NULL) : m_entry(entry, TRANSFER_REF) {} GeeMapEntryWrapper(const GeeMapEntryWrapper &other): m_entry(other.m_entry) {} Key key() const { return reinterpret_cast(const_cast(gee_map_entry_get_key(m_entry))); } Value value() const { return reinterpret_cast(const_cast(gee_map_entry_get_value(m_entry))); } }; SE_END_CXX #endif // INCL_GEE_SUPPORT syncevolution_1.4/src/syncevo/HashConfigNode.h000066400000000000000000000033511230021373600216250ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly */ #ifndef INCL_EVOLUTION_HASH_CONFIG_NODE # define INCL_EVOLUTION_HASH_CONFIG_NODE #include #include #include #include SE_BEGIN_CXX /** * Implements a ConfigNode with an in-memory hash table. */ class HashConfigNode : public ConfigNode { std::map m_props; const std::string m_name; public: /** * @param name a string for debugging and error reporting */ HashConfigNode(const std::string &name = "hash config node") : m_name(name) {} /* keep underlying methods visible; our own setProperty() would hide them */ using ConfigNode::setProperty; virtual string getName() const { return m_name; } virtual void flush() {} virtual string readProperty(const string &property) const { std::map::const_iterator it = m_props.find(property); if (it == m_props.end()) { return ""; } else { return it->second; } } virtual void setProperty(const string &property, const string &value, const string &comment = "", const string *defValue = NULL) { m_props[property] = value; } virtual void readProperties(std::map &props) const { props = m_props; } virtual void writeProperties(const PropsType &props) { m_props.insert(props.begin(), props.end()); } virtual void removeProperty(const std::string &property) { m_props.erase(property); } virtual bool exists() const { return true; } virtual void clear() { m_props.clear(); } }; SE_END_CXX #endif syncevolution_1.4/src/syncevo/IdentityProvider.cpp000066400000000000000000000103351230021373600226450ustar00rootroot00000000000000/* * Copyright (C) 2013 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include SE_BEGIN_CXX const char USER_IDENTITY_PLAIN_TEXT[] = "user"; const char USER_IDENTITY_SYNC_CONFIG[] = "id"; Credentials IdentityProviderCredentials(const UserIdentity &identity, const InitStateString &password) { Credentials cred; if (identity.m_provider == USER_IDENTITY_PLAIN_TEXT) { cred.m_username = identity.m_identity; cred.m_password = password; } else { // We could use the gSSO password plugin to request // username/password. But it is uncertain whether that is useful, // therefore that is not implemented at the moment. SE_THROW(StringPrintf("%s: need username+password as credentials", identity.toString().c_str())); } return cred; } class CredentialsProvider : public AuthProvider { Credentials m_creds; public: CredentialsProvider(const std::string &username, const std::string &password) { m_creds.m_username = username; m_creds.m_password = password; } virtual bool wasConfigured() const { return !m_creds.m_username.empty() || !m_creds.m_password.empty(); } virtual bool methodIsSupported(AuthMethod method) const { return method == AUTH_METHOD_CREDENTIALS; } virtual Credentials getCredentials() const { return m_creds; } virtual std::string getOAuth2Bearer(int failedTokens) const { SE_THROW("OAuth2 not supported"); return ""; } virtual std::string getUsername() const { return m_creds.m_username; } }; boost::shared_ptr AuthProvider::create(const UserIdentity &identity, const InitStateString &password) { boost::shared_ptr authProvider; if (identity.m_provider == USER_IDENTITY_PLAIN_TEXT) { SE_LOG_DEBUG(NULL, "using plain username/password for %s", identity.toString().c_str()); authProvider.reset(new CredentialsProvider(identity.m_identity, password)); } else { SE_LOG_DEBUG(NULL, "looking for identity provider for %s", identity.toString().c_str()); BOOST_FOREACH (IdentityProvider *idProvider, IdentityProvider::getRegistry()) { if (boost::iequals(idProvider->m_key, identity.m_provider)) { authProvider = idProvider->create(identity.m_identity, password); if (!authProvider) { SE_THROW(StringPrintf("identity provider for '%s' is disabled in this installation", identity.m_provider.c_str())); } break; } } if (!authProvider) { SE_THROW(StringPrintf("unknown identity provider '%s' in '%s'", identity.m_provider.c_str(), identity.toString().c_str())); } } return authProvider; } std::list &IdentityProvider::getRegistry() { static std::list providers; return providers; } IdentityProvider::IdentityProvider(const std::string &key, const std::string &descr) : m_key(key), m_descr(descr) { getRegistry().push_back(this); } IdentityProvider::~IdentityProvider() { getRegistry().erase(std::find(getRegistry().begin(), getRegistry().end(), this)); } SE_END_CXX syncevolution_1.4/src/syncevo/IdentityProvider.h000066400000000000000000000123301230021373600223070ustar00rootroot00000000000000/* * Copyright (C) 2013 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_SYNC_EVOLUTION_IDENTITY_PROVIDER # define INCL_SYNC_EVOLUTION_IDENTITY_PROVIDER #include #include #include SE_BEGIN_CXX extern const char USER_IDENTITY_PLAIN_TEXT[]; extern const char USER_IDENTITY_SYNC_CONFIG[]; struct UserIdentity; // from SyncConfig.h struct Credentials { std::string m_username; std::string m_password; }; /** * Returns username/password for an identity. The password is the * string configured for it inside SyncEvolution. It may be empty and/or unset if * the plain text password comes from the identity provider. * * If the credentials cannot be retrieved, an error is thrown, so don't use this * in cases where a different authentication method might also work. */ Credentials IdentityProviderCredentials(const UserIdentity &identity, const InitStateString &password); /** * Supports multiple different ways of authorizing the user. * Actual implementations are IdentityProvider specific. */ class AuthProvider { public: /** * Creates an AuthProvider matching the identity.m_provider value * or throws an exception if that fails. Never returns NULL. */ static boost::shared_ptr create(const UserIdentity &identity, const InitStateString &password); enum AuthMethod { AUTH_METHOD_NONE, AUTH_METHOD_CREDENTIALS, AUTH_METHOD_OAUTH2, AUTH_METHOD_MAX }; /** * Return true if some kind of credentials were configured by the user. * They don't have to be usable. */ virtual bool wasConfigured() const { return true; } /** * Returns true if the given method is supported and currently possible. */ virtual bool methodIsSupported(AuthMethod method) const = 0; /** * Returns username/password credentials. Throws an error if not supported. */ virtual Credentials getCredentials() const = 0; /** * Returns the 'Bearer b64token' string required for logging into * services supporting OAuth2 or throws an exception when we don't * have a valid token. Internally this will refresh tokens * automatically. * * See http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-20#section-2.1 * * An application should: * - request a token and try to use it * - in case of failure try to request a token again, and try to use it * again (in case the first token has expired just before using it) * - if the second token also fails, request a third token with full * re-authentication as above. * - if that fails, then give up. * * To achieve that, the caller must count how often he got a token that * did not work. * * @param failedTokens zero when asking for initial token, one for refresh, two for full re-authorization * * @return a base64 encoded token, ready to be used in "Authorization: Bearer %s" */ virtual std::string getOAuth2Bearer(int failedTokens) const = 0; /** * Returns username at the remote service. Works for * username/password credentials and may be made to work for * OAuth2. At the moment, code should not depend on it when using * OAuth2. */ virtual std::string getUsername() const = 0; }; /** * Instantiating this class adds a new provider. * Deleting it removes it. */ class IdentityProvider { public: /** returns NULL if disabled, valid AuthProvider if possible, and throws error if something goes wrong */ virtual boost::shared_ptr create(const InitStateString &username, const InitStateString &password) = 0; /** * All known providers. */ static std::list &getRegistry(); /** * @param key short, unique word without colons used to select this provider * in an identity string, for example "gsso" * @param descr one or more lines describing the provider and its syntax, * for example * "gsso:\n" * " authentication using libgsignond + libaccounts\n" */ IdentityProvider(const std::string &key, const std::string &descr); virtual ~IdentityProvider(); const std::string m_key; const std::string m_descr; }; SE_END_CXX #endif syncevolution_1.4/src/syncevo/IniConfigNode.cpp000066400000000000000000000230641230021373600220170ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include #include #include #include using namespace std; SE_BEGIN_CXX IniBaseConfigNode::IniBaseConfigNode(const boost::shared_ptr &data) : m_data(data) { } void IniBaseConfigNode::flush() { if (!m_modified) { return; } if (m_data->isReadonly()) { throw std::runtime_error(m_data->getName() + ": internal error: flushing read-only config node not allowed"); } boost::shared_ptr file = m_data->write(); toFile(*file); m_modified = false; } IniFileConfigNode::IniFileConfigNode(const boost::shared_ptr &data) : IniBaseConfigNode(data) { read(); } IniFileConfigNode::IniFileConfigNode(const std::string &path, const std::string &fileName, bool readonly) : IniBaseConfigNode(boost::shared_ptr(new FileDataBlob(path, fileName, readonly))) { read(); } void IniFileConfigNode::toFile(std::ostream &file) { BOOST_FOREACH(const string &line, m_lines) { file << line << std::endl; } } void IniFileConfigNode::read() { boost::shared_ptr file(m_data->read()); std::string line; while (getline(*file, line)) { m_lines.push_back(line); } m_modified = false; } /** * get property and value from line, if any present */ static bool getContent(const string &line, string &property, string &value, bool &isComment, bool fuzzyComments) { size_t start = 0; while (start < line.size() && isspace(line[start])) { start++; } // empty line? if (start == line.size()) { return false; } // Comment? Potentially keep reading, might be commented out assignment. isComment = false; if (line[start] == '#') { if (!fuzzyComments) { return false; } isComment = true; } // recognize # = as commented out (= default) value if (isComment) { start++; while (start < line.size() && isspace(line[start])) { start++; } } // extract property size_t end = start; while (end < line.size() && !isspace(line[end])) { end++; } property = line.substr(start, end - start); // skip assignment start = end; while (start < line.size() && isspace(line[start])) { start++; } if (start == line.size() || line[start] != '=') { // invalid syntax or we tried to read a comment as assignment return false; } // extract value start++; while (start < line.size() && isspace(line[start])) { start++; } value = line.substr(start); // remove trailing white space: usually it is // added accidentally by users size_t numspaces = 0; while (numspaces < value.size() && isspace(value[value.size() - 1 - numspaces])) { numspaces++; } value.erase(value.size() - numspaces); // @TODO: strip quotation marks around value?! return true; } /** * check whether the line contains the property and if so, extract its value */ static bool getValue(const string &line, const string &property, string &value, bool &isComment, bool fuzzyComments) { string curProp; return getContent(line, curProp, value, isComment, fuzzyComments) && !strcasecmp(curProp.c_str(), property.c_str()); } InitStateString IniFileConfigNode::readProperty(const string &property) const { string value; BOOST_FOREACH(const string &line, m_lines) { bool isComment; if (getValue(line, property, value, isComment, false)) { return InitStateString(value, true); } } return InitStateString(); } void IniFileConfigNode::readProperties(ConfigProps &props) const { map res; string value, property; BOOST_FOREACH(const string &line, m_lines) { bool isComment; if (getContent(line, property, value, isComment, false)) { // don't care about the result: only the first instance // of the property counts, so it doesn't matter when // inserting it again later fails props.insert(ConfigProps::value_type(property, InitStateString(value, true))); } } } void IniFileConfigNode::removeProperty(const string &property) { string value; list::iterator it = m_lines.begin(); while (it != m_lines.end()) { const string &line = *it; bool isComment; if (getValue(line, property, value, isComment, false)) { it = m_lines.erase(it); m_modified = true; } else { ++it; } } } void IniFileConfigNode::writeProperty(const string &property, const InitStateString &newvalue, const string &comment) { string newstr; string oldvalue; bool isDefault = false; if (!newvalue.wasSet()) { newstr += "# "; isDefault = true; } newstr += property + " = " + newvalue; BOOST_FOREACH(string &line, m_lines) { bool isComment; if (getValue(line, property, oldvalue, isComment, true)) { if (newvalue != oldvalue || (isComment && !isDefault)) { line = newstr; m_modified = true; } return; } } // add each line of the comment as separate line in .ini file if (comment.size()) { list commentLines; ConfigProperty::splitComment(comment, commentLines); if (m_lines.size()) { m_lines.push_back(""); } BOOST_FOREACH(const string &comment, commentLines) { m_lines.push_back(string("# ") + comment); } } m_lines.push_back(newstr); m_modified = true; } void IniFileConfigNode::clear() { m_lines.clear(); m_modified = true; } IniHashConfigNode::IniHashConfigNode(const boost::shared_ptr &data) : IniBaseConfigNode(data) { read(); } IniHashConfigNode::IniHashConfigNode(const string &path, const string &fileName, bool readonly) : IniBaseConfigNode(boost::shared_ptr(new FileDataBlob(path, fileName, readonly))) { read(); } void IniHashConfigNode::read() { boost::shared_ptr file(m_data->read()); std::string line; while (std::getline(*file, line)) { string property, value; bool isComment; if (getContent(line, property, value, isComment, false)) { m_props.insert(StringPair(property, value)); } } m_modified = false; } void IniHashConfigNode::toFile(std::ostream &file) { BOOST_FOREACH(const StringPair &prop, m_props) { file << prop.first << " = " << prop.second << std::endl; } } void IniHashConfigNode::readProperties(ConfigProps &props) const { BOOST_FOREACH(const StringPair &prop, m_props) { props.insert(ConfigProps::value_type(prop.first, InitStateString(prop.second, true))); } } void IniHashConfigNode::writeProperties(const ConfigProps &props) { if (!props.empty()) { m_props.insert(props.begin(), props.end()); m_modified = true; } } InitStateString IniHashConfigNode::readProperty(const string &property) const { std::map::const_iterator it = m_props.find(property); if (it != m_props.end()) { return InitStateString(it->second, true); } else { return InitStateString(); } } void IniHashConfigNode::removeProperty(const string &property) { map::iterator it = m_props.find(property); if(it != m_props.end()) { m_props.erase(it); m_modified = true; } } void IniHashConfigNode::clear() { if (!m_props.empty()) { m_props.clear(); m_modified = true; } } void IniHashConfigNode::writeProperty(const string &property, const InitStateString &newvalue, const string &comment) { // we only store explicitly set properties if (!newvalue.wasSet()) { removeProperty(property); return; } map::iterator it = m_props.find(property); if(it != m_props.end()) { if (it->second != newvalue) { it->second = newvalue; m_modified = true; } } else { m_props.insert(StringPair(property, newvalue)); m_modified = true; } } SE_END_CXX syncevolution_1.4/src/syncevo/IniConfigNode.h000066400000000000000000000106401230021373600214600ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_EVOLUTION_INI_CONFIG_NODE # define INCL_EVOLUTION_INI_CONFIG_NODE #include #include #include #include #include SE_BEGIN_CXX /** * A base class for .ini style data blobs. */ class IniBaseConfigNode: public ConfigNode { protected: boost::shared_ptr m_data; bool m_modified; /** * Open or create a new blob. The blob will be read (if it exists) * but not created or written to unless flush() is called explicitly. */ IniBaseConfigNode(const boost::shared_ptr &data); /** * a virtual method to serial data structure to the file * It is used by flush function to flush memory into disk file */ virtual void toFile(std::ostream &file) = 0; public: virtual void flush(); virtual void reload() = 0; virtual std::string getName() const { return m_data->getName(); } virtual bool isVolatile() const { return false; } virtual bool exists() const { return m_data->exists(); } virtual bool isReadOnly() const { return true; } }; /** * This class started its life as the Posix implementation of the * ManagementNode in the Funambol C++ client library. Nowadays it is * part of the SyncEvolution ConfigTree (see there for details). * * Each node is mapped to one file whose location is determined by * the ConfigTree when the node gets created. Each node represents * one .ini file with entries of the type * \s*=\s*\s*\n * * Comments look like: * \s*# * */ class IniFileConfigNode : public IniBaseConfigNode { std::list m_lines; void read(); protected: virtual void toFile(std::ostream &file); public: IniFileConfigNode(const boost::shared_ptr &data); IniFileConfigNode(const std::string &path, const std::string &fileName, bool readonly); /* keep underlying methods visible; our own setProperty() would hide them */ using ConfigNode::setProperty; virtual InitStateString readProperty(const std::string &property) const; virtual void writeProperty(const std::string &property, const InitStateString &value, const std::string &comment = ""); virtual void readProperties(ConfigProps &props) const; virtual void removeProperty(const std::string &property); virtual void clear(); virtual void reload() { clear(); read(); } }; /** * The main difference from FileConfigNode is to store pair of 'property-value' * in a map to avoid O(n^2) string comparison * Here comments for property default value are discarded and unset * properties are not stored. */ class IniHashConfigNode: public IniBaseConfigNode { std::map m_props; /** * Map used to store pairs */ void read(); protected: virtual void toFile(std::ostream & file); public: IniHashConfigNode(const boost::shared_ptr &data); IniHashConfigNode(const std::string &path, const std::string &fileName, bool readonly); virtual InitStateString readProperty(const std::string &property) const; virtual void writeProperty(const std::string &property, const InitStateString &value, const std::string &comment = ""); virtual void readProperties(ConfigProps &props) const; virtual void writeProperties(const ConfigProps &props); virtual void removeProperty(const std::string &property); virtual void clear(); virtual void reload() { clear(); read(); } }; SE_END_CXX #endif // INCL_EVOLUTION_INI_CONFIG_NODE syncevolution_1.4/src/syncevo/LocalTransportAgent.cpp000066400000000000000000001341401230021373600232700ustar00rootroot00000000000000/* * Copyright (C) 2010 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include SE_BEGIN_CXX class NoopAgentDestructor { public: void operator () (TransportAgent *agent) throw() {} }; LocalTransportAgent::LocalTransportAgent(SyncContext *server, const std::string &clientContext, void *loop) : m_server(server), m_clientContext(SyncConfig::normalizeConfigString(clientContext)), m_status(INACTIVE), m_loop(loop ? GMainLoopCXX(static_cast(loop), ADD_REF) : GMainLoopCXX(g_main_loop_new(NULL, false), TRANSFER_REF)) { } boost::shared_ptr LocalTransportAgent::create(SyncContext *server, const std::string &clientContext, void *loop) { boost::shared_ptr self(new LocalTransportAgent(server, clientContext, loop)); self->m_self = self; return self; } LocalTransportAgent::~LocalTransportAgent() { } void LocalTransportAgent::start() { // compare normalized context names to detect forbidden sync // within the same context; they could be set up, but are more // likely configuration mistakes string peer, context; SyncConfig::splitConfigString(m_clientContext, peer, context); if (!peer.empty()) { SE_THROW(StringPrintf("invalid local sync URL: '%s' references a peer config, should point to a context like @%s instead", m_clientContext.c_str(), context.c_str())); } // TODO (?): check that there are no conflicts between the active // sources. The old "contexts must be different" check achieved that // via brute force (because by definition, databases from different // contexts are meant to be independent), but it was too coarse // and ruled out valid configurations. // if (m_clientContext == m_server->getContextName()) { // SE_THROW(StringPrintf("invalid local sync inside context '%s', need second context with different databases", context.c_str())); // } if (m_forkexec) { SE_THROW("local transport already started"); } m_status = ACTIVE; m_forkexec = ForkExecParent::create("syncevo-local-sync"); #ifdef USE_DLT if (getenv("SYNCEVOLUTION_USE_DLT")) { m_forkexec->addEnvVar("SYNCEVOLUTION_USE_DLT", StringPrintf("%d", LoggerDLT::getCurrentDLTLogLevel())); } #endif m_forkexec->m_onConnect.connect(boost::bind(&LocalTransportAgent::onChildConnect, this, _1)); // fatal problems, including quitting child with non-zero status m_forkexec->m_onFailure.connect(boost::bind(&LocalTransportAgent::onFailure, this, _2)); // watch onQuit and remember whether the child is still running, // because it might quit prematurely with a zero return code (for // example, when an unexpected slow sync is detected) m_forkexec->m_onQuit.connect(boost::bind(&LocalTransportAgent::onChildQuit, this, _1)); m_forkexec->start(); } /** * Uses the D-Bus API provided by LocalTransportParent. */ class LocalTransportParent : private GDBusCXX::DBusRemoteObject { public: static const char *path() { return "/"; } static const char *interface() { return "org.syncevolution.localtransport.parent"; } static const char *destination() { return "local.destination"; } static const char *askPasswordName() { return "AskPassword"; } static const char *storeSyncReportName() { return "StoreSyncReport"; } LocalTransportParent(const GDBusCXX::DBusConnectionPtr &conn) : GDBusCXX::DBusRemoteObject(conn, path(), interface(), destination()), m_askPassword(*this, askPasswordName()), m_storeSyncReport(*this, storeSyncReportName()) {} /** LocalTransportAgent::askPassword() */ GDBusCXX::DBusClientCall1 m_askPassword; /** LocalTransportAgent::storeSyncReport() */ GDBusCXX::DBusClientCall0 m_storeSyncReport; }; /** * Uses the D-Bus API provided by LocalTransportAgentChild. */ class LocalTransportChild : public GDBusCXX::DBusRemoteObject { public: static const char *path() { return "/"; } static const char *interface() { return "org.syncevolution.localtransport.child"; } static const char *destination() { return "local.destination"; } static const char *logOutputName() { return "LogOutput"; } static const char *startSyncName() { return "StartSync"; } static const char *sendMsgName() { return "SendMsg"; } LocalTransportChild(const GDBusCXX::DBusConnectionPtr &conn) : GDBusCXX::DBusRemoteObject(conn, path(), interface(), destination()), m_logOutput(*this, logOutputName(), false), m_startSync(*this, startSyncName()), m_sendMsg(*this, sendMsgName()) {} /** * information from server config about active sources: * mapping is from server source names to child source name + sync mode * (again as set on the server side!) */ typedef std::map ActiveSources_t; /** use this to send a message back from child to parent */ typedef boost::shared_ptr< GDBusCXX::Result2< std::string, GDBusCXX::DBusArray > > ReplyPtr; /** log output with level and message; process name will be added by parent */ GDBusCXX::SignalWatch2 m_logOutput; /** LocalTransportAgentChild::startSync() */ GDBusCXX::DBusClientCall2 > m_startSync; /** LocalTransportAgentChild::sendMsg() */ GDBusCXX::DBusClientCall2 > m_sendMsg; }; void LocalTransportAgent::logChildOutput(const std::string &level, const std::string &message) { Logger::MessageOptions options(Logger::strToLevel(level.c_str())); options.m_processName = &m_clientContext; // Child should have written this into its own log file and/or syslog/dlt already. // Only pass it on to a user of the command line interface. options.m_flags = Logger::MessageOptions::ALREADY_LOGGED; SyncEvo::Logger::instance().messageWithOptions(options, "%s", message.c_str()); } void LocalTransportAgent::onChildConnect(const GDBusCXX::DBusConnectionPtr &conn) { SE_LOG_DEBUG(NULL, "child is ready"); m_parent.reset(new GDBusCXX::DBusObjectHelper(conn, LocalTransportParent::path(), LocalTransportParent::interface(), GDBusCXX::DBusObjectHelper::Callback_t(), true)); m_parent->add(this, &LocalTransportAgent::askPassword, LocalTransportParent::askPasswordName()); m_parent->add(this, &LocalTransportAgent::storeSyncReport, LocalTransportParent::storeSyncReportName()); m_parent->activate(); m_child.reset(new LocalTransportChild(conn)); m_child->m_logOutput.activate(boost::bind(&LocalTransportAgent::logChildOutput, this, _1, _2)); // now tell child what to do LocalTransportChild::ActiveSources_t sources; BOOST_FOREACH(const string &sourceName, m_server->getSyncSources()) { SyncSourceNodes nodes = m_server->getSyncSourceNodesNoTracking(sourceName); SyncSourceConfig source(sourceName, nodes); std::string sync = source.getSync(); if (sync != "disabled") { string targetName = source.getURINonEmpty(); sources[sourceName] = std::make_pair(targetName, sync); } } m_child->m_startSync.start(m_clientContext, StringPair(m_server->getConfigName(), m_server->isEphemeral() ? "ephemeral" : m_server->getRootPath()), static_cast(m_server->getLogDir()), m_server->getDoLogging(), std::make_pair(m_server->getSyncUser(), m_server->getSyncPassword()), m_server->getConfigProps(), sources, boost::bind(&LocalTransportAgent::storeReplyMsg, m_self, _1, _2, _3)); } void LocalTransportAgent::onFailure(const std::string &error) { m_status = FAILED; g_main_loop_quit(m_loop.get()); SE_LOG_ERROR(NULL, "local transport failed: %s", error.c_str()); m_parent.reset(); m_child.reset(); } void LocalTransportAgent::onChildQuit(int status) { SE_LOG_DEBUG(NULL, "child process has quit with status %d", status); g_main_loop_quit(m_loop.get()); } static void GotPassword(const boost::shared_ptr< GDBusCXX::Result1 > &reply, const std::string &password) { reply->done(password); } static void PasswordException(const boost::shared_ptr< GDBusCXX::Result1 > &reply) { // TODO: refactor, this is the same as dbusErrorCallback try { // If there is no pending exception, the process will abort // with "terminate called without an active exception"; // dbusErrorCallback() should only be called when there is // a pending exception. // TODO: catch this misuse in a better way throw; } catch (...) { // let D-Bus parent log the error std::string explanation; Exception::handle(explanation, HANDLE_EXCEPTION_NO_ERROR); reply->failed(GDBusCXX::dbus_error("org.syncevolution.localtransport.error", explanation)); } } void LocalTransportAgent::askPassword(const std::string &passwordName, const std::string &descr, const ConfigPasswordKey &key, const boost::shared_ptr< GDBusCXX::Result1 > &reply) { // pass that work to our own SyncContext and its UI - currently blocks SE_LOG_DEBUG(NULL, "local sync parent: asked for password %s, %s", passwordName.c_str(), descr.c_str()); try { if (m_server) { m_server->getUserInterfaceNonNull().askPasswordAsync(passwordName, descr, key, // TODO refactor: use dbus-callbacks.h boost::bind(GotPassword, reply, _1), boost::bind(PasswordException, reply)); } else { SE_LOG_DEBUG(NULL, "local sync parent: password request failed because no m_server"); reply->failed(GDBusCXX::dbus_error("org.syncevolution.localtransport.error", "not connected to UI")); } } catch (...) { PasswordException(reply); } } void LocalTransportAgent::storeSyncReport(const std::string &report) { SE_LOG_DEBUG(NULL, "got child sync report:\n%s", report.c_str()); m_clientReport = SyncReport(report); } void LocalTransportAgent::getClientSyncReport(SyncReport &report) { report = m_clientReport; } void LocalTransportAgent::setContentType(const std::string &type) { m_contentType = type; } // workaround for limitations of bind+signals when used together with plain GMainLoop pointer // (pointer to undefined struct) static void gMainLoopQuit(GMainLoopCXX *loop) { g_main_loop_quit(loop->get()); } void LocalTransportAgent::shutdown() { SE_LOG_DEBUG(NULL, "parent is shutting down"); if (m_forkexec) { // block until child is done boost::signals2::scoped_connection c(m_forkexec->m_onQuit.connect(boost::bind(gMainLoopQuit, &m_loop))); // don't kill the child here - we expect it to complete by // itself at some point // TODO: how do we detect a child which gets stuck after its last // communication with the parent? // m_forkexec->stop(); while (m_forkexec->getState() != ForkExecParent::TERMINATED) { SE_LOG_DEBUG(NULL, "waiting for child to stop"); g_main_loop_run(m_loop.get()); } m_forkexec.reset(); m_parent.reset(); m_child.reset(); } } void LocalTransportAgent::send(const char *data, size_t len) { if (m_child) { m_status = ACTIVE; m_child->m_sendMsg.start(m_contentType, GDBusCXX::makeDBusArray(len, (uint8_t *)(data)), boost::bind(&LocalTransportAgent::storeReplyMsg, m_self, _1, _2, _3)); } else { m_status = FAILED; SE_THROW_EXCEPTION(TransportException, "cannot send message because child process is gone"); } } void LocalTransportAgent::storeReplyMsg(const std::string &contentType, const GDBusCXX::DBusArray &reply, const std::string &error) { m_replyMsg.assign(reinterpret_cast(reply.second), reply.first); m_replyContentType = contentType; if (error.empty()) { m_status = GOT_REPLY; } else { // Only an error if the client hasn't shut down normally. if (m_clientReport.empty()) { SE_LOG_ERROR(NULL, "sending message to child failed: %s", error.c_str()); m_status = FAILED; } } g_main_loop_quit(m_loop.get()); } void LocalTransportAgent::cancel() { if (m_forkexec) { SE_LOG_DEBUG(NULL, "killing local transport child in cancel()"); m_forkexec->stop(); } m_status = CANCELED; } TransportAgent::Status LocalTransportAgent::wait(bool noReply) { if (m_status == ACTIVE) { // need next message; for noReply == true we are done if (noReply) { m_status = INACTIVE; } else { while (m_status == ACTIVE) { SE_LOG_DEBUG(NULL, "waiting for child to send message"); if (m_forkexec && m_forkexec->getState() == ForkExecParent::TERMINATED) { m_status = FAILED; if (m_clientReport.getStatus() != STATUS_OK && m_clientReport.getStatus() != STATUS_HTTP_OK) { // Report that status, with an error message which contains the explanation // added to the client's error. We are a bit fuzzy about matching the status: // 10xxx matches xxx and vice versa. int status = m_clientReport.getStatus(); if (status >= sysync::LOCAL_STATUS_CODE && status <= sysync::LOCAL_STATUS_CODE_END) { status -= sysync::LOCAL_STATUS_CODE; } std::string explanation = StringPrintf("failure on target side %s of local sync", m_clientContext.c_str()); static const pcrecpp::RE re("\\((?:local|remote), status (\\d+)\\): (.*)"); int clientStatus; std::string clientExplanation; if (re.PartialMatch(m_clientReport.getError(), &clientStatus, &clientExplanation) && (status == clientStatus || status == clientStatus - sysync::LOCAL_STATUS_CODE)) { explanation += ": "; explanation += clientExplanation; } SE_THROW_EXCEPTION_STATUS(StatusException, explanation, m_clientReport.getStatus()); } else { SE_THROW_EXCEPTION(TransportException, "child process quit without sending its message"); } } g_main_loop_run(m_loop.get()); } } } return m_status; } void LocalTransportAgent::getReply(const char *&data, size_t &len, std::string &contentType) { if (m_status != GOT_REPLY) { SE_THROW("internal error, no reply available"); } contentType = m_replyContentType; data = m_replyMsg.c_str(); len = m_replyMsg.size(); } void LocalTransportAgent::setTimeout(int seconds) { // setTimeout() was meant for unreliable transports like HTTP // which cannot determine whether the peer is still alive. The // LocalTransportAgent uses sockets and will notice when a peer // dies unexpectedly, so timeouts should never be necessary. // // Quite the opposite, because the "client" in a local sync // with WebDAV on the client side can be quite slow, incorrect // timeouts were seen where the client side took longer than // the default timeout of 5 minutes to process a message and // send a reply. // // Therefore we ignore the request to set a timeout here and thus // local send/receive operations are allowed to continue for as // long as they like. // // m_timeoutSeconds = seconds; } class LocalTransportUI : public UserInterface { boost::shared_ptr m_parent; public: LocalTransportUI(const boost::shared_ptr &parent) : m_parent(parent) {} /** implements password request by asking the parent via D-Bus */ virtual string askPassword(const string &passwordName, const string &descr, const ConfigPasswordKey &key) { SE_LOG_DEBUG(NULL, "local transport child: requesting password %s, %s via D-Bus", passwordName.c_str(), descr.c_str()); std::string password; std::string error; bool havePassword = false; m_parent->m_askPassword.start(passwordName, descr, key, boost::bind(&LocalTransportUI::storePassword, this, boost::ref(password), boost::ref(error), boost::ref(havePassword), _1, _2)); SuspendFlags &s = SuspendFlags::getSuspendFlags(); while (!havePassword) { if (s.getState() != SuspendFlags::NORMAL) { SE_THROW_EXCEPTION_STATUS(StatusException, StringPrintf("User did not provide the '%s' password.", passwordName.c_str()), SyncMLStatus(sysync::LOCERR_USERABORT)); } g_main_context_iteration(NULL, true); } if (!error.empty()) { Exception::tryRethrowDBus(error); SE_THROW(StringPrintf("retrieving password failed: %s", error.c_str())); } return password; } virtual bool savePassword(const std::string &passwordName, const std::string &password, const ConfigPasswordKey &key) { SE_THROW("not implemented"); return false; } virtual void readStdin(std::string &content) { SE_THROW("not implemented"); } private: void storePassword(std::string &res, std::string &errorRes, bool &haveRes, const std::string &password, const std::string &error) { if (!error.empty()) { SE_LOG_DEBUG(NULL, "local transport child: D-Bus password request failed: %s", error.c_str()); errorRes = error; } else { SE_LOG_DEBUG(NULL, "local transport child: D-Bus password request succeeded"); res = password; } haveRes = true; } }; static void abortLocalSync(int sigterm) { // logging anything here is not safe (our own logging system might // have been interrupted by the SIGTERM and thus be in an inconsistent // state), but let's try it anyway SE_LOG_INFO(NULL, "local sync child shutting down due to SIGTERM"); // raise the signal again after disabling the handler, to ensure that // the exit status is "killed by signal xxx" - good because then // the whoever killed used gets the information that we didn't die for // some other reason signal(sigterm, SIG_DFL); raise(sigterm); } /** * Provides the "LogOutput" signal. * LocalTransportAgentChild adds the method implementations * before activating it. */ class LocalTransportChildImpl : public GDBusCXX::DBusObjectHelper { public: LocalTransportChildImpl(const GDBusCXX::DBusConnectionPtr &conn) : GDBusCXX::DBusObjectHelper(conn, LocalTransportChild::path(), LocalTransportChild::interface(), GDBusCXX::DBusObjectHelper::Callback_t(), true), m_logOutput(*this, LocalTransportChild::logOutputName()) { add(m_logOutput); }; GDBusCXX::EmitSignal2 m_logOutput; }; class ChildLogger : public Logger { std::auto_ptr m_parentLogger; boost::weak_ptr m_child; public: ChildLogger(const boost::shared_ptr &child) : m_parentLogger(new LogRedirect(LogRedirect::STDERR_AND_STDOUT)), m_child(child) {} ~ChildLogger() { m_parentLogger.reset(); } /** * Write message into our own log and send to parent. */ virtual void messagev(const MessageOptions &options, const char *format, va_list args) { if (options.m_level <= m_parentLogger->getLevel()) { m_parentLogger->process(); boost::shared_ptr child = m_child.lock(); if (child) { // prefix is used to set session path // for general server output, the object path field is dbus server // the object path can't be empty for object paths prevent using empty string. string strLevel = Logger::levelToStr(options.m_level); string log = StringPrintfV(format, args); child->m_logOutput(strLevel, log); } } } }; class LocalTransportAgentChild : public TransportAgent { /** final return code of our main(): non-zero indicates that we need to shut down */ int m_ret; /** * sync report for client side of the local sync */ SyncReport m_clientReport; /** used to capture libneon output */ boost::scoped_ptr m_parentLogger; /** * provides connection to parent, created in constructor */ boost::shared_ptr m_forkexec; /** * proxy for the parent's D-Bus API in onConnect() */ boost::shared_ptr m_parent; /** * our D-Bus interface, created in onConnect() */ boost::shared_ptr m_child; /** * sync context, created in Sync() D-Bus call */ boost::scoped_ptr m_client; /** * use this D-Bus result handle to send a message from child to parent * in response to sync() or (later) sendMsg() */ LocalTransportChild::ReplyPtr m_msgToParent; void setMsgToParent(const LocalTransportChild::ReplyPtr &reply, const std::string &reason) { if (m_msgToParent) { m_msgToParent->failed(GDBusCXX::dbus_error("org.syncevolution.localtransport.error", "cancelling message: " + reason)); } m_msgToParent = reply; } /** content type for message to parent */ std::string m_contentType; /** * message from parent */ std::string m_message; /** * content type of message from parent */ std::string m_messageType; /** * true after parent has received sync report, or sending failed */ bool m_reportSent; /** * INACTIVE when idle, * ACTIVE after having sent and while waiting for next message, * GOT_REPLY when we have a message to be processed, * FAILED when permanently broken */ Status m_status; /** * one loop run + error checking */ void step(const std::string &status) { SE_LOG_DEBUG(NULL, "local transport: %s", status.c_str()); if (!m_forkexec || m_forkexec->getState() == ForkExecChild::DISCONNECTED) { SE_THROW("local transport child no longer has a parent, terminating"); } g_main_context_iteration(NULL, true); if (m_ret) { SE_THROW("local transport child encountered a problem, terminating"); } } static void onParentQuit() { // Never free this state blocker. We can only abort and // quit from now on. static boost::shared_ptr abortGuard; SE_LOG_ERROR(NULL, "sync parent quit unexpectedly"); abortGuard = SuspendFlags::getSuspendFlags().abort(); } void onConnect(const GDBusCXX::DBusConnectionPtr &conn) { SE_LOG_DEBUG(NULL, "child connected to parent"); // provide our own API m_child.reset(new LocalTransportChildImpl(conn)); m_child->add(this, &LocalTransportAgentChild::startSync, LocalTransportChild::startSyncName()); m_child->add(this, &LocalTransportAgentChild::sendMsg, LocalTransportChild::sendMsgName()); m_child->activate(); // set up connection to parent m_parent.reset(new LocalTransportParent(conn)); } void onFailure(SyncMLStatus status, const std::string &reason) { SE_LOG_DEBUG(NULL, "child fork/exec failed: %s", reason.c_str()); // record failure for parent if (!m_clientReport.getStatus()) { m_clientReport.setStatus(status); } if (!reason.empty() && m_clientReport.getError().empty()) { m_clientReport.setError(reason); } // return to step() m_ret = 1; } // D-Bus API, see LocalTransportChild; // must keep number of parameters < 9, the maximum supported by // our D-Bus binding void startSync(const std::string &clientContext, const StringPair &serverConfig, // config name + root path const std::string &serverLogDir, bool serverDoLogging, const std::pair &serverSyncCredentials, const FullProps &serverConfigProps, const LocalTransportChild::ActiveSources_t &sources, const LocalTransportChild::ReplyPtr &reply) { setMsgToParent(reply, "sync() was called"); Logger::setProcessName(clientContext); SE_LOG_DEBUG(NULL, "Sync() called, starting the sync"); const char *delay = getenv("SYNCEVOLUTION_LOCAL_CHILD_DELAY2"); if (delay) { Sleep(atoi(delay)); } // initialize sync context m_client.reset(new SyncContext(std::string("target-config") + clientContext, serverConfig.first, serverConfig.second == "ephemeral" ? serverConfig.second : serverConfig.second + "/." + clientContext, boost::shared_ptr(this, NoopAgentDestructor()), serverDoLogging)); if (serverConfig.second == "ephemeral") { m_client->makeEphemeral(); } boost::shared_ptr ui(new LocalTransportUI(m_parent)); m_client->setUserInterface(ui); // allow proceeding with sync even if no "target-config" was created, // because information about username/password (for WebDAV) or the // sources (for file backends) might be enough m_client->setConfigNeeded(false); // apply temporary config filters m_client->setConfigFilter(true, "", serverConfigProps.createSyncFilter(m_client->getConfigName())); BOOST_FOREACH(const string &sourceName, m_client->getSyncSources()) { m_client->setConfigFilter(false, sourceName, serverConfigProps.createSourceFilter(m_client->getConfigName(), sourceName)); } // Copy non-empty credentials from main config, because // that is where the GUI knows how to store them. A better // solution would be to require that credentials are in the // "target-config" config. // // Interactive password requests later in SyncContext::sync() // will end up in our LocalTransportContext::askPassword() // implementation above, which will pass the question to // the local sync parent. if (!serverSyncCredentials.first.toString().empty()) { m_client->setSyncUsername(serverSyncCredentials.first.toString(), true); } if (!serverSyncCredentials.second.empty()) { m_client->setSyncPassword(serverSyncCredentials.second, true); } // debugging mode: write logs inside sub-directory of parent, // otherwise use normal log settings if (!serverDoLogging) { m_client->setLogDir(std::string(serverLogDir) + "/child", true); } // disable all sources temporarily, will be enabled by next loop BOOST_FOREACH(const string &targetName, m_client->getSyncSources()) { SyncSourceNodes targetNodes = m_client->getSyncSourceNodes(targetName); SyncSourceConfig targetSource(targetName, targetNodes); targetSource.setSync("disabled", true); } // activate all sources in client targeted by main config, // with right uri BOOST_FOREACH(const LocalTransportChild::ActiveSources_t::value_type &entry, sources) { // mapping is from server (source) to child (target) const std::string &sourceName = entry.first; const std::string &targetName = entry.second.first; std::string sync = entry.second.second; SyncMode mode = StringToSyncMode(sync); if (mode != SYNC_NONE) { SyncSourceNodes targetNodes = m_client->getSyncSourceNodes(targetName); SyncSourceConfig targetSource(targetName, targetNodes); string fullTargetName = clientContext + "/" + targetName; if (!targetNodes.dataConfigExists()) { if (targetName.empty()) { m_client->throwError("missing URI for one of the sources"); } else { m_client->throwError(StringPrintf("%s: source not configured", fullTargetName.c_str())); } } // All of the config setting is done as volatile, // so none of the regular config nodes have to // be written. If a sync mode was set, it must have been // done before in this loop => error in original config. if (!targetSource.isDisabled()) { m_client->throwError(StringPrintf("%s: source targetted twice by %s", fullTargetName.c_str(), serverConfig.first.c_str())); } // invert data direction if (mode == SYNC_REFRESH_FROM_LOCAL) { mode = SYNC_REFRESH_FROM_REMOTE; } else if (mode == SYNC_REFRESH_FROM_REMOTE) { mode = SYNC_REFRESH_FROM_LOCAL; } else if (mode == SYNC_ONE_WAY_FROM_LOCAL) { mode = SYNC_ONE_WAY_FROM_REMOTE; } else if (mode == SYNC_ONE_WAY_FROM_REMOTE) { mode = SYNC_ONE_WAY_FROM_LOCAL; } else if (mode == SYNC_LOCAL_CACHE_SLOW) { // Remote side is running in caching mode and // asking for refresh. Send all our data. mode = SYNC_SLOW; } else if (mode == SYNC_LOCAL_CACHE_INCREMENTAL) { // Remote side is running in caching mode and // asking for an update. Use two-way mode although // nothing is going to come back (simpler that way // than using one-way, which has special code // paths in libsynthesis). mode = SYNC_TWO_WAY; } targetSource.setSync(PrettyPrintSyncMode(mode, true), true); targetSource.setURI(sourceName, true); } } // ready for m_client->sync() m_status = ACTIVE; } void sendMsg(const std::string &contentType, const GDBusCXX::DBusArray &data, const LocalTransportChild::ReplyPtr &reply) { SE_LOG_DEBUG(NULL, "child got message of %ld bytes", (long)data.first); setMsgToParent(LocalTransportChild::ReplyPtr(), "sendMsg() was called"); if (m_status == ACTIVE) { m_msgToParent = reply; m_message.assign(reinterpret_cast(data.second), data.first); m_messageType = contentType; m_status = GOT_REPLY; } else { reply->failed(GDBusCXX::dbus_error("org.syncevolution.localtransport.error", "child not expecting any message")); } } public: LocalTransportAgentChild() : m_ret(0), m_forkexec(SyncEvo::ForkExecChild::create()), m_reportSent(false), m_status(INACTIVE) { m_forkexec->m_onConnect.connect(boost::bind(&LocalTransportAgentChild::onConnect, this, _1)); m_forkexec->m_onFailure.connect(boost::bind(&LocalTransportAgentChild::onFailure, this, _1, _2)); // When parent quits, we need to abort whatever we do and shut // down. There's no way how we can complete our work without it. // // Note that another way how this process can detect the // death of the parent is when it currently is waiting for // completion of a method call to the parent, like a request // for a password. However, that does not cover failures // like the parent not asking us to sync in the first place // and also does not work with libdbus (https://bugs.freedesktop.org/show_bug.cgi?id=49728). m_forkexec->m_onQuit.connect(&onParentQuit); m_forkexec->connect(); } boost::shared_ptr createLogger() { return boost::shared_ptr(new ChildLogger(m_child)); } void run() { SuspendFlags &s = SuspendFlags::getSuspendFlags(); while (!m_parent) { if (s.getState() != SuspendFlags::NORMAL) { SE_LOG_DEBUG(NULL, "aborted, returning while waiting for parent"); return; } step("waiting for parent"); } while (!m_client) { if (s.getState() != SuspendFlags::NORMAL) { SE_LOG_DEBUG(NULL, "aborted, returning while waiting for Sync() call from parent"); } step("waiting for Sync() call from parent"); } try { // ignore SIGINT signal in local sync helper from now on: // the parent process will handle those and tell us when // we are expected to abort by sending a SIGTERM struct sigaction new_action; memset(&new_action, 0, sizeof(new_action)); new_action.sa_handler = SIG_IGN; sigemptyset(&new_action.sa_mask); sigaction(SIGINT, &new_action, NULL); // SIGTERM would be caught by SuspendFlags and set the "abort" // state. But a lot of code running in this process cannot // check that flag in a timely manner (blocking calls in // libneon, activesync client libraries, ...). Therefore // it is better to abort inside the signal handler. new_action.sa_handler = abortLocalSync; sigaction(SIGTERM, &new_action, NULL); SE_LOG_DEBUG(NULL, "LocalTransportChild: ignore SIGINT, die in SIGTERM"); SE_LOG_INFO(NULL, "target side of local sync ready"); m_client->sync(&m_clientReport); } catch (...) { string explanation; SyncMLStatus status = Exception::handle(explanation); m_clientReport.setStatus(status); if (!explanation.empty() && m_clientReport.getError().empty()) { m_clientReport.setError(explanation); } if (m_parent) { std::string report = m_clientReport.toString(); SE_LOG_DEBUG(NULL, "child sending sync report after failure:\n%s", report.c_str()); m_parent->m_storeSyncReport.start(report, boost::bind(&LocalTransportAgentChild::syncReportReceived, this, _1)); // wait for acknowledgement for report once: // we are in some kind of error state, better // do not wait too long if (m_parent) { SE_LOG_DEBUG(NULL, "waiting for parent's ACK for sync report"); g_main_context_iteration(NULL, true); } } throw; } if (m_parent) { // send final report, ignore result std::string report = m_clientReport.toString(); SE_LOG_DEBUG(NULL, "child sending sync report:\n%s", report.c_str()); m_parent->m_storeSyncReport.start(report, boost::bind(&LocalTransportAgentChild::syncReportReceived, this, _1)); while (!m_reportSent && m_parent && s.getState() == SuspendFlags::NORMAL) { step("waiting for parent's ACK for sync report"); } } } void syncReportReceived(const std::string &error) { SE_LOG_DEBUG(NULL, "sending sync report to parent: %s", error.empty() ? "done" : error.c_str()); m_reportSent = true; } int getReturnCode() const { return m_ret; } /** * set transport specific URL of next message */ virtual void setURL(const std::string &url) {} /** * define content type for post, see content type constants */ virtual void setContentType(const std::string &type) { m_contentType = type; } /** * Requests an normal shutdown of the transport. This can take a * while, for example if communication is still pending. * Therefore wait() has to be called to ensure that the * shutdown is complete and that no error occurred. * * Simply deleting the transport is an *unnormal* shutdown that * does not communicate with the peer. */ virtual void shutdown() { SE_LOG_DEBUG(NULL, "child local transport shutting down"); if (m_msgToParent) { // Must send non-zero message, empty messages cause an // error during D-Bus message decoding on the receiving // side. Content doesn't matter, ignored by parent. m_msgToParent->done("shutdown-message", GDBusCXX::makeDBusArray(1, (uint8_t *)"")); m_msgToParent.reset(); } if (m_status != FAILED) { m_status = CLOSED; } } /** * start sending message * * Memory must remain valid until reply is received or * message transmission is canceled. * * @param data start address of data to send * @param len number of bytes */ virtual void send(const char *data, size_t len) { SE_LOG_DEBUG(NULL, "child local transport sending %ld bytes", (long)len); if (m_msgToParent) { m_status = ACTIVE; m_msgToParent->done(m_contentType, GDBusCXX::makeDBusArray(len, (uint8_t *)(data))); m_msgToParent.reset(); } else { m_status = FAILED; SE_THROW("cannot send data to parent because parent is not waiting for message"); } } /** * cancel an active message transmission * * Blocks until send buffer is no longer in use. * Returns immediately if nothing pending. */ virtual void cancel() {} /** * Wait for completion of an operation initiated earlier. * The operation can be a send with optional reply or * a close request. * * Returns immediately if no operations is pending. * * @param noReply true if no reply is required for a running send; * only relevant for transports used by a SyncML server */ virtual Status wait(bool noReply = false) { SuspendFlags &s = SuspendFlags::getSuspendFlags(); while (m_status == ACTIVE && s.getState() == SuspendFlags::NORMAL) { step("waiting for next message"); } return m_status; } /** * Tells the transport agent to stop the transmission the given * amount of seconds after send() was called. The transport agent * will then stop the message transmission and return a TIME_OUT * status in wait(). * * @param seconds number of seconds to wait before timing out, zero for no timeout */ virtual void setTimeout(int seconds) {} /** * provides access to reply data * * Memory pointer remains valid as long as * transport agent is not deleted and no other * message is sent. */ virtual void getReply(const char *&data, size_t &len, std::string &contentType) { SE_LOG_DEBUG(NULL, "processing %ld bytes in child", (long)m_message.size()); if (m_status != GOT_REPLY) { SE_THROW("getReply() called in child when no reply available"); } data = m_message.c_str(); len = m_message.size(); contentType = m_messageType; } }; int LocalTransportMain(int argc, char **argv) { // delay the client for debugging purposes const char *delay = getenv("SYNCEVOLUTION_LOCAL_CHILD_DELAY"); if (delay) { Sleep(atoi(delay)); } SyncContext::initMain("syncevo-local-sync"); // Our stderr is either connected to the original stderr (when // SYNCEVOLUTION_DEBUG is set) or the local sync's parent // LogRedirect. However, that stderr is not normally used. // Instead we install our own LogRedirect for both stdout (for // Execute() and synccompare, which then knows that it needs to // capture the output) and stderr (to get output like the one from // libneon into the child log) in LocalTransportAgentChild and // send all logging output to the local sync parent via D-Bus, to // be forwarded to the user as part of the normal message stream // of the sync session. setvbuf(stderr, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); // SIGPIPE must be ignored, some system libs (glib GIO?) trigger // it. SIGINT/TERM will be handled via SuspendFlags once the sync // runs. struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sa, NULL); try { if (getenv("SYNCEVOLUTION_DEBUG")) { Logger::instance().setLevel(Logger::DEBUG); } // process name will be set to target config name once it is known Logger::setProcessName("syncevo-local-sync"); boost::shared_ptr child(new LocalTransportAgentChild); PushLogger logger; // Temporary handle is necessary to avoid compiler issue with // clang (ambiguous brackets). { Logger::Handle handle(child->createLogger()); logger.reset(handle); } #ifdef USE_DLT // Set by syncevo-dbus-server for us. bool useDLT = getenv("SYNCEVOLUTION_USE_DLT") != NULL; PushLogger loggerdlt; if (useDLT) { loggerdlt.reset(new LoggerDLT(DLT_SYNCEVO_LOCAL_HELPER_ID, "SyncEvolution local sync helper")); } #endif child->run(); int ret = child->getReturnCode(); logger.reset(); child.reset(); return ret; } catch ( const std::exception &ex ) { SE_LOG_ERROR(NULL, "%s", ex.what()); } catch (...) { SE_LOG_ERROR(NULL, "unknown error"); } return 1; } SE_END_CXX syncevolution_1.4/src/syncevo/LocalTransportAgent.h000066400000000000000000000130621230021373600227340ustar00rootroot00000000000000/* * Copyright (C) 2010 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_LOCALTRANSPORTAGENT #define INCL_LOCALTRANSPORTAGENT #include "config.h" #include #include #include #include #include #include // This needs to be defined before including gdbus-cxx-bridge.h! #define DBUS_CXX_EXCEPTION_HANDLER SyncEvo::SyncEvoHandleException #include "gdbus-cxx-bridge.h" #include #include #include SE_BEGIN_CXX class SyncContext; /** * The main() function of the local transport helper. * Implements the child side of local sync. */ int LocalTransportMain(int argc, char **argv); // internal in LocalTransportAgent.cpp class LocalTransportChild; /** * message send/receive with a forked process as peer * * Uses pipes to send a message and then get the response. * Limited to server forking the client. Because the client * has access to the full server setup after the fork, * no SAN message is needed and the first message goes * from client to server. * * Most messages will be SyncML message and response. In addition, * password requests also need to be passed through the server via * dedicated messages, because it is the one with a UI. */ class LocalTransportAgent : public TransportAgent { private: LocalTransportAgent(SyncContext *server, const std::string &clientContext, void *loop = NULL); boost::weak_ptr m_self; public: /** * @param server the server side of the sync; * must remain valid while transport exists * @param clientContext name of the context which contains the client's * sources, must start with @ sign * @param loop optional glib loop to use when waiting for IO; * transport will *not* increase the reference count */ static boost::shared_ptr create(SyncContext *server, const std::string &clientContext, void *loop = NULL); ~LocalTransportAgent(); /** * Set up message passing and fork the client. */ void start(); /** * Copies the client's sync report. If the client terminated * unexpectedly or shutdown() hasn't completed yet, the * STATUS_DIED_PREMATURELY sync result code will be set. */ void getClientSyncReport(SyncReport &report); // TransportAgent implementation virtual void setURL(const std::string &url) {} virtual void setContentType(const std::string &type); virtual void shutdown(); virtual void send(const char *data, size_t len); virtual void cancel(); virtual Status wait(bool noReply = false); virtual void getReply(const char *&data, size_t &len, std::string &contentType); virtual void setTimeout(int seconds); private: SyncContext *m_server; std::string m_clientContext; Status m_status; SyncReport m_clientReport; GMainLoopCXX m_loop; boost::shared_ptr m_forkexec; std::string m_contentType; std::string m_replyContentType; std::string m_replyMsg; /** * provides the D-Bus API expected by the forked process: * - password requests * - store the child's sync report */ boost::shared_ptr m_parent; /** * provides access to the forked process' D-Bus API * - start sync (returns child's first message) * - send server reply (returns child's next message or empty when done) * - emits output via signal * * Only non-NULL when child is running and connected. */ boost::shared_ptr m_child; void logChildOutput(const std::string &level, const std::string &message); void onChildConnect(const GDBusCXX::DBusConnectionPtr &conn); void onFailure(const std::string &error); void onChildQuit(int status); void askPassword(const std::string &passwordName, const std::string &descr, const ConfigPasswordKey &key, const boost::shared_ptr< GDBusCXX::Result1 > &reply); void storeSyncReport(const std::string &report); void storeReplyMsg(const std::string &contentType, const GDBusCXX::DBusArray &reply, const std::string &error); /** * utility function: calculate deadline for operation starting now * * @param seconds timeout in seconds, 0 for the default */ Timespec deadline(unsigned seconds) { return seconds ? (Timespec::monotonic() + seconds) : Timespec(); } }; SE_END_CXX #endif // INCL_LOCALTRANSPORTAGENT syncevolution_1.4/src/syncevo/LogDLT.cpp000066400000000000000000000074271230021373600204360ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #ifdef USE_DLT #include #include SE_BEGIN_CXX static DltLogLevelType SyncEvoLevel2DLTLevel(Logger::Level level) { switch (level) { case Logger::NONE: return DLT_LOG_OFF; case Logger::ERROR: return DLT_LOG_ERROR; case Logger::WARNING: return DLT_LOG_WARN; case Logger::SHOW: case Logger::INFO: return DLT_LOG_INFO; case Logger::DEV: case Logger::DEBUG: return DLT_LOG_DEBUG; } return DLT_LOG_OFF; } static LoggerDLT *LoggerDLTInstance; LoggerDLT::LoggerDLT(const char *appid, const char *description) : m_parentLogger(Logger::instance()), m_dltContext(calloc(1, sizeof(DltContext))) { DLT_REGISTER_APP(appid, description); int level = atoi(getEnv("SYNCEVOLUTION_USE_DLT", "-1")); if (level > 0) { DLT_REGISTER_CONTEXT_LL_TS(*(DltContext *)m_dltContext, "SYNC", "SyncEvolution messages", (DltLogLevelType)level, DLT_TRACE_STATUS_OFF); } else { DLT_REGISTER_CONTEXT(*(DltContext *)m_dltContext, "SYNC", "SyncEvolution messages"); } LoggerDLTInstance = this; } LoggerDLT::~LoggerDLT() { DLT_UNREGISTER_CONTEXT(*(DltContext *)m_dltContext); free(m_dltContext); DLT_UNREGISTER_APP(); LoggerDLTInstance = NULL; } void LoggerDLT::messagev(const MessageOptions &options, const char *format, va_list args) { // always to parent first (usually stdout): // if the parent is a LogRedirect instance, then // it'll flush its own output first, which ensures // that the new output comes later (as desired) { va_list argscopy; va_copy(argscopy, args); m_parentLogger.messagev(options, format, argscopy); va_end(argscopy); } DltContextData log; if (!(options.m_flags & MessageOptions::ALREADY_LOGGED) && dlt_user_log_write_start((DltContext *)m_dltContext, &log, SyncEvoLevel2DLTLevel(options.m_level)) > 0) { std::string buffer = StringPrintfV(format, args); // Avoid almost empty messages. They are triggered by // SyncEvolution to format the INFO output and don't add any // valuable information to the DLT log. if (!buffer.empty() && buffer != "\n") { dlt_user_log_write_string(&log, buffer.c_str()); dlt_user_log_write_finish(&log); } } } int LoggerDLT::getCurrentDLTLogLevel() { if (LoggerDLTInstance) { for (int level = DLT_LOG_VERBOSE; level > DLT_LOG_DEFAULT; --level) { DltContextData log; // Emulates DLT_LOG(): logging active if dlt_user_log_write_start() returns something > 0. // Otherwise discard the DltContextData without doing anything. if (dlt_user_log_write_start((DltContext *)LoggerDLTInstance->m_dltContext, &log, (DltLogLevelType)level) > 0) { return level; } } } return DLT_LOG_DEFAULT; } SE_END_CXX #endif // USE_DLT syncevolution_1.4/src/syncevo/LogDLT.h000066400000000000000000000031651230021373600200760ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_LOGDLT #define INCL_LOGDLT #include #ifdef USE_DLT #include #include #include SE_BEGIN_CXX /** * A logger which writes to DLT and passes log messages * through to its parent. */ class LoggerDLT : public Logger { Handle m_parentLogger; // avoid dependency on dlt.h here void *m_dltContext; public: LoggerDLT(const char *appid, const char *description); ~LoggerDLT(); virtual void messagev(const MessageOptions &options, const char *format, va_list args); /** * Extracts current log level from the LoggerDLT which was * pushed onto the stack, DLT_LOG_DEFAULT if none active. */ static int getCurrentDLTLogLevel(); }; SE_END_CXX #endif // USE_DLT #endif // INCL_LOGSYSLOG syncevolution_1.4/src/syncevo/LogRedirect.cpp000066400000000000000000000671311230021373600215520ustar00rootroot00000000000000/* * Copyright (C) 2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "config.h" #include #include #include #include "test.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_GLIB # include #endif #include using namespace std; SE_BEGIN_CXX LogRedirect *LogRedirect::m_redirect; std::set LogRedirect::m_knownErrors; void LogRedirect::abortHandler(int sig) throw() { // Don't know state of logging system, don't log here! // SE_LOG_ERROR(NULL, "caught signal %d, shutting down", sig); // shut down redirection, also flushes to log { RecMutex::Guard guard = lock(); if (m_redirect) { m_redirect->restore(); } } // Raise same signal again. Because our handler // is automatically removed, this will abort // for real now. raise(sig); } void LogRedirect::init() { m_processing = false; m_buffer = NULL; m_len = 0; m_out = NULL; m_err = NULL; m_streams = false; m_stderr.m_original = m_stderr.m_read = m_stderr.m_write = m_stderr.m_copy = -1; m_stdout.m_original = m_stdout.m_read = m_stdout.m_write = m_stdout.m_copy = -1; const char *lines = getenv("SYNCEVOLUTION_SUPPRESS_ERRORS"); if (lines) { typedef boost::split_iterator string_split_iterator; string_split_iterator it = boost::make_split_iterator(lines, boost::first_finder("\n", boost::is_iequal())); while (it != string_split_iterator()) { m_knownErrors.insert(std::string(it->begin(), it->end())); ++it; } } // CONSOLEPRINTF in libsynthesis. m_knownErrors.insert("SYSYNC Rejected with error:"); // libneon 'Request ends, status 207 class 2xx, error line:' m_knownErrors.insert("xx, error line:\n"); // some internal Qt warning (?) m_knownErrors.insert("Qt: Session management error: None of the authentication protocols specified are supported"); } LogRedirect::LogRedirect(Mode mode, const char *filename) { init(); m_processing = true; if (!getenv("SYNCEVOLUTION_DEBUG")) { redirect(STDERR_FILENO, m_stderr); if (mode == STDERR_AND_STDOUT) { redirect(STDOUT_FILENO, m_stdout); m_out = filename ? fopen(filename, "w") : fdopen(dup(m_stdout.m_copy), "w"); if (!m_out) { restore(m_stdout); restore(m_stderr); perror(filename ? filename : "LogRedirect fdopen"); } } else if (filename) { m_out = fopen(filename, "w"); if (!m_out) { perror(filename); } } // Separate FILE, will write into same file as normal output // if a filename was given (for testing), otherwise to original // stderr. m_err = fdopen(dup((filename && m_out) ? fileno(m_out) : m_stderr.m_copy), "w"); } // Modify process state while holding the Logger mutex. RecMutex::Guard guard = lock(); if (m_redirect) { SE_LOG_WARNING(NULL, "LogRedirect already instantiated?!"); } m_redirect = this; if (!getenv("SYNCEVOLUTION_DEBUG")) { struct sigaction new_action, old_action; memset(&new_action, 0, sizeof(new_action)); new_action.sa_handler = abortHandler; sigemptyset(&new_action.sa_mask); // disable handler after it was called once new_action.sa_flags = SA_RESETHAND; // block signals while we handler is active // to prevent recursive calls sigaddset(&new_action.sa_mask, SIGABRT); sigaddset(&new_action.sa_mask, SIGSEGV); sigaddset(&new_action.sa_mask, SIGBUS); sigaction(SIGABRT, &new_action, &old_action); sigaction(SIGSEGV, &new_action, &old_action); sigaction(SIGBUS, &new_action, &old_action); } m_processing = false; } LogRedirect::LogRedirect(ExecuteFlags flags) { init(); // This instance does not modify process state and // doesn't have to be thread-safe. m_streams = true; if (!(flags & EXECUTE_NO_STDERR)) { redirect(STDERR_FILENO, m_stderr); } if (!(flags & EXECUTE_NO_STDOUT)) { redirect(STDOUT_FILENO, m_stdout); } } LogRedirect::~LogRedirect() throw() { RecMutex::Guard guard; if (!m_streams) { guard = lock(); } if (m_redirect == this) { m_redirect = NULL; } process(); restore(); m_processing = true; if (m_out) { fclose(m_out); } if (m_err) { fclose(m_err); } if (m_buffer) { free(m_buffer); } } void LogRedirect::remove() throw() { restore(); } void LogRedirect::removeRedirect() throw() { if (m_redirect) { // We were forked. Ignore mutex (might be held by thread which was not // forked) and restore the forked process' state to the one it was // before setting up redirection. // // Do the minimal amount of work possible in restore(), i.e., // suppress the processing of streams. m_redirect->m_streams = false; m_redirect->restore(m_redirect->m_stdout); m_redirect->restore(m_redirect->m_stderr); } } void LogRedirect::restore() throw() { RecMutex::Guard guard; if (!m_streams) { guard = lock(); } if (m_processing) { return; } m_processing = true; restore(m_stdout); restore(m_stderr); m_processing = false; } void LogRedirect::messagev(const MessageOptions &options, const char *format, va_list args) { RecMutex::Guard guard = lock(); // check for other output first process(); if (!(options.m_flags & MessageOptions::ONLY_GLOBAL_LOG)) { // Choose output channel: SHOW goes to original stdout, // everything else to stderr. LoggerStdout::write(options.m_level == SHOW ? (m_out ? m_out : stdout) : (m_err ? m_err : stderr), options.m_level, getLevel(), options.m_prefix, options.m_processName, format, args); } } void LogRedirect::redirect(int original, FDs &fds) throw() { fds.m_original = original; fds.m_write = fds.m_read = -1; fds.m_copy = dup(fds.m_original); if (fds.m_copy >= 0) { if (m_streams) { // According to Stevens, Unix Network Programming, "Unix // domain datagram sockets are similar to UDP sockets: the // provide an *unreliable* datagram service that preserves // record boundaries." (14.4 Socket Functions, // p. 378). But unit tests showed that they do block on // Linux and thus seem reliable. Not sure what the official // spec is. // // To avoid the deadlock risk, we must use UDP. But when we // want "reliable" behavior *and* detect that all output was // processed, we have to use streams despite loosing // the write() boundaries, because Unix domain datagram sockets // do not flag "end of data". int sockets[2]; #define USE_UNIX_DOMAIN_DGRAM 0 if (!socketpair(AF_LOCAL, USE_UNIX_DOMAIN_DGRAM ? SOCK_DGRAM : SOCK_STREAM, 0, sockets)) { // success fds.m_write = sockets[0]; fds.m_read = sockets[1]; return; } else { perror("LogRedirect::redirect() socketpair"); } } else { int write = socket(AF_INET, SOCK_DGRAM, 0); if (write >= 0) { int read = socket(AF_INET, SOCK_DGRAM, 0); if (read >= 0) { struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr("127.0.0.1"); bool bound = false; for (int port = 1025; !bound && port < 10000; port++) { addr.sin_port = htons(port); if (!bind(read, (struct sockaddr *)&addr, sizeof(addr))) { bound = true; } } if (bound) { if (!connect(write, (struct sockaddr *)&addr, sizeof(addr))) { if (dup2(write, fds.m_original) >= 0) { // success fds.m_write = write; fds.m_read = read; return; } perror("LogRedirect::redirect() dup2"); } perror("LogRedirect::redirect connect"); } close(read); } close(write); } } close(fds.m_copy); fds.m_copy = -1; } else { perror("LogRedirect::redirect() dup"); } } void LogRedirect::restore(FDs &fds) throw() { if (!m_streams && fds.m_copy >= 0) { // flush our own redirected output and process what they might have written if (fds.m_original == STDOUT_FILENO) { fflush(stdout); std::cout << std::flush; } else { fflush(stderr); std::cerr << std::flush; } process(fds); dup2(fds.m_copy, fds.m_original); } if (fds.m_copy >= 0) { close(fds.m_copy); } if (fds.m_write >= 0) { close(fds.m_write); } if (fds.m_read >= 0) { close(fds.m_read); } fds.m_copy = fds.m_write = fds.m_read = -1; } bool LogRedirect::process(FDs &fds) throw() { bool have_message; bool data_read = false; if (fds.m_read <= 0) { return data_read; } ssize_t available = 0; do { have_message = false; // keep peeking at the data with increasing buffer sizes until // we are sure that we don't truncate it size_t newlen = std::max((size_t)1024, m_len); while (true) { // increase buffer? if (newlen > m_len) { void *buffer = realloc(m_buffer, newlen); if (!buffer) { // Nothing changed. if (available) { // We already read some data of a // datagram. Give up on the rest of the data, // process what we have below. if ((size_t)available == m_len) { // Need the byte for nul termination. available--; } have_message = true; break; } else { // Give up. SyncContext::throwError("out of memory"); return false; } } else { m_buffer = (char *)buffer; m_len = newlen; } } // read, but leave space for nul byte; // when using datagrams, we only peek here and remove the // datagram below, without rereading the data if (!USE_UNIX_DOMAIN_DGRAM && m_streams) { available = recv(fds.m_read, m_buffer, m_len - 1, MSG_DONTWAIT); if (available == 0) { return data_read; } else if (available == -1) { if (errno == EAGAIN) { // pretend that data was read, so that caller invokes us again return true; } else { SyncContext::throwError("reading output", errno); return false; } } else { // data read, process it data_read = true; break; } } else { available = recv(fds.m_read, m_buffer, m_len - 1, MSG_DONTWAIT|MSG_PEEK); have_message = available >= 0; } if (available < (ssize_t)m_len - 1) { break; } else { // try again with twice the buffer newlen *= 2; } } if (have_message) { if (USE_UNIX_DOMAIN_DGRAM || !m_streams) { // swallow packet, even if empty or we couldn't receive it recv(fds.m_read, NULL, 0, MSG_DONTWAIT); } data_read = true; } if (available > 0) { m_buffer[available] = 0; // Now pass it to logger, with a level determined by // the channel. This is the point where we can filter // out known noise. std::string prefix; Logger::Level level = Logger::DEV; char *text = m_buffer; if (fds.m_original == STDOUT_FILENO) { // stdout: not sure what this could be, so show it level = Logger::SHOW; char *eol = strchr(text, '\n'); if (!m_stdoutData.empty()) { // try to complete previous line, can be done // if text contains a line break if (eol) { m_stdoutData.append(text, eol - text); text = eol + 1; Logger::instance().message(level, prefix.empty() ? NULL : &prefix, NULL, 0, NULL, "%s", m_stdoutData.c_str()); m_stdoutData.clear(); } } // avoid sending incomplete line at end of text, // must be done when there is no line break or // it is not at the end of the buffer eol = strrchr(text, '\n'); if (eol != m_buffer + available - 1) { if (eol) { m_stdoutData.append(eol + 1); *eol = 0; } else { m_stdoutData.append(text); *text = 0; } } // output might have been processed as part of m_stdoutData, // don't log empty string below if (!*text) { continue; } } else if (fds.m_original == STDERR_FILENO) { // stderr: not normally useful for users, so we // can filter it more aggressively. For example, // swallow extra line breaks, glib inserts those. while (*text == '\n') { text++; } const char *glib_debug_prefix = "** ("; // ** (client-test:875): WARNING **: const char *glib_msg_prefix = "** Message:"; prefix = "stderr"; if ((!strncmp(text, glib_debug_prefix, strlen(glib_debug_prefix)) && strstr(text, " **:")) || !strncmp(text, glib_msg_prefix, strlen(glib_msg_prefix))) { level = Logger::DEBUG; prefix = "glib"; } else { level = Logger::DEV; } // If the text contains the word "error", it probably // is severe enough to show to the user, regardless of // who produced it... except for errors suppressed // explicitly. if (strcasestr(text, "error") && !ignoreError(text)) { level = Logger::ERROR; } } // avoid explicit newline at end of output, // logging will add it for each message() // invocation size_t len = strlen(text); if (len > 0 && text[len - 1] == '\n') { text[len - 1] = 0; } Logger::instance().message(level, prefix.empty() ? NULL : &prefix, NULL, 0, NULL, "%s", text); available = 0; } } while(have_message); return data_read; } void LogRedirect::addIgnoreError(const std::string &error) { RecMutex::Guard guard = Logger::lock(); m_knownErrors.insert(error); } bool LogRedirect::ignoreError(const std::string &text) { RecMutex::Guard guard = Logger::lock(); BOOST_FOREACH(const std::string &entry, m_knownErrors) { if (text.find(entry) != text.npos) { return true; } } return false; } void LogRedirect::process() { RecMutex::Guard guard; if (m_streams) { // iterate until both sockets are closed by peer while (true) { fd_set readfds; fd_set errfds; int maxfd = 0; FD_ZERO(&readfds); FD_ZERO(&errfds); if (m_stdout.m_read >= 0) { FD_SET(m_stdout.m_read, &readfds); FD_SET(m_stdout.m_read, &errfds); maxfd = m_stdout.m_read; } if (m_stderr.m_read >= 0) { FD_SET(m_stderr.m_read, &readfds); FD_SET(m_stderr.m_read, &errfds); if (m_stderr.m_read > maxfd) { maxfd = m_stderr.m_read; } } if (maxfd == 0) { // both closed return; } int res = select(maxfd + 1, &readfds, NULL, &errfds, NULL); switch (res) { case -1: // fatal, cannot continue SyncContext::throwError("waiting for output", errno); return; break; case 0: // None ready? Try again. break; default: if (m_stdout.m_read >= 0 && FD_ISSET(m_stdout.m_read, &readfds)) { if (!process(m_stdout)) { // Exact status of a Unix domain datagram socket upon close // of the remote end is a bit uncertain. For TCP, we would end // up here: marked by select as "ready for read", but no data -> EOF. close(m_stdout.m_read); m_stdout.m_read = -1; } } if (m_stdout.m_read >= 0 && FD_ISSET(m_stdout.m_read, &errfds)) { // But in practice, Unix domain sockets don't mark the stream // as "closed". This is an attempt to detect that situation // via the FDs exception status, but that also doesn't work. close(m_stdout.m_read); m_stdout.m_read = -1; } if (m_stderr.m_read >= 0 && FD_ISSET(m_stderr.m_read, &readfds)) { if (!process(m_stderr)) { close(m_stderr.m_read); m_stderr.m_read = -1; } } if (m_stderr.m_read >= 0 && FD_ISSET(m_stderr.m_read, &errfds)) { close(m_stderr.m_read); m_stderr.m_read = -1; } break; } } } else { guard = lock(); } if (m_processing) { return; } m_processing = true; process(m_stdout); process(m_stderr); // avoid hanging onto excessive amounts of memory m_len = std::min((size_t)(4 * 1024), m_len); m_buffer = (char *)realloc(m_buffer, m_len); if (!m_buffer) { m_len = 0; } m_processing = false; } void LogRedirect::flush() throw() { RecMutex::Guard guard = lock(); process(); if (!m_stdoutData.empty()) { std::string buffer; std::swap(buffer, m_stdoutData); Logger::instance().message(Logger::SHOW, NULL, NULL, 0, NULL, "%s", buffer.c_str()); } } #ifdef ENABLE_UNIT_TESTS class LogRedirectTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(LogRedirectTest); CPPUNIT_TEST(simple); CPPUNIT_TEST(largeChunk); CPPUNIT_TEST(streams); CPPUNIT_TEST(overload); #ifdef HAVE_GLIB CPPUNIT_TEST(glib); #endif CPPUNIT_TEST_SUITE_END(); /** * redirect stdout/stderr, then intercept the log messages and * store them for inspection */ class LogBuffer : public Logger, private boost::noncopyable { public: std::stringstream m_streams[DEBUG + 1]; PushLogger m_redirect; LogBuffer(LogRedirect::Mode mode = LogRedirect::STDERR_AND_STDOUT) { m_redirect.reset(new LogRedirect(mode)); addLogger(boost::shared_ptr(this, NopDestructor())); } ~LogBuffer() { removeLogger(this); m_redirect.reset(); } virtual void messagev(const MessageOptions &options, const char *format, va_list args) { CPPUNIT_ASSERT(options.m_level <= DEBUG && options.m_level >= 0); m_streams[options.m_level] << StringPrintfV(format, args); } }; public: void simple() { LogBuffer buffer; static const char *simpleMessage = "hello world"; CPPUNIT_ASSERT_EQUAL((ssize_t)strlen(simpleMessage), write(STDOUT_FILENO, simpleMessage, strlen(simpleMessage))); buffer.m_redirect->flush(); CPPUNIT_ASSERT_EQUAL(buffer.m_streams[Logger::SHOW].str(), std::string(simpleMessage)); } void largeChunk() { LogBuffer buffer; std::string large; large.append(60 * 1024, 'h'); CPPUNIT_ASSERT_EQUAL((ssize_t)large.size(), write(STDOUT_FILENO, large.c_str(), large.size())); buffer.m_redirect->flush(); CPPUNIT_ASSERT_EQUAL(large.size(), buffer.m_streams[Logger::SHOW].str().size()); CPPUNIT_ASSERT_EQUAL(large, buffer.m_streams[Logger::SHOW].str()); } void streams() { LogBuffer buffer; static const char *simpleMessage = "hello world"; CPPUNIT_ASSERT_EQUAL((ssize_t)strlen(simpleMessage), write(STDOUT_FILENO, simpleMessage, strlen(simpleMessage))); static const char *errorMessage = "such a cruel place"; CPPUNIT_ASSERT_EQUAL((ssize_t)strlen(errorMessage), write(STDERR_FILENO, errorMessage, strlen(errorMessage))); // process() keeps unfinished STDOUT lines buffered buffer.m_redirect->process(); CPPUNIT_ASSERT_EQUAL(std::string(errorMessage), buffer.m_streams[Logger::DEV].str()); CPPUNIT_ASSERT_EQUAL(string(""), buffer.m_streams[Logger::SHOW].str()); // flush() makes them available buffer.m_redirect->flush(); CPPUNIT_ASSERT_EQUAL(std::string(errorMessage), buffer.m_streams[Logger::DEV].str()); CPPUNIT_ASSERT_EQUAL(std::string(simpleMessage), buffer.m_streams[Logger::SHOW].str()); } void overload() { LogBuffer buffer; std::string large; large.append(1024, 'h'); for (int i = 0; i < 4000; i++) { CPPUNIT_ASSERT_EQUAL((ssize_t)large.size(), write(STDOUT_FILENO, large.c_str(), large.size())); } buffer.m_redirect->flush(); CPPUNIT_ASSERT(buffer.m_streams[Logger::SHOW].str().size() > large.size()); } #ifdef HAVE_GLIB void glib() { fflush(stdout); fflush(stderr); static const char *filename = "LogRedirectTest_glib.out"; int new_stdout = open(filename, O_RDWR|O_CREAT|O_TRUNC, S_IRWXU); // check that intercept all glib message and don't print anything to stdout int orig_stdout = -1; try { // need to restore the current state below; would be nice // to query it instead of assuming that Logger::glogFunc // is the current log handler g_log_set_default_handler(g_log_default_handler, NULL); orig_stdout = dup(STDOUT_FILENO); dup2(new_stdout, STDOUT_FILENO); LogBuffer buffer(LogRedirect::STDERR); fprintf(stdout, "normal message stdout\n"); fflush(stdout); fprintf(stderr, "normal message stderr\n"); fflush(stderr); // ** (process:13552): WARNING **: test warning g_warning("test warning"); // ** Message: test message g_message("test message"); // ** (process:13552): CRITICAL **: test critical g_critical("test critical"); // would abort: // g_error("error") // ** (process:13552): DEBUG: test debug g_debug("test debug"); buffer.m_redirect->process(); std::string error = buffer.m_streams[Logger::ERROR].str(); std::string warning = buffer.m_streams[Logger::WARNING].str(); std::string show = buffer.m_streams[Logger::SHOW].str(); std::string info = buffer.m_streams[Logger::INFO].str(); std::string dev = buffer.m_streams[Logger::DEV].str(); std::string debug = buffer.m_streams[Logger::DEBUG].str(); CPPUNIT_ASSERT_EQUAL(string(""), error); CPPUNIT_ASSERT_EQUAL(string(""), warning); CPPUNIT_ASSERT_EQUAL(string(""), show); CPPUNIT_ASSERT_EQUAL(string(""), info); CPPUNIT_ASSERT_EQUAL(string(""), error); CPPUNIT_ASSERT(dev.find("normal message stderr") != dev.npos); CPPUNIT_ASSERT(debug.find("test warning") != debug.npos); } catch(...) { g_log_set_default_handler(Logger::glogFunc, NULL); dup2(orig_stdout, STDOUT_FILENO); throw; } g_log_set_default_handler(Logger::glogFunc, NULL); dup2(orig_stdout, STDOUT_FILENO); lseek(new_stdout, 0, SEEK_SET); char out[128]; ssize_t l = read(new_stdout, out, sizeof(out) - 1); CPPUNIT_ASSERT(l > 0); out[l] = 0; CPPUNIT_ASSERT(boost::starts_with(std::string(out), "normal message stdout")); } #endif }; SYNCEVOLUTION_TEST_SUITE_REGISTRATION(LogRedirectTest); #endif // ENABLE_UNIT_TESTS SE_END_CXX syncevolution_1.4/src/syncevo/LogRedirect.h000066400000000000000000000201061230021373600212060ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_LOGREDIRECT #define INCL_LOGREDIRECT #include #include #include #include #include SE_BEGIN_CXX /** * Intercepts all text written to stdout or stderr and passes it * through the currently active logger, which may or may not be * this instance itself. In addition, it catches SIGSEGV, SIGABRT, * SIGBUS and processes pending output before shutting down * by raising these signals again. * * The interception is done by replacing the file descriptors * 1 and 2. The original file descriptors are preserved; the * original FD 1 is used for writing log messages that are * intended to reach the user. * * This class tries to be simple and therefore avoids threads * and forking. It intentionally doesn't protect against multiple * threads accessing it. This is something that has to be avoided * by the user. The redirected output has to be read whenever * possible, ideally before producing other log output (process()). * * Because the same thread that produces the output also reads it, * there can be a deadlock if more output is produced than the * in-kernel buffers allow. Pipes and stream sockets therefore cannot * be used. Unreliable datagram sockets work: * - normal write() calls produce packets * - if the sender always writes complete lines, the reader * will not split them because it can receive the complete packet * * Unix Domain datagram sockets would be nice: * - socketpair() creates an anonymous connection, no-one else * can send us unwanted data (in contrast to, say, UDP) * - unlimited chunk size * - *but* packets are *not* dropped if too much output is produced * (found with LogRedirectTest::overload test and confirmed by * "man unix") * * To avoid deadlocks, UDP sockets have to be used. It has drawbacks: * - chunk size limited by maximum size of IP4 packets * - more complex to set up (currently assumes that 127.0.0.1 is the * local interface) * - anyone running locally can send us log data * * The implementation contains code for both; UDP is active by default * because the potential deadlock is considered more severe than UDP's * disadvantages. * * Because this class is to be used early in the startup * of the application and in low-level error scenarios, it * must not throw exceptions or return errors. If something * doesn't work, it stops redirecting output. * * Redirection and signal handlers are disabled if the environment * variable SYNCEVOLUTION_DEBUG is set (regardless of its value). * * In contrast to stderr, stdout is only passed into the logging * system as complete lines. That's because it may include data (like * synccompare output) which is not printed line-oriented and * inserting line breaks (as the logging system does) is undesirable. * If an output packet does not end in a line break, that last line * is buffered and written together with the next packet, or in flush(). */ class LogRedirect : public LoggerStdout { public: struct FDs { int m_original; /** the original output FD, 2 for stderr */ int m_copy; /** a duplicate of the original output file descriptor */ int m_write; /** the write end of the replacement */ int m_read; /** the read end of the replacement */ }; /** ignore any error output containing "error" */ static void addIgnoreError(const std::string &error); /** * Messages containing text listed in * SYNCEVOLUTION_SUPPRESS_ERRORS env variable (new-line separated) * or registered via addIgnoreError() are not real errors and * should only be logged for developers. */ static bool ignoreError(const std::string &text); private: FDs m_stdout, m_stderr; bool m_streams; /**< using reliable streams instead of UDP */ FILE *m_out; /** a stream for Logger::SHOW output which isn't redirected */ FILE *m_err; /** corresponding stream for any other output */ char *m_buffer; /** typically fairly small buffer for reading */ std::string m_stdoutData; /**< incomplete stdout line */ size_t m_len; /** total length of buffer */ bool m_processing; /** flag to detect recursive process() calls */ static LogRedirect *m_redirect; /**< single active instance, for signal handler */ static std::set m_knownErrors; /** texts contained in errors which are to be ignored */ // non-virtual helper functions which can always be called, // including the constructor and destructor void redirect(int original, FDs &fds) throw(); void restore(FDs &fds) throw(); void restore() throw(); /** @return true if data was available */ bool process(FDs &fds) throw(); static void abortHandler(int sig) throw(); void init(); public: enum Mode { STDERR_AND_STDOUT, STDERR }; /** * Redirect both stderr and stdout or just stderr, * using UDP so that we don't block when not reading * redirected output. * * messagev() only writes messages to the previous stdout * or the optional file which pass the filtering (relevant, * suppress known errors, ...). * * May only be called when there is no other active LogRedirect * instance. Not thread-safe, in contrast to the actual logging * method and redirect handling. * * Does not add or remove the logger from the logger stack. * That must be done by the caller. */ LogRedirect(Mode mode, const char *filename = NULL); ~LogRedirect() throw(); virtual void remove() throw(); /** * Remove redirection (if any) after a fork and before an exec. */ static void removeRedirect() throw(); /** * Meant to be used for redirecting output of a specific command * via fork()/exec(). Prepares reliable streams, as determined by * ExecuteFlags, without touch file descriptor 1 and 2 and without * installing itself as logger. In such an instance, process() * will block until both streams get closed on the writing end. */ LogRedirect(ExecuteFlags flags); /** true if stdout is redirected */ static bool redirectingStdout() { return m_redirect && m_redirect->m_stdout.m_read > 0; } /** true if stderr is redirected */ static bool redirectingStderr() { return m_redirect && m_redirect->m_stderr.m_read > 0; } /** reset any redirection, if active */ static void reset() { if (m_redirect) { m_redirect->flush(); m_redirect->restore(); } } const FDs &getStdout() { return m_stdout; } const FDs &getStderr() { return m_stderr; } /** * Read currently available redirected output and handle it. * * When using unreliable output redirection, it will always * keep going without throwing exceptions. When using reliable * redirection and a fatal error occurs, then and exception * is thrown. */ void process(); /** same as process(), but also dump all cached output */ void flush() throw(); /** format log messages via normal LogStdout and print to a valid stream owned by us */ virtual void messagev(const MessageOptions &options, const char *format, va_list args); }; SE_END_CXX #endif // INCL_LOGREDIRECT syncevolution_1.4/src/syncevo/LogStdout.cpp000066400000000000000000000052501230021373600212650ustar00rootroot00000000000000/* * Copyright (C) 2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include #include using namespace std; SE_BEGIN_CXX LoggerStdout::LoggerStdout(FILE *file) : m_file(file), m_closeFile(false) {} LoggerStdout::LoggerStdout(const std::string &filename) : m_file(fopen(filename.c_str(), "w")), m_closeFile(true) { if (!m_file) { throw std::string(filename + ": " + strerror(errno)); } } LoggerStdout::~LoggerStdout() { if (m_closeFile) { fclose(m_file); } } static void appendOutput(std::string &output, std::string &chunk, size_t expectedTotal) { if (expectedTotal) { output.reserve(expectedTotal); } output.append(chunk); } void LoggerStdout::write(FILE *file, Level msglevel, Level filelevel, const std::string *prefix, const std::string *procname, const char *format, va_list args) { if (file && msglevel <= filelevel) { // TODO: print debugging information, perhaps only in log file std::string output; formatLines(msglevel, filelevel, procname, prefix, format, args, boost::bind(appendOutput, boost::ref(output), _1, _2)); fwrite(output.c_str(), 1, output.size(), file); fflush(file); } } void LoggerStdout::messagev(const MessageOptions &options, const char *format, va_list args) { if (!(options.m_flags & MessageOptions::ONLY_GLOBAL_LOG)) { write(m_file, options.m_level, getLevel(), options.m_prefix, options.m_processName, format, args); } } SE_END_CXX syncevolution_1.4/src/syncevo/LogStdout.h000066400000000000000000000036571230021373600207430ustar00rootroot00000000000000/* * Copyright (C) 2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_LOGSTDOUT #define INCL_LOGSTDOUT #include #include #include #include #include SE_BEGIN_CXX /** * A logger which writes to stdout or a file. */ class LoggerStdout : public Logger { FILE *m_file; bool m_closeFile; public: /** * write to stdout by default * * @param file override default file; NULL disables printing */ LoggerStdout(FILE *file = stdout); /** * open and own the given log file * * @param filename will be opened relative to current directory */ LoggerStdout(const std::string &filename); ~LoggerStdout(); void write(FILE *file, Level msglevel, Level filelevel, const std::string *prefix, const std::string *procname, const char *format, va_list args); virtual void messagev(const MessageOptions &options, const char *format, va_list args); }; SE_END_CXX #endif // INCL_LOGSTDOUT syncevolution_1.4/src/syncevo/LogSyslog.cpp000066400000000000000000000063561230021373600212730ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include #include #include using namespace std; SE_BEGIN_CXX LoggerSyslog::LoggerSyslog(const std::string &processName) : m_processName(processName), m_parentLogger(Logger::instance()) { // valgrind tells us that openlog() does not copy the string. // Must provide pointer to a permanent copy. openlog(m_processName.c_str(), LOG_CONS | LOG_NDELAY | LOG_PID, LOG_USER); } LoggerSyslog::~LoggerSyslog() { closelog(); } static void printToSyslog(int sysloglevel, std::string &chunk, size_t expectedTotal) { if (!expectedTotal) { // Might contain line breaks in the middle, split it. size_t pos = 0; while(true) { size_t next = chunk.find('\n', pos); if (next == chunk.npos) { // Line break is guaranteed to be last character, // so we have printed everything now. return; } chunk[next] = 0; syslog(sysloglevel, "%s", chunk.c_str() + pos); pos = next + 1; } } else { // Single line. We can print the trailing line break. syslog(sysloglevel, "%s", chunk.c_str()); } } void LoggerSyslog::messagev(const MessageOptions &options, const char *format, va_list args) { // always to parent first (usually stdout): // if the parent is a LogRedirect instance, then // it'll flush its own output first, which ensures // that the new output comes later (as desired) { va_list argscopy; va_copy(argscopy, args); m_parentLogger.messagev(options, format, argscopy); va_end(argscopy); } if (options.m_level <= getLevel()) { const std::string none; formatLines(options.m_level, getLevel(), &none, // Process name is set when opening the syslog, don't repeat it. options.m_prefix, format, args, boost::bind(printToSyslog, getSyslogLevel(options.m_level), _1, _2)); } } int LoggerSyslog::getSyslogLevel(Level level) { switch (level) { case ERROR: return LOG_ERR; case WARNING: return LOG_WARNING; case SHOW: return LOG_NOTICE; case INFO: case DEV: return LOG_INFO; case DEBUG: default: return LOG_DEBUG; } } SE_END_CXX syncevolution_1.4/src/syncevo/LogSyslog.h000066400000000000000000000027231230021373600207320ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_LOGSYSLOG #define INCL_LOGSYSLOG #include #include #include #include #include SE_BEGIN_CXX /** * A logger which writes to syslog. */ class LoggerSyslog : public Logger { const std::string m_processName; Handle m_parentLogger; public: /** * Write to syslog by default. */ LoggerSyslog(const std::string &processName); ~LoggerSyslog(); virtual void messagev(const MessageOptions &options, const char *format, va_list args); private: static int getSyslogLevel(Level level); }; SE_END_CXX #endif // INCL_LOGSYSLOG syncevolution_1.4/src/syncevo/Logging.cpp000066400000000000000000000307621230021373600207350ustar00rootroot00000000000000/* * Copyright (C) 2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include #include #include SE_BEGIN_CXX static RecMutex logMutex; /** * POD to have it initialized without relying on a constructor to run. */ static std::string *logProcessName; void Logger::setProcessName(const std::string &name) { RecMutex::Guard guard = logMutex.lock(); if (!logProcessName) { logProcessName = new std::string(name); } else { *logProcessName = name; } } std::string Logger::getProcessName() { RecMutex::Guard guard = logMutex.lock(); return logProcessName ? *logProcessName : ""; } RecMutex::Guard Logger::lock() { return logMutex.lock(); } Logger::Logger() : m_level(INFO) { } Logger::~Logger() { } /** * Create (if necessary) and return the logger stack. * It has at least one entry. * * logMutex must be locked when calling this. */ static std::vector &LoggersSingleton() { // allocate array once and never free it because it might be needed till // the very end of the application life cycle static std::vector *loggers; if (!loggers) { loggers = new std::vector; // Ensure that the array is never empty. boost::shared_ptr logger(new LoggerStdout); loggers->push_back(logger); } return *loggers; } Logger::Handle Logger::instance() { RecMutex::Guard guard = logMutex.lock(); std::vector &loggers = LoggersSingleton(); return loggers.back(); } void Logger::addLogger(const Handle &logger) { RecMutex::Guard guard = logMutex.lock(); std::vector &loggers = LoggersSingleton(); loggers.push_back(logger); } void Logger::removeLogger(Logger *logger) { RecMutex::Guard guard = logMutex.lock(); std::vector &loggers = LoggersSingleton(); for (ssize_t i = loggers.size() - 1; i >= 0; --i) { if (loggers[i] == logger) { loggers[i].remove(); loggers.erase(loggers.begin() + i); break; } } } void Logger::formatLines(Level msglevel, Level outputlevel, const std::string *processName, const std::string *prefix, const char *format, va_list args, boost::function print) { std::string tag; // in case of 'SHOW' level, don't print level and prefix information if (msglevel != SHOW) { std::string reltime; std::string procname; std::string firstLine; // Run non-blocking operations on shared data while // holding the mutex. { RecMutex::Guard guard = logMutex.lock(); const std::string *realProcname; if (processName) { realProcname = processName; } else { if (!logProcessName) { logProcessName = new std::string; } realProcname = logProcessName; } if (!realProcname->empty()) { procname.reserve(realProcname->size() + 1); procname += " "; procname += *realProcname; } if (outputlevel >= DEBUG) { // add relative time stamp Timespec now = Timespec::monotonic(); if (!m_startTime) { // first message, start counting time m_startTime = now; time_t nowt = time(NULL); struct tm tm_gm, tm_local; char buffer[2][80]; gmtime_r(&nowt, &tm_gm); localtime_r(&nowt, &tm_local); reltime = " 00:00:00"; strftime(buffer[0], sizeof(buffer[0]), "%a %Y-%m-%d %H:%M:%S", &tm_gm); strftime(buffer[1], sizeof(buffer[1]), "%H:%M %z %Z", &tm_local); std::string line = StringPrintf("[DEBUG%s%s] %s UTC = %s\n", procname.c_str(), reltime.c_str(), buffer[0], buffer[1]); } else { if (now >= m_startTime) { Timespec delta = now - m_startTime; reltime = StringPrintf(" %02ld:%02ld:%02ld", delta.tv_sec / (60 * 60), (delta.tv_sec % (60 * 60)) / 60, delta.tv_sec % 60); } else { reltime = " ??:??:??"; } } } } if (!firstLine.empty()) { print(firstLine, 1); } tag = StringPrintf("[%s%s%s] %s%s", levelToStr(msglevel), procname.c_str(), reltime.c_str(), prefix ? prefix->c_str() : "", prefix ? ": " : ""); } std::string output = StringPrintfV(format, args); if (!tag.empty()) { // Print individual lines. // // Total size is guessed by assuming an average line length of // around 40 characters to predict number of lines. size_t expectedTotal = (output.size() / 40 + 1) * tag.size() + output.size(); size_t pos = 0; while (true) { size_t next = output.find('\n', pos); if (next != output.npos) { std::string line; line.reserve(tag.size() + next + 1 - pos); line.append(tag); line.append(output, pos, next + 1 - pos); print(line, expectedTotal); pos = next + 1; } else { break; } } if (pos < output.size() || output.empty()) { // handle dangling last line or empty chunk (don't // want empty line for that, print at least the tag) std::string line; line.reserve(tag.size() + output.size() - pos + 1); line.append(tag); line.append(output, pos, output.size() - pos); line += '\n'; print(line, expectedTotal); } } else { if (!boost::ends_with(output, "\n")) { // append newline if necessary output += '\n'; } print(output, 0); } } Logger::MessageOptions::MessageOptions(Level level) : m_level(level), m_prefix(NULL), m_file(NULL), m_line(0), m_function(NULL), m_processName(NULL), m_flags(0) { } Logger::MessageOptions::MessageOptions(Level level, const std::string *prefix, const char *file, int line, const char *function, int flags) : m_level(level), m_prefix(prefix), m_file(file), m_line(line), m_function(function), m_processName(NULL), m_flags(flags) { } Logger::Handle::Handle() throw () { } Logger::Handle::Handle(Logger *logger) throw () { m_logger.reset(logger); } Logger::Handle::Handle(const Handle &other) throw () { m_logger = other.m_logger; } Logger::Handle & Logger::Handle::operator = (const Handle &other) throw () { if (this != &other) { m_logger = other.m_logger; } return *this; } Logger::Handle::~Handle() throw () { m_logger.reset(); } void Logger::Handle::message(Level level, const std::string *prefix, const char *file, int line, const char *function, const char *format, ...) { va_list args; va_start(args, format); m_logger->messagev(MessageOptions(level, prefix, file, line, function), format, args); va_end(args); } void Logger::Handle::message(Level level, const std::string &prefix, const char *file, int line, const char *function, const char *format, ...) { va_list args; va_start(args, format); m_logger->messagev(MessageOptions(level, &prefix, file, line, function), format, args); va_end(args); } void Logger::Handle::messageWithOptions(const MessageOptions &options, const char *format, ...) { va_list args; va_start(args, format); m_logger->messagev(options, format, args); va_end(args); } const char *Logger::levelToStr(Level level) { switch (level) { case SHOW: return "SHOW"; case ERROR: return "ERROR"; case WARNING: return "WARNING"; case INFO: return "INFO"; case DEV: return "DEVELOPER"; case DEBUG: return "DEBUG"; default: return "???"; } } Logger::Level Logger::strToLevel(const char *str) { // order is based on a rough estimate of message frequency of the // corresponding type if (!str || !strcmp(str, "DEBUG")) { return DEBUG; } else if (!strcmp(str, "INFO")) { return INFO; } else if (!strcmp(str, "SHOW")) { return SHOW; } else if (!strcmp(str, "ERROR")) { return ERROR; } else if (!strcmp(str, "WARNING")) { return WARNING; } else if (!strcmp(str, "DEV")) { return DEV; } else { return DEBUG; } } #ifdef HAVE_GLIB void Logger::glogFunc(const gchar *logDomain, GLogLevelFlags logLevel, const gchar *message, gpointer userData) { Level level = (logLevel & (G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL)) ? ERROR : // glib warnings are usually not relevant for users, only for developers. (logLevel & G_LOG_LEVEL_WARNING) ? DEV : (logLevel & (G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO)) ? SHOW : DEBUG; // Downgrade some know error messages as registered with // the LogRedirect helper class. That messages are registered // there is a historic artifact. if (level != DEBUG && LogRedirect::ignoreError(message)) { level = DEBUG; } Logger::instance().message(level, NULL, NULL, 0, NULL, "%s%s%s", logDomain ? logDomain : "", logDomain ? ": " : "", message); } #endif int Logger::sysyncPrintf(FILE *stream, const char *format, ...) { va_list args; va_start(args, format); static const std::string prefix("SYSYNC"); if (boost::starts_with(format, prefix) && format[prefix.size()] == ' ') { // Skip initial "SYSYNC " prefix, because it will get re-added // in a better way (= to each line) via the prefix parameter. format += prefix.size() + 1; } Logger::instance().messagev(MessageOptions(DEBUG, &prefix, NULL, 0, NULL), format, args); va_end(args); return 0; } SE_END_CXX syncevolution_1.4/src/syncevo/Logging.h000066400000000000000000000361521230021373600204010ustar00rootroot00000000000000/* * Copyright (C) 2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_LOGGING #define INCL_LOGGING #include #include #include #ifdef HAVE_CONFIG_H # include #endif #ifdef HAVE_GLIB # include #endif #include #include #include #include #include #include SE_BEGIN_CXX /** * Abstract interface for logging in SyncEvolution. Can be * implemented by other classes to add information (like a certain * prefix) before passing the message on to a global instance for the * actual processing. * * The static methods provide some common utility code and manage a * global stack of loggers. The one pushed latest is called first to * handle a new message. It can find its parent logger (= the one * added just before it) and optionally pass the message up the chain * before or after processing it itself. * * All methods must be thread-safe. */ class Logger { public: /** * Which of these levels is the right one for a certain message * is a somewhat subjective choice. Here is a definition how they * are supposed to be used: * - error: severe problem which the user and developer have to * know about * - warning: a problem that was handled, but users and developers * probably will want to know about * - info: information about a sync session which the user * will want to read during/after each sync session * - developer: information about a sync session that is not * interesting for a user (for example, because it * is constant and already known) but which should * be in each log because developers need to know * it. Messages logged with this calls will be included * at LOG_LEVEL_INFO, therefore messages should be small and * not recur so that the log file size remains small. * - debug: most detailed logging, messages may be arbitrarily large * * Here is a decision tree which helps to pick the right level: * - an error: => ERROR * - a non-fatal error: => WARNING * - it changes during each sync or marks important steps * in the sync: INFO * - same as before, but without the [INFO] prefix added to each line: => SHOW * - small, non-recurring message which is important for developers * who read a log produced at LOG_LEVEL_INFO: DEVELOPER * - everything else: DEBUG */ typedef enum { /** * no error messages printed */ NONE = -1, /** * only error messages printed */ ERROR, /** * error and warning messages printed */ WARNING, /** * "Normal" stdout output which is meant to be seen by a * user. */ SHOW, /** * errors and info messages for users and developers will be * printed: use this to keep the output consise and small */ INFO, /** * important messages to developers */ DEV, /** * all messages will be printed, including detailed debug * messages */ DEBUG } Level; static const char *levelToStr(Level level); /** always returns a valid level, also for NULL, by falling back to DEBUG */ static Level strToLevel(const char *str); /** * additional, short string identifying the SyncEvolution process; * empty if master process * * Included by LoggerStdout in the [INFO/DEBUG/...] tag. */ static void setProcessName(const std::string &name); static std::string getProcessName(); /** * Obtains the recursive logging mutex. * * All calls offered by the Logger class already lock that mutex * internally, but sometimes it may be necessary to protect a larger * region of logging related activity. */ static RecMutex::Guard lock(); #ifdef HAVE_GLIB /** * can be used in g_log_set_handler() to redirect log messages * into our own logging; must be called for each log domain * that may be relevant */ static void glogFunc(const gchar *logDomain, GLogLevelFlags logLevel, const gchar *message, gpointer userData); #endif /** * can be used as replacement for libsynthesis console printf function, * logs at DEBUG level * * @param stream is ignored * @param format guaranteed to start with "SYSYNC " * @return always 0 */ static int sysyncPrintf(FILE *stream, const char *format, ...); Logger(); virtual ~Logger(); /** * Prepare logger for removal from logging stack. May be called * multiple times. * * The logger should stop doing anything right away and just pass * on messages until it gets deleted eventually. */ virtual void remove() throw () {} /** * Collects all the parameters which may get passed to * messagev. */ class MessageOptions { public: /** level for current message */ Level m_level; /** inserted at beginning of each line, if non-NULL */ const std::string *m_prefix; /** source file where message comes from, if non-NULL */ const char *m_file; /** source line number, if file is non-NULL */ int m_line; /** surrounding function name, if non-NULL */ const char *m_function; /** name of the process which originally created the message, if different from current one */ const std::string *m_processName; /** additional flags */ int m_flags; enum { /** * The message was written into a global log (syslog, dlt, ...) * already. Such a message must not be logged again. */ ALREADY_LOGGED = 1<<0, /** * The message must be written into a global log, * but not to stdout. */ ONLY_GLOBAL_LOG = 1<<1 }; MessageOptions(Level level); MessageOptions(Level level, const std::string *prefix, const char *file, int line, const char *function, int flags = 0); }; /** * output a single message * * @param options carries additional information about the message * @param format sprintf format * @param args parameters for sprintf: consumed by this function, * make copy with va_copy() if necessary! */ virtual void messagev(const MessageOptions &options, const char *format, va_list args) = 0; /** * A convenience and backwards-compatibility class which allows * calling some methods of the underlying pointer directly similar * to the Logger reference returned in previous SyncEvolution * releases. */ class Handle { boost::shared_ptr m_logger; public: Handle() throw (); Handle(Logger *logger) throw (); template Handle(const boost::shared_ptr &logger) throw () : m_logger(logger) {} template Handle(const boost::weak_ptr &logger) throw () : m_logger(logger.lock()) {} Handle(const Handle &other) throw (); Handle &operator = (const Handle &other) throw (); ~Handle() throw (); operator bool () const { return m_logger; } bool operator == (Logger *logger) const { return m_logger.get() == logger; } Logger *get() const { return m_logger.get(); } void messagev(const MessageOptions &options, const char *format, va_list args) { m_logger->messagev(options, format, args); } void message(Level level, const std::string *prefix, const char *file, int line, const char *function, const char *format, ...) #ifdef __GNUC__ __attribute__((format(printf, 7, 8))) #endif ; void message(Level level, const std::string &prefix, const char *file, int line, const char *function, const char *format, ...) #ifdef __GNUC__ __attribute__((format(printf, 7, 8))) #endif ; void messageWithOptions(const MessageOptions &options, const char *format, ...) #ifdef __GNUC__ __attribute__((format(printf, 3, 4))) #endif ; void setLevel(Level level) { m_logger->setLevel(level); } Level getLevel() { return m_logger->getLevel(); } void remove() throw () { m_logger->remove(); } }; /** * Grants access to the singleton which implements logging. * The implementation of this function and thus the Log * class itself is platform specific: if no Log instance * has been set yet, then this call has to create one. */ static Handle instance(); /** * Overrides the current default Logger implementation. * * @param logger will be used for all future logging activities */ static void addLogger(const Handle &logger); /** * Remove the specified logger. * * Note that the logger might still be in use afterwards, for * example when a different thread currently uses it. Therefore * loggers should be small stub classes. If they need access to * more expensive classes to do their work, they shold hold weak * reference to those and only lock them when logging. */ static void removeLogger(Logger *logger); virtual void setLevel(Level level) { m_level = level; } virtual Level getLevel() { return m_level; } protected: /** * Prepares the output. The result is passed back to the caller * line-by-line (expectedTotal > 0) and/or as full chunk * (expectedTotal = 0). The expected size is just a guess, be * prepared to handle more output. * * Each chunk already includes the necessary line breaks (in * particular after the last line when it contains the entire * output). It may be modified by the callback. * * @param processName NULL means use the current process' name, * empty means use none */ void formatLines(Level msglevel, Level outputlevel, const std::string *processName, const std::string *prefix, const char *format, va_list args, boost::function print); private: Level m_level; /** * Set by formatLines() before writing the first message if log * level is debugging, together with printing a message that gives * the local time. */ Timespec m_startTime; }; /** * Takes a logger and adds it to the stack * as long as the instance exists. */ template class PushLogger : boost::noncopyable { Logger::Handle m_logger; public: PushLogger() {} /** * Can use Handle directly here. */ PushLogger(const Logger::Handle &logger) : m_logger(logger) { if (m_logger) { Logger::addLogger(m_logger); } } /** * Take any type that a Handle constructor accepts, then use it as * Handle. */ template PushLogger(M logger) : m_logger(Logger::Handle(logger)) { if (m_logger) { Logger::addLogger(m_logger); } } ~PushLogger() throw () { if (m_logger) { Logger::removeLogger(m_logger.get()); } } operator bool () const { return m_logger; } void reset(const Logger::Handle &logger) { if (m_logger) { Logger::removeLogger(m_logger.get()); } m_logger = logger; if (m_logger) { Logger::addLogger(m_logger); } } template void reset(M logger) { if (m_logger) { Logger::removeLogger(m_logger.get()); } m_logger = Logger::Handle(logger); if (m_logger) { Logger::addLogger(m_logger); } } void reset() { if (m_logger) { Logger::removeLogger(m_logger.get()); } m_logger = Logger::Handle(); } L *get() { return static_cast(m_logger.get()); } L * operator -> () { return get(); } }; /** * Wraps Logger::message() in the current default logger. * and adds file and line where the message comes from. * * This macro reverses _prefix and _level to avoid the situation where * the compiler mistakes a NULL _prefix with the _format parameter * (happened once while doing code refactoring). * * @TODO make source and line info optional for release * @TODO add function name (GCC extension) */ #define SE_LOG(_prefix, _level, _format, _args...) \ SyncEvo::Logger::instance().message(_level, \ _prefix, \ __FILE__, \ __LINE__, \ NULL, \ _format, \ ##_args); #define SE_LOG_SHOW(_prefix, _format, _args...) SE_LOG(_prefix, SyncEvo::Logger::SHOW, _format, ##_args) #define SE_LOG_ERROR(_prefix, _format, _args...) SE_LOG(_prefix, SyncEvo::Logger::ERROR, _format, ##_args) #define SE_LOG_WARNING(_prefix, _format, _args...) SE_LOG(_prefix, SyncEvo::Logger::WARNING, _format, ##_args) #define SE_LOG_INFO(_prefix, _format, _args...) SE_LOG(_prefix, SyncEvo::Logger::INFO, _format, ##_args) #define SE_LOG_DEV(_prefix, _format, _args...) SE_LOG(_prefix, SyncEvo::Logger::DEV, _format, ##_args) #define SE_LOG_DEBUG(_prefix, _format, _args...) SE_LOG(_prefix, SyncEvo::Logger::DEBUG, _format, ##_args) SE_END_CXX #endif // INCL_LOGGING syncevolution_1.4/src/syncevo/MapSyncSource.cpp000066400000000000000000000324351230021373600221010ustar00rootroot00000000000000/* * Copyright (C) 2010 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include SE_BEGIN_CXX SDKInterface *SubSyncSource::getSynthesisAPI() const { return m_parent ? m_parent->getSynthesisAPI() : NULL; } MapSyncSource::MapSyncSource(const SyncSourceParams ¶ms, const boost::shared_ptr &sub) : TestingSyncSource(params), m_sub(sub) { boost::shared_ptr safeNode(new SafeConfigNode(params.m_nodes.getTrackingNode())); m_trackingNode.reset(new PrefixConfigNode("item-", safeNode)); m_metaNode = safeNode; // parameters don't matter because actual implementation is in sub source SyncSourceLogging::init(std::list(), ", ", m_operations); m_sub->setParent(this); // Redirect backup/restore into sub source, if it defines a backup // operation. Otherwise continue to use our own, // SyncSourceRevision based implementation. The expectation is // that a custom backup operation implies a custom restore, // because of custom data formats in the data dump. Therefore if() // only checks the m_backupData pointer. if (m_sub->getOperations().m_backupData) { m_operations.m_backupData = m_sub->getOperations().m_backupData; m_operations.m_restoreData = m_sub->getOperations().m_restoreData; } } void MapSyncSource::enableServerMode() { SyncSourceAdmin::init(m_operations, this); SyncSourceBlob::init(m_operations, getCacheDir()); } bool MapSyncSource::serverModeEnabled() const { return m_operations.m_loadAdminData; } void MapSyncSource::detectChanges(SyncSourceRevisions::ChangeMode mode) { // erase content which might have been set in a previous call reset(); // read old list from node (matches endSync() code) m_revisions.clear(); ConfigProps props; m_trackingNode->readProperties(props); BOOST_FOREACH(const StringPair &prop, props) { const std::string &mainid = prop.first; const std::string &value = prop.second; size_t pos = value.find('/'); bool okay = false; if (pos == 0) { pos = value.find('/', 1); if (pos != value.npos) { std::string revision = m_escape.unescape(value.substr(1, pos - 1)); size_t nextpos = value.find('/', pos + 1); if (nextpos != value.npos) { std::string uid = m_escape.unescape(value.substr(pos + 1, nextpos - pos - 1)); SubRevisionEntry &ids = m_revisions[mainid]; ids.m_revision = revision; ids.m_uid = uid; pos = nextpos; while ((nextpos = value.find('/', pos + 1)) != value.npos) { std::string subid = m_escape.unescape(value.substr(pos + 1, nextpos - pos - 1)); ids.m_subids.insert(subid); pos = nextpos; } okay = true; } } } if (!okay) { SE_LOG_DEBUG(getDisplayName(), "unsupported or corrupt revision entry: %s = %s", mainid.c_str(), value.c_str()); } } // determine how to update that list and find changes switch (mode) { case SyncSourceRevisions::CHANGES_NONE: // nothing to do, just tell sub source m_sub->setAllSubItems(m_revisions); break; case SyncSourceRevisions::CHANGES_FULL: { // update the list and compare to find changes SubRevisionMap_t newRevisions; if (m_revisions.empty()) { // nothing to reuse, just ask for current items m_sub->listAllSubItems(newRevisions); } else { // update old information newRevisions = m_revisions; m_sub->updateAllSubItems(newRevisions); } // deleted items... BOOST_FOREACH(const SubRevisionMap_t::value_type &entry, m_revisions) { const std::string &mainid = entry.first; const SubRevisionEntry &ids = entry.second; if (newRevisions.find(entry.first) == newRevisions.end()) { BOOST_FOREACH(const std::string &subid, ids.m_subids) { addItem(createLUID(mainid, subid), DELETED); } } } // added or updated items... BOOST_FOREACH(const SubRevisionMap_t::value_type &entry, newRevisions) { const std::string &mainid = entry.first; const SubRevisionEntry &ids = entry.second; SubRevisionMap_t::iterator it = m_revisions.find(entry.first); if (it == m_revisions.end()) { // all sub-items are added BOOST_FOREACH(const std::string &subid, ids.m_subids) { addItem(createLUID(mainid, subid), NEW); } } else if (it->second.m_revision != ids.m_revision) { // merged item was modified, some of its sub-items // might have been removed... BOOST_FOREACH(const std::string &subid, it->second.m_subids) { if (ids.m_subids.find(subid) == ids.m_subids.end()) { addItem(createLUID(mainid, subid), DELETED); } } // ... or added/modified BOOST_FOREACH(const std::string &subid, ids.m_subids) { if (it->second.m_subids.find(subid) == it->second.m_subids.end()){ addItem(createLUID(mainid, subid), NEW); } else { addItem(createLUID(mainid, subid), UPDATED); } } } } // continue with up-to-date list m_revisions = newRevisions; break; } case SyncSourceRevisions::CHANGES_SLOW: // replace with current list, don't bother about finding changes m_revisions.clear(); m_sub->listAllSubItems(m_revisions); break; default: // ?! throwError("unknown change mode"); break; } // always set the full list of luids in SyncSourceChanges BOOST_FOREACH(const SubRevisionMap_t::value_type &entry, m_revisions) { const std::string &mainid = entry.first; const SubRevisionEntry &ids = entry.second; BOOST_FOREACH(const std::string &subid, ids.m_subids) { addItem(createLUID(mainid, subid)); } } } void MapSyncSource::beginSync(const std::string &lastToken, const std::string &resumeToken) { m_sub->begin(); // TODO: copy-and-pasted from TrackingSyncSource::beginSync() - refactor! // vvvvvvvvvvvvvvvvv // use the most reliable (and most expensive) method by default SyncSourceRevisions::ChangeMode mode = SyncSourceRevisions::CHANGES_FULL; // resume token overrides the normal token; safe to ignore in most // cases and this detectChanges() is done independently of the // token, but let's do it right here anyway string token; if (!resumeToken.empty()) { token = resumeToken; } else { token = lastToken; } // slow sync if token is empty if (token.empty()) { SE_LOG_DEBUG(getDisplayName(), "slow sync or testing, do full item scan to detect changes"); mode = SyncSourceRevisions::CHANGES_SLOW; } else { string oldRevision = m_metaNode->readProperty("databaseRevision"); if (!oldRevision.empty()) { string newRevision = m_sub->subDatabaseRevision(); SE_LOG_DEBUG(getDisplayName(), "old database revision '%s', new revision '%s'", oldRevision.c_str(), newRevision.c_str()); if (newRevision == oldRevision) { SE_LOG_DEBUG(getDisplayName(), "revisions match, no item changes"); mode = SyncSourceRevisions::CHANGES_NONE; } // Reset old revision. If anything goes wrong, then we // don't want to rely on a possibly incorrect optimization. m_metaNode->setProperty("databaseRevision", ""); m_metaNode->flush(); } } if (mode == SyncSourceRevisions::CHANGES_FULL) { SE_LOG_DEBUG(getDisplayName(), "using full item scan to detect changes"); } detectChanges(mode); // TODO: refactor ^^^^^^^^^^^^^^^^^^^^^^^^ } std::string MapSyncSource::endSync(bool success) { m_sub->endSubSync(success); // TODO: refactor vvvvvvvvvvvvvvvv if (success) { string updatedRevision = m_sub->subDatabaseRevision(); m_metaNode->setProperty("databaseRevision", updatedRevision); // This part is different from TrackingSyncSource: our luid/rev information // is in m_revisions and only gets dumped into m_trackingNode at the very end here. m_trackingNode->clear(); BOOST_FOREACH(const SubRevisionMap_t::value_type &entry, m_revisions) { const std::string &mainid = entry.first; const SubRevisionEntry &ids = entry.second; std::stringstream buffer; buffer << '/' << m_escape.escape(ids.m_revision) << '/'; buffer << m_escape.escape(ids.m_uid) << '/'; BOOST_FOREACH(const std::string &subid, ids.m_subids) { buffer << m_escape.escape(subid) << '/'; } m_trackingNode->setProperty(mainid, buffer.str()); } // flush both nodes, just in case; in practice, the properties // end up in the same file and only get flushed once m_trackingNode->flush(); m_metaNode->flush(); } else { // The Synthesis docs say that we should rollback in case of // failure. Cannot do that for data, so lets at least keep // the revision map unchanged. } // no token handling at the moment (not needed for clients): // return a non-empty token to distinguish an incremental // sync from a slow sync in beginSync() return "1"; // TODO: refactor ^^^^^^^^^^^^^^^^^^^ } SyncSourceRaw::InsertItemResult MapSyncSource::insertItem(const std::string &luid, const std::string &item) { StringPair ids = splitLUID(luid); SubSyncSource::SubItemResult res = m_sub->insertSubItem(ids.first, ids.second, item); // anything changed? if (res.m_state != ITEM_NEEDS_MERGE) { SubRevisionEntry &entry = m_revisions[res.m_mainid]; entry.m_uid = res.m_uid; entry.m_revision = res.m_revision; entry.m_subids.insert(res.m_subid); } return SyncSourceRaw::InsertItemResult(createLUID(res.m_mainid, res.m_subid), res.m_revision, res.m_state); } void MapSyncSource::readItem(const std::string &luid, std::string &item) { StringPair ids = splitLUID(luid); m_sub->readSubItem(ids.first, ids.second, item); } void MapSyncSource::deleteItem(const string &luid) { StringPair ids = splitLUID(luid); std::string rev = m_sub->removeSubItem(ids.first, ids.second); SubRevisionMap_t::iterator it = m_revisions.find(ids.first); if (it != m_revisions.end()) { it->second.m_subids.erase(ids.second); if (it->second.m_subids.empty()) { // last sub item removed, remove merged item m_revisions.erase(it); } else { // still some sub items, update revision in merged item it->second.m_revision = rev; } } } std::string MapSyncSource::getDescription(const string &luid) { StringPair ids = splitLUID(luid); return m_sub->getSubDescription(ids.first, ids.second); } std::string MapSyncSource::createLUID(const std::string &uid, const std::string &subid) { std::string luid = m_escape.escape(uid); if (!subid.empty()) { luid += '/'; luid += m_escape.escape(subid); } return luid; } std::pair MapSyncSource::splitLUID(const std::string &luid) { size_t index = luid.find('/'); if (index != luid.npos) { return make_pair(m_escape.unescape(luid.substr(0, index)), m_escape.unescape(luid.substr(index + 1))); } else { return make_pair(m_escape.unescape(luid), ""); } } StringEscape MapSyncSource::m_escape('%', "/"); void MapSyncSource::removeAllItems() { BOOST_FOREACH(const SubRevisionMap_t::value_type &entry, m_revisions) { m_sub->removeMergedItem(entry.first); } m_revisions.clear(); } SE_END_CXX syncevolution_1.4/src/syncevo/MapSyncSource.h000066400000000000000000000311101230021373600215330ustar00rootroot00000000000000/* * Copyright (C) 2010 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_MAPSYNCSOURCE #define INCL_MAPSYNCSOURCE #include #include #include SE_BEGIN_CXX class MapSyncSource; /** * rev + uid + list of subid; mainid is part of the context */ struct SubRevisionEntry { std::string m_revision; std::string m_uid; set m_subids; }; /** * mainid to rev + uid + list of subid * * List must contain an empty entry for the main item, if and only * if one exists. */ typedef map SubRevisionMap_t; /** * This is the API that must be implemented in addition to * TrackingSyncSource and SyncSourceLogging by a source to * be wrapped by MapSyncSource. * * The original interface will only be used in "raw" mode, which * should bypass any kind of cache used by the implementation. * They are guaranteed to be passed merged items. * * The new methods with mainid and subid are using during a sync * and should use the cache. They work on single items but modify * merged items. Thus the revision string of all sub items in * the same merged item will get modified when manipulating * one of its sub items. */ class SubSyncSource : virtual public SyncSourceBase { public: class SubItemResult { public: SubItemResult() : m_state(ITEM_OKAY) {} /** * @param mainid the ID used to access a set of items; may be different * from a (iCalendar 2.0) UID; during an update the mainid must * not be changed, so return the original one here * @param subid optional subid, same rules as for mainid * @param revision the revision string of the merged item after the operation; leave empty if not used * @param uid an arbitrary string, stored, but not used by MapSyncSource; * used in the CalDAV backend to associate mainid (= resource path) * with UID (= part of the item content, but with special semantic) * @param state report about what was done with the data */ SubItemResult(const string &mainid, const string &subid, const string &revision, const string &uid, InsertItemResultState state) : m_mainid(mainid), m_subid(subid), m_revision(revision), m_uid(uid), m_state(state) {} string m_mainid; string m_subid; string m_revision; string m_uid; InsertItemResultState m_state; }; SubSyncSource() : m_parent(NULL) {} /** * tells SubSyncSource about MapSyncSource which wraps it, * for getSynthesisAPI() */ void setParent(MapSyncSource *parent) { m_parent = parent; } MapSyncSource *getParent() const { return m_parent; } virtual SDKInterface *getSynthesisAPI() const; /** called after open() and before any of the following methods */ virtual void begin() = 0; /** called after a sync */ virtual void endSubSync(bool success) = 0; /** * A unique identifier for the current state of the complete database. * The semantic is the following: * - empty string implies "state unknown" or "identifier not supported" * - id not empty and ID1 == ID2 implies "nothing has changed"; * the inverse is not true (ids may be different although nothing has changed) * * Matches TrackingSyncSource::databaseRevision(). */ virtual std::string subDatabaseRevision() { return ""; } /** * Either listAllSubItems(), setAllSubItems(), or updateAllSubitems() * will be called after begin(). * * In the first case, the sub source is expected to provide a full list * of its items. In the second case, the caller was able to determine * that its cached copy of that list is still correct and provides it * the the source. In the third case, some revision information is know, * but it may be obsolete (revision string and/or subids changed or removed) * or incomplete (new items missing). The callee then must update the * information, possibly by falling back to listAllSubItems(). */ virtual void listAllSubItems(SubRevisionMap_t &revisions) = 0; /** * Called instead of listAllSubItems(). */ virtual void updateAllSubItems(SubRevisionMap_t &revisions) { revisions.clear(); listAllSubItems(revisions); } /** * Called instead of listAllSubItems(). */ virtual void setAllSubItems(const SubRevisionMap_t &revisions) = 0; virtual SubItemResult insertSubItem(const std::string &mainid, const std::string &subid, const std::string &item) = 0; virtual void readSubItem(const std::string &mainid, const std::string &subid, std::string &item) = 0; /** * Ensure that the sub-item does not exist. It is not an error to be called * for a non-existent sub-item or item. * * @return empty string if item is empty after removal, otherwise new revision string */ virtual std::string removeSubItem(const string &mainid, const std::string &subid) = 0; /** * Remove all sub-items belonging to mainid. */ virtual void removeMergedItem(const std::string &mainid) = 0; /** * Called whenever this class thinks that the item may no longer be * needed. Might be wrong... */ virtual void flushItem(const string &mainid) = 0; /** * Describe sub-item. Might be called for item which does not exist and * must not throw an error in that case. Providing a description is optional * and should only be done when it is reasonably cheap. */ virtual std::string getSubDescription(const string &mainid, const string &subid) = 0; /** * Called after MapSyncSource already populated the info structure. */ virtual void updateSynthesisInfo(SynthesisInfo &info, XMLConfigFragments &fragments) {} private: MapSyncSource *m_parent; }; /** * This class wraps an underlying SyncSource and maps each of the * underlying items into one or more items in this class. The main use case * are CalDAV and Exchange Web Services, where each item is a set * of all events with the same UID, whereas SyncEvolution and SyncML * treat each individual event as one item. * * Terminology: * - single item = item as presented by this class (VEVENT) * - merged item = combination of all items sharing the same luid/uid (VCALENDAR) * - luid = SyncEvolution locally unique ID (VEVENT), mapped to mainid+subid * - mainid = ID for accessing the set of items (WebDAV resource path) * - subid = unique ID (RECURRENCE-ID) for sub-items (VEVENT) inside underlying item (VCALENDAR) * - uid = another unique ID shared by underlying items (iCalendar 2.0 UID), * not used by this class * * "luid" is composed from "mainid" and "subid" by backslash-escaping a * slash (/), then concatenating the results with a slash as * separator. If the subid is empty, no slash is added. The main * advantage of this scheme is that it works well with sub sources * which use URI escaping. * * This class expects an extended TrackingSyncSource (= * SubSyncSource) merely because that interface is the most convenient * to work with. If necessary, the logic may be refactored later on. * * This class uses much of the same infrastructure as the TrackingSyncSource, * except for change detection. This used to be done with TrackingSyncSource * as base class, but there are some key differences which made this very * awkward, primarily the fact that all merged items share the same revision * string. Now the tracking node is used to store one entry per merged item, in * the format "ref- = ////..." * * The following rules apply: * - A single item is added if its luid is new, updated if it exists and * the merged item's revision string is different, deleted if the luid is * gone (same logic as in normal TrackingSyncSource). * - A mainid is assigned to a new merged item by creating the merged item. * - Changes for an existing merged item may be applied to a cache, * which is explicitly flushed by this class. This implies that * such local changes must keep the mainid stable and have control * over the subid. * - Item logging is offered by this class (LoggingSyncSource), but * entirely depends on the sub source to implement the functionality. */ class MapSyncSource : public TestingSyncSource, // == SyncSourceSession, SyncSourceChanges, SyncSourceDelete, SyncSourceSerialize virtual public SyncSourceAdmin, virtual public SyncSourceBlob, virtual public SyncSourceLogging { public: /** * @param sub must also implement TrackingSyncSource and SyncSourceLogging interfaces! */ MapSyncSource(const SyncSourceParams ¶ms, const boost::shared_ptr &sub); ~MapSyncSource() {} /** compose luid from mainid and subid */ static std::string createLUID(const std::string &mainid, const std::string &subid); /** split luid into mainid (first) and subid (second) */ static StringPair splitLUID(const std::string &luid); virtual void enableServerMode(); virtual bool serverModeEnabled() const; virtual std::string getPeerMimeType() const { return getMimeType(); } virtual Databases getDatabases() { return dynamic_cast(*m_sub).getDatabases(); } virtual void open() { dynamic_cast(*m_sub).open(); } virtual void beginSync(const std::string &lastToken, const std::string &resumeToken); virtual std::string endSync(bool success); virtual bool isEmpty() { return dynamic_cast(*m_sub).getOperations().m_isEmpty(); } virtual InsertItemResult insertItem(const std::string &luid, const std::string &item); virtual void readItem(const std::string &luid, std::string &item); virtual void deleteItem(const string &luid); virtual void close() { dynamic_cast(*m_sub).close(); } virtual std::string getMimeType() const { return dynamic_cast(*m_sub).getMimeType(); } virtual std::string getMimeVersion() const { return dynamic_cast(*m_sub).getMimeVersion(); } virtual std::string getDescription(sysync::KeyH aItemKey) { return dynamic_cast(*m_sub).getDescription(aItemKey); } virtual std::string getDescription(const string &luid); /* TestingSyncSource */ virtual void removeAllItems(); protected: virtual void getSynthesisInfo(SynthesisInfo &info, XMLConfigFragments &fragments) { TestingSyncSource::getSynthesisInfo(info, fragments); m_sub->updateSynthesisInfo(info, fragments); } private: boost::shared_ptr m_sub; /** escape / in uid with %2F, so that splitMainIDValue() and splitLUID() can use / as separator */ static StringEscape m_escape; std::string m_oldLUID; /** * information about the current set of items: * initialized as part of beginSync(), * updated as items are modified, * stored in endSync() */ SubRevisionMap_t m_revisions; /** on-disk representation of m_revisions */ boost::shared_ptr m_trackingNode; /** * Stores meta information besides the item list: * - "databaseRevision" = result of databaseRevision() at end of last sync * * Shares the same key/value store as m_trackingNode, * which uses the "item-" prefix in its keys to * avoid name clashes. */ boost::shared_ptr m_metaNode; /** mirrors SyncSourceRevisions::detectChanges() */ void detectChanges(SyncSourceRevisions::ChangeMode mode); }; SE_END_CXX #endif // INCL_MAPSYNCSOURCE syncevolution_1.4/src/syncevo/MultiplexConfigNode.cpp000066400000000000000000000104271230021373600232620ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include SE_BEGIN_CXX FilterConfigNode * MultiplexConfigNode::getNode(const std::string &property, const ConfigProperty **found) const { BOOST_FOREACH(const ConfigProperty *prop, m_registry) { bool match = false; BOOST_FOREACH(const std::string &name, prop->getNames()) { if (name == property) { match = true; break; } } if (match) { if (found) { *found = prop; } FilterConfigNode *node = m_nodes[prop->isHidden()][prop->getSharing()].get(); return node; } } return NULL; } void MultiplexConfigNode::addFilter(const std::string &property, const InitStateString &value) { FilterConfigNode::addFilter(property, value); for (int i = 0; i < 2; i++) { for (int e = 0; e < 3; e++) { if (m_nodes[i][e]) { m_nodes[i][e]->addFilter(property, value); } } } } void MultiplexConfigNode::setFilter(const ConfigFilter &filter) { FilterConfigNode::setFilter(filter); for (int i = 0; i < 2; i++) { for (int e = 0; e < 3; e++) { if (m_nodes[i][e]) { m_nodes[i][e]->setFilter(filter); } } } } void MultiplexConfigNode::flush() { for (int i = 0; i < 2; i++) { for (int e = 0; e < 3; e++) { if (m_nodes[i][e]) { m_nodes[i][e]->flush(); } } } } InitStateString MultiplexConfigNode::readProperty(const std::string &property) const { FilterConfigNode *node = getNode(property); if (node) { return node->readProperty(property); } else { return InitStateString(); } } void MultiplexConfigNode::writeProperty(const std::string &property, const InitStateString &value, const std::string &comment) { const ConfigProperty *prop; FilterConfigNode *node = getNode(property, &prop); if (node) { node->writeProperty(property, value, comment); } else { SE_THROW(property + ": not supported by configuration multiplexer"); } } void MultiplexConfigNode::readProperties(PropsType &props) const { for (int i = 0; i < 2; i++) { for (int e = 0; e < 3; e++) { if (m_nodes[i][e]) { m_nodes[i][e]->readProperties(props); } } } } void MultiplexConfigNode::removeProperty(const std::string &property) { #if 1 SE_THROW(property + ": removing via configuration multiplexer not supported"); #else for (int i = 0; i < 2; i++) { for (int e = 0; e < 3; e++) { if (m_nodes[i][e]) { m_nodes[i][e]->removeProperty(property); } } } #endif } void MultiplexConfigNode::clear() { #if 1 SE_THROW("configuration multiplexer cannot be cleared"); #else for (int i = 0; i < 2; i++) { for (int e = 0; e < 3; e++) { if (m_nodes[i][e]) { m_nodes[i][e]->clear(); } } } #endif } bool MultiplexConfigNode::exists() const { for (int i = 0; i < 2; i++) { for (int e = 0; e < 3; e++) { if (m_nodes[i][e] && m_nodes[i][e]->exists()) { return true; } } } return false; } SE_END_CXX syncevolution_1.4/src/syncevo/MultiplexConfigNode.h000066400000000000000000000102431230021373600227230ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_SYNCEVO_MULTIPLEX_CONFIG_NODE # define INCL_SYNCEVO_MULTIPLEX_CONFIG_NODE #include #include #include SE_BEGIN_CXX /** * Joins properties from the different nodes that might be used by a * SyncConfig or SyncSourceConfig (global/shared/not shared, * hidden/user-visible) and presents them as one node. Reading takes * the union of all set properties. Writing is directed to the * node for which the property was registered. */ class MultiplexConfigNode : public FilterConfigNode { const std::string m_name; boost::shared_ptr m_nodes[2][3]; const ConfigPropertyRegistry &m_registry; bool m_havePeerNodes; int m_hiddenLower, m_hiddenUpper; FilterConfigNode *getNode(const std::string &property, const ConfigProperty **prop = NULL) const; public: /** join both hidden and user-visible properties */ MultiplexConfigNode(const std::string &name, const ConfigPropertyRegistry ®istry) : FilterConfigNode(boost::shared_ptr()), m_name(name), m_registry(registry), m_havePeerNodes(true), m_hiddenLower(0), m_hiddenUpper(1) {} /** only join hidden or user-visible properties */ MultiplexConfigNode(const std::string &name, const ConfigPropertyRegistry ®istry, bool hidden) : FilterConfigNode(boost::shared_ptr()), m_name(name), m_registry(registry), m_havePeerNodes(true), m_hiddenLower(hidden), m_hiddenUpper(hidden) {} /** true when peer nodes are used (default), false when they are dummy nodes */ bool getHavePeerNodes() const { return m_havePeerNodes; } void setHavePeerNodes(bool havePeerNodes) { m_havePeerNodes = havePeerNodes; } /** configure the nodes to use */ void setNode(bool hidden, ConfigProperty::Sharing sharing, const boost::shared_ptr &node) { m_nodes[hidden][sharing] = node; } void setNode(bool hidden, ConfigProperty::Sharing sharing, const boost::shared_ptr &node) { m_nodes[hidden][sharing].reset(new FilterConfigNode(node)); } virtual void addFilter(const std::string &property, const InitStateString &value); virtual void setFilter(const ConfigFilter &filter); virtual std::string getName() const { return m_name; } virtual bool isVolatile() const { return false; } // it is safe to pretend that it is not volatile, even if some or even all of its sub-nodes are virtual void flush(); virtual InitStateString readProperty(const std::string &property) const; virtual void writeProperty(const std::string &property, const InitStateString &value, const std::string &comment = std::string("")); virtual void readProperties(PropsType &props) const; /* * removing or clearing something is not implemented because it is * not certain what should be deleted: only properties which are * not shared?! */ virtual void removeProperty(const std::string &property); virtual void clear(); /** * true if any of the nodes exists */ virtual bool exists() const; }; SE_END_CXX #endif // INCL_SYNCEVO_MULTIPLEX_CONFIG_NODE syncevolution_1.4/src/syncevo/ObexTransportAgent.cpp000066400000000000000000000720251230021373600231360ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #ifdef ENABLE_OBEX #ifdef ENABLE_BLUETOOTH #include #include #include #include #endif #include #include #include #include #include #include #include #include #include SE_BEGIN_CXX ObexTransportAgent::ObexTransportAgent (OBEX_TRANS_TYPE type, GMainLoop *loop) : m_status(INACTIVE), m_transType(type), m_context(g_main_context_ref(loop ? g_main_loop_get_context(loop) : g_main_context_default())), m_address(""), m_port(-1), m_buffer(NULL), m_bufferSize(0), m_timeoutSeconds(0), m_disconnecting(false), m_connectStatus(START) { } ObexTransportAgent::~ObexTransportAgent() { } /* * Only setURL if the address/port has not been initialized; * because the URL is not likely be changed during a * sync session. * For Bluetooth devices the URL maybe splitted as two parts: * address and channel, the delimiter is '+'. **/ void ObexTransportAgent::setURL(const std::string &url) { if(m_address.empty()){ if(m_transType == OBEX_BLUETOOTH) { size_t pos = url.find_last_of('+'); if (pos != std::string::npos) { m_address = url.substr(0,pos); m_port = atoi(url.substr(pos+1, std::string::npos).c_str()); if(!m_port) { SE_THROW_EXCEPTION(TransportException, "ObexTransport: Malformed url"); } } else { m_address = url; } if(m_address.empty()) { SE_THROW_EXCEPTION (TransportException, "ObexTransport: Malformed url"); } } } } void ObexTransportAgent::setContentType(const std::string &type) { m_contentType = type; } void ObexTransportAgent::setTimeout(int seconds) { m_timeoutSeconds = seconds; } void ObexTransportAgent::connect() { m_obexReady = false; if(m_transType == OBEX_BLUETOOTH) { if(m_port == -1) { EDSAbiWrapperInit(); // sdp_connect may be a pointer when EVOLUTION_COMPATIBILITY is enabled. // Must check whether we really have an implementation of the sdp_ calls // before using them. if (!SyncEvoHaveLibbluetooth) { SE_THROW_EXCEPTION (TransportException, "no suitable libbluetooth found, try setting Bluetooth channel manually (obex-bt://+)"); } //use sdp to detect the appropriate channel //Do not use BDADDR_ANY to avoid a warning bdaddr_t bdaddr, anyaddr ={{0,0,0,0,0,0}}; str2ba (m_address.c_str(), &bdaddr); m_sdp.set(sdp_connect(&anyaddr, &bdaddr, SDP_NON_BLOCKING), "ObexTransport Bluetooth sdp connect failed"); m_connectStatus = SDP_START; GIOChannelPtr sdpIO(g_io_channel_unix_new(sdp_get_socket(m_sdp)), "sdp socket channel"); m_sdpEvent.set(g_io_add_watch(sdpIO, (GIOCondition) (G_IO_IN|G_IO_OUT|G_IO_HUP|G_IO_ERR|G_IO_NVAL), sdp_source_cb, static_cast (this)), "IO watch for SDP"); } else { m_connectStatus = ADDR_READY; connectInit(); } } /*Wait until connection is sucessfully set up*/ wait(true); if (m_connectStatus != CONNECTED) { SE_THROW_EXCEPTION (TransportException, "ObexTransport: connection setup failed"); } } /* * Called when the address of remote peer is available, maybe via some * discovery mechanism. */ void ObexTransportAgent::connectInit () { if (m_connectStatus != ADDR_READY) { SE_THROW_EXCEPTION (TransportException, "ObexTransport: address info for remote peer not ready"); } if(m_transType == OBEX_BLUETOOTH) { bdaddr_t bdaddr; str2ba (m_address.c_str(), &bdaddr); if(m_port == -1) { SE_THROW_EXCEPTION (TransportException, "ObexTransport: no channel found for Bluetooth peer"); } int sockfd = socket (AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); if (sockfd == -1) { SE_THROW_EXCEPTION (TransportException, "Error creating Bluetooth socket"); } cxxptr sockObj (new Socket (sockfd)); SE_LOG_DEV(NULL, "Connecting Bluetooth device with address %s and channel %d", m_address.c_str(), m_port); // Init the OBEX handle ObexPtr handle(OBEX_Init (OBEX_TRANS_FD, obex_event, 0), "Obex Handle"); OBEX_SetUserData (handle, static_cast (this)); // Connect the device, do not use BtOBEX_TransportConnect as it is // blocking. sockaddr_rc any; bdaddr_t anyaddr ={{0,0,0,0,0,0}}; memset(&any, 0, sizeof(any)); any.rc_family = AF_BLUETOOTH; bacpy (&any.rc_bdaddr, &anyaddr); any.rc_channel = 0; if (bind (sockfd, (struct sockaddr *) &any, sizeof (sockaddr_rc)) <0){ SE_THROW_EXCEPTION (TransportException, "ObexTransport: Socket bind failed"); } //set socket to non-blocking int flags = fcntl (sockfd, F_GETFL); fcntl(sockfd, F_SETFL, flags & ~O_NONBLOCK); //create the io channel GIOChannelPtr channel(g_io_channel_unix_new (sockfd), "socket io channel"); GLibEvent obexEvent(g_io_add_watch (channel, (GIOCondition) (G_IO_IN|G_IO_OUT|G_IO_HUP|G_IO_ERR|G_IO_NVAL), obex_fd_source_cb, static_cast (this)), "socket io channel watch"); //connect to remote device sockaddr_rc peer; memset(&peer, 0, sizeof(peer)); peer.rc_family = AF_BLUETOOTH; bacpy (&peer.rc_bdaddr , &bdaddr); peer.rc_channel = m_port; if (::connect (sockfd, (struct sockaddr *) &peer, sizeof (sockaddr_rc)) == -1) { //check the error status if (errno == EINPROGRESS || errno == EAGAIN){ m_connectStatus = INIT0; //copy sockobj so that it will not close the underlying socket m_sock = sockObj; m_obexEvent = obexEvent; m_channel = channel; m_handle = handle; return; } else { SE_LOG_ERROR(NULL, "connect failed with error code %d", errno); SE_THROW_EXCEPTION (TransportException, "ObexTransport: connect request failed with error"); } } // Put Connect Cmd to init the session m_connectStatus = INIT1; m_sock = sockObj; m_obexEvent = obexEvent; m_channel = channel; m_handle = handle; connectReq(); }//Bluetooth else { m_status = FAILED; SE_LOG_ERROR(NULL, "ObexTransport: unsuported transport type"); return; } } /* After OBEX Handle is inited and the device is connected, * send the connect cmd to initialize the session */ void ObexTransportAgent::connectReq() { if (m_connectStatus!=INIT1 || !m_handle) { SE_THROW_EXCEPTION (TransportException, "ObexTransport: OBEX Handle not inited or device not connected"); } //set up transport mtu OBEX_SetTransportMTU (m_handle, DEFAULT_RX_MTU, DEFAULT_TX_MTU); // set up the fd transport if (FdOBEX_TransportSetup (m_handle, m_sock->get(), m_sock->get(), OBEX_MAXIMUM_MTU) <0) { SE_THROW_EXCEPTION (TransportException, "ObexTransport: Fd transport set up failed"); } obex_object_t *connect = newCmd(OBEX_CMD_CONNECT); /* Add the header for the sync target */ obex_headerdata_t header; header.bs = (unsigned char *) "SYNCML-SYNC"; OBEX_ObjectAddHeader(m_handle, connect, OBEX_HDR_TARGET, header, strlen ((char *) header.bs), OBEX_FL_FIT_ONE_PACKET); m_obexReady = false; m_requestStart = time (NULL); if (OBEX_Request (m_handle, connect) <0) { SE_THROW_EXCEPTION (TransportException, "ObexTransport: OBEX connect init failed"); } m_connectStatus = INIT2; } void ObexTransportAgent::shutdown() { //reset up obex fd soruce GLibEvent obexEventSource; if (m_channel) { obexEventSource.set(g_io_add_watch(m_channel, (GIOCondition) (G_IO_IN|G_IO_OUT|G_IO_HUP|G_IO_ERR|G_IO_NVAL), obex_fd_source_cb, static_cast (this)), "OBEX shutdown event"); } if (m_handle) { /* It might be true there is an ongoing OBEX request undergoing, * must cancel it before sending another cmd */ OBEX_CancelRequest (m_handle, 0); } //block a while waiting for the disconnect response, we will disconnects //always even without a response. m_obexReady = false; if (!m_disconnecting) { m_disconnecting = true; if (m_handle) { obex_object_t *disconnect = newCmd(OBEX_CMD_DISCONNECT); //add header "connection id" obex_headerdata_t header; header.bq4 = m_connectId; OBEX_ObjectAddHeader (m_handle, disconnect, OBEX_HDR_CONNECTION, header, sizeof (m_connectId), OBEX_FL_FIT_ONE_PACKET); if (OBEX_Request (m_handle, disconnect) <0) { m_status = FAILED; SE_THROW_EXCEPTION (TransportException, "ObexTransport: OBEX disconnect cmd failed"); } } } m_obexEvent = obexEventSource; } /** * Send the request to peer */ void ObexTransportAgent::send(const char *data, size_t len) { SE_LOG_DEV(NULL, "ObexTransport send is called"); cxxptr sockObj = m_sock; GIOChannelPtr channel = m_channel; if(m_connectStatus != CONNECTED) { SE_THROW_EXCEPTION (TransportException, "ObexTransport send: underlying transport is not conncted"); } obex_object_t *put = newCmd(OBEX_CMD_PUT); //add header "connection id" obex_headerdata_t header; header.bq4 = m_connectId; OBEX_ObjectAddHeader (m_handle, put, OBEX_HDR_CONNECTION, header, sizeof (m_connectId), OBEX_FL_FIT_ONE_PACKET); //add header "target" header.bs = reinterpret_cast (const_cast (m_contentType.c_str())); OBEX_ObjectAddHeader (m_handle, put, OBEX_HDR_TYPE, header, m_contentType.size()+1, 0); //add header "length" header.bq4 = len; OBEX_ObjectAddHeader (m_handle, put, OBEX_HDR_LENGTH, header, sizeof (size_t), 0); //add header "body" header.bs = reinterpret_cast (data); OBEX_ObjectAddHeader (m_handle, put, OBEX_HDR_BODY, header, len, 0); //reset up the OBEX fd source GLibEvent obexEventSource(g_io_add_watch (channel, (GIOCondition) (G_IO_IN|G_IO_OUT|G_IO_HUP|G_IO_ERR|G_IO_NVAL), obex_fd_source_cb, static_cast (this)), "OBEX fd event"); //send the request m_status = ACTIVE; m_requestStart = time (NULL); m_obexReady = false; if (OBEX_Request (m_handle, put) < 0) { SE_THROW_EXCEPTION (TransportException, "ObexTransport: send failed"); } m_sock = sockObj; m_channel = channel; m_obexEvent = obexEventSource; } /* * Abort the transport session; don't wait for anything. */ void ObexTransportAgent::cancel() { m_requestStart = 0; m_connectStatus = END; if (m_handle) { OBEX_TransportDisconnect(m_handle); } if (m_disconnecting) { SE_LOG_WARNING(NULL, "Cancel disconncting process"); if (m_status != CLOSED) { m_status = FAILED; } } else { // called during normal operations; // set m_disconnecting like the abort handler // did before it was removed m_disconnecting = true; m_status = CANCELED; // remove the event source from the agent before calling shutdown(), // will be freed when this function returns GLibEvent obexEventSource = m_obexEvent; shutdown(); } } /** * 1)Wait until the connection is set up. * 2)Wait until the response is ready, which means: * waits for the Put request being successfully sent * sends the Get request to pull response * waits until Get response is successfully received * * Runs the main loop manually so that it does not block other components. */ TransportAgent::Status ObexTransportAgent::wait(bool noReply) { while (!m_obexReady) { g_main_context_iteration (m_context, TRUE); if (m_status == FAILED || m_status == CANCELED) { m_obexEvent.set(0); m_sock.set(0); m_channel.set(0); if (m_status == FAILED) { SE_THROW_EXCEPTION(TransportException, "OBEX/Bluetooth transport error or problem on the peer"); } else { SE_THROW_EXCEPTION_STATUS(StatusException, "transport aborted", SyncMLStatus(sysync::LOCERR_USERABORT)); } } } //remove the obex event source here //only at this point we can be sure obexEvent is propertely set up cxxptr sockObj = m_sock; GLibEvent obexEvent = m_obexEvent; GIOChannelPtr channel = m_channel; if (!noReply) { //send the Get request obex_object_t *get = newCmd(OBEX_CMD_GET); //add header "connection id" obex_headerdata_t header; header.bq4 = m_connectId; OBEX_ObjectAddHeader (m_handle, get, OBEX_HDR_CONNECTION, header, sizeof (m_connectId), OBEX_FL_FIT_ONE_PACKET); //add header "target" header.bs = reinterpret_cast (m_contentType.c_str()); OBEX_ObjectAddHeader (m_handle, get, OBEX_HDR_TYPE, header, m_contentType.size()+1, 0); //send the request m_obexReady = false; if (OBEX_Request (m_handle, get) < 0) { SE_THROW_EXCEPTION (TransportException, "ObexTransport: get failed"); } while (!m_obexReady) { g_main_context_iteration (m_context, TRUE); if (m_status == FAILED) { SE_THROW_EXCEPTION (TransportException, "ObexTransprotAgent: Underlying transport error"); } } } if(m_status != CLOSED) { m_sock = sockObj; m_channel = channel; } return m_status; } /** * read the response from the buffer */ void ObexTransportAgent::getReply(const char *&data, size_t &len, std::string &contentType){ cxxptr sockObj = m_sock; GIOChannelPtr channel = m_channel; if (m_status != GOT_REPLY || !m_buffer || !m_bufferSize) { SE_THROW_EXCEPTION (TransportException, ""); } data = m_buffer; len = m_bufferSize; // There is no content type sent back from the peer according to the spec contentType = ""; m_sock = sockObj; m_channel = channel; } gboolean ObexTransportAgent::sdp_source_cb (GIOChannel *io, GIOCondition cond, void *udata) { return static_cast (udata) -> sdp_source_cb_impl(io, cond); } gboolean ObexTransportAgent::obex_fd_source_cb (GIOChannel *io, GIOCondition cond, void *udata) { return static_cast (udata) -> obex_fd_source_cb_impl(io, cond); } void ObexTransportAgent::sdp_callback (uint8_t type, uint16_t status, uint8_t *rsp, size_t size, void *udata) { return static_cast (udata) -> sdp_callback_impl(type, status, rsp, size); } void ObexTransportAgent::obex_event (obex_t *handle, obex_object_t *object, int mode, int event, int obex_cmd, int obex_rsp){ (static_cast (OBEX_GetUserData (handle))) ->obex_callback(object, mode, event, obex_cmd, obex_rsp); } gboolean ObexTransportAgent::sdp_source_cb_impl (GIOChannel *io, GIOCondition cond) { try { if(cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)){ SE_THROW_EXCEPTION (TransportException, "SDP connection end unexpectedly"); } else if ((cond &G_IO_OUT) && m_connectStatus == SDP_START) { m_connectStatus = SDP_REQ; sdp_set_notify (m_sdp, sdp_callback, static_cast (this)); const unsigned char syncml_client_uuid[] = { 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x02, 0xEE, 0x00, 0x00, 0x02}; uint32_t range = 0x0000ffff; uuid_t uuid; sdp_list_t *services, *attrs; sdp_uuid128_create(&uuid, syncml_client_uuid); services = sdp_list_append(NULL, &uuid); attrs = sdp_list_append(NULL, &range); if (sdp_service_search_attr_async(m_sdp, services, SDP_ATTR_REQ_RANGE, attrs) < 0) { sdp_list_free (attrs, NULL); sdp_list_free (services, NULL); SE_THROW_EXCEPTION(TransportException, "ObexTransport: Bluetooth sdp service search failed"); } sdp_list_free(attrs, NULL); sdp_list_free(services, NULL); return TRUE; } else if ((cond &G_IO_IN) && m_connectStatus == SDP_REQ) { sdp_process (m_sdp); m_sdp.set(0); return FALSE; } else { return TRUE; } } catch (...) { handleException("sdp_source_cb"); } m_sdp.set(0); return FALSE; } void ObexTransportAgent::sdp_callback_impl (uint8_t type, uint16_t status, uint8_t *rsp, size_t size) { size_t scanned; int bufSize = size; int channel = -1; try { m_connectStatus = SDP_DONE; if (status || type != SDP_SVC_SEARCH_ATTR_RSP) { SE_THROW_EXCEPTION (TransportException, "ObexTransportAgent: Bluetooth service search failed"); } int seqSize = 0; uint8_t dtdp; #if defined(HAVE_BLUEZ_SAFE) || defined(EVOLUTION_COMPATIBILITY) scanned = sdp_extract_seqtype_safe(rsp, bufSize, &dtdp, &seqSize); #elif defined(HAVE_BLUEZ_BUFSIZE) scanned = sdp_extract_seqtype(rsp, bufSize, &dtdp, &seqSize); #else scanned = sdp_extract_seqtype(rsp, &dtdp, &seqSize); #endif if (!scanned || !seqSize) { SE_THROW_EXCEPTION (TransportException, "ObexTransportAgent: Bluetooth service search failed"); } rsp += scanned; bufSize -= scanned; do { sdp_record_t *rec; sdp_list_t *protos; int recSize; recSize = 0; #if defined(HAVE_BLUEZ_SAFE) || defined(EVOLUTION_COMPATIBILITY) rec = sdp_extract_pdu_safe(rsp, bufSize, &recSize); #elif defined(HAVE_BLUEZ_BUFSIZE) rec = sdp_extract_pdu(rsp, bufSize, &recSize); #else rec = sdp_extract_pdu(rsp, &recSize); #endif if (!rec) { SE_THROW_EXCEPTION (TransportException, "ObexTransportAgent: sdp_extract_pdu failed"); } if (!recSize) { sdp_record_free(rec); SE_THROW_EXCEPTION (TransportException, "ObexTransportAgent: sdp_extract_pdu failed"); } if (!sdp_get_access_protos(rec, &protos)) { channel = sdp_get_proto_port(protos, RFCOMM_UUID); sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL); sdp_list_free(protos, NULL); } sdp_record_free(rec); if (channel > 0) { break; } scanned += recSize; rsp += recSize; bufSize -= recSize; } while (scanned < size && bufSize > 0); if (channel <= 0) { SE_THROW_EXCEPTION (TransportException, "ObexTransportAgent: Bluetooth service search failed"); } m_port = channel; m_connectStatus = ADDR_READY; connectInit(); } catch (...) { handleException("sdp_callback_impl"); } } /* obex event source callbackg*/ gboolean ObexTransportAgent::obex_fd_source_cb_impl (GIOChannel *io, GIOCondition cond) { cxxptr sockObj = m_sock; GIOChannelPtr channel = m_channel; if (m_status == CLOSED) { return TRUE; } try { if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) { SE_THROW_EXCEPTION (TransportException, StringPrintf("obex_fd_source_cb_impl: got event %s%s%s", (cond & G_IO_HUP) ?"HUP":"", (cond & G_IO_ERR) ?"ERR":"", (cond & G_IO_NVAL) ?"NVAL":"")); } if (m_connectStatus == INIT0 && (cond & G_IO_OUT)) { int status = -1; socklen_t optLen = sizeof (int); if (!getsockopt (sockObj->get(), SOL_SOCKET, SO_ERROR, &status, &optLen) && status == 0) { m_connectStatus = INIT1; m_sock = sockObj; m_channel = channel; connectReq(); return TRUE; } else { SE_THROW_EXCEPTION (TransportException, "OBEXTransport: socket connect failed"); } } time_t now = time(NULL); if (m_timeoutSeconds && (m_requestStart != 0) && (now >= m_timeoutSeconds +m_requestStart)) { m_sock = sockObj; m_channel = channel; //timeout m_status = TIME_OUT; //currently we will not support transport resend for //OBEX transport ?? cancel(); return TRUE; } if (OBEX_HandleInput (m_handle, OBEX_POLL_INTERVAL) <0 && errno != EINTR) { //transport error //no way to recovery, simply abort //disconnect without sending disconnect request //The failure may already has been processed during //OBEX callback, thus no need to handle it again if status is //already marked as FAILED if (m_status != FAILED){ cancel(); } } m_sock = sockObj; m_channel = channel; return TRUE; } catch (...) { handleException("obex_fd_source_cb_impl"); } return FALSE; } /** * Obex Event Callback */ void ObexTransportAgent::obex_callback (obex_object_t *object, int mode, int event, int obex_cmd, int obex_rsp) { try { switch (event) { case OBEX_EV_PROGRESS: SE_LOG_DEV(NULL, "OBEX progress"); break; case OBEX_EV_REQDONE: m_obexReady = true; m_requestStart = 0; if (obex_rsp != OBEX_RSP_SUCCESS) { SE_LOG_ERROR(NULL, "OBEX Request %d got a failed response %s", obex_cmd,OBEX_ResponseToString(obex_rsp)); m_status = FAILED; return; } else { switch (obex_cmd) { case OBEX_CMD_CONNECT: { uint8_t headertype = 0; obex_headerdata_t header; uint32_t len; while (OBEX_ObjectGetNextHeader (m_handle, object, &headertype, &header, &len)) { if (headertype == OBEX_HDR_CONNECTION) { m_connectId = header.bq4; } else if (headertype == OBEX_HDR_WHO) { SE_LOG_DEV(NULL, "OBEX Transport: get header who from connect response with value %.*s", len, header.bs); } else { SE_LOG_WARNING(NULL, "OBEX Transport: Unknow header from connect response"); } } if (m_connectId == 0) { m_status = FAILED; SE_LOG_ERROR(NULL, "No connection id received from connect response"); } m_connectStatus = CONNECTED; break; } case OBEX_CMD_DISCONNECT: { if (m_connectStatus == CONNECTED) { m_connectStatus = END; OBEX_TransportDisconnect (m_handle); m_status = CLOSED; } break; } case OBEX_CMD_GET: { int length = 0; uint8_t headertype = 0; obex_headerdata_t header; uint32_t len; while (OBEX_ObjectGetNextHeader (m_handle, object, &headertype, &header, &len)) { if (headertype == OBEX_HDR_LENGTH) { length = header.bq4; } else if (headertype == OBEX_HDR_BODY) { if (length ==0) { length = len; SE_LOG_DEV(NULL, "No length header for get response is recevied, using body size %d", len); } if (length ==0) { m_status = FAILED; SE_LOG_ERROR(NULL, "ObexTransport: Get zero sized response body for Get"); } m_buffer.set(new char[length], "buffer"); m_bufferSize = length; memcpy (m_buffer, header.bs, length); } else { SE_LOG_WARNING(NULL, "Unknow header received for Get cmd"); } } if( !length || !m_buffer) { m_status = FAILED; SE_LOG_ERROR(NULL, "Get Cmd response have no body"); } m_status = GOT_REPLY; break; } } }//else break; case OBEX_EV_LINKERR: { if (obex_rsp == 0 && m_disconnecting) { //disconnct event m_connectStatus = END; OBEX_TransportDisconnect (m_handle); //a normal case, same as OBEX_EV_DONE m_obexReady = true; m_status = CLOSED; } else if (obex_rsp !=0) { SE_LOG_ERROR(NULL, "ObexTransport Error %d", obex_rsp); m_status = FAILED; return; } break; } case OBEX_EV_STREAMEMPTY: case OBEX_EV_STREAMAVAIL: break; } } catch (...) { handleException("obex_callback"); } } void ObexTransportAgent::handleException(const char *where) { SE_LOG_DEBUG(NULL, "ObexTransport: exception thrown in %s", where); if (m_status == FAILED || m_status == CANCELED ) { // don't change anything, don't bother the user with error // messages - something went already wrong } else { // log error and remember put transport into error state Exception::handle(); m_status = FAILED; } } obex_object_t* ObexTransportAgent::newCmd(uint8_t cmd) { obex_object_t *cmdObject = OBEX_ObjectNew (m_handle, cmd); if(!cmdObject) { m_status = FAILED; SE_LOG_ERROR(NULL, "ObexTransport: OBEX Object New failed"); return NULL; } else { return cmdObject; } } #endif //ENABLE_OBEX SE_END_CXX syncevolution_1.4/src/syncevo/ObexTransportAgent.h000066400000000000000000000154201230021373600225770ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_OBEXTRANSPORTAGENT #define INCL_OBEXTRANSPORTAGENT #include #ifdef ENABLE_OBEX #include #include #include #include #ifdef ENABLE_BLUETOOTH #include #include #endif #include SE_BEGIN_CXX /** * utility class for various enties stored by * ObexTransportAgent in SmartPtr */ class ObexUnref { public: static void unref(GMainContext *context) { g_main_context_unref(context); } static void unref(sdp_session_t *sdp) { sdp_close(sdp); } static void unref(GIOChannel *channel) { g_io_channel_unref(channel); } static void unref(obex_t *handle) { OBEX_Cleanup(handle); } }; typedef eptr GMainContextPtr; typedef eptr SDPSessionPtr; typedef eptr GIOChannelPtr; typedef eptr ObexPtr; class Socket { int socketfd; public: Socket() {socketfd = -1;} Socket(int fd) {socketfd = fd;} ~Socket() { if (socketfd !=-1) {::close (socketfd);} } int get() {return socketfd;} }; /** * message send/receive with libopenobex * should work with a transport binding (Bluetooth, USB, etc.) */ class ObexTransportAgent : public TransportAgent { public: enum OBEX_TRANS_TYPE{ OBEX_BLUETOOTH, OBEX_USB, INVALID }; /** * @param loop the glib loop to use when waiting for IO; * transport will increase the reference count; * if NULL a new loop in the default context is used */ ObexTransportAgent(OBEX_TRANS_TYPE type, GMainLoop *loop); ~ObexTransportAgent(); virtual void setURL (const std::string &url); virtual void setContentType(const std::string &type); virtual void shutdown(); virtual void send(const char *data, size_t len); virtual void cancel(); virtual Status wait(bool noReply); virtual void getReply(const char *&data, size_t &len, std::string &contentType); virtual void setTimeout(int seconds); /* Obex specific api: connecting the underlying transport */ void connect(); private: /*call back used by libopenobex, will route to member function obex_callback*/ static void obex_event (obex_t *handle, obex_object_t *object, int mode, int event, int obex_cmd, int obex_rsp); /* callback used by obex fd poll, will route to member function * obex_fd_source_cb_impl */ static gboolean obex_fd_source_cb (GIOChannel *io, GIOCondition cond, void *udata); /* callback used by Bluetooth sdp poll, will route to member function * sdp_source_cb_impl */ static gboolean sdp_source_cb (GIOChannel *io, GIOCondition cond, void *udata); /* callback called when a sdp async transaction is finished, route to * member function sdp_callback_impl*/ static void sdp_callback (uint8_t type, uint16_t status, uint8_t *rsp, size_t size, void *user_data); void obex_callback (obex_object_t *object, int mode, int event, int obex_cmd, int obex_rsp); gboolean obex_fd_source_cb_impl (GIOChannel *io, GIOCondition cond); gboolean sdp_source_cb_impl (GIOChannel *io, GIOCondition cond); void sdp_callback_impl (uint8_t type, uint16_t status, uint8_t *rsp, size_t size); /** * Handle exception thrown by any of the C callbacks. * Exception must not escape into calling C function. * Instead, set bad status and wait for that to * be discovered in wait(). */ void handleException(const char *where); /* First phase of OBEX connect: connect to remote peer */ void connectInit (); /* Second phase of OBEX connect: send connect cmd to initalize */ void connectReq (); /* wrapper of OBEX_ObjectNew*/ obex_object_t * newCmd (uint8_t cmd); static const int DEFAULT_RX_MTU=32767; static const int DEFAULT_TX_MTU=32767; /*Indicates when the OBEX transport has finished it's part of working, * it's the application to turn to do something */ bool m_obexReady; Status m_status; /* * The underlying transport type: Bluetooth, USB. */ OBEX_TRANS_TYPE m_transType; /** context that needs to be kept alive while waiting for OBEX */ GMainContextPtr m_context; /* The address of the remote device * macadd for Bluetooth; device name for usb; host name for * tcp/ip */ std::string m_address; /* * Service port for the remote device * channel for Bluetooth, port for tcp/ip */ int m_port; /*The underlying socket fd*/ cxxptr m_sock; GLibEvent m_obexEvent; GIOChannelPtr m_channel; std::string m_contentType; arrayptr m_buffer; int m_bufferSize; SDPSessionPtr m_sdp; GLibEvent m_sdpEvent; int m_timeoutSeconds; time_t m_requestStart; /** OBEX poll interval */ static const int OBEX_POLL_INTERVAL = 1; uint32_t m_connectId; //already fired disconnect bool m_disconnecting; ObexPtr m_handle; enum CONNECT_STATUS { START, SDP_START, //sdp transaction start SDP_REQ, //sdp request has been sent SDP_DONE, //sdp transaction finished ADDR_READY, //address is prepared INIT0, //connect is called but not finished INIT1, //connect is finished. INIT2, //connect cmd is sent, but not finished. CONNECTED, //connection sucessfully setup ERROR, //connection in error state END }; CONNECT_STATUS m_connectStatus; }; SE_END_CXX #endif //ENABLE_OBEX #endif syncevolution_1.4/src/syncevo/PrefixConfigNode.cpp000066400000000000000000000054341230021373600225360ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include #include SE_BEGIN_CXX PrefixConfigNode::PrefixConfigNode(const string prefix, const boost::shared_ptr &node) : m_prefix(prefix), m_node(node), m_readOnlyNode(node) { } PrefixConfigNode::PrefixConfigNode(const string prefix, const boost::shared_ptr &node) : m_prefix(prefix), m_readOnlyNode(node) { } InitStateString PrefixConfigNode::readProperty(const string &property) const { return m_readOnlyNode->readProperty(m_prefix + property); } void PrefixConfigNode::writeProperty(const string &property, const InitStateString &value, const string &comment) { m_node->writeProperty(m_prefix + property, value, comment); } void PrefixConfigNode::readProperties(ConfigProps &props) const { ConfigProps original; m_readOnlyNode->readProperties(original); BOOST_FOREACH(const StringPair &prop, original) { string key = prop.first; string value = prop.second; if (boost::starts_with(key, m_prefix)) { props[key.substr(m_prefix.size())] = value; } } } void PrefixConfigNode::clear() { ConfigProps original; m_readOnlyNode->readProperties(original); BOOST_FOREACH(const StringPair &prop, original) { string key = prop.first; if (boost::starts_with(key, m_prefix)) { m_node->removeProperty(key); } } } void PrefixConfigNode::removeProperty(const string &property) { m_node->removeProperty(m_prefix + property); } void PrefixConfigNode::flush() { if (!m_node.get()) { SyncContext::throwError(getName() + ": read-only, flushing not allowed"); } m_node->flush(); } SE_END_CXX syncevolution_1.4/src/syncevo/PrefixConfigNode.h000066400000000000000000000053001230021373600221730ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_EVOLUTION_PREFIX_CONFIG_NODE # define INCL_EVOLUTION_PREFIX_CONFIG_NODE #include #include #include #include #include #include #include SE_BEGIN_CXX /** * This class acts as filter between a real config node and its user: * a fixed prefix is added to each key when setting/getting a property. * The list of properties only includes the key/value pairs with * a matching prefix. * * The purpose is to have multiple users accessing the same underlying * node without running into namespace conflicts. */ class PrefixConfigNode : public ConfigNode { public: /** read-write access to underlying node */ PrefixConfigNode(const std::string prefix, const boost::shared_ptr &node); /** read-only access to underlying node */ PrefixConfigNode(const std::string prefix, const boost::shared_ptr &node); virtual std::string getName() const { return m_readOnlyNode->getName(); } virtual bool isVolatile() const { return m_readOnlyNode->isVolatile(); } /* ConfigNode API */ virtual void flush(); virtual InitStateString readProperty(const std::string &property) const; virtual void writeProperty(const std::string &property, const InitStateString &value, const std::string &comment = ""); virtual void readProperties(ConfigProps &props) const; virtual void removeProperty(const std::string &property); virtual bool exists() const { return m_readOnlyNode->exists(); } virtual bool isReadOnly() const { return !m_node || m_readOnlyNode->isReadOnly(); } virtual void clear(); private: std::string m_prefix; boost::shared_ptr m_node; boost::shared_ptr m_readOnlyNode; }; SE_END_CXX #endif syncevolution_1.4/src/syncevo/SafeConfigNode.cpp000066400000000000000000000045541230021373600221610ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include SE_BEGIN_CXX SafeConfigNode::SafeConfigNode(const boost::shared_ptr &node) : m_node(node), m_readOnlyNode(node), m_strictMode(true) { } SafeConfigNode::SafeConfigNode(const boost::shared_ptr &node) : m_readOnlyNode(node), m_strictMode(true) { } InitStateString SafeConfigNode::readProperty(const string &property) const { InitStateString res = m_readOnlyNode->readProperty(escape(property)); return InitStateString(unescape(res.get()), res.wasSet()); } void SafeConfigNode::writeProperty(const string &property, const InitStateString &value, const string &comment) { m_node->writeProperty(escape(property), InitStateString(escape(value.get()), value.wasSet()), comment); } void SafeConfigNode::readProperties(ConfigProps &props) const { ConfigProps original; m_readOnlyNode->readProperties(original); BOOST_FOREACH(const StringPair &prop, original) { string key = unescape(prop.first); string value = unescape(prop.second); props[key] = value; } } void SafeConfigNode::removeProperty(const string &property) { m_node->removeProperty(escape(property)); } void SafeConfigNode::flush() { if (!m_node.get()) { SyncContext::throwError(getName() + ": read-only, flushing not allowed"); } m_node->flush(); } SE_END_CXX syncevolution_1.4/src/syncevo/SafeConfigNode.h000066400000000000000000000066371230021373600216320ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_EVOLUTION_SAFE_CONFIG_NODE # define INCL_EVOLUTION_SAFE_CONFIG_NODE #include #include #include #include #include #include #include SE_BEGIN_CXX /** * This class acts as filter between a real config node and its user: * key/value strings which normally wouldn't be valid are escaped * before passing them into the underlying node. When reading, they * are unescaped again. * * Unsafe characters are replaced by ! followed by two characters * giving the character value in hex notation. */ class SafeConfigNode : public ConfigNode { public: /** read-write access to underlying node */ SafeConfigNode(const boost::shared_ptr &node); /** read-only access to underlying node */ SafeConfigNode(const boost::shared_ptr &node); /** * chooses which characters are accepted by underlying node: * in strict mode, only alphanumeric and -_ are supported; * in non-strict mode, only line breaks, = and spaces at start and end are escaped */ void setMode(bool strict) { m_strictMode = strict; } bool getMode() { return m_strictMode; } virtual std::string getName() const { return m_readOnlyNode->getName(); } virtual bool isVolatile() const { return m_readOnlyNode->isVolatile(); } /* keep underlying methods visible; our own setProperty() would hide them */ using ConfigNode::setProperty; /* ConfigNode API */ virtual void flush(); virtual InitStateString readProperty(const std::string &property) const; virtual void writeProperty(const std::string &property, const InitStateString &value, const std::string &comment = ""); virtual void readProperties(ConfigProps &props) const; virtual void removeProperty(const std::string &property); virtual bool exists() const { return m_readOnlyNode->exists(); } virtual bool isReadOnly() const { return !m_node || m_readOnlyNode->isReadOnly(); } virtual void clear() { m_node->clear(); } private: boost::shared_ptr m_node; boost::shared_ptr m_readOnlyNode; bool m_strictMode; /** * turn str into something which can be used as key or value in ConfigNode */ std::string escape(const std::string &str) const { return StringEscape::escape(str, '!', m_strictMode ? StringEscape::STRICT : StringEscape::INI_VALUE); } static std::string unescape(const std::string &str) { return StringEscape::unescape(str, '!'); } }; SE_END_CXX #endif syncevolution_1.4/src/syncevo/SafeOstream.cpp000066400000000000000000000027661230021373600215630ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include SE_BEGIN_CXX SafeOstream::SafeOstream(const std::string filename) : m_filename(filename) { size_t pos = filename.rfind('/'); if (pos == filename.npos) { m_tmpFilename = ".#"; m_tmpFilename += filename; } else { m_tmpFilename.assign(filename, 0, pos + 1); m_tmpFilename += ".#"; m_tmpFilename += filename.substr(pos + 1); } open(m_tmpFilename.c_str()); } SafeOstream::~SafeOstream() { close(); if (bad() || rename(m_tmpFilename.c_str(), m_filename.c_str())) { SyncContext::throwError(m_tmpFilename, errno); } } SE_END_CXX syncevolution_1.4/src/syncevo/SafeOstream.h000066400000000000000000000032551230021373600212220ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_EVOLUTION_SAFE_OSTREAM # define INCL_EVOLUTION_SAFE_OSTREAM #include #include #include SE_BEGIN_CXX /** * Writes into temporary file (.# prefix) first, then renames to real * file only when no error encountered at the time of deleting the * instance. Once instantiated, the only way to safe the content of the * real file is to set the "fail" bit. In that sense it is similar to * instantiating a normal ofstream, which would directly overwrite * the file at creation time. */ class SafeOstream : public std::ofstream { std::string m_filename; std::string m_tmpFilename; public: /** * @param filename real filename, without the .# prefix */ SafeOstream(const std::string filename); /** * on success, rename file */ ~SafeOstream(); }; SE_END_CXX #endif // INCL_EVOLUTION_SAFE_OSTREAM syncevolution_1.4/src/syncevo/SingleFileConfigTree.cpp000066400000000000000000000203771230021373600233370ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include "test.h" #include #include #include #include #include #include #include #include SE_BEGIN_CXX using namespace std; SingleFileConfigTree::SingleFileConfigTree(const boost::shared_ptr &data) : m_data(data) { readFile(); } SingleFileConfigTree::SingleFileConfigTree(const string &fullpath) : m_data(new FileDataBlob(fullpath, true)) { readFile(); } boost::shared_ptr SingleFileConfigTree::open(const string &filename) { string normalized = normalizePath(string("/") + filename); boost::shared_ptr &entry = m_nodes[normalized]; if (entry) { return entry; } string name = m_data->getName() + " - " + normalized; boost::shared_ptr data; BOOST_FOREACH(const FileContent_t::value_type &file, m_content) { if (file.first == normalized) { data.reset(new StringDataBlob(name, file.second, true)); break; } } if (!data) { /* * creating new files not supported, would need support for detecting * StringDataBlob::write() */ data.reset(new StringDataBlob(name, boost::shared_ptr(), true)); } entry.reset(new IniFileConfigNode(data)); return entry; } void SingleFileConfigTree::flush() { // not implemented, cannot write anyway } void SingleFileConfigTree::reload() { SE_THROW("SingleFileConfigTree::reload() not implemented"); } void SingleFileConfigTree::remove(const string &path) { SE_THROW("internal error: SingleFileConfigTree::remove() called"); } void SingleFileConfigTree::reset() { m_nodes.clear(); readFile(); } boost::shared_ptr SingleFileConfigTree::open(const string &path, PropertyType type, const string &otherId) { string fullpath = path; if (!fullpath.empty()) { fullpath += "/"; } switch (type) { case visible: fullpath += "config.ini"; break; case hidden: fullpath += ".internal.ini"; break; case other: fullpath += ".other.ini"; break; case server: fullpath += ".server.ini"; break; } return open(fullpath); } boost::shared_ptr SingleFileConfigTree::add(const string &path, const boost::shared_ptr &bode) { SE_THROW("SingleFileConfigTree::add() not supported"); } static void checkChild(const string &normalized, const string &node, set &subdirs) { if (boost::starts_with(node, normalized)) { string remainder = node.substr(normalized.size()); size_t offset = remainder.find('/'); if (offset != remainder.npos) { // only directories underneath path matter subdirs.insert(remainder.substr(0, offset)); } } } list SingleFileConfigTree::getChildren(const string &path) { set subdirs; string normalized = normalizePath(string("/") + path); if (normalized != "/") { normalized += "/"; } // must check both actual files as well as unsaved nodes BOOST_FOREACH(const FileContent_t::value_type &file, m_content) { checkChild(normalized, file.first, subdirs); } BOOST_FOREACH(const NodeCache_t::value_type &file, m_nodes) { checkChild(normalized, file.first, subdirs); } list result; BOOST_FOREACH(const string &dir, subdirs) { result.push_back(dir); } return result; } void SingleFileConfigTree::readFile() { boost::shared_ptr in(m_data->read()); boost::shared_ptr content; string line; m_content.clear(); while (getline(*in, line)) { if (boost::starts_with(line, "=== ") && boost::ends_with(line, " ===")) { string name = line.substr(4, line.size() - 8); name = normalizePath(string("/") + name); content.reset(new string); m_content[name] = content; } else if (content) { (*content) += line; (*content) += "\n"; } } } #ifdef ENABLE_UNIT_TESTS class SingleIniTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(SingleIniTest); CPPUNIT_TEST(simple); CPPUNIT_TEST_SUITE_END(); void simple() { boost::shared_ptr data(new string); data->assign("# comment\n" "# foo\n" "=== foo/config.ini ===\n" "foo = bar\n" "foo2 = bar2\n" "=== foo/.config.ini ===\n" "foo_internal = bar_internal\n" "foo2_internal = bar2_internal\n" "=== /bar/.internal.ini ===\n" "bar = foo\n" "=== sources/addressbook/config.ini ===\n" "=== sources/calendar/config.ini ===\n" "evolutionsource = Personal\n"); boost::shared_ptr blob(new StringDataBlob("test", data, true)); SingleFileConfigTree tree(blob); boost::shared_ptr node; node = tree.open("foo/config.ini"); CPPUNIT_ASSERT(node); CPPUNIT_ASSERT(node->exists()); CPPUNIT_ASSERT_EQUAL(string("test - /foo/config.ini"), node->getName()); CPPUNIT_ASSERT(node->readProperty("foo").wasSet()); CPPUNIT_ASSERT_EQUAL(string("bar"), node->readProperty("foo").get()); CPPUNIT_ASSERT_EQUAL(string("bar2"), node->readProperty("foo2").get()); CPPUNIT_ASSERT(node->readProperty("foo2").wasSet()); CPPUNIT_ASSERT_EQUAL(string(""), node->readProperty("no_such_bar").get()); CPPUNIT_ASSERT(!node->readProperty("no_such_bar").wasSet()); node = tree.open("/foo/config.ini"); CPPUNIT_ASSERT(node); CPPUNIT_ASSERT(node->exists()); node = tree.open("foo//.config.ini"); CPPUNIT_ASSERT(node); CPPUNIT_ASSERT(node->exists()); CPPUNIT_ASSERT_EQUAL(string("bar_internal"), node->readProperty("foo_internal").get()); CPPUNIT_ASSERT_EQUAL(string("bar2_internal"), node->readProperty("foo2_internal").get()); node = tree.open("bar///./.internal.ini"); CPPUNIT_ASSERT(node); CPPUNIT_ASSERT(node->exists()); CPPUNIT_ASSERT_EQUAL(string("foo"), node->readProperty("bar").get()); node = tree.open("sources/addressbook/config.ini"); CPPUNIT_ASSERT(node); CPPUNIT_ASSERT(node->exists()); node = tree.open("sources/calendar/config.ini"); CPPUNIT_ASSERT(node); CPPUNIT_ASSERT(node->exists()); CPPUNIT_ASSERT_EQUAL(string("Personal"), node->readProperty("evolutionsource").get()); node = tree.open("no-such-source/config.ini"); CPPUNIT_ASSERT(node); CPPUNIT_ASSERT(!node->exists()); list dirs = tree.getChildren(""); CPPUNIT_ASSERT_EQUAL(string("bar|foo|no-such-source|sources"), boost::join(dirs, "|")); dirs = tree.getChildren("sources/"); CPPUNIT_ASSERT_EQUAL(string("addressbook|calendar"), boost::join(dirs, "|")); } }; SYNCEVOLUTION_TEST_SUITE_REGISTRATION(SingleIniTest); #endif // ENABLE_UNIT_TESTS SE_END_CXX syncevolution_1.4/src/syncevo/SingleFileConfigTree.h000066400000000000000000000060341230021373600227760ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_EVOLUTION_SINGLE_FILE_CONFIG_TREE # define INCL_EVOLUTION_SINGLE_FILE_CONFIG_TREE #include #include #include #include #include #include SE_BEGIN_CXX /** * This class handles data blobs which contain multiple .ini files, using * the following format: * @verbatim # comment # ... === /[.internal.ini|config.ini|template.ini|...] === === === ... * @endverbatim * * This is based on the assumption that the === ... === file separator * is not part of valid .ini file content. * * Right now, only reading such a single data blob is implemented. */ class SingleFileConfigTree : public ConfigTree { public: /** * @param data access to complete file data */ SingleFileConfigTree(const boost::shared_ptr &data); SingleFileConfigTree(const std::string &fullpath); /** * same as open(), with full file name (like sources/addressbook/config.ini) * instead of path + type */ boost::shared_ptr open(const std::string &filename); /* ConfigTree API */ virtual void flush(); virtual void reload(); virtual void remove(const std::string &path); virtual void reset(); virtual boost::shared_ptr open(const std::string &path, PropertyType type, const std::string &otherId = std::string("")); virtual boost::shared_ptr add(const std::string &path, const boost::shared_ptr &bode); std::list getChildren(const std::string &path); private: boost::shared_ptr m_data; /** * maps from normalized file name (see normalizePath()) to content for that name */ typedef std::map > FileContent_t; FileContent_t m_content; /** cache of all nodes ever accessed */ typedef std::map< std::string, boost::shared_ptr > NodeCache_t; NodeCache_t m_nodes; /** * populate m_content from m_data */ void readFile(); }; SE_END_CXX #endif syncevolution_1.4/src/syncevo/SmartPtr.h000066400000000000000000000145161230021373600205670ustar00rootroot00000000000000/* * Copyright (C) 2005-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_EVOLUTION_SMART_POINTER #define INCL_EVOLUTION_SMART_POINTER #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #ifdef HAVE_GLIB # include #endif #include #include #include #include #include #include #include SE_BEGIN_CXX template class UnrefFree { public: static void unref(T *pointer) { free(pointer); } }; class Unref { public: /** * C character string - beware, Funambol C++ client library strings must * be returned with delete [], use boost::scoped_array */ static void unref(char *pointer) { free(pointer); } #ifdef HAVE_GLIB static void unref(GObject *pointer) { g_object_unref(pointer); } /** free a list of GObject and the objects */ static void unref(GList *pointer) { if (pointer) { GList *next = pointer; do { g_object_unref(G_OBJECT(next->data)); next = next->next; } while (next); g_list_free(pointer); } } #ifdef ENABLE_EBOOK static void unref(EBookQuery *pointer) { e_book_query_unref(pointer); } #endif // ENABLE_EBOOK #endif // HAVE_GLIB #ifdef ENABLE_ICAL static void unref(icalcomponent *pointer) { icalcomponent_free(pointer); } static void unref(icalproperty *pointer) { icalproperty_free(pointer); } static void unref(icalparameter *pointer) { icalparameter_free(pointer); } static void unref(icaltimezone *pointer) { icaltimezone_free(pointer, 1); } #endif // ENABLE_ICAL }; #ifdef HAVE_GLIB class UnrefGLibEvent { public: static void unref(guint event) { g_source_remove(event); } }; class UnrefGString { public: static void unref(gchar *ptr) { g_free(ptr); } }; #endif // HAVE_GLIB /** * a smart pointer implementation for objects for which * a unref() function exists inside R; * trying to store a NULL object raises an exception, * unreferencing valid objects is done automatically */ template class SmartPtr { protected: T m_pointer; public: /** * create a smart pointer that owns the given object; * passing a NULL pointer and a name for the object raises an error */ SmartPtr(T pointer = 0, const char *objectName = NULL) : m_pointer( pointer ) { if (!pointer && objectName ) { throw std::runtime_error(std::string("Error allocating ") + objectName); } }; ~SmartPtr() { set(0); } /** assignment and copy construction transfer ownership to the copy */ SmartPtr(SmartPtr &other) { m_pointer = other.m_pointer; other.m_pointer = 0; } SmartPtr & operator = (SmartPtr &other) { if (this != &other) { set (other.m_pointer); other.m_pointer = 0; } return *this; } /** * store another object in this pointer, replacing any which was * referenced there before; * passing a NULL pointer and a name for the object raises an error */ void set( T pointer, const char *objectName = NULL ) { if (m_pointer) { R::unref((base)m_pointer); } if (!pointer && objectName) { throw std::runtime_error(std::string("Error allocating ") + objectName); } m_pointer = pointer; } void reset( T pointer = NULL ) { set(pointer, NULL); } /** * transfer ownership over the pointer to caller and stop tracking it: * pointer tracked by smart pointer is set to NULL and the original * pointer is returned */ T release() { T res = m_pointer; m_pointer = 0; return res; } SmartPtr &operator = ( T pointer ) { set( pointer ); return *this; } T get() { return m_pointer; } T operator-> () { return m_pointer; } // T &operator* () { return *m_pointer; } operator T () { return m_pointer; } operator void * () { return (void *)m_pointer; } operator bool () { return m_pointer != 0; } }; template class eptr : public SmartPtr { typedef SmartPtr base_t; public: eptr(T *pointer = NULL, const char *objectName = NULL) : base_t(pointer, objectName) { } eptr &operator = ( T *pointer ) { base_t::set(pointer); return *this; } void reset(T *pointer = NULL) { base_t::set(pointer); } }; template class CxxUnref { public: static void unref(T *pointer) { delete pointer; } }; /** eptr for normal C++ objects */ template class cxxptr : public eptr > { public: cxxptr(T *pointer = NULL, const char *objectName = NULL) : eptr > (pointer, objectName) { }; }; template class ArrayUnref { public: static void unref(T *pointer) { delete [] pointer; } }; /** eptr for array of objects or types */ template class arrayptr : public eptr > { public: arrayptr(T *pointer = NULL, const char *objectName = NULL) : eptr > (pointer, objectName) { }; }; #ifdef HAVE_GLIB /** eptr for glib event handle - not reference counted, owned by at most one instance */ typedef SmartPtr GLibEvent; typedef SmartPtr GStringPtr; // for GMainLoop see GLibSupport.h #endif SE_END_CXX #endif syncevolution_1.4/src/syncevo/SoupTransportAgent.cpp000066400000000000000000000206241230021373600231650ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #ifdef ENABLE_LIBSOUP #include #include #include #ifdef HAVE_LIBSOUP_SOUP_GNOME_FEATURES_H #include #endif #include SE_BEGIN_CXX SoupTransportAgent::SoupTransportAgent(GMainLoop *loop) : m_session(soup_session_async_new()), m_loop(loop ? g_main_loop_ref(loop) : g_main_loop_new(NULL, TRUE), "Soup main loop"), m_status(INACTIVE), m_timeoutSeconds(0), m_response(0) { #ifdef HAVE_LIBSOUP_SOUP_GNOME_FEATURES_H // use default GNOME proxy settings soup_session_add_feature_by_type(m_session.get(), SOUP_TYPE_PROXY_RESOLVER_GNOME); #endif } SoupTransportAgent::~SoupTransportAgent() { if (m_session.get()) { // ensure that no callbacks for this session will be triggered // in the future, they would use a stale pointer to this agent // instance soup_session_abort(m_session.get()); } } void SoupTransportAgent::setURL(const std::string &url) { m_URL = url; } void SoupTransportAgent::setProxy(const std::string &proxy) { if (!proxy.empty()) { eptr uri(soup_uri_new(proxy.c_str()), "Proxy URI"); g_object_set(m_session.get(), SOUP_SESSION_PROXY_URI, uri.get(), NULL); } else { g_object_set(m_session.get(), SOUP_SESSION_PROXY_URI, NULL, NULL); } } void SoupTransportAgent::setProxyAuth(const std::string &user, const std::string &password) { /** * @TODO: handle "authenticate" signal for both proxy and HTTP server * (https://sourceforge.net/tracker/index.php?func=detail&aid=2056162&group_id=146288&atid=764733). * * Proxy authentication is available, but still needs to be hooked up * with libsoup. Should we make this interactive? Needs an additional * API for TransportAgent into caller. * * HTTP authentication is not available. */ m_proxyUser = user; m_proxyPassword = password; } void SoupTransportAgent::setSSL(const std::string &cacerts, bool verifyServer, bool verifyHost) { m_verifySSL = verifyServer || verifyHost; m_cacerts = cacerts; } void SoupTransportAgent::setContentType(const std::string &type) { m_contentType = type; } void SoupTransportAgent::setUserAgent(const std::string &agent) { g_object_set(m_session.get(), SOUP_SESSION_USER_AGENT, agent.c_str(), NULL); } void SoupTransportAgent::setTimeout(int seconds) { m_timeoutSeconds = seconds; } void SoupTransportAgent::send(const char *data, size_t len) { // ownership is transferred to libsoup in soup_session_queue_message() eptr message(soup_message_new("POST", m_URL.c_str())); if (!message.get()) { SE_THROW_EXCEPTION(TransportException, "could not allocate SoupMessage"); } // use CA certificates if available and needed, // fail if not available and needed if (m_verifySSL) { if (!m_cacerts.empty()) { g_object_set(m_session.get(), SOUP_SESSION_SSL_CA_FILE, m_cacerts.c_str(), NULL); } else { SoupURI *uri = soup_message_get_uri(message.get()); if (!strcmp(uri->scheme, SOUP_URI_SCHEME_HTTPS)) { SE_THROW_EXCEPTION(TransportException, "SSL certificate checking requested, but no CA certificate file configured"); } } } soup_message_set_request(message.get(), m_contentType.c_str(), SOUP_MEMORY_TEMPORARY, data, len); m_status = ACTIVE; if (m_timeoutSeconds) { m_message = message.get(); m_timeoutEventSource = g_timeout_add_seconds(m_timeoutSeconds, TimeoutCallback, static_cast (this)); } soup_session_queue_message(m_session.get(), message.release(), SessionCallback, static_cast(this)); } void SoupTransportAgent::cancel() { m_status = CANCELED; soup_session_abort(m_session.get()); if(g_main_loop_is_running(m_loop.get())) g_main_loop_quit(m_loop.get()); } TransportAgent::Status SoupTransportAgent::wait(bool noReply) { if (!m_failure.empty()) { std::string failure; std::swap(failure, m_failure); SE_THROW_EXCEPTION(TransportException, failure); } switch (m_status) { case CLOSED: return CLOSED; break; case ACTIVE: // block in main loop until our HandleSessionCallback() stops the loop g_main_loop_run(m_loop.get()); break; default: break; } /** For a canceled message, does not throw exception, just print a warning, the * upper layer may decide to retry */ if(m_status == TIME_OUT || m_status == FAILED){ std::string failure; std::swap(failure, m_failure); SE_LOG_INFO(NULL, "SoupTransport Failure: %s", failure.c_str()); } if (!m_failure.empty()) { std::string failure; std::swap(failure, m_failure); SE_THROW_EXCEPTION(TransportException, failure); } m_timeoutEventSource.set(0); return m_status; } void SoupTransportAgent::getReply(const char *&data, size_t &len, std::string &contentType) { if (m_response) { data = m_response->data; len = m_response->length; contentType = m_responseContentType; } else { data = NULL; len = 0; } } void SoupTransportAgent::SessionCallback(SoupSession *session, SoupMessage *msg, gpointer user_data) { static_cast(user_data)->HandleSessionCallback(session, msg); } void SoupTransportAgent::HandleSessionCallback(SoupSession *session, SoupMessage *msg) { // keep a reference to the data m_responseContentType = ""; if (msg->response_body) { m_response = soup_message_body_flatten(msg->response_body); const char *soupContentType = soup_message_headers_get(msg->response_headers, "Content-Type"); if (soupContentType) { m_responseContentType = soupContentType; } } else { m_response = NULL; } if (msg->status_code != 200) { m_failure = m_URL; m_failure += " via libsoup: "; m_failure += msg->reason_phrase ? msg->reason_phrase : "failed"; m_status = FAILED; if (m_responseContentType.find("text") != std::string::npos) { SE_LOG_DEBUG(NULL, "unexpected HTTP response: status %d/%s, content type %s, body:\n%.*s", msg->status_code, msg->reason_phrase ? msg->reason_phrase : "", m_responseContentType.c_str(), m_response ? (int)m_response->length : 0, m_response ? m_response->data : ""); } } else { m_status = GOT_REPLY; } g_main_loop_quit(m_loop.get()); } gboolean SoupTransportAgent::processCallback() { //stop the message processing and mark status as timeout guint message_status = SOUP_STATUS_CANCELLED; soup_session_cancel_message(m_session.get(), m_message, message_status); m_status = TIME_OUT; return FALSE; } gboolean SoupTransportAgent::TimeoutCallback(gpointer transport) { SoupTransportAgent * sTransport = static_cast(transport); return sTransport->processCallback(); } SE_END_CXX #endif // ENABLE_LIBSOUP syncevolution_1.4/src/syncevo/SoupTransportAgent.h000066400000000000000000000072661230021373600226410ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_SOUPTRANSPORTAGENT #define INCL_SOUPTRANSPORTAGENT #include "config.h" #ifdef ENABLE_LIBSOUP #include #include #include #include #include SE_BEGIN_CXX class GLibUnref { public: static void unref(GMainLoop *pointer) { g_main_loop_unref(pointer); } static void unref(SoupMessageBody *pointer) { soup_message_body_free(pointer); } static void unref(SoupBuffer *pointer) { soup_buffer_free(pointer); } static void unref(SoupURI *pointer) { soup_uri_free(pointer); } }; /** * message send/receive with libsoup * * An asynchronous soup session is used and the main loop * is invoked in the wait() method to make progress. */ class SoupTransportAgent : public HTTPTransportAgent { public: /** * @param loop the glib loop to use when waiting for IO; * transport will increase the reference count; * if NULL a new loop in the default context is used */ SoupTransportAgent(GMainLoop *loop = NULL); ~SoupTransportAgent(); virtual void setURL(const std::string &url); virtual void setProxy(const std::string &proxy); virtual void setProxyAuth(const std::string &user, const std::string &password); virtual void setSSL(const std::string &cacerts, bool verifyServer, bool verifyHost); virtual void setContentType(const std::string &type); virtual void setUserAgent(const std::string &agent); virtual void shutdown() { m_status = CLOSED; } virtual void send(const char *data, size_t len); virtual void cancel(); virtual Status wait(bool noReply = false); virtual void getReply(const char *&data, size_t &len, std::string &contentType); virtual void setTimeout(int seconds); gboolean processCallback(); private: std::string m_proxyUser; std::string m_proxyPassword; std::string m_cacerts; bool m_verifySSL; std::string m_URL; std::string m_contentType; eptr m_session; eptr m_loop; Status m_status; std::string m_failure; SoupMessage *m_message; GLibEvent m_timeoutEventSource; int m_timeoutSeconds; /** This function is called regularly to detect timeout */ static gboolean TimeoutCallback (gpointer data); /** response, copied from SoupMessage */ eptr m_response; std::string m_responseContentType; /** SoupSessionCallback, redirected into user_data->HandleSessionCallback() */ static void SessionCallback(SoupSession *session, SoupMessage *msg, gpointer user_data); void HandleSessionCallback(SoupSession *session, SoupMessage *msg); }; SE_END_CXX #endif // ENABLE_LIBSOUP #endif // INCL_TRANSPORTAGENT syncevolution_1.4/src/syncevo/StringDataBlob.cpp000066400000000000000000000034561230021373600222060ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include SE_BEGIN_CXX class FinalizeWrite { boost::shared_ptr m_data; public: FinalizeWrite(const boost::shared_ptr &data) : m_data(data) {} void operator() (std::ostringstream *stream) { if (stream) { m_data->assign(stream->str()); delete stream; } } }; StringDataBlob::StringDataBlob(const std::string &name, const boost::shared_ptr &data, bool readonly) : m_name(name), m_data(data), m_readonly(readonly) { } boost::shared_ptr StringDataBlob::write() { return boost::shared_ptr(new std::ostringstream, FinalizeWrite(m_data)); } boost::shared_ptr StringDataBlob::read() { return boost::shared_ptr(new std::istringstream(m_data ? *m_data : "")); } SE_END_CXX syncevolution_1.4/src/syncevo/StringDataBlob.h000066400000000000000000000040371230021373600216470ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_EVOLUTION_STRING_DATA_BLOB # define INCL_EVOLUTION_STRING_DATA_BLOB #include #include #include #include SE_BEGIN_CXX /** * Stores data chunk in memory. * Ownership of that memory is shared. */ class StringDataBlob : public DataBlob { std::string m_name; boost::shared_ptr m_data; bool m_readonly; public: /** * @param name name for the data blob * @param data reference to string holding data, NULL pointer if it doesn't exist * @param readonly true if write() is meant to fail */ StringDataBlob(const std::string &name, const boost::shared_ptr &data, bool readonly); /** writing ends and data is updated when the ostream pointer is destructed */ virtual boost::shared_ptr write(); virtual boost::shared_ptr read(); virtual boost::shared_ptr getData() { return m_data; } virtual std::string getName() const { return m_name; } virtual bool exists() const { return m_data; } virtual bool isReadonly() const { return m_readonly; } }; SE_END_CXX #endif // INCL_EVOLUTION_STRING_DATA_BLOB syncevolution_1.4/src/syncevo/SuspendFlags.cpp000066400000000000000000000247031230021373600217430ustar00rootroot00000000000000/* * Copyright (C) 2005-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include SE_BEGIN_CXX static RecMutex suspendRecMutex; SuspendFlags::SuspendFlags() : m_level(Logger::INFO), m_state(NORMAL), m_receivedSignals(0), m_lastSuspend(0), m_senderFD(-1), m_receiverFD(-1), m_activeSignals(0) { } SuspendFlags::~SuspendFlags() { deactivate(); } SuspendFlags &SuspendFlags::getSuspendFlags() { // never free the instance, other singletons might depend on it RecMutex::Guard guard = suspendRecMutex.lock(); static SuspendFlags *flags; if (!flags) { flags = new SuspendFlags; } return *flags; } static gboolean SignalChannelReadyCB(GIOChannel *source, GIOCondition condition, gpointer data) throw() { try { RecMutex::Guard guard = suspendRecMutex.lock(); SuspendFlags &me = SuspendFlags::getSuspendFlags(); me.printSignals(); } catch (...) { Exception::handle(); } return TRUE; } /** * Own glib IO watch for file descriptor * which calls printSignals() */ class GLibGuard : public SuspendFlags::Guard { GIOChannel *m_channel; guint m_channelReady; public: GLibGuard(int fd) { // glib watch which calls printSignals() m_channel = g_io_channel_unix_new(fd); m_channelReady = g_io_add_watch(m_channel, G_IO_IN, SignalChannelReadyCB, NULL); } ~GLibGuard() { if (m_channelReady) { g_source_remove(m_channelReady); m_channelReady = 0; } if (m_channel) { g_io_channel_unref(m_channel); m_channel = NULL; } } }; SuspendFlags::State SuspendFlags::getState() const { RecMutex::Guard guard = suspendRecMutex.lock(); if (m_abortBlocker.lock()) { // active abort blocker return ABORT; } else if (m_suspendBlocker.lock()) { // active suspend blocker return SUSPEND; } else { return m_state; } } uint32_t SuspendFlags::getReceivedSignals() const { RecMutex::Guard guard = suspendRecMutex.lock(); return m_receivedSignals; } Logger::Level SuspendFlags::getLevel() const { RecMutex::Guard guard = suspendRecMutex.lock(); return m_level; } void SuspendFlags::setLevel(Logger::Level level) { RecMutex::Guard guard = suspendRecMutex.lock(); m_level = level; } bool SuspendFlags::isAborted() { RecMutex::Guard guard = suspendRecMutex.lock(); printSignals(); return getState() == ABORT; } bool SuspendFlags::isSuspended() { RecMutex::Guard guard = suspendRecMutex.lock(); printSignals(); return getState() == SUSPEND; } bool SuspendFlags::isNormal() { RecMutex::Guard guard = suspendRecMutex.lock(); printSignals(); return getState() == NORMAL; } void SuspendFlags::checkForNormal() { RecMutex::Guard guard = suspendRecMutex.lock(); printSignals(); if (getState() != NORMAL) { SE_THROW_EXCEPTION_STATUS(StatusException, "aborting as requested by user", (SyncMLStatus)sysync::LOCERR_USERABORT); } } boost::shared_ptr SuspendFlags::suspend() { return block(m_suspendBlocker); } boost::shared_ptr SuspendFlags::abort() { return block(m_abortBlocker); } boost::shared_ptr SuspendFlags::block(boost::weak_ptr &blocker) { RecMutex::Guard guard = suspendRecMutex.lock(); State oldState = getState(); boost::shared_ptr res = blocker.lock(); if (!res) { res.reset(new StateBlocker); blocker = res; } State newState = getState(); // only alert receiving side if going from normal -> suspend // or suspend -> abort if (newState > oldState && m_senderFD >= 0) { unsigned char msg = newState; write(m_senderFD, &msg, 1); } // don't depend on pipes or detecting that change, alert // listeners directly if (newState != oldState) { m_stateChanged(*this); } return res; } boost::shared_ptr SuspendFlags::activate(uint32_t sigmask) { SE_LOG_DEBUG(NULL, "SuspendFlags: (re)activating, currently %s", m_senderFD > 0 ? "active" : "inactive"); if (m_senderFD > 0) { return m_guard.lock(); } int fds[2]; if (pipe(fds)) { SE_THROW(StringPrintf("allocating pipe for signals failed: %s", strerror(errno))); } // nonblocking, to avoid deadlocks when the pipe's buffer overflows fcntl(fds[0], F_SETFL, fcntl(fds[0], F_GETFL) | O_NONBLOCK); fcntl(fds[1], F_SETFL, fcntl(fds[1], F_GETFL) | O_NONBLOCK); m_senderFD = fds[1]; m_receiverFD = fds[0]; SE_LOG_DEBUG(NULL, "SuspendFlags: activating signal handler(s) with fds %d->%d", m_senderFD, m_receiverFD); for (int sig = 0; sig < 32; sig++) { if (sigmask & (1< guard(new GLibGuard(m_receiverFD)); m_guard = guard; return guard; } void SuspendFlags::deactivate() { SE_LOG_DEBUG(NULL, "SuspendFlags: deactivating fds %d->%d", m_senderFD, m_receiverFD); if (m_receiverFD >= 0) { for (int sig = 0; sig < 32; sig++) { if (m_activeSignals & (1<= 0) { msg[0] = (unsigned char)(ABORT_MAX + sig); write(me.m_senderFD, msg, msg[1] == ABORT_MAX ? 1 : 2); } } void SuspendFlags::printSignals() { RecMutex::Guard guard = suspendRecMutex.lock(); if (m_receiverFD >= 0) { unsigned char msg; while (read(m_receiverFD, &msg, 1) == 1) { SE_LOG_DEBUG(NULL, "SuspendFlags: read %d from fd %d", msg, m_receiverFD); const char *str = NULL; switch (msg) { case SUSPEND: str = "Asking to suspend...\nPress CTRL-C again quickly (within 2s) to stop immediately (can cause problems in the future!)"; break; case SUSPEND_AGAIN: str = "Suspend in progress...\nPress CTRL-C again quickly (within 2s) to stop immediately (can cause problems in the future!)"; break; case ABORT: str = "Aborting immediately ..."; break; case ABORT_AGAIN: str = "Already aborting as requested earlier ..."; break; default: { int sig = msg - ABORT_MAX; SE_LOG_DEBUG(NULL, "reveived signal %d", sig); m_receivedSignals |= 1< #include #include #include #include #include #include SE_BEGIN_CXX /** * A singleton which is responsible for signal handling. Supports * "SIGINT = suspend sync" and "two quick successive SIGINT = abort" * semantic. SIGTERM is always aborting. * * Can be polled and in addition, flags state changes by writing to a * file descriptor for integration into an event loop. * * All methods are thread-safe. activate() and deactivate() need to * modify global process state and should only be used when it is safe * to do so. */ class SuspendFlags { public: /** SIGINT twice within this amount of seconds aborts the sync */ static const time_t ABORT_INTERVAL = 2; enum State { /** keep sync running */ NORMAL, /** suspend sync */ SUSPEND, /** abort sync */ ABORT, /** suspend sync request received again (only written to event FD, not returned by getState()) */ SUSPEND_AGAIN, /** abort sync request received again (only written to event FD, not returned by getState()) */ ABORT_AGAIN, ABORT_MAX }; /** access to singleton */ static SuspendFlags &getSuspendFlags(); /** * Current status. It is a combination of several indicators: * - state set via signals (cannot be reset) * - "suspend" while requested with suspend() (resets when no longer needed) * - "abort" while requested with aborted() (also resets) * * The overall state is the maximum (NORMAL < SUSPEND < ABORT). */ State getState() const; /** * Returns or-ed mask of all signals handled so far. * See activate(). */ uint32_t getReceivedSignals() const; /** * Checks for status changes and returns true iff status is ABORT. */ bool isAborted(); /** * Checks for status changes and returns true iff status is SUSPEND. */ bool isSuspended(); /** * Checks for status changes and returns true iff status is NORMAL. */ bool isNormal(); /** * Checks for status changes and throws a "aborting as requested by user" StatusException with * LOCERR_USERABORT as status code if the current state is not * NORMAL. In other words, suspend and abort are both * treated like an abort request. */ void checkForNormal(); /** * Users of this class can read a single char for each received * signal from this file descriptor. The char is the State that * was entered by that signal. This can be used to be notified * immediately about changes, without having to poll. * * -1 if not activated. */ int getEventFD() const { return m_receiverFD; } class Guard { public: virtual ~Guard() { getSuspendFlags().deactivate(); } }; /** * Allocate file descriptors, set signal handlers for the chosen * signals (SIGINT and SIGTERM by default). Once the returned * guard is freed, it will automatically deactivate signal * handling. * * Additional signals like SIGURG or SIGIO may also be used. It is * unlikely that any library used by SyncEvolution occupies these * signals for its own use. * * Only SIGINT and SIGTERM influence the overall State. All * received signals, including SIGINT and SIGTERM, are recorded * and can be retrieved via getReceivedSignals(). * * It is possible to call activate multiple times. All following * calls do nothing except creating a new reference to the same * guard. In particular they cannot add or remove handled * signals. */ boost::shared_ptr activate(uint32_t sigmask = (1< StateChanged_t; StateChanged_t m_stateChanged; /** * React to a SIGINT or SIGTERM. * * Installed as signal handler by activate() if no other signal * handler was set. May also be called by other signal handlers, * regardless whether activated or not. */ static void handleSignal(int sig); class StateBlocker {}; /** * Requests a state change to "suspend". The request * remains active and affects getState() until * the returned StateBlocker is destructed, i.e., * the last reference is dropped. * * A state change will be pushed into the pipe if it really * changed as part of taking the suspend lock. */ boost::shared_ptr suspend(); /** * Same as suspend(), except that it asks for an abort. */ boost::shared_ptr abort(); /** log level of the "aborting" messages */ Logger::Level getLevel() const; void setLevel(Logger::Level level); private: SuspendFlags(); ~SuspendFlags(); Logger::Level m_level; /** free file descriptors, restore signal handlers */ void deactivate(); /** state as observed by signal handler */ State m_state; /** or-ed bit mask of all received signals */ uint32_t m_receivedSignals; /** time is measured inside signal handler */ time_t m_lastSuspend; int m_senderFD, m_receiverFD; // For the sake of simplicity we only support signals in the 1-31 range. uint32_t m_activeSignals; struct sigaction m_oldSignalHandlers[32]; boost::weak_ptr m_guard; boost::weak_ptr m_suspendBlocker, m_abortBlocker; boost::shared_ptr block(boost::weak_ptr &blocker); }; SE_END_CXX #endif // INCL_SUSPENDFLAGS syncevolution_1.4/src/syncevo/SyncConfig.cpp000066400000000000000000005114421230021373600214100ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include SE_BEGIN_CXX const char *const SourceAdminDataName = "adminData"; int ConfigVersions[CONFIG_LEVEL_MAX][CONFIG_VERSION_MAX] = { { CONFIG_ROOT_MIN_VERSION, CONFIG_ROOT_CUR_VERSION }, { CONFIG_CONTEXT_MIN_VERSION, CONFIG_CONTEXT_CUR_VERSION }, { CONFIG_PEER_MIN_VERSION, CONFIG_PEER_CUR_VERSION }, }; std::string ConfigLevel2String(ConfigLevel level) { switch (level) { case CONFIG_LEVEL_ROOT: return "config root"; break; case CONFIG_LEVEL_CONTEXT: return "context config"; break; case CONFIG_LEVEL_PEER: return "peer config"; break; default: return StringPrintf("config level %d (?)", level); break; } } PropertySpecifier PropertySpecifier::StringToPropSpec(const std::string &spec, int flags) { PropertySpecifier res; size_t slash = spec.find('/'); if (slash != spec.npos) { // no normalization needed at the moment res.m_source = spec.substr(0, slash); slash++; } else { slash = 0; } size_t at = spec.find('@', slash); if (at != spec.npos) { // Context or config? if (spec.find('@', at + 1) != spec.npos) { // has a second @ sign, must be config name res.m_config = spec.substr(at + 1); } else { // context, include leading @ sign res.m_config = spec.substr(at); } if (flags & NORMALIZE_CONFIG) { res.m_config = SyncConfig::normalizeConfigString(res.m_config, SyncConfig::NORMALIZE_LONG_FORMAT); } } else { at = spec.size(); } res.m_property = spec.substr(slash, at - slash); return res; } UserIdentity::UserIdentity() : m_provider(InitStateString(USER_IDENTITY_PLAIN_TEXT, false)) { } UserIdentity UserIdentity::fromString(const InitStateString &idString) { UserIdentity id; if (idString.wasSet()) { size_t off = idString.find(':'); if (off != idString.npos) { id.m_provider = off ? idString.substr(0, off) : InitStateString(USER_IDENTITY_PLAIN_TEXT, false); id.m_identity = idString.substr(off + 1); } else { id.m_provider = InitStateString(USER_IDENTITY_PLAIN_TEXT, false); id.m_identity = idString; } } return id; } InitStateString UserIdentity::toString() const { std::string str; if (m_provider.wasSet()) { str += m_provider; str += ':'; } str += m_identity; return InitStateString(str, m_provider.wasSet() || m_identity.wasSet()); } std::string PropertySpecifier::toString() { std::string res; res.reserve(m_source.size() + 1 + m_property.size() + 1 + m_config.size()); res += m_source; if (!m_source.empty()) { res += '/'; } res += m_property; if (!m_config.empty()) { if (m_config[0] != '@') { res += '@'; } res += m_config; } return res; } string ConfigProperty::getName(const ConfigNode &node) const { if (m_names.empty()) { // shouldn't happen return "???"; } if (m_names.size() == 1) { // typical case for most properties return m_names.front(); } // pick the name already used in the node BOOST_FOREACH(const std::string &name, m_names) { string value; if (node.getProperty(name, value)) { return name; } } // main name as fallback return m_names.front(); } void ConfigProperty::splitComment(const string &comment, list &commentLines) { size_t start = 0; while (true) { size_t end = comment.find('\n', start); if (end == comment.npos) { commentLines.push_back(comment.substr(start)); break; } else { commentLines.push_back(comment.substr(start, end - start)); start = end + 1; } } } void ConfigProperty::throwValueError(const ConfigNode &node, const string &name, const string &value, const string &error) const { SyncContext::throwError(node.getName() + ": " + name + " = " + value + ": " + error); } std::string ConfigProperty::sharing2str(Sharing sharing) { switch (sharing) { case GLOBAL_SHARING: return "global"; break; case SOURCE_SET_SHARING: return "shared"; break; case NO_SHARING: return "unshared"; break; } return "???"; } string SyncConfig::normalizeConfigString(const string &config, NormalizeFlags flags) { string normal = config; boost::to_lower(normal); BOOST_FOREACH(char &character, normal) { if (!isprint(character) || character == '/' || character == '\\' || character == ':') { character = '_'; } } if (boost::ends_with(normal, "@default")) { if (flags & NORMALIZE_SHORTHAND) { normal.resize(normal.size() - strlen("@default")); } } else if (boost::ends_with(normal, "@")) { normal.resize(normal.size() - 1); } else { size_t at = normal.rfind('@'); if (at == normal.npos && !(flags & NORMALIZE_IS_NEW)) { // No explicit context. Pick the first server which matches // when ignoring their context. Peer list is sorted by name, // therefore shorter config names (= without context) are // found first, as intended. BOOST_FOREACH(const StringPair &entry, getConfigs()) { string entry_peer, entry_context; splitConfigString(entry.first, entry_peer, entry_context); if (normal == entry_peer) { // found a matching, existing config, use it normal = entry.first; break; } } } if (!(flags & NORMALIZE_SHORTHAND) && normal.find('@') == normal.npos) { // explicitly include @default context specifier normal += "@default"; } } if (normal.empty()) { // default context is meant with the empty string, // better make that explicit normal = "@default"; } return normal; } std::string SyncConfig::DeviceDescription::getFingerprint() const { std::string fingerprint; /** In the case that we have the PnpInformation we prefer it over * the mutable device name. The is true even if we only found the * vendor component of the PnpInformation. */ if (m_pnpInformation) { if(m_pnpInformation->isKnownProduct()) fingerprint = m_pnpInformation->m_product; else fingerprint = m_pnpInformation->m_vendor; } else { fingerprint = m_deviceName; } return fingerprint; } bool SyncConfig::splitConfigString(const string &config, string &peer, string &context) { string::size_type at = config.rfind('@'); if (at != config.npos) { peer = config.substr(0, at); context = config.substr(at + 1); return true; } else { peer = config; context = "default"; return false; } } static SyncConfig::ConfigWriteMode defaultConfigWriteMode() { return SyncContext::isStableRelease() ? SyncConfig::MIGRATE_AUTOMATICALLY : SyncConfig::ASK_USER_TO_MIGRATE; } SyncConfig::SyncConfig() : m_layout(HTTP_SERVER_LAYOUT), // use more compact layout with shorter paths and less source nodes m_configWriteMode(defaultConfigWriteMode()) { // initialize properties SyncConfig::getRegistry(); SyncSourceConfig::getRegistry(); m_peerPath = m_contextPath = "volatile"; makeVolatile(); } void SyncConfig::makeVolatile() { m_tree.reset(new VolatileConfigTree()); m_fileTree.reset(); m_peerNode.reset(new VolatileConfigNode()); m_hiddenPeerNode = m_peerNode; m_globalNode = m_peerNode; m_contextNode = m_peerNode; m_contextHiddenNode = m_peerNode; m_props[false] = m_peerNode; m_props[true] = m_peerNode; } void SyncConfig::makeEphemeral() { m_ephemeral = true; // m_hiddenPeerNode.reset(new VolatileConfigNode()); // m_contextHiddenNode = m_hiddenPeerNode; } /** * The goal is to have only one FileConfigTree instance per file system * location. This ensures that in-memory representations remain in sync * when instantiating a SyncConfig is created multiple times. In addition, * FilterConfigNodes also must only exit once per underlying node. This * allows sharing cached passwords between configs when using indirect * password lookup. * * The implementation in both cases is the same: keep a cache with * weak pointers. When asked for an instance, consult the cache first. * If the instance still exists, we can return that shared pointer. If * not, we create a new one. * * It is necessary to garbage-collect obsolete entries, because the * lookup parameters might never be used again. */ class ConfigCache { typedef std::map< std::pair, boost::weak_ptr > TreeMap; TreeMap m_trees; typedef std::map< ConfigNode *, boost::weak_ptr > NodeMap; NodeMap m_nodes; template void purge(M &map) { typename M::iterator it = map.begin(); while (it != map.end()) { if (!it->second.lock()) { typename M::iterator next = it; ++next; map.erase(it); it = next; } else { ++it; } } } void purge(); public: boost::shared_ptr createTree(const std::string &root, SyncConfig::Layout layout); /** * The filter is only installed when creating a new node. It is * assumed to be the same when reusing the node. */ boost::shared_ptr createNode(const boost::shared_ptr &node, const FilterConfigNode::ConfigFilter &filter = FilterConfigNode::ConfigFilter()); static ConfigCache &singleton(); }; ConfigCache &ConfigCache::singleton() { static ConfigCache instance; return instance; } void ConfigCache::purge() { purge(m_trees); purge(m_nodes); } boost::shared_ptr ConfigCache::createTree(const std::string &root, SyncConfig::Layout layout) { TreeMap::mapped_type &entry = m_trees[TreeMap::key_type(root, layout)]; boost::shared_ptr result; result = entry.lock(); if (!result) { result.reset(new FileConfigTree(root, layout)); entry = result; } purge(); return result; } boost::shared_ptr ConfigCache::createNode(const boost::shared_ptr &node, const FilterConfigNode::ConfigFilter &filter) { NodeMap::mapped_type &entry = m_nodes[node.get()]; boost::shared_ptr result; result = entry.lock(); if (!result) { result.reset(new FilterConfigNode(node, filter)); entry = result; } purge(); return result; } SyncConfig::SyncConfig(const string &peer, boost::shared_ptr tree, const string &redirectPeerRootPath) : m_layout(SHARED_LAYOUT), m_redirectPeerRootPath(redirectPeerRootPath), m_configWriteMode(defaultConfigWriteMode()) { // initialize properties SyncConfig::getRegistry(); SyncSourceConfig::getRegistry(); string root; m_peer = normalizeConfigString(peer); // except for SHARED_LAYOUT (set below), // everything is below the directory called like // the peer m_peerPath = m_contextPath = m_peer; if (tree.get() != NULL) { // existing tree points into simple configuration m_tree = tree; m_layout = HTTP_SERVER_LAYOUT; m_peerPath = m_contextPath = ""; } else { // search for configuration in various places... root = getOldRoot(); string path = root + "/" + m_peerPath; if (!access((path + "/spds/syncml/config.txt").c_str(), F_OK)) { m_layout = SYNC4J_LAYOUT; } else { m_layout = SHARED_LAYOUT; root = getNewRoot(); path = root + "/" + m_peerPath; if (!access((path + "/config.ini").c_str(), F_OK) && !access((path + "/sources").c_str(), F_OK) && access((path + "/peers").c_str(), F_OK)) { m_layout = HTTP_SERVER_LAYOUT; } else { // check whether config name specifies a context, // otherwise use "default" splitConfigString(m_peer, m_peerPath, m_contextPath); if (!m_peerPath.empty()) { m_peerPath = m_contextPath + "/peers/" + m_peerPath; } } } m_fileTree = ConfigCache::singleton().createTree(root, m_layout); m_tree = m_fileTree; } string path; boost::shared_ptr node; switch (m_layout) { case SYNC4J_LAYOUT: // all properties reside in the same node path = m_peerPath + "/spds/syncml"; node = m_tree->open(path, ConfigTree::visible); m_peerNode = ConfigCache::singleton().createNode(node); m_globalNode = m_contextNode = m_peerNode; m_hiddenPeerNode = m_contextHiddenNode = m_globalHiddenNode = node; m_props[false] = m_peerNode; m_props[true] = ConfigCache::singleton().createNode(m_hiddenPeerNode); break; case HTTP_SERVER_LAYOUT: { // properties which are normally considered shared are // stored in the same nodes as the per-peer properties, // except for global ones path = ""; node = m_tree->open(path, ConfigTree::visible); m_globalNode = ConfigCache::singleton().createNode(node); node = m_tree->open(path, ConfigTree::hidden); m_globalHiddenNode = node; path = m_peerPath; node = m_tree->open(path, ConfigTree::visible); m_peerNode = ConfigCache::singleton().createNode(node); m_contextNode = m_peerNode; m_hiddenPeerNode = m_contextHiddenNode = m_tree->open(path, ConfigTree::hidden); // similar multiplexing as for SHARED_LAYOUT, // with two nodes underneath boost::shared_ptr mnode; mnode.reset(new MultiplexConfigNode(m_peerNode->getName(), getRegistry(), false)); m_props[false] = mnode; mnode->setNode(false, ConfigProperty::GLOBAL_SHARING, m_globalNode); mnode->setNode(false, ConfigProperty::SOURCE_SET_SHARING, m_peerNode); mnode->setNode(false, ConfigProperty::NO_SHARING, m_peerNode); mnode.reset(new MultiplexConfigNode(m_peerNode->getName(), getRegistry(), true)); m_props[true] = mnode; mnode->setNode(true, ConfigProperty::GLOBAL_SHARING, m_globalHiddenNode); mnode->setNode(true, ConfigProperty::SOURCE_SET_SHARING, m_peerNode); mnode->setNode(true, ConfigProperty::NO_SHARING, m_peerNode); break; } case SHARED_LAYOUT: // really use different nodes for everything path = ""; node = m_tree->open(path, ConfigTree::visible); m_globalNode = ConfigCache::singleton().createNode(node); node = m_tree->open(path, ConfigTree::hidden); m_globalHiddenNode = node; path = m_peerPath; if (path.empty()) { if (!m_redirectPeerRootPath.empty()) { node.reset(new IniFileConfigNode(m_redirectPeerRootPath, ".internal.ini", false)); node = m_tree->add(m_redirectPeerRootPath + "/.internal.ini", node); } else { node.reset(new DevNullConfigNode(m_contextPath + " without peer config")); } } else { node = m_tree->open(path, ConfigTree::visible); } m_peerNode = ConfigCache::singleton().createNode(node); if (path.empty()) { m_hiddenPeerNode = m_peerNode; } else { m_hiddenPeerNode = m_tree->open(path, ConfigTree::hidden); } path = m_contextPath; node = m_tree->open(path, ConfigTree::visible); m_contextNode = ConfigCache::singleton().createNode(node); m_contextHiddenNode = m_tree->open(path, ConfigTree::hidden); // Instantiate multiplexer with the most specific node name in // the set, the peer node's name. This is slightly inaccurate: // error messages generated for this node in will reference // the wrong config.ini file for shared properties. But // there no shared properties which can trigger such an error // at the moment, so this is good enough for now (MB#8037). boost::shared_ptr mnode; mnode.reset(new MultiplexConfigNode(m_peerNode->getName(), getRegistry(), false)); mnode->setHavePeerNodes(!m_peerPath.empty()); m_props[false] = mnode; mnode->setNode(false, ConfigProperty::GLOBAL_SHARING, m_globalNode); mnode->setNode(false, ConfigProperty::SOURCE_SET_SHARING, m_contextNode); mnode->setNode(false, ConfigProperty::NO_SHARING, m_peerNode); mnode.reset(new MultiplexConfigNode(m_hiddenPeerNode->getName(), getRegistry(), true)); mnode->setHavePeerNodes(!m_peerPath.empty()); m_props[true] = mnode; mnode->setNode(true, ConfigProperty::SOURCE_SET_SHARING, m_contextHiddenNode); mnode->setNode(true, ConfigProperty::NO_SHARING, m_hiddenPeerNode); mnode->setNode(true, ConfigProperty::GLOBAL_SHARING, m_globalHiddenNode); break; } // read version check for (ConfigLevel level = CONFIG_LEVEL_ROOT; level < CONFIG_LEVEL_MAX; level = (ConfigLevel)(level + 1)) { if (exists(level)) { if (getConfigVersion(level, CONFIG_MIN_VERSION) > ConfigVersions[level][CONFIG_CUR_VERSION]) { SE_LOG_INFO(NULL, "config version check failed: %s has format %d, but this SyncEvolution release only supports format %d", ConfigLevel2String(level).c_str(), getConfigVersion(level, CONFIG_MIN_VERSION), ConfigVersions[level][CONFIG_CUR_VERSION]); // our code is too old to read the config, reject it SE_THROW_EXCEPTION_STATUS(StatusException, StringPrintf("SyncEvolution %s is too old to read configuration '%s', please upgrade SyncEvolution.", VERSION, peer.c_str()), STATUS_RELEASE_TOO_OLD); } } } // Note that the version check does not reject old configs because // they are too old; so far, any release must be able to read any // older config. } void SyncConfig::prepareConfigForWrite() { // check versions before bumping to something incompatible with the // previous user of the config for (ConfigLevel level = CONFIG_LEVEL_ROOT; level < CONFIG_LEVEL_MAX; level = (ConfigLevel)(level + 1)) { if (getLayout() < SHARED_LAYOUT && level < CONFIG_LEVEL_PEER) { // old configs do not have explicit root or context, // only check peer config itself continue; } if (exists(level)) { if (getConfigVersion(level, CONFIG_CUR_VERSION) < ConfigVersions[level][CONFIG_MIN_VERSION]) { // release which created config will no longer be able to read // updated config; either alert user or migrate automatically string config; switch (level) { case CONFIG_LEVEL_CONTEXT: config = getContextName(); break; case CONFIG_LEVEL_PEER: config = getConfigName(); break; case CONFIG_LEVEL_ROOT: case CONFIG_LEVEL_MAX: // keep compiler happy, not reached for _MAX break; } SE_LOG_INFO(NULL, "must change format of %s '%s' in backward-incompatible way", ConfigLevel2String(level).c_str(), config.c_str()); if (m_configWriteMode == MIGRATE_AUTOMATICALLY) { // migrate config and anything beneath it, // so no further checking needed migrate(config); break; } else { SE_THROW_EXCEPTION_STATUS(StatusException, StringPrintf("Proceeding would modify config '%s' such " "that the previous SyncEvolution release " "will not be able to use it. Stopping now. " "Please explicitly acknowledge this step by " "running the following command on the command " "line: syncevolution --migrate '%s'", config.c_str(), config.c_str()), STATUS_MIGRATION_NEEDED); } } } } // now set current versions at all levels, // but without reducing versions: if a config has format // "cur = 10", then properties or features added in that // format remain even if the config is (temporarily?) used // by a SyncEvolution binary which has "cur = 5". for (ConfigLevel level = CONFIG_LEVEL_ROOT; level < CONFIG_LEVEL_MAX; level = (ConfigLevel)(level + 1)) { if (level == CONFIG_LEVEL_PEER && m_peerPath.empty()) { // no need (and no possibility) to set per-peer version) break; } for (ConfigLimit limit = CONFIG_MIN_VERSION; limit < CONFIG_VERSION_MAX; limit = (ConfigLimit)(limit + 1)) { // set if equal to ensure that version == 0 (the default) // is set explicitly if (getConfigVersion(level, limit) <= ConfigVersions[level][limit]) { setConfigVersion(level, limit, ConfigVersions[level][limit]); } } } flush(); } void SyncConfig::migrate(const std::string &config) { if (config.empty()) { // migrating root not yet supported SE_THROW("internal error, migrating config root not implemented"); } else { // migrate using the higher-level logic in the Cmdline class Cmdline migrate(m_peer.c_str(), "--migrate", config.c_str(), NULL); bool res = migrate.parse() && migrate.run(); if (!res) { SE_THROW(StringPrintf("migration of config '%s' failed", config.c_str())); } // files that our tree access may have changed, refresh our // in-memory copy m_tree->reload(); } } string SyncConfig::getRootPath() const { return m_fileTree ? normalizePath(m_fileTree->getRoot() + "/" + ((m_layout == SYNC4J_LAYOUT || hasPeerProperties()) ? m_peerPath : m_contextPath)) : ""; } void SyncConfig::addPeers(const string &root, const std::string &configname, SyncConfig::ConfigList &res) { FileConfigTree tree(root, SyncConfig::HTTP_SERVER_LAYOUT); list servers = tree.getChildren(""); BOOST_FOREACH(const string &server, servers) { // sanity check: only list server directories which actually // contain a configuration. To distinguish between a context // (~/.config/syncevolution/default) and an HTTP server config // (~/.config/syncevolution/scheduleworld), we check for the // "peer" subdirectory that is only in the former. // // Contexts which don't have a peer are therefore incorrectly // listed as a peer. Short of adding a special hidden file // this can't be fixed. This is probably overkill and thus not // done yet. string peerPath = server + "/peers"; if (!access((root + "/" + peerPath).c_str(), F_OK)) { // not a real HTTP server, search for peers BOOST_FOREACH(const string &peer, tree.getChildren(peerPath)) { res.push_back(pair (normalizeConfigString(peer + "@" + server), root + "/" + peerPath + "/" + peer)); } } else if (!access((root + "/" + server + "/" + configname).c_str(), F_OK)) { res.push_back(pair (server, root + "/" + server)); } } } /** returns true if a precedes b (strict weak ordering) */ static bool cmpConfigEntries(const StringPair &a, const StringPair &b) { string peerA, contextA, peerB, contextB; SyncConfig::splitConfigString(a.first, peerA, contextA); SyncConfig::splitConfigString(b.first, peerB, contextB); int res; res = contextA.compare(contextB); if (res == 0) { res = peerA.compare(peerB); if (res == 0) { res = a.second.compare(b.second); } } return res < 0; } SyncConfig::ConfigList SyncConfig::getConfigs() { ConfigList res; addPeers(getOldRoot(), "config.txt", res); addPeers(getNewRoot(), "config.ini", res); // Sort the list by (context, peer name, path); // better than returning it in random order. // This sort order (compared to simple lexical // sorting based on the full config name) has // the advantage that peer names or contexts with // suffix (foo.old vs. foo) come later. res.sort(cmpConfigEntries); return res; } static string SyncEvolutionTemplateDir() { string templateDir(TEMPLATE_DIR); const char *envvar = getenv("SYNCEVOLUTION_TEMPLATE_DIR"); if (envvar) { templateDir = envvar; } return templateDir; } SyncConfig::TemplateList SyncConfig::matchPeerTemplates(const DeviceList &peers, bool fuzzyMatch) { TemplateList result; // match against all possible templates without any assumption on directory // layout, the match is entirely based on the metadata template.ini string templateDir(SyncEvolutionTemplateDir()); std::queue > directories; directories.push(templateDir); templateDir = SubstEnvironment("${XDG_CONFIG_HOME}/syncevolution-templates"); directories.push(templateDir); while (!directories.empty()) { string sDir = directories.front(); directories.pop(); if (isDir(sDir)) { // check all sub directories ReadDir dir(sDir); BOOST_FOREACH(const string &entry, dir) { // ignore hidden files, . and .. if (!boost::starts_with(entry, ".")) { directories.push(sDir + "/" + entry); } } } else { TemplateConfig templateConf (sDir); if (boost::ends_with(sDir, "~") || !templateConf.isTemplateConfig()) { // ignore temporary files and files which do // not contain a valid template continue; } BOOST_FOREACH (const DeviceList::value_type &entry, peers){ std::string fingerprint(entry.getFingerprint()); // peerName should be empty if no reliable device info is on hand. std::string peerName = entry.m_pnpInformation ? fingerprint : ""; int rank = templateConf.metaMatch (entry.getFingerprint(), entry.m_matchMode); if (fuzzyMatch){ if (rank > TemplateConfig::NO_MATCH) { result.push_back (boost::shared_ptr( new TemplateDescription(templateConf.getTemplateId(), templateConf.getDescription(), rank, peerName, entry.m_deviceId, entry.m_deviceName, sDir, templateConf.getFingerprint(), templateConf.getTemplateName() ) )); } } else if (rank == TemplateConfig::BEST_MATCH){ result.push_back (boost::shared_ptr( new TemplateDescription(templateConf.getTemplateId(), templateConf.getDescription(), rank, peerName, entry.m_deviceId, entry.m_deviceName, sDir, templateConf.getFingerprint(), templateConf.getTemplateName()) )); break; } } } } result.sort (TemplateDescription::compare_op); return result; } boost::shared_ptr SyncConfig::createPeerTemplate(const string &server) { if (server.empty()) { // Empty template name => no such template. This check is // necessary because otherwise we end up with SyncConfig(""), // which is a configuration where peer-specific properties // cannot be set, triggering an errror in config->setDevID(). return boost::shared_ptr(); } // case insensitive search for read-only file template config string templateConfig(SyncEvolutionTemplateDir()); // before starting another fuzzy match process, first try to load the // template directly taking the parameter as the path if (server == "none") { // nothing to read from, just set some defaults below } else if (TemplateConfig::isTemplateConfig(server)) { templateConfig = server; } else { SyncConfig::DeviceList devices; devices.push_back (DeviceDescription("", server, MATCH_ALL)); templateConfig = ""; TemplateList templates = matchPeerTemplates (devices, false); if (!templates.empty()) { templateConfig = templates.front()->m_path; } if (templateConfig.empty()) { // return "not found" return boost::shared_ptr(); } } boost::shared_ptr tree(new SingleFileConfigTree(templateConfig)); boost::shared_ptr config(new SyncConfig(server, tree)); boost::shared_ptr source; config->setDefaults(false); config->setDevID(string("syncevolution-") + UUID()); // leave the rest empty for special "none" template if (server == "none") { return config; } // check for icon if (config->getIconURI().empty()) { string dirname, filename; splitPath(templateConfig, dirname, filename); ReadDir dir(getDirname(dirname)); // remove last suffix, regardless what it is size_t pos = filename.rfind('.'); if (pos != filename.npos) { filename.resize(pos); } filename += "-icon"; BOOST_FOREACH(const string &entry, dir) { if (boost::istarts_with(entry, filename)) { config->setIconURI("file://" + dirname + "/" + entry); break; } } } // "default" maps to SyncEvolution server template, which is not // consumer ready. When used as "default" by the GTK sync UI, // the UI expects the "consumer ready" flag to be set. Do that // here. Also unset the peer name, because otherwise it shows // up in the UI. if (server == "default") { config->setConsumerReady(true); config->setUserPeerName(InitStateString()); } return config; } bool SyncConfig::exists() const { return m_peerPath.empty() ? m_contextNode->exists() : m_peerNode->exists(); } bool SyncConfig::exists(ConfigLevel level) const { switch (level) { case CONFIG_LEVEL_ROOT: return m_globalNode->exists(); break; case CONFIG_LEVEL_CONTEXT: return m_contextNode->exists(); break; case CONFIG_LEVEL_PEER: return m_peerNode->exists(); break; default: return false; } } string SyncConfig::getContextName() const { string peer, context; splitConfigString(getConfigName(), peer, context); return string("@") + context; } string SyncConfig::getPeerName() const { string peer, context; splitConfigString(getConfigName(), peer, context); return peer; } list SyncConfig::getPeers() const { list res; if (!hasPeerProperties()) { std::string rootPath = getRootPath(); if (!rootPath.empty()) { FileConfigTree tree(getRootPath(), SHARED_LAYOUT); res = tree.getChildren("peers"); } } return res; } void SyncConfig::preFlush(UserInterface &ui) { /* Iterator over all sync global and source properties * one by one and check whether they need to save password */ /* save password in the global config node */ ConfigPropertyRegistry& registry = getRegistry(); BOOST_FOREACH(const ConfigProperty *prop, registry) { prop->savePassword(ui, *this); } /** grep each source and save their password */ list configuredSources = getSyncSources(); BOOST_FOREACH(const string &sourceName, configuredSources) { //boost::shared_ptr sc = getSyncSourceConfig(sourceName); ConfigPropertyRegistry& registry = SyncSourceConfig::getRegistry(); SyncSourceNodes sourceNodes = getSyncSourceNodes(sourceName); BOOST_FOREACH(const ConfigProperty *prop, registry) { prop->savePassword(ui, *this, sourceName); } } } void SyncConfig::flush() { if (!isEphemeral()) { if (m_fileTree && m_layout == SHARED_LAYOUT && !hasPeerProperties()) { // Ensure that "peers" directory exists for new-style // configs. It would not get created when flushing nodes // for pure context configs otherwise (it's empty), and we // need it to detect new-syle configs. mkdir_p(m_fileTree->getRoot() + "/" + m_contextPath + "/peers"); } m_tree->flush(); } } void SyncConfig::remove() { boost::shared_ptr tree = m_tree; // stop using the config nodes, they might get removed now makeVolatile(); tree->remove(m_peerPath.empty() ? m_contextPath : m_peerPath); } boost::shared_ptr SyncConfig::getSyncSourceConfig(const string &name) { SyncSourceNodes nodes = getSyncSourceNodes(name); return boost::shared_ptr(new PersistentSyncSourceConfig(name, nodes)); } list SyncConfig::getSyncSources() const { // Return *all* sources configured in this context, // not just those configured for the peer. This // is necessary so that sources created for some other peer // show up for the current one, to prevent overwriting // existing properties unintentionally. // Returned sources are an union of: // 1. contextpath/sources // 2. peers/[one-peer]/sources // 3. sources in source filter set sources; list sourceList; if (m_layout == SHARED_LAYOUT) { // get sources in context sourceList = m_tree->getChildren(m_contextPath + "/sources"); sources.insert(sourceList.begin(), sourceList.end()); // get sources from peer if it's not empty and merge into // full set of sources if (!m_peerPath.empty()) { sourceList = m_tree->getChildren(m_peerPath + "/sources"); sources.insert(sourceList.begin(), sourceList.end()); } } else { // get sources from peer sourceList = m_tree->getChildren(m_peerPath + (m_layout == SYNC4J_LAYOUT ? "/spds/sources" : "/sources")); sources.insert(sourceList.begin(), sourceList.end()); } // get sources from filter and union them into returned sources BOOST_FOREACH(const SourceProps::value_type &value, m_sourceFilters) { if (value.first.empty()) { // ignore filter for all sources continue; } sources.insert(value.first); } // Convert back to simple list. As a nice side-effect of // temporarily using a set, the final list is sorted. return list(sources.begin(), sources.end()); } SyncSourceNodes SyncConfig::getSyncSourceNodes(const string &name, const string &changeId) { if (m_nodeCache.find(name) != m_nodeCache.end()) { // reuse existing set of nodes return m_nodeCache[name]; } /** shared source properties */ boost::shared_ptr sharedNode; /** per-peer source properties */ boost::shared_ptr peerNode; /** per-peer internal properties and meta data */ boost::shared_ptr hiddenPeerNode, serverNode, trackingNode; string cacheDir; // store configs lower case even if the UI uses mixed case string lower = name; boost::to_lower(lower); boost::shared_ptr node; string sharedPath, peerPath; switch (m_layout) { case SYNC4J_LAYOUT: peerPath = m_peerPath + "/spds/sources/" + lower; break; case HTTP_SERVER_LAYOUT: peerPath = m_peerPath + "/sources/" + lower; break; case SHARED_LAYOUT: if (!m_peerPath.empty()) { peerPath = m_peerPath + "/sources/" + lower; } sharedPath = m_contextPath + string("/sources/") + lower; break; } // Compatibility mode for reading configs which have "type" instead // of "backend/databaseFormat/syncFormat/forceSyncFormat": determine // the new values based on the old property, then inject the new // values into the SyncSourceNodes by adding an intermediate layer // of FilterConfigNodes. The top FilterConfigNode layer is the one // which might get modified, the one underneath remains hidden and // thus preserves the new values even if the caller does a setFilter(). bool compatMode = getConfigVersion(CONFIG_LEVEL_CONTEXT, CONFIG_CUR_VERSION) < 1; SourceType sourceType; if (compatMode) { node = m_tree->open(peerPath.empty() ? sharedPath : peerPath, ConfigTree::visible); string type; if (node->getProperty("type", type)) { sourceType = SourceType(type); } else { // not set: avoid compatibility mode compatMode = false; } } if (peerPath.empty()) { node.reset(new DevNullConfigNode(m_contextPath + " without peer configuration")); peerNode = ConfigCache::singleton().createNode(node); hiddenPeerNode = trackingNode = serverNode = node; } else { // Here we assume that m_tree is a FileConfigTree. Otherwise getRootPath() // will not point into a normal file system. We fall back to not allowing // the usage of a cache dir by using /dev/null in that case. std::string rootPath = getRootPath(); if (rootPath.empty()) { cacheDir = "/dev/null"; } else { cacheDir = rootPath + "/" + peerPath + "/.cache"; } node = m_tree->open(peerPath, ConfigTree::visible); if (compatMode) { boost::shared_ptr compat(new FilterConfigNode(node)); compat->addFilter("syncFormat", InitStateString(sourceType.m_format, !sourceType.m_format.empty())); compat->addFilter("forceSyncFormat", sourceType.m_forceFormat ? InitStateString("1", true) : InitStateString("0", false)); if (sharedPath.empty()) { compat->addFilter("databaseFormat", InitStateString(sourceType.m_localFormat, !sourceType.m_localFormat.empty())); compat->addFilter("backend", InitStateString(sourceType.m_backend, !sourceType.m_backend.empty())); } node = compat; } peerNode = ConfigCache::singleton().createNode(node, m_sourceFilters.createSourceFilter(name)); hiddenPeerNode = m_tree->open(peerPath, ConfigTree::hidden); trackingNode = m_tree->open(peerPath, ConfigTree::other, changeId); serverNode = m_tree->open(peerPath, ConfigTree::server, changeId); } if (isEphemeral()) { // Throw away meta data. trackingNode.reset(new VolatileConfigNode); hiddenPeerNode.reset(new VolatileConfigNode); serverNode.reset(new VolatileConfigNode); } else if (!m_redirectPeerRootPath.empty()) { // Local sync: overwrite per-peer nodes with nodes inside the // parents tree. Otherwise different configs syncing locally // against the same context end up sharing .internal.ini and // .other.ini files inside that context. string path = m_redirectPeerRootPath + "/sources/" + lower; trackingNode.reset(new IniHashConfigNode(path, ".other.ini", false)); trackingNode = m_tree->add(path + "/.other.ini", trackingNode); if (peerPath.empty()) { hiddenPeerNode = peerNode; } else { hiddenPeerNode = boost::static_pointer_cast(m_tree->add(path + "/.internal.ini", peerNode)); } } if (sharedPath.empty()) { sharedNode = peerNode; } else { node = m_tree->open(sharedPath, ConfigTree::visible); if (compatMode) { boost::shared_ptr compat(new FilterConfigNode(node)); compat->addFilter("databaseFormat", InitStateString(sourceType.m_localFormat, !sourceType.m_localFormat.empty())); compat->addFilter("backend", InitStateString(sourceType.m_backend, !sourceType.m_backend.empty())); node = compat; } sharedNode = ConfigCache::singleton().createNode(node, m_sourceFilters.createSourceFilter(name)); } SyncSourceNodes nodes(!peerPath.empty(), sharedNode, peerNode, hiddenPeerNode, trackingNode, serverNode, cacheDir); m_nodeCache.insert(make_pair(name, nodes)); return nodes; } ConstSyncSourceNodes SyncConfig::getSyncSourceNodes(const string &name, const string &changeId) const { return const_cast(this)->getSyncSourceNodes(name, changeId); } SyncSourceNodes SyncConfig::getSyncSourceNodesNoTracking(const string &name) { SyncSourceNodes nodes = getSyncSourceNodes(name); boost::shared_ptr dummy(new VolatileConfigNode()); return SyncSourceNodes(nodes.m_havePeerNode, nodes.m_sharedNode, nodes.m_peerNode, nodes.m_hiddenPeerNode, dummy, nodes.m_serverNode, nodes.m_cacheDir); } static ConfigProperty syncPropSyncURL("syncURL", "Identifies how to contact the peer,\n" "best explained with some examples.\n\n" "HTTP(S) SyncML servers::\n\n" " http://my.funambol.com/sync\n" " http://sync.scheduleworld.com/funambol/ds\n" " https://m.google.com/syncml\n\n" "OBEX over Bluetooth uses the MAC address, with\n" "the channel chosen automatically::\n\n" " obex-bt://00:0A:94:03:F3:7E\n\n" "If the automatism fails, the channel can also be specified::\n\n" " obex-bt://00:0A:94:03:F3:7E+16\n\n" "For peers contacting us via Bluetooth, the MAC address is\n" "used to identify it before the sync starts. Multiple\n" "urls can be specified in one syncURL property::\n\n" " obex-bt://00:0A:94:03:F3:7E obex-bt://00:01:02:03:04:05\n\n" "In the future this might be used to contact the peer\n" "via one of several transports; right now, only the first\n" "one is tried." // MB #9446 ); static ConfigProperty syncPropDevID("deviceId", "The SyncML server gets this string and will use it to keep track of\n" "changes that still need to be synchronized with this particular\n" "client; it must be set to something unique (like the pseudo-random\n" "string created automatically for new configurations) among all clients\n" "accessing the same server.\n" "myFUNAMBOL also requires that the string starts with sc-pim-"); static ConfigProperty syncPropUsername("username", "user name used for authorization with the SyncML server", ""); static BoolConfigProperty syncPropPeerIsClient("PeerIsClient", "Indicates whether this configuration is about a\n" "client peer or server peer.\n", "FALSE"); static SafeConfigProperty syncPropRemoteDevID("remoteDeviceId", "SyncML ID of our peer, empty if unknown; must be set only when\n" "the peer is a SyncML client contacting us via HTTP.\n" "Clients contacting us via OBEX/Bluetooth can be identified\n" "either via this remoteDeviceId property or by their MAC\n" "address, if that was set in the syncURL property.\n" "\n" "If this property is empty and the peer synchronizes with\n" "this configuration chosen by some other means, then its ID\n" "is recorded here automatically and later used to verify that\n" "the configuration is not accidentally used by a different\n" "peer."); static class SyncPasswordConfigProperty : public PasswordConfigProperty { public: SyncPasswordConfigProperty() : PasswordConfigProperty("password", "password used for authorization with the peer;\n" "in addition to specifying it directly as plain text, it can\n" "also be read from the standard input or from an environment\n" "variable of your choice::\n\n" " plain text : password = \n" " ask : password = -\n" " env variable: password = ${}\n") {} virtual void checkPassword(UserInterface &ui, SyncConfig &config, int flags, const std::string &sourceName = "") const { PasswordConfigProperty::checkPassword(ui, config, flags, syncPropUsername, sourceName); } virtual void savePassword(UserInterface &ui, SyncConfig &config, const std::string &sourceName = "") const { PasswordConfigProperty::savePassword(ui, config, syncPropUsername, sourceName); } ConfigPasswordKey getPasswordKey(const string &descr, const string &serverName, FilterConfigNode &globalConfigNode, const string &sourceName, const boost::shared_ptr &sourceConfigNode) const { ConfigPasswordKey key; bool peerIsClient = syncPropPeerIsClient.getPropertyValue(globalConfigNode); key.server = syncPropSyncURL.getProperty(globalConfigNode); key.description = StringPrintf("sync password for %s", descr.c_str()); if (peerIsClient && key.server.empty()) { /** * Fall back to username/remoteDeviceId as key. */ key.server = syncPropRemoteDevID.getProperty(globalConfigNode); } else { /** * Here we use server sync url without protocol prefix and * user account name as the key in the keyring. * The URL must not be empty, otherwise we end up * overwriting the password of some other service just * because it happens to have the same username. */ size_t start = key.server.find("://"); /* we don't preserve protocol prefix for it may change */ if (start != key.server.npos) { key.server = key.server.substr(start + 3); } } key.user = getUsername(syncPropUsername, globalConfigNode); return key; } } syncPropPassword; static BoolConfigProperty syncPropPreventSlowSync("preventSlowSync", "During a slow sync, the SyncML server must match all items\n" "of the client with its own items and detect which ones it\n" "already has based on properties of the items. This is slow\n" "(client must send all its data) and can lead to duplicates\n" "(when the server fails to match correctly).\n" "It is therefore sometimes desirable to wipe out data on one\n" "side with a refresh-from-client/server sync instead of doing\n" "a slow sync.\n" "When this option is enabled, slow syncs that could cause problems\n" "are not allowed to proceed. Instead, the affected sources are\n" "skipped, allowing the user to choose a suitable sync mode in\n" "the next run (slow sync selected explicitly, refresh sync).\n" "The following situations are handled:\n\n" "- running as client with no local data => unproblematic,\n" " slow sync is allowed to proceed automatically\n" "- running as client with local data => client has no\n" " information about server, so slow sync might be problematic\n" " and is prevented\n" "- client has data, server asks for slow sync because all its data\n" " was deleted (done by Memotoo and Mobical, because they treat\n" " this as 'user wants to start from scratch') => the sync would\n" " recreate all the client's data, even if the user really wanted\n" " to have it deleted, therefore slow sync is prevented\n", "TRUE"); static BoolConfigProperty syncPropUseProxy("useProxy", "set to T to choose an HTTP proxy explicitly; otherwise the default\n" "proxy settings of the underlying HTTP transport mechanism are used;\n" "only relevant when contacting the peer via HTTP", "FALSE"); static ConfigProperty syncPropProxyHost("proxyHost", "proxy URL (``http://:``)"); static ConfigProperty syncPropProxyUsername("proxyUsername", "authentication for proxy: username"); static class ProxyPasswordConfigProperty : public PasswordConfigProperty { public: ProxyPasswordConfigProperty() : PasswordConfigProperty("proxyPassword", "proxy password, can be specified in different ways,\n" "see SyncML server password for details\n", "", "proxy") {} /** * re-implement this function for it is necessary to do a check * before retrieving proxy password */ virtual void checkPassword(UserInterface &ui, SyncConfig &config, int flags, const std::string &sourceName = std::string()) const { /* if useProxy is set 'true', then check proxypassword */ if (config.getUseProxy()) { PasswordConfigProperty::checkPassword(ui, config, flags, syncPropProxyUsername, sourceName); } } virtual void savePassword(UserInterface &ui, SyncConfig &config, const std::string &sourceName = std::string()) const { PasswordConfigProperty::savePassword(ui, config, syncPropProxyUsername, sourceName); } virtual ConfigPasswordKey getPasswordKey(const std::string &descr, const std::string &serverName, FilterConfigNode &globalConfigNode, const std::string &sourceName, const boost::shared_ptr &sourceConfigNode) const { ConfigPasswordKey key; key.server = syncPropProxyHost.getProperty(globalConfigNode); key.user = getUsername(syncPropProxyUsername, globalConfigNode); key.description = StringPrintf("proxy password for %s", descr.c_str()); return key; } } syncPropProxyPassword; static StringConfigProperty syncPropClientAuthType("clientAuthType", "- empty or \"md5\" for secure method (recommended)\n" "- \"basic\" for insecure method\n" "\n" "This setting is only for debugging purpose and only\n" "has an effect during the initial sync of a client.\n" "Later it remembers the method that was supported by\n" "the server and uses that. When acting as server,\n" "clients contacting us can use both basic and md5\n" "authentication.\n", "md5", "", Values() + (Aliases("basic") + "syncml:auth-basic") + (Aliases("md5") + "syncml:auth-md5" + "")); static ULongConfigProperty syncPropMaxMsgSize("maxMsgSize", "The maximum size of each message can be set (maxMsgSize) and the\n" "peer can be told to never sent items larger than a certain\n" "threshold (maxObjSize). Presumably the peer has to truncate or\n" "skip larger items. Sizes are specified as number of bytes.", "150000"); static UIntConfigProperty syncPropMaxObjSize("maxObjSize", "", "4000000"); static BoolConfigProperty syncPropWBXML("enableWBXML", "use the more compact binary XML (WBXML) for messages between client and server;\n" "not applicable when the peer is a SyncML client, because then the client\n" "chooses the encoding", "TRUE"); static BoolConfigProperty syncPropRefreshSync("enableRefreshSync", "Use the more advanced refresh-from-server sync mode to\n" "implement the refresh-from-remote operation. Some SyncML\n" "servers do not support this. Therefore the default is to\n" "delete local data before doing a slow sync, which has the\n" "same effect. However, some servers work better when they\n" "are told explicitly that the sync is a refresh sync. For\n" "example, Funambol's One Media server rejects too many slow\n" "syncs in a row with a 417 'retry later' error.\n", "FALSE"); static ConfigProperty syncPropLogDir("logdir", "full path to directory where automatic backups and logs\n" "are stored for all synchronizations; if unset, then\n" "\"${XDG_CACHE_HOME}/syncevolution/\" (which\n" "usually expands to ${HOME}/.cache/...) will be used;\n" "if \"none\", then no backups of the databases are made and any\n" "output is printed directly to the screen"); static UIntConfigProperty syncPropMaxLogDirs("maxlogdirs", "Controls how many session directories are kept at most in the logdir.\n" "Unless set to zero, SyncEvolution will remove old directories and\n" "all their content to prevent the number of log directories from\n" "growing beyond the given limit. It tries to be intelligent and will\n" "remove sessions in which nothing interesting happened (no errors,\n" "no data changes) in favor of keeping sessions where something\n" "happened, even if those sessions are older.", "10"); static UIntConfigProperty syncPropLogLevel("loglevel", "level of detail for log messages:\n" "- 0 (or unset) = INFO messages without log file, DEBUG with log file\n" "- 1 = only ERROR messages\n" "- 2 = also INFO messages\n" "- 3 = also DEBUG messages\n" "> 3 = increasing amounts of debug messages for developers"); static UIntConfigProperty syncPropNotifyLevel("notifyLevel", "Level of detail for desktop notifications. Currently such\n" "notifications are generated only for automatically started\n" "sync sessions.\n" "\n" "0 - suppress all notifications\n" "1 - show only errors\n" "2 - show information about changes and errors (in practice currently the same as level 3)\n" "3 - show all notifications, including starting a sync\n", "3"); static BoolConfigProperty syncPropPrintChanges("printChanges", "enables or disables the detailed (and sometimes slow) comparison\n" "of database content before and after a sync session", "TRUE"); static BoolConfigProperty syncPropDumpData("dumpData", "enables or disables the automatic backup of database content\n" "before and after a sync session (always enabled if printChanges is enabled)", "TRUE"); static SecondsConfigProperty syncPropRetryDuration("RetryDuration", "The total amount of time in seconds in which the SyncML\n" "client tries to get a response from the server.\n" "During this time, the client will resend messages\n" "in regular intervals (RetryInterval) if no response\n" "is received or the message could not be delivered due\n" "to transport problems. When this time is exceeded\n" "without a response, the synchronization aborts without\n" "sending further messages to the server.\n" "\n" "When acting as server, this setting controls how long\n" "a client is allowed to not send a message before the\n" "synchronization is aborted." ,"5M"); static SecondsConfigProperty syncPropRetryInterval("RetryInterval", "The number of seconds between the start of SyncML message sending\n" "and the start of the retransmission. If the interval has\n" "already passed when a message send returns, the\n" "message is resent immediately. Resending without\n" "any delay will never succeed and therefore specifying 0\n" "disables retries.\n" "\n" "Servers cannot resend messages, so this setting has no\n" "effect in that case.\n" "\n" "The WebDAV backend also resends messages after a temporary\n" "network error. It uses exponential backoff to determine when\n" "the server is available again. This setting is divided by 24\n" "to obtain the initial delay (default: 2m => 5s), which is then\n" "doubled for each retry." ,"2M"); static SafeConfigProperty syncPropPeerName("PeerName", "An arbitrary name for the peer referenced by this config.\n" "Might be used by a GUI. The command line tool always uses the\n" "the configuration name."); static ConfigProperty syncPropSyncMLVersion("SyncMLVersion", "On a client, the latest commonly supported SyncML version\n" "is used when contacting a server. One of '1.0/1.1/1.2' can\n" "be used to pick a specific version explicitly.\n" "\n" "On a server, this option controls what kind of Server Alerted\n" "Notification is sent to the client to start a synchronization.\n" "By default, first the format from 1.2 is tried, then in case\n" "of failure, the older one from 1.1. 1.2/1.1 can be set\n" "explicitly, which disables the automatism.\n" "\n" "Instead or in adddition to the version, several keywords can\n" "be set in this property (separated by spaces or commas):\n" "\n" "- NOCTCAP - avoid sending CtCap meta information\n" "- NORESTART - disable the sync mode extension that SyncEvolution\n" " client and server use to negotiate whether both sides support\n" " running multiple sync iterations in the same session\n" "- REQUESTMAXTIME=

    -- m_prefix = DIR_PREFIX; m_prefix += peer; } } /** * access existing log directory to extract status information */ void openLogdir(const string &dir) { boost::shared_ptr filenode(new IniFileConfigNode(dir, "status.ini", true)); m_info.reset(new SafeConfigNode(filenode)); m_info->setMode(false); m_readonly = true; } /* * get the corresponding peer name encoded in the logging dir name. * The dir name must match the format(see startSession). Otherwise, * empty string is returned. */ string getPeerNameFromLogdir(const string &dir) { // extract the dir name from the fullpath string iDirPath, iDirName; parseLogDir(dir, iDirPath, iDirName); // extract the peer name from the dir name string dirPrefix, peerName, dateTime; if(parseDirName(iDirName, dirPrefix, peerName, dateTime)) { return unescapePeer(peerName); } return ""; } /** * read sync report for session selected with openLogdir() */ void readReport(SyncReport &report) { report.clear(); if (!m_info) { return; } *m_info >> report; } /** * write sync report for current session */ void writeReport(SyncReport &report) { if (m_info) { *m_info << report; /* write in slightly different format and flush at the end */ writeTimestamp("start", report.getStart(), false); writeTimestamp("end", report.getEnd(), true); } } enum SessionMode { SESSION_USE_PATH, /**< write directly into path, don't create and manage subdirectories */ SESSION_READ_ONLY, /**< access an existing session directory identified with path */ SESSION_CREATE /**< create a new session directory inside the given path */ }; // setup log directory and redirect logging into it // @param path path to configured backup directy, empty for using default, "none" if not creating log file // @param mode determines how path is interpreted and which session is accessed // @param maxlogdirs number of backup dirs to preserve in path, 0 if unlimited // @param logLevel 0 = default, 1 = ERROR, 2 = INFO, 3 = DEBUG // @param report record information about session here (may be NULL) void startSession(const string &path, SessionMode mode, int maxlogdirs, int logLevel, SyncReport *report) { m_maxlogdirs = maxlogdirs; m_report = report; m_logfile = ""; if (boost::iequals(path, "none")) { m_path = ""; } else { setLogdir(path); if (mode == SESSION_CREATE) { // create unique directory name in the given directory time_t ts = time(NULL); struct tm tmbuffer; struct tm *tm = localtime_r(&ts, &tmbuffer); if (!tm) { SE_THROW("localtime_r() failed"); } stringstream base; base << "-" << setfill('0') << setw(4) << tm->tm_year + 1900 << "-" << setw(2) << tm->tm_mon + 1 << "-" << setw(2) << tm->tm_mday << "-" << setw(2) << tm->tm_hour << "-" << setw(2) << tm->tm_min; // If other sessions, regardless of which peer, have // the same date and time, then append a sequence // number to ensure correct sorting. Solve this by // finding the maximum sequence number for any kind of // date time. Backwards running clocks or changing the // local time will still screw our ordering, though. typedef std::map SeqMap_t; SeqMap_t dateTimes2Seq; if (isDir(m_logdir)) { ReadDir dir(m_logdir); BOOST_FOREACH(const string &entry, dir) { string dirPrefix, peerName, dateTime; if (parseDirName(entry, dirPrefix, peerName, dateTime)) { // dateTime = -2010-01-31-12-00[-rev] size_t off = 0; for (int i = 0; off != dateTime.npos && i < 5; i++) { off = dateTime.find('-', off + 1); } int sequence; if (off != dateTime.npos) { sequence = dateTime[off + 1] - 'a'; dateTime.resize(off); } else { sequence = -1; } pair entry = dateTimes2Seq.insert(make_pair(dateTime, sequence)); if (sequence > entry.first->second) { entry.first->second = sequence; } } } } stringstream path; path << base.str(); SeqMap_t::iterator it = dateTimes2Seq.find(path.str()); if (it != dateTimes2Seq.end()) { path << "-" << (char)('a' + it->second + 1); } m_path = m_logdir + "/"; m_path += m_prefix; m_path += path.str(); mkdir_p(m_path); } else { m_path = m_logdir; if (mkdir(m_path.c_str(), S_IRWXU) && errno != EEXIST) { SE_LOG_DEBUG(NULL, "%s: %s", m_path.c_str(), strerror(errno)); SyncContext::throwError(m_path, errno); } } m_logfile = m_path + "/" + LogfileBasename + ".html"; } // update log level of default logger and our own replacement Logger::Level level; switch (logLevel) { case 0: // default for console output level = Logger::INFO; break; case 1: level = Logger::ERROR; break; case 2: level = Logger::INFO; break; default: if (m_logfile.empty() || getenv("SYNCEVOLUTION_DEBUG")) { // no log file or user wants to see everything: // print all information to the console level = Logger::DEBUG; } else { // have log file: avoid excessive output to the console, // full information is in the log file level = Logger::INFO; } break; } if (mode != SESSION_USE_PATH) { Logger::instance().setLevel(level); } boost::shared_ptr logger(new LogDirLogger(m_self)); logger->setLevel(level); m_logger.reset(logger); time_t start = time(NULL); if (m_report) { m_report->setStart(start); } m_readonly = mode == SESSION_READ_ONLY; if (!m_path.empty()) { boost::shared_ptr filenode(new IniFileConfigNode(m_path, "status.ini", m_readonly)); m_info.reset(new SafeConfigNode(filenode)); m_info->setMode(false); if (mode != SESSION_READ_ONLY) { // Create a status.ini which contains an error. // Will be overwritten later on, unless we crash. m_info->setProperty("status", STATUS_DIED_PREMATURELY); m_info->setProperty("error", InitStateString("synchronization process died prematurely", true)); writeTimestamp("start", start); } } } /** sets a fixed directory for database files without redirecting logging */ void setPath(const string &path) { m_path = path; } // return log directory, empty if not enabled const string &getLogdir() { return m_path; } // return log file, empty if not enabled const string &getLogfile() { return m_logfile; } /** * remove backup dir(s) if exceeding limit * * Assign a priority to each session dir, with lower * meaning "less important". Then sort by priority and (if * equal) creation time (aka index) in ascending * order. The sessions at the beginning of the sorted * vector are then removed first. * * DUMPS = any kind of database dump was made * ERROR = session failed * CHANGES = local data modified since previous dump (based on dumps * of the current peer, for simplicity reasons), * dump created for the first time, * changes made during sync (detected with dumps and statistics) * * The goal is to preserve as many database dumps as possible * and ideally those where something happened. * * Some criteria veto the removal of a session: * - it is the only one holding a dump of a specific source * - it is the last session */ void expire() { if (m_logdir.size() && m_maxlogdirs > 0 ) { vector dirs; getLogdirs(dirs); /** stores priority and index in "dirs"; after sorting, delete from the start */ vector< pair > victims; /** maps from source name to list of information about dump, oldest first */ typedef map< string, list > Dumps_t; Dumps_t dumps; for (size_t i = 0; i < dirs.size(); i++) { bool changes = false; bool havedumps = false; bool errors = false; LogDir logdir(m_client); logdir.openLogdir(dirs[i]); SyncReport report; logdir.readReport(report); SyncMLStatus status = report.getStatus(); if (status != STATUS_OK && status != STATUS_HTTP_OK) { errors = true; } BOOST_FOREACH(SyncReport::SourceReport_t source, report) { string &sourcename = source.first; SyncSourceReport &sourcereport = source.second; list &dumplist = dumps[sourcename]; if (sourcereport.m_backupBefore.isAvailable() || sourcereport.m_backupAfter.isAvailable()) { // yes, we have backup dumps havedumps = true; DumpInfo info(i, sourcereport.m_backupBefore.getNumItems(), sourcereport.m_backupAfter.getNumItems()); // now check for changes, if none found yet if (!changes) { if (dumplist.empty()) { // new backup dump changes = true; } else { DumpInfo &previous = dumplist.back(); changes = // item count changed -> items changed previous.m_itemsDumpedAfter != info.m_itemsDumpedBefore || sourcereport.wasChanged(SyncSourceReport::ITEM_LOCAL) || sourcereport.wasChanged(SyncSourceReport::ITEM_REMOTE) || haveDifferentContent(sourcename, dirs[previous.m_dirIndex], "after", dirs[i], "before"); } } dumplist.push_back(info); } } Priority pri = havedumps ? (changes ? HAS_DUMPS_WITH_CHANGES : errors ? HAS_DUMPS_NO_CHANGES_WITH_ERRORS : HAS_DUMPS_NO_CHANGES) : (changes ? NO_DUMPS_WITH_CHANGES : errors ? NO_DUMPS_WITH_ERRORS : NO_DUMPS_NO_ERRORS); victims.push_back(make_pair(pri, i)); } sort(victims.begin(), victims.end()); int deleted = 0; for (size_t e = 0; e < victims.size() && (int)dirs.size() - deleted > m_maxlogdirs; ++e) { size_t index = victims[e].second; string &path = dirs[index]; // preserve latest session if (index != dirs.size() - 1) { bool mustkeep = false; // also check whether it holds the only backup of a source BOOST_FOREACH(Dumps_t::value_type dump, dumps) { if (dump.second.size() == 1 && dump.second.front().m_dirIndex == index) { mustkeep = true; break; } } if (!mustkeep) { SE_LOG_DEBUG(NULL, "removing %s", path.c_str()); rm_r(path); ++deleted; } } } } } // finalize session void endSession() { time_t end = time(NULL); if (m_report) { m_report->setEnd(end); } if (m_info) { if (!m_readonly) { writeTimestamp("end", end); if (m_report) { RecMutex::Guard guard = Logger::lock(); writeReport(*m_report); } m_info->flush(); } m_info.reset(); } } // Remove redirection of logging. void restore() { m_logger.reset(); } ~LogDir() { restore(); } #if 0 /** * A quick check for level = SHOW text dumps whether the text dump * starts with the [ERROR] prefix; used to detect error messages * from forked process which go through this instance but are not * already tagged as error messages and thus would not show up as * "first error" in sync reports. * * Example for the problem: * [ERROR] onConnect not implemented [from child process] * [ERROR] child process quit with return code 1 [from parent] * ... * Changes applied during synchronization: * ... * First ERROR encountered: child process quit with return code 1 */ static bool isErrorString(const char *format, va_list args) { const char *text; if (!strcmp(format, "%s")) { va_list argscopy; va_copy(argscopy, args); text = va_arg(argscopy, const char *); va_end(argscopy); } else { text = format; } return boost::starts_with(text, "[ERROR"); } #endif /** * Compare two database dumps just based on their inodes. * @return true if inodes differ */ static bool haveDifferentContent(const string &sourceName, const string &firstDir, const string &firstSuffix, const string &secondDir, const string &secondSuffix) { string first = firstDir + "/" + sourceName + "." + firstSuffix; string second = secondDir + "/" + sourceName + "." + secondSuffix; ReadDir firstContent(first); ReadDir secondContent(second); set firstInodes; BOOST_FOREACH(const string &name, firstContent) { struct stat buf; string fullpath = first + "/" + name; if (stat(fullpath.c_str(), &buf)) { SyncContext::throwError(fullpath, errno); } firstInodes.insert(buf.st_ino); } BOOST_FOREACH(const string &name, secondContent) { struct stat buf; string fullpath = second + "/" + name; if (stat(fullpath.c_str(), &buf)) { SyncContext::throwError(fullpath, errno); } set::iterator it = firstInodes.find(buf.st_ino); if (it == firstInodes.end()) { // second dir has different file return true; } else { firstInodes.erase(it); } } if (!firstInodes.empty()) { // first dir has different file return true; } // exact match of inodes return false; } private: enum Priority { NO_DUMPS_NO_ERRORS, NO_DUMPS_WITH_ERRORS, NO_DUMPS_WITH_CHANGES, HAS_DUMPS_NO_CHANGES, HAS_DUMPS_NO_CHANGES_WITH_ERRORS, HAS_DUMPS_WITH_CHANGES }; struct DumpInfo { size_t m_dirIndex; int m_itemsDumpedBefore; int m_itemsDumpedAfter; DumpInfo(size_t dirIndex, int itemsDumpedBefore, int itemsDumpedAfter) : m_dirIndex(dirIndex), m_itemsDumpedBefore(itemsDumpedBefore), m_itemsDumpedAfter(itemsDumpedAfter) {} }; /** * Find all entries in a given directory, return as sorted array of full paths in ascending order. * If m_prefix doesn't contain peer name information, then all log dirs for different peers in the * logdir are returned. */ void getLogdirs(vector &dirs) { if (m_logdir != "none" && !isDir(m_logdir)) { return; } string peer = m_client.getConfigName(); string peerName, context; SyncConfig::splitConfigString(peer, peerName, context); ReadDir dir(m_logdir); BOOST_FOREACH(const string &entry, dir) { string tmpDirPrefix, tmpPeer, tmpDateTime; // if directory name could not be parsed, ignore it if(parseDirName(entry, tmpDirPrefix, tmpPeer, tmpDateTime)) { if(!peerName.empty() && (m_prefix == (tmpDirPrefix + tmpPeer))) { // if peer name exists, match the logs for the given peer dirs.push_back(m_logdir + "/" + entry); } else if(peerName.empty()) { // if no peer name and only context, match for all logs under the given context string tmpName, tmpContext; SyncConfig::splitConfigString(unescapePeer(tmpPeer), tmpName, tmpContext); if( context == tmpContext && boost::starts_with(m_prefix, tmpDirPrefix)) { dirs.push_back(m_logdir + "/" + entry); } } } } // sort vector in ascending order // if no peer name if(peerName.empty()){ sort(dirs.begin(), dirs.end(), LogDirNames()); } else { sort(dirs.begin(), dirs.end()); } } // store time stamp in session info void writeTimestamp(const string &key, time_t val, bool flush = true) { if (m_info) { char buffer[160]; struct tm tmbuffer, *tm; // be nice and store a human-readable date in addition the seconds since the epoch tm = localtime_r(&val, &tmbuffer); if (tm) { strftime(buffer, sizeof(buffer), "%s, %Y-%m-%d %H:%M:%S %z", tm); } else { // Less suitable fallback. Won't work correctly for 32 // bit long beyond 2038. sprintf(buffer, "%lu", (long unsigned)val); } m_info->setProperty(key, buffer); if (flush) { m_info->flush(); } } } }; LogDirLogger::LogDirLogger(const boost::weak_ptr &logdir) : m_parentLogger(Logger::instance()), m_logdir(logdir) #ifdef USE_DLT , m_useDLT(getenv("SYNCEVOLUTION_USE_DLT") != NULL) #endif { } void LogDirLogger::remove() throw () { // Forget reference to LogDir. This prevents accessing it in // future messagev() calls. RecMutex::Guard guard = Logger::lock(); m_logdir.reset(); } void LogDirLogger::messagev(const MessageOptions &options, const char *format, va_list args) { // Protects ordering of log messages and access to shared // variables like m_report and m_engine. RecMutex::Guard guard = Logger::lock(); // always to parent first (usually stdout): // if the parent is a LogRedirect instance, then // it'll flush its own output first, which ensures // that the new output comes later (as desired) va_list argscopy; va_copy(argscopy, args); m_parentLogger.messagev(options, format, argscopy); va_end(argscopy); // Special handling of our own messages: include in sync report // (always, because that is how we did it traditionally) and write // to our own syncevolution-log.html (if not already logged). // // The TestLocalSync.testServerFailure and some others check that // we record the child's error message in our sync report. If we // don't then it shows up later marked as an "error on the target // side", which is probably not what we want. boost::shared_ptr logdir; if ((bool)(logdir = m_logdir.lock())) { if (logdir->m_report && options.m_level <= ERROR && logdir->m_report->getError().empty()) { va_list argscopy; va_copy(argscopy, args); string error = StringPrintfV(format, argscopy); va_end(argscopy); logdir->m_report->setError(error); } if (!(options.m_flags & MessageOptions::ALREADY_LOGGED) && #ifdef USE_DLT // Don't send to libsynthesis if using DLT, // because then it would end up getting logged // in DLT twice. !m_useDLT && #endif logdir->m_client.getEngine().get()) { va_list argscopy; va_copy(argscopy, args); // Once to Synthesis log, with full debugging. // The API does not support a process name, so // put it into the prefix as " ". std::string prefix; if (options.m_processName) { prefix += *options.m_processName; } if (options.m_prefix) { if (!prefix.empty()) { prefix += " "; } prefix += *options.m_prefix; } logdir->m_client.getEngine().doDebug(options.m_level, prefix.empty() ? NULL : prefix.c_str(), options.m_file, options.m_line, options.m_function, format, argscopy); va_end(argscopy); } } } const char* const LogDirNames::DIR_PREFIX = "SyncEvolution-"; /** * This class owns the sync sources. For historic reasons (required * by Funambol) SyncSource instances are stored as plain pointers * deleted by this class. Virtual sync sources were added later * and are stored as shared pointers which are freed automatically. * It is possible to iterate over the two classes of sources * separately. * * The SourceList ensures that all sources (normal and virtual) have * a valid and unique integer ID as needed for Synthesis. Traditionally * this used to be a simple hash of the source name (which is unique * by design), without checking for hash collisions. Now the ID is assigned * the first time a source is added here and doesn't have one yet. * For backward compatibility (the ID is stored in the .synthesis dir), * the same Hash() value is tested first. Assuming that there were no * hash conflicts, the same IDs will be generated as before. * * Together with a logdir, the SourceList * handles writing of per-sync files as well as the final report. * It is not stateless. The expectation is that it is instantiated * together with a SyncContext for one particular operation (sync * session, status check, restore). In contrast to a SyncContext, * this class has to be recreated for another operation. * * When running as client, only the active sources get added. They can * be dumped one after the other before running a sync. * * As a server, all sources get added, regardless whether they are * active. This implies that at least their "type" must be valid. Then * later when a client really starts using them, they are opened() and * database dumps are made. * * Virtual datastores are stored here when they get initialized * together with the normal sources by the user of SourceList. * * */ class SourceList : private vector { typedef vector inherited; public: enum LogLevel { LOGGING_QUIET, /**< avoid all extra output */ LOGGING_SUMMARY, /**< sync report, but no database comparison */ LOGGING_FULL /**< everything */ }; typedef std::vector< boost::shared_ptr > VirtualSyncSources_t; /** reading our set of virtual sources is okay, modifying it is not */ const VirtualSyncSources_t &getVirtualSources() { return m_virtualSources; } void addSource(const boost::shared_ptr &source) { checkSource(source.get()); m_virtualSources.push_back(source); } using inherited::iterator; using inherited::const_iterator; using inherited::empty; using inherited::begin; using inherited::end; using inherited::rbegin; using inherited::rend; using inherited::size; /** transfers ownership (historic reasons for storing plain pointer...) */ void addSource(cxxptr &source) { checkSource(source); push_back(source.release()); } private: VirtualSyncSources_t m_virtualSources; /**< all configured virtual data sources (aka Synthesis ) */ boost::shared_ptr m_logdir; /**< our logging directory */ SyncContext &m_client; /**< the context in which we were instantiated */ set m_prepared; /**< remember for which source we dumped databases successfully */ string m_intro; /**< remembers the dumpLocalChanges() intro and only prints it again when different from last dumpLocalChanges() call */ bool m_doLogging; /**< true iff the normal logdir handling is enabled (creating and expiring directoties, before/after comparison) */ bool m_reportTodo; /**< true if syncDone() shall print a final report */ LogLevel m_logLevel; /**< chooses how much information is printed */ string m_previousLogdir; /**< remember previous log dir before creating the new one */ /** create name in current (if set) or previous logdir */ string databaseName(SyncSource &source, const string &suffix, string logdir = "") { if (!logdir.size()) { logdir = m_logdir->getLogdir(); } return logdir + "/" + source.getName() + "." + suffix; } /** ensure that Synthesis ID is set and unique */ void checkSource(SyncSource *source) { if (source->getSynthesisID()) { return; } int id = Hash(source->getName()) % INT_MAX; while (true) { // avoid negative values if (id < 0) { id = -id; } // avoid zero, it means unset if (!id) { id = 1; } // check for collisions bool collision = false; BOOST_FOREACH(const string &other, m_client.getSyncSources()) { boost::shared_ptr sc(m_client.getSyncSourceConfig(other)); int other_id = sc->getSynthesisID(); if (other_id == id) { ++id; collision = true; break; } } if (!collision) { source->setSynthesisID(id); return; } } } public: /** allow iterating over sources */ const inherited *getSourceSet() const { return this; } LogLevel getLogLevel() const { return m_logLevel; } void setLogLevel(LogLevel logLevel) { m_logLevel = logLevel; } /** * Dump into files with a certain suffix, optionally store report * in member of SyncSourceReport. Remembers which sources were * dumped before a sync and only dumps those again afterward. * * @param suffix "before/after/current" - before sync, after sync, during status check * @param excludeSource when not empty, only dump that source */ void dumpDatabases(const string &suffix, BackupReport SyncSourceReport::*report, const string &excludeSource = "") { // Identify all logdirs of current context, of any peer. Used // to search for previous backups of each source, if // necessary. SyncContext context(m_client.getContextName()); boost::shared_ptr logdir(LogDir::create(context)); vector dirs; logdir->previousLogdirs(dirs); BOOST_FOREACH(SyncSource *source, *this) { if ((!excludeSource.empty() && excludeSource != source->getName()) || (suffix == "after" && m_prepared.find(source->getName()) == m_prepared.end())) { continue; } string dir = databaseName(*source, suffix); boost::shared_ptr node = ConfigNode::createFileNode(dir + ".ini"); SE_LOG_DEBUG(NULL, "creating %s", dir.c_str()); rm_r(dir); BackupReport dummy; if (source->getOperations().m_backupData) { SyncSource::Operations::ConstBackupInfo oldBackup; // Now look for a backup of the current source, // starting with the most recent one. for (vector::const_reverse_iterator it = dirs.rbegin(); it != dirs.rend(); ++it) { const string &sessiondir = *it; string oldBackupDir; SyncSource::Operations::BackupInfo::Mode mode = SyncSource::Operations::BackupInfo::BACKUP_AFTER; oldBackupDir = databaseName(*source, "after", sessiondir); if (!isDir(oldBackupDir)) { mode = SyncSource::Operations::BackupInfo::BACKUP_BEFORE; oldBackupDir = databaseName(*source, "before", sessiondir); if (!isDir(oldBackupDir)) { // try next session continue; } } oldBackup.m_mode = mode; oldBackup.m_dirname = oldBackupDir; oldBackup.m_node = ConfigNode::createFileNode(oldBackupDir + ".ini"); break; } mkdir_p(dir); SyncSource::Operations::BackupInfo newBackup(suffix == "before" ? SyncSource::Operations::BackupInfo::BACKUP_BEFORE : suffix == "after" ? SyncSource::Operations::BackupInfo::BACKUP_AFTER : SyncSource::Operations::BackupInfo::BACKUP_OTHER, dir, node); source->getOperations().m_backupData(oldBackup, newBackup, report ? source->*report : dummy); SE_LOG_DEBUG(NULL, "%s created", dir.c_str()); // remember that we have dumped at the beginning of a sync if (suffix == "before") { m_prepared.insert(source->getName()); } } } } void restoreDatabase(SyncSource &source, const string &suffix, bool dryrun, SyncSourceReport &report) { string dir = databaseName(source, suffix); boost::shared_ptr node = ConfigNode::createFileNode(dir + ".ini"); if (!node->exists()) { SyncContext::throwError(dir + ": no such database backup found"); } if (source.getOperations().m_restoreData) { source.getOperations().m_restoreData(SyncSource::Operations::ConstBackupInfo(SyncSource::Operations::BackupInfo::BACKUP_OTHER, dir, node), dryrun, report); } } SourceList(SyncContext &client, bool doLogging) : m_logdir(LogDir::create(client)), m_client(client), m_doLogging(doLogging), m_reportTodo(true), m_logLevel(LOGGING_FULL) { } // call as soon as logdir settings are known void startSession(const string &logDirPath, int maxlogdirs, int logLevel, SyncReport *report) { m_logdir->setLogdir(logDirPath); m_previousLogdir = m_logdir->previousLogdir(); if (m_doLogging) { m_logdir->startSession(logDirPath, LogDir::SESSION_CREATE, maxlogdirs, logLevel, report); } else { // Run debug session without paying attention to // the normal logdir handling. The log level here // refers to stdout. The log file will be as complete // as possible. m_logdir->startSession(logDirPath, LogDir::SESSION_USE_PATH, 0, 1, report); } } /** read-only access to existing session, identified in logDirPath */ void accessSession(const string &logDirPath) { m_logdir->setLogdir(logDirPath); m_previousLogdir = m_logdir->previousLogdir(); m_logdir->startSession(logDirPath, LogDir::SESSION_READ_ONLY, 0, 0, NULL); } /** return log directory, empty if not enabled */ const string &getLogdir() { return m_logdir->getLogdir(); } /** return previous log dir found in startSession() */ const string &getPrevLogdir() const { return m_previousLogdir; } /** set directory for database files without actually redirecting the logging */ void setPath(const string &path) { m_logdir->setPath(path); } /** * If possible (directory to compare against available) and enabled, * then dump changes applied locally. * * @param oldSession directory to compare against; "" searches in sessions of current peer * as selected by context for the lastest one involving each source * @param oldSuffix suffix of old database dump: usually "after" * @param currentSuffix the current database dump suffix: "current" * when not doing a sync, otherwise "before" * @param excludeSource when not empty, only dump that source */ bool dumpLocalChanges(const string &oldSession, const string &oldSuffix, const string &newSuffix, const string &excludeSource, const string &intro = "Local data changes to be applied remotely during synchronization:\n", const string &config = "CLIENT_TEST_LEFT_NAME='after last sync' CLIENT_TEST_RIGHT_NAME='current data' CLIENT_TEST_REMOVED='removed since last sync' CLIENT_TEST_ADDED='added since last sync'") { if (m_logLevel <= LOGGING_SUMMARY) { return false; } vector dirs; if (oldSession.empty()) { m_logdir->previousLogdirs(dirs); } BOOST_FOREACH(SyncSource *source, *this) { if ((!excludeSource.empty() && excludeSource != source->getName()) || (newSuffix == "after" && m_prepared.find(source->getName()) == m_prepared.end())) { continue; } // dump only if not done before or changed if (m_intro != intro) { SE_LOG_SHOW(NULL, "%s", intro.c_str()); m_intro = intro; } string oldDir; if (oldSession.empty()) { // Now look for the latest session involving the current source, // starting with the most recent one. for (vector::const_reverse_iterator it = dirs.rbegin(); it != dirs.rend(); ++it) { const string &sessiondir = *it; boost::shared_ptr oldsession(LogDir::create(m_client)); oldsession->openLogdir(sessiondir); SyncReport report; oldsession->readReport(report); if (report.find(source->getName()) != report.end()) { // source was active in that session, use dump // made there oldDir = databaseName(*source, oldSuffix, sessiondir); break; } } } else { oldDir = databaseName(*source, oldSuffix, oldSession); } string newDir = databaseName(*source, newSuffix); SE_LOG_SHOW(NULL, "*** %s ***", source->getDisplayName().c_str()); string cmd = string("env CLIENT_TEST_COMPARISON_FAILED=10 " + config + " synccompare '" ) + oldDir + "' '" + newDir + "'"; int ret = Execute(cmd, EXECUTE_NO_STDERR); switch (ret == -1 ? ret : WIFEXITED(ret) ? WEXITSTATUS(ret) : -1) { case 0: SE_LOG_SHOW(NULL, "no changes"); break; case 10: break; default: SE_LOG_SHOW(NULL, "Comparison was impossible."); break; } } SE_LOG_SHOW(NULL, "\n"); return true; } // call when all sync sources are ready to dump // pre-sync databases // @param sourceName limit preparation to that source void syncPrepare(const string &sourceName) { if (m_prepared.find(sourceName) != m_prepared.end()) { // data dump was already done (can happen when running multiple // SyncML sessions) return; } if (m_logdir->getLogfile().size() && m_doLogging && (m_client.getDumpData() || m_client.getPrintChanges())) { // dump initial databases SE_LOG_INFO(NULL, "creating complete data backup of source %s before sync (%s)", sourceName.c_str(), (m_client.getDumpData() && m_client.getPrintChanges()) ? "enabled with dumpData and needed for printChanges" : m_client.getDumpData() ? "because it was enabled with dumpData" : m_client.getPrintChanges() ? "needed for printChanges" : "???"); dumpDatabases("before", &SyncSourceReport::m_backupBefore, sourceName); if (m_client.getPrintChanges()) { // compare against the old "after" database dump dumpLocalChanges("", "after", "before", sourceName, StringPrintf("%s data changes to be applied during synchronization:\n", m_client.isLocalSync() ? m_client.getContextName().c_str() : "Local")); } } } // call at the end of a sync with success == true // if all went well to print report void syncDone(SyncMLStatus status, SyncReport *report) { // record status - failures from now only affect post-processing // and thus do no longer change that result if (report) { report->setStatus(status == 0 ? STATUS_HTTP_OK : status); } // dump database after sync if explicitly enabled or // needed for comparison; // in the latter case only if dumping it at the beginning completed if (m_doLogging && (m_client.getDumpData() || (m_client.getPrintChanges() && m_reportTodo && !m_prepared.empty()))) { try { SE_LOG_INFO(NULL, "creating complete data backup after sync (%s)", (m_client.getDumpData() && m_client.getPrintChanges()) ? "enabled with dumpData and needed for printChanges" : m_client.getDumpData() ? "because it was enabled with dumpData" : m_client.getPrintChanges() ? "needed for printChanges" : "???"); dumpDatabases("after", &SyncSourceReport::m_backupAfter); } catch (...) { Exception::handle(); // not exactly sure what the problem was, but don't // try it again m_prepared.clear(); } } if (m_doLogging) { if (m_reportTodo && !m_prepared.empty() && report) { // update report with more recent information about m_backupAfter updateSyncReport(*report); } // ensure that stderr is seen again m_logdir->restore(); // write out session status m_logdir->endSession(); if (m_reportTodo) { // haven't looked at result of sync yet; // don't do it again m_reportTodo = false; string logfile = m_logdir->getLogfile(); if (status == STATUS_OK) { SE_LOG_SHOW(NULL, "\nSynchronization successful."); } else if (logfile.size()) { SE_LOG_SHOW(NULL, "\nSynchronization failed, see %s for details.", logfile.c_str()); } else { SE_LOG_SHOW(NULL, "\nSynchronization failed."); } // pretty-print report if (m_logLevel > LOGGING_QUIET) { std::string procname = Logger::getProcessName(); SE_LOG_SHOW(NULL, "\nChanges applied during synchronization%s%s%s:", procname.empty() ? "" : " (", procname.c_str(), procname.empty() ? "" : ")"); } if (m_logLevel > LOGGING_QUIET && report) { ostringstream out; out << *report; std::string slowSync = report->slowSyncExplanation(m_client.getPeer()); if (!slowSync.empty()) { out << endl << slowSync; } SE_LOG_SHOW(NULL, "%s", out.str().c_str()); } // compare databases? if (m_client.getPrintChanges()) { dumpLocalChanges(m_logdir->getLogdir(), "before", "after", "", StringPrintf("\nData modified %s during synchronization:\n", m_client.isLocalSync() ? m_client.getContextName().c_str() : "locally"), "CLIENT_TEST_LEFT_NAME='before sync' CLIENT_TEST_RIGHT_NAME='after sync' CLIENT_TEST_REMOVED='removed during sync' CLIENT_TEST_ADDED='added during sync'"); } // now remove some old logdirs m_logdir->expire(); } } else { // finish debug session m_logdir->restore(); m_logdir->endSession(); } } /** copies information about sources into sync report */ void updateSyncReport(SyncReport &report) { BOOST_FOREACH(SyncSource *source, *this) { report.addSyncSourceReport(source->getName(), *source); } } /** returns names of active sources */ set getSources() { set res; BOOST_FOREACH(SyncSource *source, *this) { res.insert(source->getName()); } return res; } ~SourceList() { // free sync sources BOOST_FOREACH(SyncSource *source, *this) { delete source; } } /** find sync source by name (both normal and virtual sources) */ SyncSource *operator [] (const string &name) { BOOST_FOREACH(SyncSource *source, *this) { if (name == source->getName()) { return source; } } BOOST_FOREACH(boost::shared_ptr &source, m_virtualSources) { if (name == source->getName()) { return source.get(); } } return NULL; } /** find by XML (the ID used by Synthesis to identify sources in progress events) */ SyncSource *lookupBySynthesisID(int synthesisid) { BOOST_FOREACH(SyncSource *source, *this) { if (source->getSynthesisID() == synthesisid) { return source; } } BOOST_FOREACH(boost::shared_ptr &source, m_virtualSources) { if (source->getSynthesisID() == synthesisid) { return source.get(); } } return NULL; } std::list getSourceNames() const; }; std::list SourceList::getSourceNames() const { std::list sourceNames; BOOST_FOREACH (SyncSource *source, *this) { sourceNames.push_back(source->getName()); } return sourceNames; } void unref(SourceList *sourceList) { delete sourceList; } UserInterface &SyncContext::getUserInterfaceNonNull() { if (m_userInterface) { return *m_userInterface; } else { // Doesn't use keyring. static SimpleUserInterface dummy("0"); return dummy; } } void SyncContext::requestAnotherSync() { if (m_activeContext && m_activeContext->m_engine.get() && m_activeContext->m_session) { SharedKey sessionKey = m_activeContext->m_engine.OpenSessionKey(m_activeContext->m_session); m_activeContext->m_engine.SetInt32Value(sessionKey, "restartsync", true); } } const std::vector *SyncContext::getSources() const { return m_sourceListPtr ? m_sourceListPtr->getSourceSet() : NULL; } string SyncContext::getUsedSyncURL() { vector urls = getSyncURL(); BOOST_FOREACH (string url, urls) { if (boost::starts_with(url, "http://") || boost::starts_with(url, "https://")) { #ifdef ENABLE_LIBSOUP return url; #elif defined(ENABLE_LIBCURL) return url; #endif } else if (url.find("obex-bt://") ==0) { #ifdef ENABLE_BLUETOOTH return url; #endif } else if (boost::starts_with(url, "local://")) { return url; } } return ""; } static void CancelTransport(TransportAgent *agent, SuspendFlags &flags) { if (flags.getState() == SuspendFlags::ABORT) { SE_LOG_DEBUG(NULL, "CancelTransport: cancelling because of SuspendFlags::ABORT"); agent->cancel(); } } /** * common initialization for all kinds of transports, to be called * before using them */ static void InitializeTransport(const boost::shared_ptr &agent, int timeout) { agent->setTimeout(timeout); // Automatically call cancel() when we an abort request // is detected. Relies of automatic connection management // to disconnect when agent is deconstructed. SuspendFlags &flags(SuspendFlags::getSuspendFlags()); flags.m_stateChanged.connect(SuspendFlags::StateChanged_t::slot_type(CancelTransport, agent.get(), _1).track(agent)); } boost::shared_ptr SyncContext::createTransportAgent(void *gmainloop) { string url = getUsedSyncURL(); m_retryInterval = getRetryInterval(); m_retryDuration = getRetryDuration(); int timeout = m_serverMode ? m_retryDuration : min(m_retryInterval, m_retryDuration); if (m_localSync) { string peer = url.substr(strlen("local://")); boost::shared_ptr agent(LocalTransportAgent::create(this, peer, gmainloop)); InitializeTransport(agent, timeout); agent->start(); return agent; } else if (boost::starts_with(url, "http://") || boost::starts_with(url, "https://")) { #ifdef ENABLE_LIBSOUP boost::shared_ptr agent(new SoupTransportAgent(static_cast(gmainloop))); agent->setConfig(*this); InitializeTransport(agent, timeout); return agent; #elif defined(ENABLE_LIBCURL) boost::shared_ptr agent(new CurlTransportAgent()); agent->setConfig(*this); InitializeTransport(agent, timeout); return agent; #endif } else if (url.find("obex-bt://") ==0) { #ifdef ENABLE_BLUETOOTH std::string btUrl = url.substr (strlen ("obex-bt://"), std::string::npos); boost::shared_ptr agent(new ObexTransportAgent(ObexTransportAgent::OBEX_BLUETOOTH, static_cast(gmainloop))); agent->setURL (btUrl); InitializeTransport(agent, timeout); // this will block already agent->connect(); return agent; #endif } SE_THROW("unsupported transport type is specified in the configuration"); } void SyncContext::displayServerMessage(const string &message) { SE_LOG_INFO(NULL, "message from server: %s", message.c_str()); } void SyncContext::displaySyncProgress(sysync::TProgressEventEnum type, int32_t extra1, int32_t extra2, int32_t extra3) { } bool SyncContext::displaySourceProgress(SyncSource &source, const SyncSourceEvent &event, bool flush) { if (!flush) { // Certain events do not need to be printed immediately. // For example, instead of multiple PEV_ITEMRECEIVED events // foo: received 1/100 // foo: received 2/100 // foo: ... // foo: received 100/100 // it is better to just print one: // foo: received 100/100 switch (event.m_type) { case sysync::PEV_ITEMPROCESSED: // Ignore this one completely. There is one such event // after each PEV_ITEMRECEIVED, so processing // PEV_ITEMPROCESSED would break the merging of // PEV_ITEMRECEIVED, at least the way it is implemented // now. PEV_ITEMPROCESSED also doesn't add much // information. return true; case sysync::PEV_DELETING: case sysync::PEV_ITEMRECEIVED: case sysync::PEV_ITEMSENT: // Flush when switching to a different event type or source. if (m_sourceEvent.m_type != sysync::PEV_NOP && (m_sourceEvent.m_type != event.m_type || m_sourceProgress != &source)) { displaySourceProgress(*m_sourceProgress, m_sourceEvent, true); } m_sourceEvent.m_type = event.m_type; m_sourceEvent.m_extra1 = event.m_extra1; m_sourceEvent.m_extra2 = event.m_extra2; m_sourceEvent.m_extra3 = event.m_extra3; m_sourceProgress = &source; return true; break; default: if (m_sourceEvent.m_type != sysync::PEV_NOP) { displaySourceProgress(*m_sourceProgress, m_sourceEvent, true); m_sourceEvent.m_type = sysync::PEV_NOP; } break; } } switch(event.m_type) { case sysync::PEV_PREPARING: /* preparing (e.g. preflight in some clients), extra1=progress, extra2=total */ /* extra2 might be zero */ /* * At the moment, preparing items doesn't do any real work. * Printing this progress just increases the output and slows * us down. Disabled. */ if (true || source.getFinalSyncMode() == SYNC_NONE) { // not active, suppress output } else if (event.m_extra2) { SE_LOG_INFO(NULL, "%s: preparing %d/%d", source.getDisplayName().c_str(), event.m_extra1, event.m_extra2); } else { SE_LOG_INFO(NULL, "%s: preparing %d", source.getDisplayName().c_str(), event.m_extra1); } break; case sysync::PEV_DELETING: /* deleting (zapping datastore), extra1=progress, extra2=total */ if (event.m_extra2) { SE_LOG_INFO(NULL, "%s: deleting %d/%d", source.getDisplayName().c_str(), event.m_extra1, event.m_extra2); } else { SE_LOG_INFO(NULL, "%s: deleting %d", source.getDisplayName().c_str(), event.m_extra1); } break; case sysync::PEV_ALERTED: { /* datastore alerted (extra1=0 for normal, 1 for slow, 2 for first time slow, extra2=1 for resumed session, extra3 0=twoway, 1=fromserver, 2=fromclient */ // -1 is used for alerting a restore from backup. Synthesis won't use this bool peerIsClient = getPeerIsClient(); if (event.m_extra1 != -1) { SE_LOG_INFO(NULL, "%s: %s %s sync%s (%s)", source.getDisplayName().c_str(), event.m_extra2 ? "resuming" : "starting", event.m_extra1 == 0 ? "normal" : event.m_extra1 == 1 ? "slow" : event.m_extra1 == 2 ? "first time" : "unknown", event.m_extra3 == 0 ? ", two-way" : event.m_extra3 == 1 ? " from server" : event.m_extra3 == 2 ? " from client" : ", unknown direction", peerIsClient ? "peer is client" : "peer is server"); SimpleSyncMode mode = SIMPLE_SYNC_NONE; SyncMode sync = StringToSyncMode(source.getSync()); switch (event.m_extra1) { case 0: switch (event.m_extra3) { case 0: mode = SIMPLE_SYNC_TWO_WAY; if (m_serverMode && m_serverAlerted) { if (sync == SYNC_ONE_WAY_FROM_SERVER || sync == SYNC_ONE_WAY_FROM_LOCAL) { // As in the slow/refresh-from-server case below, // pretending to do a two-way incremental sync // is a correct way of executing the requested // one-way sync, as long as the client doesn't // send any of its own changes. The Synthesis // engine does that. mode = SIMPLE_SYNC_ONE_WAY_FROM_LOCAL; } else if (sync == SYNC_LOCAL_CACHE_SLOW || sync == SYNC_LOCAL_CACHE_INCREMENTAL) { mode = SIMPLE_SYNC_LOCAL_CACHE_INCREMENTAL; } } break; case 1: mode = peerIsClient ? SIMPLE_SYNC_ONE_WAY_FROM_LOCAL : SIMPLE_SYNC_ONE_WAY_FROM_REMOTE; break; case 2: mode = peerIsClient ? SIMPLE_SYNC_ONE_WAY_FROM_REMOTE : SIMPLE_SYNC_ONE_WAY_FROM_LOCAL; break; } break; case 1: case 2: switch (event.m_extra3) { case 0: mode = SIMPLE_SYNC_SLOW; if (m_serverMode && m_serverAlerted) { if (sync == SYNC_REFRESH_FROM_SERVER || sync == SYNC_REFRESH_FROM_LOCAL) { // We run as server and told the client to refresh // its data. A slow sync is how some clients (the // Synthesis engine included) execute that sync mode; // let's be optimistic and assume that the client // did as it was told and deleted its data. mode = SIMPLE_SYNC_REFRESH_FROM_LOCAL; } else if (sync == SYNC_LOCAL_CACHE_SLOW || sync == SYNC_LOCAL_CACHE_INCREMENTAL) { mode = SIMPLE_SYNC_LOCAL_CACHE_SLOW; } } break; case 1: mode = peerIsClient ? SIMPLE_SYNC_REFRESH_FROM_LOCAL : SIMPLE_SYNC_REFRESH_FROM_REMOTE; break; case 2: mode = peerIsClient ? SIMPLE_SYNC_REFRESH_FROM_REMOTE : SIMPLE_SYNC_REFRESH_FROM_LOCAL; break; } break; } if (SyncMode(mode) != SYNC_NONE) { SE_LOG_DEBUG(NULL, "reading: set read-ahead based on sync mode %s", PrettyPrintSyncMode(SyncMode(mode)).c_str()); switch (mode) { case SIMPLE_SYNC_NONE: case SIMPLE_SYNC_INVALID: case SIMPLE_SYNC_RESTORE_FROM_BACKUP: case SIMPLE_SYNC_ONE_WAY_FROM_REMOTE: case SIMPLE_SYNC_REFRESH_FROM_REMOTE: case SIMPLE_SYNC_LOCAL_CACHE_INCREMENTAL: source.setReadAheadOrder(SyncSourceBase::READ_NONE); break; case SIMPLE_SYNC_TWO_WAY: case SIMPLE_SYNC_ONE_WAY_FROM_LOCAL: source.setReadAheadOrder(SyncSourceBase::READ_CHANGED_ITEMS); break; case SIMPLE_SYNC_SLOW: case SIMPLE_SYNC_REFRESH_FROM_LOCAL: case SIMPLE_SYNC_LOCAL_CACHE_SLOW: source.setReadAheadOrder(SyncSourceBase::READ_ALL_ITEMS); break; } } if (source.getFinalSyncMode() == SYNC_NONE) { source.recordFinalSyncMode(SyncMode(mode)); source.recordFirstSync(event.m_extra1 == 2); source.recordResumeSync(event.m_extra2 == 1); } else if (SyncMode(mode) != SYNC_NONE) { // Broadcast statistics before moving into next cycle. m_sourceSyncedSignal(source.getName(), source); // may happen when the source is used in multiple // SyncML sessions; only remember the initial sync // mode in that case and count all following syncs // (they should only finish the work of the initial // one) source.recordRestart(); if (m_serverMode) { // Done with first cycle, revert to normal photo // handling if it was disabled. SharedKey sessionKey = m_engine.OpenSessionKey(m_session); SharedKey contextKey = m_engine.OpenKeyByPath(sessionKey, "/sessionvars"); m_engine.SetInt32Value(contextKey, "keepPhotoData", false); } } } else { SE_LOG_INFO(NULL, "%s: restore from backup", source.getDisplayName().c_str()); source.recordFinalSyncMode(SYNC_RESTORE_FROM_BACKUP); } break; } case sysync::PEV_SYNCSTART: /* sync started */ SE_LOG_INFO(NULL, "%s: started", source.getDisplayName().c_str()); break; case sysync::PEV_ITEMRECEIVED: /* item received, extra1=current item count, extra2=number of expected changes (if >= 0) */ if (source.getFinalSyncMode() == SYNC_NONE) { } else if (event.m_extra2 > 0) { SE_LOG_INFO(NULL, "%s: received %d/%d", source.getDisplayName().c_str(), event.m_extra1, event.m_extra2); } else { SE_LOG_INFO(NULL, "%s: received %d", source.getDisplayName().c_str(), event.m_extra1); } source.recordTotalNumItemsReceived(event.m_extra1); break; case sysync::PEV_ITEMSENT: /* item sent, extra1=current item count, extra2=number of expected items to be sent (if >=0) */ if (source.getFinalSyncMode() == SYNC_NONE) { } else if (event.m_extra2 > 0) { SE_LOG_INFO(NULL, "%s: sent %d/%d", source.getDisplayName().c_str(), event.m_extra1, event.m_extra2); } else { SE_LOG_INFO(NULL, "%s: sent %d", source.getDisplayName().c_str(), event.m_extra1); } break; // Not reached, see above. case sysync::PEV_ITEMPROCESSED: /* item locally processed, extra1=# added, extra2=# updated, extra3=# deleted */ if (source.getFinalSyncMode() == SYNC_NONE) { } else if (source.getFinalSyncMode() != SYNC_NONE) { SE_LOG_INFO(NULL, "%s: added %d, updated %d, removed %d", source.getDisplayName().c_str(), event.m_extra1, event.m_extra2, event.m_extra3); } break; case sysync::PEV_SYNCEND: { /* sync finished, probably with error in extra1 (0=ok), syncmode in extra2 (0=normal, 1=slow, 2=first time), extra3=1 for resumed session) */ if (source.getFinalSyncMode() == SYNC_NONE) { SE_LOG_INFO(NULL, "%s: inactive", source.getDisplayName().c_str()); } else if(source.getFinalSyncMode() == SYNC_RESTORE_FROM_BACKUP) { SE_LOG_INFO(NULL, "%s: restore done %s", source.getDisplayName().c_str(), event.m_extra1 ? "unsuccessfully" : "successfully" ); } else { SE_LOG_INFO(NULL, "%s: %s%s sync done %s", source.getDisplayName().c_str(), event.m_extra3 ? "resumed " : "", event.m_extra2 == 0 ? "normal" : event.m_extra2 == 1 ? "slow" : event.m_extra2 == 2 ? "first time" : "unknown", event.m_extra1 ? "unsuccessfully" : "successfully"); } int32_t extra1 = event.m_extra1; switch (extra1) { case 401: // TODO: reset cached password SE_LOG_INFO(NULL, "authorization failed, check username '%s' and password", getSyncUser().toString().c_str()); break; case 403: SE_LOG_INFO(source.getDisplayName(), "log in succeeded, but server refuses access - contact server operator"); break; case 407: SE_LOG_INFO(NULL, "proxy authorization failed, check proxy username and password"); break; case 404: SE_LOG_INFO(source.getDisplayName(), "server database not found, check URI '%s'", source.getURINonEmpty().c_str()); break; case 0: break; case sysync::LOCERR_DATASTORE_ABORT: // this can mean only one thing in SyncEvolution: unexpected slow sync extra1 = STATUS_UNEXPECTED_SLOW_SYNC; // no break! default: // Printing unknown status codes here is of somewhat questionable value, // because even "good" sources will get a bad status when the overall // session turns bad. We also don't have good explanations for the // status here. SE_LOG_ERROR(source.getDisplayName(), "%s", Status2String(SyncMLStatus(event.m_extra1)).c_str()); break; } source.recordStatus(SyncMLStatus(extra1)); break; } case sysync::PEV_DSSTATS_L: /* datastore statistics for local (extra1=# added, extra2=# updated, extra3=# deleted) */ source.setItemStat(SyncSource::ITEM_LOCAL, SyncSource::ITEM_ADDED, SyncSource::ITEM_TOTAL, event.m_extra1); source.setItemStat(SyncSource::ITEM_LOCAL, SyncSource::ITEM_UPDATED, SyncSource::ITEM_TOTAL, event.m_extra2); source.setItemStat(SyncSource::ITEM_LOCAL, SyncSource::ITEM_REMOVED, SyncSource::ITEM_TOTAL, // Synthesis engine doesn't count locally // deleted items during // refresh-from-server/client. That's a matter of // taste. In SyncEvolution we'd like these // items to show up, so add it here. (source.getFinalSyncMode() == (m_serverMode ? SYNC_REFRESH_FROM_CLIENT : SYNC_REFRESH_FROM_SERVER) || source.getFinalSyncMode() == SYNC_REFRESH_FROM_REMOTE) ? source.getNumDeleted() : event.m_extra3); break; case sysync::PEV_DSSTATS_R: /* datastore statistics for remote (extra1=# added, extra2=# updated, extra3=# deleted) */ source.setItemStat(SyncSource::ITEM_REMOTE, SyncSource::ITEM_ADDED, SyncSource::ITEM_TOTAL, event.m_extra1); source.setItemStat(SyncSource::ITEM_REMOTE, SyncSource::ITEM_UPDATED, SyncSource::ITEM_TOTAL, event.m_extra2); source.setItemStat(SyncSource::ITEM_REMOTE, SyncSource::ITEM_REMOVED, SyncSource::ITEM_TOTAL, event.m_extra3); break; case sysync::PEV_DSSTATS_E: /* datastore statistics for local/remote rejects (extra1=# locally rejected, extra2=# remotely rejected) */ source.setItemStat(SyncSource::ITEM_LOCAL, SyncSource::ITEM_ANY, SyncSource::ITEM_REJECT, event.m_extra1); source.setItemStat(SyncSource::ITEM_REMOTE, SyncSource::ITEM_ANY, SyncSource::ITEM_REJECT, event.m_extra2); break; case sysync::PEV_DSSTATS_S: /* datastore statistics for server slowsync (extra1=# slowsync matches) */ source.setItemStat(SyncSource::ITEM_REMOTE, SyncSource::ITEM_ANY, SyncSource::ITEM_MATCH, event.m_extra1); break; case sysync::PEV_DSSTATS_C: /* datastore statistics for server conflicts (extra1=# server won, extra2=# client won, extra3=# duplicated) */ source.setItemStat(SyncSource::ITEM_REMOTE, SyncSource::ITEM_ANY, SyncSource::ITEM_CONFLICT_SERVER_WON, event.m_extra1); source.setItemStat(SyncSource::ITEM_REMOTE, SyncSource::ITEM_ANY, SyncSource::ITEM_CONFLICT_CLIENT_WON, event.m_extra2); source.setItemStat(SyncSource::ITEM_REMOTE, SyncSource::ITEM_ANY, SyncSource::ITEM_CONFLICT_DUPLICATED, event.m_extra3); break; case sysync::PEV_DSSTATS_D: /* datastore statistics for data volume (extra1=outgoing bytes, extra2=incoming bytes) */ source.setItemStat(SyncSource::ITEM_LOCAL, SyncSource::ITEM_ANY, SyncSource::ITEM_SENT_BYTES, event.m_extra1); source.setItemStat(SyncSource::ITEM_LOCAL, SyncSource::ITEM_ANY, SyncSource::ITEM_RECEIVED_BYTES, event.m_extra2); break; case sysync::PEV_NOP: // Handled, do not process further. return true; break; default: SE_LOG_DEBUG(NULL, "%s: progress event %d, extra %d/%d/%d", source.getDisplayName().c_str(), event.m_type, event.m_extra1, event.m_extra2, event.m_extra3); } return false; } void SyncContext::throwError(const string &error) { throwError(SyncMLStatus(STATUS_FATAL + sysync::LOCAL_STATUS_CODE), error); } void SyncContext::throwError(SyncMLStatus status, const string &error) { #ifdef IPHONE /* * Catching the runtime_exception fails due to a toolchain problem, * so do the error handling now and abort: because there is just * one sync source this is probably the only thing that can be done. * Still, it's not nice on the server... */ fatalError(NULL, error.c_str()); #else SE_THROW_EXCEPTION_STATUS(StatusException, error, status); #endif } void SyncContext::throwError(const string &action, int error) { std::string what = action + ": " + strerror(error); // be as specific if we can be: relevant for the file backend, // which is expected to return STATUS_NOT_FOUND == 404 for "file // not found" if (error == ENOENT) { throwError(STATUS_NOT_FOUND, what); } else { throwError(what); } } void SyncContext::fatalError(void *object, const char *error) { SE_LOG_ERROR(NULL, "%s", error); if (m_activeContext && m_activeContext->m_sourceListPtr) { m_activeContext->m_sourceListPtr->syncDone(STATUS_FATAL, NULL); } exit(1); } /* * There have been segfaults inside glib in the background * thread which ran the second event loop. Disabled it again, * even though the synchronous EDS API calls will block then * when EDS dies. */ #if 0 && defined(HAVE_GLIB) && defined(HAVE_EDS) # define RUN_GLIB_LOOP #endif #ifdef RUN_GLIB_LOOP static void *mainLoopThread(void *) { // The test framework uses SIGALRM for timeouts. // Block the signal here because a) the signal handler // prints a stack back trace when called and we are not // interessted in the background thread's stack and b) // it seems to have confused glib/libebook enough to // access invalid memory and segfault when it gets the SIGALRM. sigset_t blocked; sigemptyset(&blocked); sigaddset(&blocked, SIGALRM); pthread_sigmask(SIG_BLOCK, &blocked, NULL); GMainLoop *mainloop = g_main_loop_new(NULL, TRUE); if (mainloop) { g_main_loop_run(mainloop); g_main_loop_unref(mainloop); } return NULL; } #endif void SyncContext::startLoopThread() { #ifdef RUN_GLIB_LOOP // when using Evolution we must have a running main loop, // otherwise loss of connection won't be reported to us static pthread_t loopthread; static bool loopthreadrunning; if (!loopthreadrunning) { loopthreadrunning = !pthread_create(&loopthread, NULL, mainLoopThread, NULL); } #endif } SyncSource *SyncContext::findSource(const std::string &name) { if (!m_activeContext || !m_activeContext->m_sourceListPtr) { return NULL; } const char *realname = strrchr(name.c_str(), m_findSourceSeparator); if (realname) { realname++; } else { realname = name.c_str(); } return (*m_activeContext->m_sourceListPtr)[realname]; } SyncContext *SyncContext::findContext(const char *sessionName) { return m_activeContext; } void SyncContext::initSources(SourceList &sourceList) { list configuredSources = getSyncSources(); map subSources; // Disambiguate source names because we have multiple with the same // name active? string contextName; if (m_localSync) { contextName = getContextName(); } // Phase 1, check all virtual sync soruces BOOST_FOREACH(const string &name, configuredSources) { boost::shared_ptr sc(getSyncSourceConfig(name)); SyncSourceNodes source = getSyncSourceNodes (name); std::string sync = sc->getSync(); SyncMode mode = StringToSyncMode(sync); if (mode != SYNC_NONE) { SourceType sourceType = SyncSource::getSourceType(source); if (sourceType.m_backend == "virtual") { //This is a virtual sync source, check and enable the referenced //sub syncsources here SyncSourceParams params(name, source, boost::shared_ptr(this, SyncConfigNOP()), contextName); boost::shared_ptr vSource = boost::shared_ptr (new VirtualSyncSource (params)); std::vector mappedSources = vSource->getMappedSources(); BOOST_FOREACH (std::string source, mappedSources) { //check whether the mapped source is really available boost::shared_ptr source_config = getSyncSourceConfig(source); if (!source_config || !source_config->exists()) { throwError(StringPrintf("Virtual data source \"%s\" references a nonexistent datasource \"%s\".", name.c_str(), source.c_str())); } pair< map::iterator, bool > res = subSources.insert(make_pair(source, name)); if (!res.second) { throwError(StringPrintf("Data source \"%s\" included in the virtual sources \"%s\" and \"%s\". It can only be included in one virtual source at a time.", source.c_str(), res.first->second.c_str(), name.c_str())); } } FilterConfigNode::ConfigFilter vFilter; vFilter["sync"] = sync; if (!m_serverMode) { // must set special URI for clients so that // engine knows about superdatastore and its // URI vFilter["uri"] = string("<") + vSource->getName() + ">" + vSource->getURINonEmpty(); } BOOST_FOREACH (std::string source, mappedSources) { setConfigFilter (false, source, vFilter); } sourceList.addSource(vSource); } } } BOOST_FOREACH(const string &name, configuredSources) { boost::shared_ptr sc(getSyncSourceConfig(name)); SyncSourceNodes source = getSyncSourceNodes (name); if (!sc->isDisabled()) { SourceType sourceType = SyncSource::getSourceType(source); if (sourceType.m_backend != "virtual") { SyncSourceParams params(name, source, boost::shared_ptr(this, SyncConfigNOP()), contextName); cxxptr syncSource(SyncSource::createSource(params)); if (!syncSource) { throwError(name + ": type unknown" ); } if (subSources.find(name) != subSources.end()) { syncSource->recordVirtualSource(subSources[name]); } sourceList.addSource(syncSource); } } else { // the Synthesis engine is never going to see this source, // therefore we have to mark it as 100% complete and // "done" class DummySyncSource source(name, contextName); source.recordFinalSyncMode(SYNC_NONE); displaySourceProgress(source, SyncSourceEvent(sysync::PEV_PREPARING, 0, 0, 0), true); displaySourceProgress(source, SyncSourceEvent(sysync::PEV_ITEMPROCESSED, 0, 0, 0), true); displaySourceProgress(source, SyncSourceEvent(sysync::PEV_ITEMRECEIVED, 0, 0, 0), true); displaySourceProgress(source, SyncSourceEvent(sysync::PEV_ITEMSENT, 0, 0, 0), true); displaySourceProgress(source, SyncSourceEvent(sysync::PEV_SYNCEND, 0, 0, 0), true); } } } void SyncContext::startSourceAccess(SyncSource *source) { if(m_firstSourceAccess) { syncSuccessStart(); m_firstSourceAccess = false; } if (m_serverMode) { // When using the source as cache, change tracking // is not required. Disabling it can make item // changes faster. SyncMode mode = StringToSyncMode(source->getSync()); if (mode == SYNC_LOCAL_CACHE_SLOW || mode == SYNC_LOCAL_CACHE_INCREMENTAL) { source->setNeedChanges(false); } // source is active in sync, now open it source->open(); } // database dumping is delayed in both client and server m_sourceListPtr->syncPrepare(source->getName()); } // XML configuration converted to C string constants extern "C" { // including all known fragments for a client extern const char *SyncEvolutionXMLClient; // the remote rules for a client extern const char *SyncEvolutionXMLClientRules; } /** * helper class which scans directories for * XML config files */ class XMLFiles { public: enum Category { MAIN, /**< files directly under searched directories */ DATATYPES, /**< inside datatypes and datatypes/ */ SCRIPTING, /**< inside scripting and scripting/ */ REMOTERULES, /**< inside remoterules and remoterules/ */ MAX_CATEGORY }; /** search file system for XML config fragments */ void scan(const string &mode); /** datatypes, scripts and rules concatenated, empty if none found */ string get(Category category); /** main file, typically "syncevolution.xml", empty if not found */ string get(const string &file); static const string m_syncevolutionXML; private: /* base name as sort key + full file path, iterating is done in lexical order */ StringMap m_files[MAX_CATEGORY]; /** * scan a specific directory for main files directly inside it * and inside datatypes, scripting, remoterules; * it is not an error when it does not exist or is not a directory */ void scanRoot(const string &mode, const string &dir); /** * scan a datatypes/scripting/remoterules sub directory, * including the sub-directory */ void scanFragments(const string &mode, const string &dir, Category category); /** * add all .xml files to the right hash, overwriting old entries */ void addFragments(const string &dir, Category category); }; const string XMLFiles::m_syncevolutionXML("syncevolution.xml"); void XMLFiles::scan(const string &mode) { const char *dir = getenv("SYNCEVOLUTION_XML_CONFIG_DIR"); /* * read either one or the other, so that testing can run without * accidentally reading installed files */ if (dir) { scanRoot(mode, dir); } else { scanRoot(mode, XML_CONFIG_DIR); scanRoot(mode, SubstEnvironment("${XDG_CONFIG_HOME}/syncevolution-xml")); } } void XMLFiles::scanRoot(const string &mode, const string &dir) { addFragments(dir, MAIN); scanFragments(mode, dir + "/scripting", SCRIPTING); scanFragments(mode, dir + "/datatypes", DATATYPES); scanFragments(mode, dir + "/remoterules", REMOTERULES); } void XMLFiles::scanFragments(const string &mode, const string &dir, Category category) { addFragments(dir, category); addFragments(dir + "/" + mode, category); } void XMLFiles::addFragments(const string &dir, Category category) { if (!isDir(dir)) { return; } ReadDir content(dir); BOOST_FOREACH(const string &file, content) { if (boost::ends_with(file, ".xml")) { m_files[category][file] = dir + "/" + file; } } } string XMLFiles::get(Category category) { string res; BOOST_FOREACH(const StringPair &entry, m_files[category]) { string content; ReadFile(entry.second, content); res += content; } return res; } string XMLFiles::get(const string &file) { string res; StringMap::const_iterator entry = m_files[MAIN].find(file); if (entry != m_files[MAIN].end()) { ReadFile(entry->second, res); } return res; } static void substTag(string &xml, const string &tagname, const string &replacement, bool replaceElement = false) { string tag; size_t index; tag.reserve(tagname.size() + 3); tag += "<"; tag += tagname; tag += "/>"; index = xml.find(tag); if (index != xml.npos) { string tmp; tmp.reserve(tagname.size() * 2 + 2 + 3 + replacement.size()); if (!replaceElement) { tmp += "<"; tmp += tagname; tmp += ">"; } tmp += replacement; if (!replaceElement) { tmp += ""; } xml.replace(index, tag.size(), tmp); } } static void substTag(string &xml, const string &tagname, const char *replacement, bool replaceElement = false) { substTag(xml, tagname, std::string(replacement), replaceElement); } template void substTag(string &xml, const string &tagname, const T replacement, bool replaceElement = false) { stringstream str; str << replacement; substTag(xml, tagname, str.str(), replaceElement); } void SyncContext::getConfigTemplateXML(const string &mode, string &xml, string &rules, string &configname) { XMLFiles files; files.scan(mode); xml = files.get(files.m_syncevolutionXML); if (xml.empty()) { if (mode != "client") { SE_THROW(files.m_syncevolutionXML + " not found"); } configname = "builtin XML configuration"; xml = SyncEvolutionXMLClient; rules = SyncEvolutionXMLClientRules; } else { configname = "XML configuration files"; rules = files.get(XMLFiles::REMOTERULES); substTag(xml, "datatypes", files.get(XMLFiles::DATATYPES) + " \n \n \n"); substTag(xml, "scripting", files.get(XMLFiles::SCRIPTING)); } } void SyncContext::getConfigXML(string &xml, string &configname) { string rules; getConfigTemplateXML(m_serverMode ? "server" : "client", xml, rules, configname); string tag; size_t index; unsigned long hash = 0; std::set flags = getSyncMLFlags(); bool noctcap = flags.find("noctcap") != flags.end(); bool norestart = flags.find("norestart") != flags.end(); const char *PBAPSyncMode = getenv("SYNCEVOLUTION_PBAP_SYNC"); bool keepPhotoData = PBAPSyncMode && (boost::iequals(PBAPSyncMode, "incremental") || boost::iequals(PBAPSyncMode, "text")); std::string sessioninitscript = " \n"; ostringstream clientorserver; if (m_serverMode) { clientorserver << " \n" " SyncEvolution\n" " yes\n" " yes\n"; InitState configrequestmaxtime = getRequestMaxTime(); unsigned int requestmaxtime; if (configrequestmaxtime.wasSet()) { // Explicitly set, use it regardless of the kind of sync. // We allow this even if thread support was not available, // because if a user enables it explicitly, it's probably // for a good reason (= failing client), in which case // risking multithreading issues is preferable. requestmaxtime = configrequestmaxtime.get(); } else if (m_remoteInitiated || m_localSync) { // We initiated the sync (local sync, Bluetooth). The client // should not time out, so there is no need for intermediate // message sending. // // To avoid potential problems and get a single log file, // avoid it and multithreading by default. requestmaxtime = 0; } else { // We were contacted by an HTTP client. Reply to client // not later than 120 seconds while storage initializes // in a background thread. #ifdef HAVE_THREAD_SUPPORT requestmaxtime = 120; // default in seconds #else requestmaxtime = 0; #endif } if (requestmaxtime) { clientorserver << " yes\n" " " << requestmaxtime << "\n"; } else { clientorserver << " no\n"; } clientorserver << "\n" << sessioninitscript << " 300\n" "\n"; //do not send respuri if over bluetooth if (boost::starts_with (getUsedSyncURL(), "obex-bt://")) { clientorserver << " no\n" "\n"; } clientorserver << " " << (norestart ? "no" : "yes" ) << "\n"; if (noctcap) { clientorserver << " no\n" "\n"; } clientorserver<<" \n" "\n" " \n" "\n" " \n" " \n"; } else { clientorserver << " \n" " $(binfilepath)\n" " no\n" " \n"; if (getRefreshSync()) { clientorserver << " no\n"; } clientorserver << "\n" ; string syncMLVersion (getSyncMLVersion()); if (!syncMLVersion.empty()) { clientorserver << "" <\n"; } clientorserver << " " << (norestart ? "no" : "yes" ) << "\n"; if (noctcap) { clientorserver << " no\n" "\n"; } clientorserver << sessioninitscript << // SyncEvolution has traditionally not folded long lines in // vCard. Testing showed that servers still have problems with // it, so avoid it by default " yes\n" "\n" " \n" "\n" " \n" "\n" " \n" " \n"; } substTag(xml, "clientorserver", clientorserver.str(), true); tag = ""; index = xml.find(tag); if (index != xml.npos) { stringstream debug; bool logging = !m_sourceListPtr->getLogdir().empty(); int loglevel = getLogLevel(); #ifdef USE_DLT const char *useDLT = getenv("SYNCEVOLUTION_USE_DLT"); #else static const char *useDLT = NULL; #endif debug << " \n" // logpath is a config variable set by SyncContext::doSync() " $(logpath)\n" " " << (useDLT ? "" : LogfileBasename) << "" << " flush\n" " " << (useDLT ? "dlt" : "html") << "\n" " auto\n" << (useDLT ? " no\n" " no\n" : " yes\n" " yes\n") << " no\n" " separate\n" " yes\n" " yes\n"; #ifdef USE_DLT if (useDLT) { const char *contexts[] = { "PROT", "SESS", "ADMN", "DATA", "REMI", "PARS", "GEN", "TRNS", "SMLT", "SYS" }; BOOST_FOREACH (const char *context, contexts) { // Help libsynthesis debuglogger.cpp set default log levels, // based on our own one. SE_LOG_DEBUG(NULL, "default libsynthesis DLT logging of %s = %s", context, useDLT); setenv((std::string("LIBSYNTHESIS_") + context).c_str(), useDLT, false); } debug << // We have to enable all logging inside libsynthesis. // The actual filtering then takes place inside DLT. // Message logging is not supported. " \n" // Allow logging outside of sessions. " yes\n" // Don't try per-session logging, it all goes to DLT anyway. " yes\n" ; // Be extra verbose if currently enabled. Cannot be changed later on. if (atoi(useDLT) > DLT_LOG_DEBUG) { debug << " \n" " \n"; } if (atoi(useDLT) > DLT_LOG_DEBUG) { debug << " \n"; } } else #endif // USE_DLT if (logging) { debug << " yes\n" " yes\n"; debug << "" << (loglevel >= 5 ? "yes" : "no") << "\n"; debug << "" << (loglevel >= 4 ? "yes" : "no") << "\n"; if (loglevel >= 3) { debug << " doxygen\n" " \n" " \n" " \n" " \n"; } } else { debug << " no\n" " no\n" " no\n" " no\n" " "; } debug << " \n"; xml.replace(index, tag.size(), debug.str()); } XMLConfigFragments fragments; tag = ""; index = xml.find(tag); if (index != xml.npos) { stringstream datastores; BOOST_FOREACH(SyncSource *source, *m_sourceListPtr) { string fragment; source->getDatastoreXML(fragment, fragments); string name; // Make sure that sub-datastores do not interfere with the global URI namespace // by adding a / prefix. That way we can have a "calendar" // alias for "calendar+todo" without conflicting with the underlying // "calendar", which will be called "calendar+todo/calendar" in the XML config. name = source->getVirtualSource(); if (!name.empty()) { name += m_findSourceSeparator; } name += source->getName(); datastores << " \n" << " " << source->getSynthesisID() << "\n" << fragment; datastores << " on\n"; if (source->getOperations().m_writeBlob) { // BLOB support is essential for caching partially received items. datastores << " on\n"; } SyncMode mode = StringToSyncMode(source->getSync()); if (source->getForceSlowSync()) { // we *want* a slow sync, but couldn't tell the client -> force it server-side datastores << " FORCESLOWSYNC(); \n"; } else if (mode == SYNC_LOCAL_CACHE_SLOW || mode == SYNC_LOCAL_CACHE_INCREMENTAL) { if (!m_serverMode) { SE_THROW("sync modes 'local-cache-*' are only supported on the server side"); } datastores << " SETREFRESHONLY(1); SETCACHEDATA(1);\n"; // datastores << " REFRESHONLY(); CACHEDATA(); SLOWSYNC(); ALERTCODE();\n"; } else if (mode != SYNC_SLOW && // slow-sync detection not implemented when running as server, // not even when initiating the sync (direct sync with phone) !m_serverMode && // is implemented as "delete local data" + "slow sync", // so a slow sync is acceptable in this case mode != SYNC_REFRESH_FROM_SERVER && mode != SYNC_REFRESH_FROM_REMOTE && // The forceSlow should be disabled if the sync session is // initiated by a remote peer (eg. Server Alerted Sync) !m_remoteInitiated && getPreventSlowSync() && (!source->getOperations().m_isEmpty || // check is only relevant if we have local data; !source->getOperations().m_isEmpty())) { // if we cannot check, assume we have data // We are not expecting a slow sync => refuse to execute one. // This is the client check for this, server must be handled // differently (TODO, MB #2416). datastores << " \n"; } if (m_serverMode && !m_localSync) { string uri = source->getURI(); if (!uri.empty()) { datastores << " "; } } datastores << " \n\n"; } /*If there is super datastore, add it here*/ //TODO generate specific superdatastore contents (MB #8753) //Now only works for synthesis built-in events+tasks BOOST_FOREACH (boost::shared_ptr vSource, m_sourceListPtr->getVirtualSources()) { std::string superType = vSource->getSourceType().m_format; std::string evoSyncSource = vSource->getDatabaseID(); std::vector mappedSources = unescapeJoinedString (evoSyncSource, ','); // always check for a consistent config SourceType sourceType = vSource->getSourceType(); BOOST_FOREACH (std::string source, mappedSources) { //check the data type SyncSource *subSource = (*m_sourceListPtr)[source]; SourceType subType = subSource->getSourceType(); //If there is no format explictly selected in sub SyncSource, we //have no way to determine whether it works with the format //specific in the virtual SyncSource, thus no warning in this //case. if (!subType.m_format.empty() && ( sourceType.m_format != subType.m_format || sourceType.m_forceFormat != subType.m_forceFormat)) { SE_LOG_WARNING(NULL, "Virtual data source \"%s\" and sub data source \"%s\" have different data format. Will use the format in virtual data source.", vSource->getDisplayName().c_str(), source.c_str()); } } if (mappedSources.size() !=2) { vSource->throwError ("virtual data source currently only supports events+tasks combinations"); } string name = vSource->getName(); datastores << " \n"; datastores << " \n" << " F.ISEVENT:=1\n" << " e\n" << " \n" << "\n \n" << " F.ISEVENT:=0\n" << " t\n" <<" \n" ; if (m_serverMode && !m_localSync) { string uri = vSource->getURI(); if (!uri.empty()) { datastores << " "; } } if (vSource->getForceSlowSync()) { // we *want* a slow sync, but couldn't tell the client -> force it server-side datastores << " FORCESLOWSYNC(); \n"; } std::string typesupport; typesupport = vSource->getDataTypeSupport(); datastores << " \n" << typesupport << " \n"; datastores <<"\n "; } if (datastores.str().empty()) { // Add dummy datastore, the engine needs it. sync() // checks that we have a valid configuration if it is // really needed. #if 0 datastores << "" "SyncEvolution" "" "" "" "" ""; #endif } xml.replace(index, tag.size(), datastores.str()); } substTag(xml, "fieldlists", fragments.m_fieldlists.join(), true); substTag(xml, "profiles", fragments.m_profiles.join(), true); substTag(xml, "datatypedefs", fragments.m_datatypes.join(), true); substTag(xml, "remoterules", rules + fragments.m_remoterules.join(), true); if (m_serverMode) { // TODO: set the device ID for an OBEX server } else { substTag(xml, "fakedeviceid", getDevID()); } substTag(xml, "model", getMod()); substTag(xml, "manufacturer", getMan()); substTag(xml, "hardwareversion", getHwv()); // abuse (?) the firmware version to store the SyncEvolution version number substTag(xml, "firmwareversion", getSwv()); substTag(xml, "devicetype", getDevType()); substTag(xml, "maxmsgsize", std::max(getMaxMsgSize().get(), 10000ul)); substTag(xml, "maxobjsize", std::max(getMaxObjSize().get(), 1024u)); if (m_serverMode) { UserIdentity id = getSyncUser(); Credentials cred = IdentityProviderCredentials(id, getSyncPassword()); const string &user = cred.m_username; const string &password = cred.m_password; /* * Do not check username/pwd if this local sync or over * bluetooth transport. Need credentials for checking. */ if (!m_localSync && !boost::starts_with(getUsedSyncURL(), "obex-bt") && (!user.empty() || !password.empty())) { // require authentication with the configured password substTag(xml, "defaultauth", "md5\n" "basic\n" "yes\n", true); } else { // no authentication required substTag(xml, "defaultauth", "return TRUE\n" "none\n" "none\n" "yes\n", true); } } else { substTag(xml, "defaultauth", getClientAuthType()); } // if the hash code is changed, that means the content of the // config has changed, save the new hash and regen the configdate hash = Hash(xml.c_str()); if (getHashCode() != hash) { setConfigDate(); setHashCode(hash); flush(); } substTag(xml, "configdate", getConfigDate().c_str()); } SharedEngine SyncContext::createEngine() { SharedEngine engine(new sysync::TEngineModuleBridge); // This instance of the engine is used outside of the sync session // itself for logging. doSync() then reinitializes it with a full // datastore configuration. engine.Connect(m_serverMode ? #ifdef ENABLE_SYNCML_LINKED // use Synthesis client or server engine that we were linked against "[server:]" : "[]", #else // load engine dynamically "server:libsynthesis.so.0" : "libsynthesis.so.0", #endif 0, sysync::DBG_PLUGIN_NONE| sysync::DBG_PLUGIN_INT| sysync::DBG_PLUGIN_DB| sysync::DBG_PLUGIN_EXOT); SharedKey configvars = engine.OpenKeyByPath(SharedKey(), "/configvars"); string logdir; if (m_sourceListPtr) { logdir = m_sourceListPtr->getLogdir(); } engine.SetStrValue(configvars, "defout_path", logdir.size() ? logdir : "/dev/null"); engine.SetStrValue(configvars, "conferrpath", "console"); engine.SetStrValue(configvars, "binfilepath", getSynthesisDatadir().c_str()); configvars.reset(); return engine; } namespace { void GnutlsLogFunction(int level, const char *str) { SE_LOG_DEBUG("GNUTLS", "level %d: %s", level, str); } } void SyncContext::initServer(const std::string &sessionID, SharedBuffer data, const std::string &messageType) { m_serverMode = true; m_sessionID = sessionID; m_initialMessage = data; m_initialMessageType = messageType; } struct SyncContext::SyncMLMessageInfo SyncContext::analyzeSyncMLMessage(const char *data, size_t len, const std::string &messageType) { SyncContext sync; SourceList sourceList(sync, false); sourceList.setLogLevel(SourceList::LOGGING_SUMMARY); sync.m_sourceListPtr = &sourceList; SwapContext syncSentinel(&sync); sync.initServer("", SharedBuffer(), ""); SwapEngine swapengine(sync); sync.initEngine(false); sysync::TEngineProgressInfo progressInfo; sysync::uInt16 stepCmd = sysync::STEPCMD_GOTDATA; SharedSession session = sync.m_engine.OpenSession(sync.m_sessionID); SessionSentinel sessionSentinel(sync, session); sync.m_engine.WriteSyncMLBuffer(session, data, len); SharedKey sessionKey = sync.m_engine.OpenSessionKey(session); sync.m_engine.SetStrValue(sessionKey, "contenttype", messageType); // analyze main loop: runs until SessionStep() signals reply or error. // Will call our SynthesisDBPlugin callbacks, most importantly // SyncEvolution_Session_CheckDevice(), which records the device ID // for us. do { sync.m_engine.SessionStep(session, stepCmd, &progressInfo); switch (stepCmd) { case sysync::STEPCMD_OK: case sysync::STEPCMD_PROGRESS: stepCmd = sysync::STEPCMD_STEP; break; default: // whatever it is, cannot proceed break; } } while (stepCmd == sysync::STEPCMD_STEP); SyncMLMessageInfo info; info.m_deviceID = sync.getSyncDeviceID(); return info; } void SyncContext::initEngine(bool logXML) { string xml, configname; getConfigXML(xml, configname); try { m_engine.InitEngineXML(xml.c_str()); } catch (const BadSynthesisResult &ex) { SE_LOG_ERROR(NULL, "internal error, invalid XML configuration (%s):\n%s", m_sourceListPtr && !m_sourceListPtr->empty() ? "with datastores" : "without datastores", xml.c_str()); throw; } if (logXML && getLogLevel() >= 5) { SE_LOG_DEV(NULL, "Full XML configuration:\n%s", xml.c_str()); } } // This is just the declaration. The actual function pointer instance // is inside libsynthesis, which, for historic purposes, doesn't define // it in its header files (yet). extern "C" int (*SySync_ConsolePrintf)(FILE *stream, const char *format, ...); static int nopPrintf(FILE *stream, const char *format, ...) { return 0; } extern "C" { extern int (*SySync_CondTimedWait)(pthread_cond_t *cond, pthread_mutex_t *mutex, bool &aTerminated, long aMilliSecondsToWait); } #ifdef HAVE_GLIB static gboolean timeout(gpointer data) { // Call me again... return true; } static bool CondTimedWaitContinue(pthread_mutex_t *mutex, bool &terminated, long milliSecondsToWait, Timespec &deadline, SuspendFlags &flags, int &result) { // Thread has terminated? pthread_mutex_lock(mutex); if (terminated) { pthread_mutex_unlock(mutex); SE_LOG_DEBUG(NULL, "background thread completed"); return false; } pthread_mutex_unlock(mutex); // Abort? Ignore when waiting for final thread shutdown, because // in that case we just get called again. if (milliSecondsToWait > 0 && flags.isAborted()) { SE_LOG_DEBUG(NULL, "give up waiting for background thread, aborted"); // Signal error. libsynthesis then assumes that the thread still // runs and enters its parallel message sending, which eventually // returns control to us. result = 1; return false; } // Timeout? if (!milliSecondsToWait || (milliSecondsToWait > 0 && deadline <= Timespec::system())) { SE_LOG_DEBUG(NULL, "give up waiting for background thread, timeout"); result = 1; return false; } return true; } static int CondTimedWaitGLib(pthread_cond_t * /* cond */, pthread_mutex_t *mutex, bool &terminated, long milliSecondsToWait) { int result = 0; // return abstime ? pthread_cond_timedwait(cond, mutex, abstime) : pthread_cond_wait(cond, mutex); try { pthread_mutex_unlock(mutex); SE_LOG_DEBUG(NULL, "wait for background thread: %lds", milliSecondsToWait); SuspendFlags &flags = SuspendFlags::getSuspendFlags(); Timespec now = Timespec::system(); Timespec wait(milliSecondsToWait / 1000, milliSecondsToWait % 1000); Timespec deadline = now + wait; // We don't need to react to thread shutdown immediately (only // called once per sync), so a relatively long check interval of // one second is okay. GLibEvent id(g_timeout_add_seconds(1, timeout, NULL), "timeout"); GRunWhile(boost::bind(CondTimedWaitContinue, mutex, boost::ref(terminated), milliSecondsToWait, boost::ref(deadline), boost::ref(flags), boost::ref(result))); } catch (...) { Exception::handle(HANDLE_EXCEPTION_FATAL); } pthread_mutex_lock(mutex); return result; } #endif void SyncContext::initMain(const char *appname) { #if defined(HAVE_GLIB) // this is required when using glib directly or indirectly g_type_init(); g_thread_init(NULL); g_set_prgname(appname); // redirect glib logging into our own logging g_log_set_default_handler(Logger::glogFunc, NULL); // Only the main thread may use the default GMainContext. // Anything else is unsafe, see https://mail.gnome.org/archives/gtk-list/2013-April/msg00040.html // util.cpp:Sleep() checks this and uses the default context // when called by the main thread, otherwise falls back to // select(). GRunWhile() is always safe to use. g_main_context_acquire(NULL); SySync_CondTimedWait = CondTimedWaitGLib; #endif if (atoi(getEnv("SYNCEVOLUTION_DEBUG", "0")) > 3) { SySync_ConsolePrintf = Logger::sysyncPrintf; } else { SySync_ConsolePrintf = nopPrintf; } // invoke optional init parts, for example KDE KApplication init // in KDE backend GetInitMainSignal()(appname); struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sa, NULL); // Initializing a potential use of EDS early is necessary for // libsynthesis when compiled with // --enable-evolution-compatibility: in that mode libical will // only be found by libsynthesis after EDSAbiWrapperInit() // pulls it into the process by loading libecal. EDSAbiWrapperInit(); if (const char *gnutlsdbg = getenv("SYNCEVOLUTION_GNUTLS_DEBUG")) { // Enable libgnutls debugging without creating a hard dependency on it, // because we don't call it directly and might not even be linked against // it. Therefore check for the relevant symbols via dlsym(). void (*set_log_level)(int); typedef void (*LogFunc_t)(int level, const char *str); void (*set_log_function)(LogFunc_t func); set_log_level = (typeof(set_log_level))dlsym(RTLD_DEFAULT, "gnutls_global_set_log_level"); set_log_function = (typeof(set_log_function))dlsym(RTLD_DEFAULT, "gnutls_global_set_log_function"); if (set_log_level && set_log_function) { set_log_level(atoi(gnutlsdbg)); set_log_function(GnutlsLogFunction); } else { SE_LOG_ERROR(NULL, "SYNCEVOLUTION_GNUTLS_DEBUG debugging not possible, log functions not found"); } } } SyncContext::InitMainSignal &SyncContext::GetInitMainSignal() { static InitMainSignal initMainSignal; return initMainSignal; } static bool IsStableRelease = #ifdef SYNCEVOLUTION_STABLE_RELEASE true #else false #endif ; bool SyncContext::isStableRelease() { return IsStableRelease; } void SyncContext::setStableRelease(bool isStableRelease) { IsStableRelease = isStableRelease; } void SyncContext::checkConfig(const std::string &operation) const { std::string peer, context; splitConfigString(m_server, peer, context); if (isConfigNeeded() && (!exists() || peer.empty())) { if (peer.empty()) { SE_LOG_INFO(NULL, "Configuration \"%s\" does not refer to a sync peer.", m_server.c_str()); } else { SE_LOG_INFO(NULL, "Configuration \"%s\" does not exist.", m_server.c_str()); } throwError(StringPrintf("Cannot proceed with %s without a configuration.", operation.c_str())); } } SyncMLStatus SyncContext::sync(SyncReport *report) { SyncMLStatus status = STATUS_OK; checkConfig("sync"); // redirect logging as soon as possible SourceList sourceList(*this, m_doLogging); sourceList.setLogLevel(m_quiet ? SourceList::LOGGING_QUIET : getPrintChanges() ? SourceList::LOGGING_FULL : SourceList::LOGGING_SUMMARY); // careful about scope, this is needed for writing the // report below SyncReport buffer; SwapContext syncSentinel(this); try { m_sourceListPtr = &sourceList; string url = getUsedSyncURL(); if (boost::starts_with(url, "local://")) { initLocalSync(url.substr(strlen("local://"))); } if (!report) { report = &buffer; } report->clear(); if (m_localSync) { report->setRemoteName(m_localPeerContext); report->setLocalName(getContextName()); } // let derived classes override settings, like the log dir prepare(); // choose log directory sourceList.startSession(getLogDir(), getMaxLogDirs(), getLogLevel(), report); /* Must detect server or client session before creating the * underlying SynthesisEngine * */ if ( getPeerIsClient()) { m_serverMode = true; } else if (m_localSync && !m_agent) { throwError("configuration error, syncURL = local can only be used in combination with peerIsClient = 1"); } // create a Synthesis engine, used purely for logging purposes // at this time SwapEngine swapengine(*this); initEngine(false); try { // dump some summary information at the beginning of the log SE_LOG_DEV(NULL, "SyncML server account: %s", getSyncUser().toString().c_str()); SE_LOG_DEV(NULL, "client: SyncEvolution %s for %s", getSwv().c_str(), getDevType().c_str()); SE_LOG_DEV(NULL, "device ID: %s", getDevID().c_str()); SE_LOG_DEV(NULL, "%s", EDSAbiWrapperDebug()); SE_LOG_DEV(NULL, "%s", SyncSource::backendsDebug().c_str()); // ensure that config can be modified (might have to be migrated first) prepareConfigForWrite(); // instantiate backends, but do not open them yet initSources(sourceList); if (sourceList.empty()) { throwError("no sources active, check configuration"); } // request all config properties once: throwing exceptions // now is okay, whereas later it would lead to leaks in the // not exception safe client library SyncConfig dummy; set activeSources = sourceList.getSources(); dummy.copy(*this, &activeSources); // start background thread if not running yet: // necessary to catch problems with Evolution backend startLoopThread(); // ask for passwords now PasswordConfigProperty::checkPasswords(getUserInterfaceNonNull(), *this, PasswordConfigProperty::CHECK_PASSWORD_ALL, sourceList.getSourceNames()); // open each source - failing now is still safe // in clients; in servers we wait until the source // is really needed BOOST_FOREACH(SyncSource *source, sourceList) { if (m_serverMode) { source->enableServerMode(); } else { source->open(); } // request callback when starting to use source source->getOperations().m_startDataRead.getPreSignal().connect(boost::bind(&SyncContext::startSourceAccess, this, source)); } // ready to go status = doSync(); } catch (...) { // handle the exception here while the engine (and logging!) is still alive Exception::handle(&status); goto report; } } catch (...) { Exception::handle(&status); } report: if (status == SyncMLStatus(sysync::LOCERR_DATASTORE_ABORT)) { // this can mean only one thing in SyncEvolution: unexpected slow sync status = STATUS_UNEXPECTED_SLOW_SYNC; } try { // Print final report before cleaning up. // Status was okay only if all sources succeeded. // When a source or the overall sync was successful, // but some items failed, we report a "partial failure" // status. BOOST_FOREACH(SyncSource *source, sourceList) { m_sourceSyncedSignal(source->getName(), *source); if (source->getStatus() == STATUS_OK && (source->getItemStat(SyncSource::ITEM_LOCAL, SyncSource::ITEM_ANY, SyncSource::ITEM_REJECT) || source->getItemStat(SyncSource::ITEM_REMOTE, SyncSource::ITEM_ANY, SyncSource::ITEM_REJECT))) { source->recordStatus(STATUS_PARTIAL_FAILURE); } if (source->getStatus() != STATUS_OK && status == STATUS_OK) { status = source->getStatus(); break; } } // Also take into account result of client side in local sync, // if any existed. A non-success status code in the client's report // was already propagated to the parent via a TransportStatusException // in LocalTransportAgent::checkChildReport(). What we can do here // is updating the individual's sources status. if (m_localSync && m_agent && getPeerIsClient()) { boost::shared_ptr agent = boost::static_pointer_cast(m_agent); SyncReport childReport; agent->getClientSyncReport(childReport); BOOST_FOREACH(SyncSource *source, sourceList) { const SyncSourceReport *childSourceReport = childReport.findSyncSourceReport(source->getURINonEmpty()); if (childSourceReport) { SyncMLStatus parentSourceStatus = source->getStatus(); SyncMLStatus childSourceStatus = childSourceReport->getStatus(); // child source had an error *and* // parent error is either unspecific (USERABORT) or // is a remote error (HTTP error range) if (childSourceStatus != STATUS_OK && childSourceStatus != STATUS_HTTP_OK && (parentSourceStatus == SyncMLStatus(sysync::LOCERR_USERABORT) || parentSourceStatus < SyncMLStatus(sysync::LOCAL_STATUS_CODE))) { source->recordStatus(childSourceStatus); } } } } sourceList.updateSyncReport(*report); sourceList.syncDone(status, report); } catch(...) { Exception::handle(&status); } m_agent.reset(); m_sourceListPtr = NULL; return status; } bool SyncContext::sendSAN(uint16_t version) { sysync::SanPackage san; bool legacy = version < 12; /* Should be nonce sent by the server in the preceeding sync session */ string nonce = "SyncEvolution"; UserIdentity id = getSyncUser(); Credentials cred = IdentityProviderCredentials(id, getSyncPassword()); const std::string &user = cred.m_username; const std::string &password = cred.m_password; string uauthb64 = san.B64_H(user, password); /* Client is expected to conduct the sync in the backgroud */ sysync::UI_Mode mode = sysync::UI_not_specified; uint16_t sessionId = 1; string serverId = getRemoteIdentifier(); if(serverId.empty()) { serverId = getDevID(); } SE_LOG_DEBUG(NULL, "starting SAN %u auth %s nonce %s session %u server %s", version, uauthb64.c_str(), nonce.c_str(), sessionId, serverId.c_str()); san.PreparePackage( uauthb64, nonce, version, mode, sysync::Initiator_Server, sessionId, serverId); san.CreateEmptyNotificationBody(); bool hasSource = false; std::set dataSources = m_sourceListPtr->getSources(); /* For each virtual datasoruce, generate the SAN accoring to it and ignoring * sub datasource in the later phase*/ BOOST_FOREACH (boost::shared_ptr vSource, m_sourceListPtr->getVirtualSources()) { std::string evoSyncSource = vSource->getDatabaseID(); std::string sync = vSource->getSync(); SANSyncMode mode = AlertSyncMode(StringToSyncMode(sync, true), getPeerIsClient()); std::vector mappedSources = unescapeJoinedString (evoSyncSource, ','); BOOST_FOREACH (std::string source, mappedSources) { dataSources.erase (source); if (mode == SA_SLOW) { // We force a source which the client is not expected to use into slow mode. // Shouldn't we rather reject attempts to synchronize it? (*m_sourceListPtr)[source]->setForceSlowSync(true); } } dataSources.insert (vSource->getName()); } SANSyncMode syncMode = SA_INVALID; vector > alertedSources; /* For each source to be notified do the following: */ BOOST_FOREACH (string name, dataSources) { boost::shared_ptr sc(getSyncSourceConfig(name)); string sync = sc->getSync(); SANSyncMode mode = AlertSyncMode(StringToSyncMode(sync, true), getPeerIsClient()); if (mode == SA_SLOW) { (*m_sourceListPtr)[name]->setForceSlowSync(true); mode = SA_TWO_WAY; } if (mode < SA_FIRST || mode > SA_LAST) { SE_LOG_DEV(NULL, "Ignoring data source %s with an invalid sync mode", name.c_str()); continue; } syncMode = mode; hasSource = true; string uri = sc->getURINonEmpty(); SourceType sourceType = sc->getSourceType(); /*If the type is not set by user explictly, let's use backend default * value*/ if(sourceType.m_format.empty()) { sourceType.m_format = (*m_sourceListPtr)[name]->getPeerMimeType(); } if (!legacy) { /*If user did not use force type, we will always use the older type as * this is what most phones support*/ int contentTypeB = StringToContentType (sourceType.m_format, sourceType.m_forceFormat); if (contentTypeB == WSPCTC_UNKNOWN) { contentTypeB = 0; SE_LOG_DEBUG(NULL, "Unknown datasource mimetype, use 0 as default"); } SE_LOG_DEBUG(NULL, "SAN source %s uri %s type %u mode %d", name.c_str(), uri.c_str(), contentTypeB, mode); if ( san.AddSync(mode, (uInt32) contentTypeB, uri.c_str())) { SE_LOG_ERROR(NULL, "SAN: adding server alerted sync element failed"); }; } else { string mimetype = GetLegacyMIMEType(sourceType.m_format, sourceType.m_forceFormat); SE_LOG_DEBUG(NULL, "SAN source %s uri %s type %s", name.c_str(), uri.c_str(), mimetype.c_str()); alertedSources.push_back(std::make_pair(mimetype, uri)); } } if (!hasSource) { SE_THROW ("No source enabled for server alerted sync!"); } /* Generate the SAN Package */ void *buffer; size_t sanSize; if (!legacy) { if (san.GetPackage(buffer, sanSize)){ SE_LOG_ERROR(NULL, "SAN package generating failed"); return false; } //TODO log the binary SAN content } else { SE_LOG_DEBUG(NULL, "SAN with overall sync mode %d", syncMode); if (san.GetPackageLegacy(buffer, sanSize, alertedSources, syncMode, getWBXML())){ SE_LOG_ERROR(NULL, "SAN package generating failed"); return false; } //SE_LOG_DEBUG(NULL, "SAN package content: %s", (char*)buffer); } m_agent = createTransportAgent(); SE_LOG_INFO(NULL, "Server sending SAN"); m_serverAlerted = true; m_agent->setContentType(!legacy ? TransportAgent::m_contentTypeServerAlertedNotificationDS : (getWBXML() ? TransportAgent::m_contentTypeSyncWBXML : TransportAgent::m_contentTypeSyncML)); m_agent->send(reinterpret_cast (buffer), sanSize); //change content type m_agent->setContentType(getWBXML() ? TransportAgent::m_contentTypeSyncWBXML : TransportAgent::m_contentTypeSyncML); TransportAgent::Status status; do { status = m_agent->wait(); } while(status == TransportAgent::ACTIVE); if (status == TransportAgent::GOT_REPLY) { const char *reply; size_t replyLen; string contentType; m_agent->getReply (reply, replyLen, contentType); //sanity check for the reply if (contentType.empty() || contentType.find(TransportAgent::m_contentTypeSyncML) != contentType.npos || contentType.find(TransportAgent::m_contentTypeSyncWBXML) != contentType.npos) { SharedBuffer request (reply, replyLen); //TODO should generate more reasonable sessionId here string sessionId =""; initServer (sessionId, request, contentType); return true; } } return false; } static string Step2String(sysync::uInt16 stepcmd) { switch (stepcmd) { case sysync::STEPCMD_CLIENTSTART: return "STEPCMD_CLIENTSTART"; case sysync::STEPCMD_CLIENTAUTOSTART: return "STEPCMD_CLIENTAUTOSTART"; case sysync::STEPCMD_STEP: return "STEPCMD_STEP"; case sysync::STEPCMD_GOTDATA: return "STEPCMD_GOTDATA"; case sysync::STEPCMD_SENTDATA: return "STEPCMD_SENTDATA"; case sysync::STEPCMD_SUSPEND: return "STEPCMD_SUSPEND"; case sysync::STEPCMD_ABORT: return "STEPCMD_ABORT"; case sysync::STEPCMD_TRANSPFAIL: return "STEPCMD_TRANSPFAIL"; case sysync::STEPCMD_TIMEOUT: return "STEPCMD_TIMEOUT"; case sysync::STEPCMD_SAN_CHECK: return "STEPCMD_SAN_CHECK"; case sysync::STEPCMD_AUTOSYNC_CHECK: return "STEPCMD_AUTOSYNC_CHECK"; case sysync::STEPCMD_OK: return "STEPCMD_OK"; case sysync::STEPCMD_PROGRESS: return "STEPCMD_PROGRESS"; case sysync::STEPCMD_ERROR: return "STEPCMD_ERROR"; case sysync::STEPCMD_SENDDATA: return "STEPCMD_SENDDATA"; case sysync::STEPCMD_NEEDDATA: return "STEPCMD_NEEDDATA"; case sysync::STEPCMD_RESENDDATA: return "STEPCMD_RESENDDATA"; case sysync::STEPCMD_DONE: return "STEPCMD_DONE"; case sysync::STEPCMD_RESTART: return "STEPCMD_RESTART"; case sysync::STEPCMD_NEEDSYNC: return "STEPCMD_NEEDSYNC"; default: return StringPrintf("STEPCMD %d", stepcmd); } } SyncMLStatus SyncContext::doSync() { boost::shared_ptr signalGuard; // install signal handlers unless this was explicitly disabled bool catchSignals = getenv("SYNCEVOLUTION_NO_SYNC_SIGNALS") == NULL; if (catchSignals) { SE_LOG_DEBUG(NULL, "sync is starting, catch signals"); signalGuard = SuspendFlags::getSuspendFlags().activate(); } // delay the sync for debugging purposes SE_LOG_DEBUG(NULL, "ready to sync"); const char *delay = getenv("SYNCEVOLUTION_SYNC_DELAY"); if (delay) { Sleep(atoi(delay)); } SuspendFlags &flags = SuspendFlags::getSuspendFlags(); if (!flags.isNormal()) { return (SyncMLStatus)sysync::LOCERR_USERABORT; } SyncMLStatus status = STATUS_OK; std::string s; if (m_serverMode && !m_initialMessage.size() && !m_localSync) { //This is a server alerted sync ! string sanFormat (getSyncMLVersion()); uint16_t version = 12; if (boost::iequals (sanFormat, "1.2") || sanFormat == "") { version = 12; } else if (boost::iequals (sanFormat, "1.1")) { version = 11; } else { version = 10; } bool status = true; try { status = sendSAN (version); } catch (TransportException e) { if (!sanFormat.empty()){ throw; } status = false; //by pass the exception if we will try again with legacy SANFormat } if (!flags.isNormal()) { return (SyncMLStatus)sysync::LOCERR_USERABORT; } if (! status) { if (sanFormat.empty()) { SE_LOG_DEBUG(NULL, "Server Alerted Sync init with SANFormat %d failed, trying with legacy format", version); version = 11; if (!sendSAN (version)) { // return a proper error code throwError ("Server Alerted Sync init failed"); } } else { // return a proper error code throwError ("Server Alerted Sync init failed"); } } } if (!flags.isNormal()) { return (SyncMLStatus)sysync::LOCERR_USERABORT; } // re-init engine with all sources configured string xml, configname; initEngine(true); SharedKey targets; SharedKey target; if (m_serverMode) { // Server engine has no profiles. All settings have be done // via the XML configuration or function parameters (session ID // in OpenSession()). } else { // check the settings status (MUST BE DONE TO MAKE SETTINGS READY) SharedKey profiles = m_engine.OpenKeyByPath(SharedKey(), "/profiles"); m_engine.GetStrValue(profiles, "settingsstatus"); // allow creating new settings when existing settings are not up/downgradeable m_engine.SetStrValue(profiles, "overwrite", "1"); // check status again m_engine.GetStrValue(profiles, "settingsstatus"); // open first profile SharedKey profile; profile = m_engine.OpenSubkey(profiles, sysync::KEYVAL_ID_FIRST, true); if (!profile) { // no profile exists yet, create default profile profile = m_engine.OpenSubkey(profiles, sysync::KEYVAL_ID_NEW_DEFAULT); } if (!m_localSync) { // Not needed for local sync and might even be // impossible/wrong because username could refer to an // identity provider which cannot return a plain string. SE_LOG_DEBUG(NULL, "copying syncURL, username, password to Synthesis engine"); m_engine.SetStrValue(profile, "serverURI", getUsedSyncURL()); UserIdentity syncUser = getSyncUser(); InitStateString syncPassword = getSyncPassword(); boost::shared_ptr provider = AuthProvider::create(syncUser, syncPassword); Credentials cred = provider->getCredentials(); const std::string &user = cred.m_username; const std::string &password = cred.m_password; m_engine.SetStrValue(profile, "serverUser", user); m_engine.SetStrValue(profile, "serverPassword", password); } m_engine.SetInt32Value(profile, "encoding", getWBXML() ? 1 /* WBXML */ : 2 /* XML */); // Iterate over all data stores in the XML config // and match them with sync sources. // TODO: let sync sources provide their own // XML snippets (inside and inside ). targets = m_engine.OpenKeyByPath(profile, "targets"); for(target = m_engine.OpenSubkey(targets, sysync::KEYVAL_ID_FIRST, true); target; target = m_engine.OpenSubkey(targets, sysync::KEYVAL_ID_NEXT, true)) { s = m_engine.GetStrValue(target, "dbname"); SyncSource *source = findSource(s); if (source) { m_engine.SetInt32Value(target, "enabled", 1); int slow = 0; int direction = 0; string sync = source->getSync(); // this code only runs when we are the client, // take that into account for the "from-local/remote" modes SimpleSyncMode mode = SimplifySyncMode(StringToSyncMode(sync), false); if (mode == SIMPLE_SYNC_SLOW) { slow = 1; direction = 0; } else if (mode == SIMPLE_SYNC_TWO_WAY) { slow = 0; direction = 0; } else if (mode == SIMPLE_SYNC_REFRESH_FROM_REMOTE) { slow = 1; direction = 1; } else if (mode == SIMPLE_SYNC_REFRESH_FROM_LOCAL) { slow = 1; direction = 2; } else if (mode == SIMPLE_SYNC_ONE_WAY_FROM_REMOTE) { slow = 0; direction = 1; } else if (mode == SIMPLE_SYNC_ONE_WAY_FROM_LOCAL) { slow = 0; direction = 2; } else { source->throwError(string("invalid sync mode: ") + sync); } m_engine.SetInt32Value(target, "forceslow", slow); m_engine.SetInt32Value(target, "syncmode", direction); string uri = source->getURINonEmpty(); m_engine.SetStrValue(target, "remotepath", uri); } else { m_engine.SetInt32Value(target, "enabled", 0); } } // Close all keys so that engine can flush the modified config. // Otherwise the session reads the unmodified values from the // created files while the updated values are still in memory. target.reset(); targets.reset(); profile.reset(); profiles.reset(); // reopen profile keys profiles = m_engine.OpenKeyByPath(SharedKey(), "/profiles"); m_engine.GetStrValue(profiles, "settingsstatus"); profile = m_engine.OpenSubkey(profiles, sysync::KEYVAL_ID_FIRST); targets = m_engine.OpenKeyByPath(profile, "targets"); } m_retries = 0; //Create the transport agent if not already created if(!m_agent) { m_agent = createTransportAgent(); } // server in local sync initiates sync by passing data to forked process if (m_serverMode && m_localSync) { m_serverAlerted = true; } sysync::TEngineProgressInfo progressInfo; sysync::uInt16 stepCmd = (m_localSync && m_serverMode) ? sysync::STEPCMD_NEEDDATA : m_serverMode ? sysync::STEPCMD_GOTDATA : sysync::STEPCMD_CLIENTSTART; SharedSession session = m_engine.OpenSession(m_sessionID); SharedBuffer sendBuffer; std::auto_ptr sessionSentinel(new SessionSentinel(*this, session)); if (m_serverMode && !m_localSync) { m_engine.WriteSyncMLBuffer(session, m_initialMessage.get(), m_initialMessage.size()); SharedKey sessionKey = m_engine.OpenSessionKey(session); m_engine.SetStrValue(sessionKey, "contenttype", m_initialMessageType); m_initialMessage.reset(); // TODO: set "sendrespuri" session key to control // whether the generated messages contain a respURI // (not needed for OBEX) } // Sync main loop: runs until SessionStep() signals end or error. // Exceptions are caught and lead to a call of SessionStep() with // parameter STEPCMD_ABORT -> abort session as soon as possible. bool aborting = false; int suspending = 0; Timespec sendStart, resendStart; int requestNum = 0; sysync::uInt16 previousStepCmd = stepCmd; std::vector numItemsReceived; // source->getTotalNumItemsReceived() for each source, see STEPCMD_SENDDATA do { try { // check for suspend, if so, modify step command for next step // Since the suspend will actually be committed until it is // sending out a message, we can safely delay the suspend to // GOTDATA state. // After exception occurs, stepCmd will be set to abort to force // aborting, must avoid to change it back to suspend cmd. if (flags.isSuspended() && stepCmd == sysync::STEPCMD_GOTDATA) { SE_LOG_DEBUG(NULL, "suspending before SessionStep() in STEPCMD_GOTDATA as requested by user"); stepCmd = sysync::STEPCMD_SUSPEND; } // Aborting is useful while waiting for a reply and before // sending a message (which will just lead to us waiting // for the reply, but possibly after doing some slow network // IO for setting up the message send). // // While processing a message we let the engine run, because // that is a) likely to be done soon and b) may reduce the // breakage caused by aborting a running sync. // // This check here covers the "waiting for reply" case. if ((stepCmd == sysync::STEPCMD_RESENDDATA || stepCmd == sysync::STEPCMD_SENTDATA || stepCmd == sysync::STEPCMD_NEEDDATA) && flags.isAborted()) { SE_LOG_DEBUG(NULL, "aborting before SessionStep() in %s as requested by script", Step2String(stepCmd).c_str()); stepCmd = sysync::STEPCMD_ABORT; } // take next step, but don't abort twice: instead // let engine contine with its shutdown if (stepCmd == sysync::STEPCMD_ABORT) { if (aborting) { SE_LOG_DEBUG(NULL, "engine already notified of abort request, reverting to %s", Step2String(previousStepCmd).c_str()); stepCmd = previousStepCmd; } else { aborting = true; } } // same for suspending if (stepCmd == sysync::STEPCMD_SUSPEND) { if (suspending) { SE_LOG_DEBUG(NULL, "engine already notified of suspend request, reverting to %s", Step2String(previousStepCmd).c_str()); stepCmd = previousStepCmd; suspending++; } else { suspending++; } } if (stepCmd == sysync::STEPCMD_NEEDDATA) { // Engine already notified. Don't call it twice // with this state, because it doesn't know how // to handle this. Skip the SessionStep() call // and wait for response. } else { if (getLogLevel() > 4) { SE_LOG_DEBUG(NULL, "before SessionStep: %s", Step2String(stepCmd).c_str()); } m_engine.SessionStep(session, stepCmd, &progressInfo); if (getLogLevel() > 4) { SE_LOG_DEBUG(NULL, "after SessionStep: %s", Step2String(stepCmd).c_str()); } reportStepCmd(stepCmd); } if (stepCmd == sysync::STEPCMD_SENDDATA && checkForScriptAbort(session)) { SE_LOG_DEBUG(NULL, "aborting after SessionStep() in STEPCMD_SENDDATA as requested by script"); // Catch outgoing message and abort if requested by script. // Report which sources are affected, based on their status code. set sources; BOOST_FOREACH(SyncSource *source, *m_sourceListPtr) { if (source->getStatus() == STATUS_UNEXPECTED_SLOW_SYNC) { string name = source->getVirtualSource(); if (name.empty()) { name = source->getName(); } sources.insert(name); } } string explanation = SyncReport::slowSyncExplanation(m_server, sources); if (!explanation.empty()) { string sourceparam = boost::join(sources, " "); SE_LOG_ERROR(NULL, "Aborting because of unexpected slow sync for source(s): %s", sourceparam.c_str()); SE_LOG_INFO(NULL, "%s", explanation.c_str()); } else { // we should not get here, but if we do, at least log something SE_LOG_ERROR(NULL, "aborting as requested by script"); } stepCmd = sysync::STEPCMD_ABORT; continue; } else if (stepCmd == sysync::STEPCMD_SENDDATA && flags.isAborted()) { // Catch outgoing message and abort if requested by user. SE_LOG_DEBUG(NULL, "aborting after SessionStep() in STEPCMD_SENDDATA as requested by user"); stepCmd = sysync::STEPCMD_ABORT; continue; } else if (suspending == 1) { //During suspention we actually insert a STEPCMD_SUSPEND cmd //Should restore to the original step here stepCmd = previousStepCmd; continue; } switch (stepCmd) { case sysync::STEPCMD_OK: // no progress info, call step again stepCmd = sysync::STEPCMD_STEP; break; case sysync::STEPCMD_PROGRESS: // new progress info to show // Check special case of interactive display alert if (progressInfo.eventtype == sysync::PEV_DISPLAY100) { // alert 100 received from remote, message text is in // SessionKey's "displayalert" field SharedKey sessionKey = m_engine.OpenSessionKey(session); // get message from server to display s = m_engine.GetStrValue(sessionKey, "displayalert"); displayServerMessage(s); } else { switch (progressInfo.targetID) { case sysync::KEYVAL_ID_UNKNOWN: case 0 /* used with PEV_SESSIONSTART?! */: displaySyncProgress(sysync::TProgressEventEnum(progressInfo.eventtype), progressInfo.extra1, progressInfo.extra2, progressInfo.extra3); if (progressInfo.eventtype == sysync::PEV_SESSIONEND && !status) { // remember sync result status = SyncMLStatus(progressInfo.extra1); } break; default: { // specific for a certain sync source: // find it... SyncSource *source = m_sourceListPtr->lookupBySynthesisID(progressInfo.targetID); if (source) { displaySourceProgress(*source, SyncSourceEvent(sysync::TProgressEventEnum(progressInfo.eventtype), progressInfo.extra1, progressInfo.extra2, progressInfo.extra3), false); } else { throwError(std::string("unknown target ") + s); } target.reset(); break; } } } stepCmd = sysync::STEPCMD_STEP; break; case sysync::STEPCMD_ERROR: // error, terminate (should not happen, as status is // already checked above) break; case sysync::STEPCMD_RESTART: // make sure connection is closed and will be re-opened for next request // tbd: close communication channel if still open to make sure it is // re-opened for the next request stepCmd = sysync::STEPCMD_STEP; m_retries = 0; break; case sysync::STEPCMD_SENDDATA: { // We'll be busy for a while with network IO, so give // sources a chance to do some work in parallel and // flush pending progress notifications. if (m_sourceListPtr) { bool needResults = true; if (numItemsReceived.size() < m_sourceListPtr->size()) { numItemsReceived.insert(numItemsReceived.end(), m_sourceListPtr->size() - numItemsReceived.size(), 0); } for (size_t i = 0; i < numItemsReceived.size(); i++) { SyncSource *source = (*m_sourceListPtr->getSourceSet())[i]; int received = source->getTotalNumItemsReceived(); SE_LOG_DEBUG(source->getDisplayName(), "total number of items received %d", received); if (numItemsReceived[i] != received) { numItemsReceived[i] = received; needResults = false; } } BOOST_FOREACH (SyncSource *source, *m_sourceListPtr) { source->flushItemChanges(); if (needResults) { source->finishItemChanges(); } displaySourceProgress(*source, SyncSourceEvent(), false); } } // send data to remote SharedKey sessionKey = m_engine.OpenSessionKey(session); if (m_serverMode) { m_agent->setURL(""); } else { // use OpenSessionKey() and GetValue() to retrieve "connectURI" // and "contenttype" to be used to send data to the server s = m_engine.GetStrValue(sessionKey, "connectURI"); m_agent->setURL(s); } s = m_engine.GetStrValue(sessionKey, "contenttype"); m_agent->setContentType(s); sessionKey.reset(); sendStart = resendStart = Timespec::monotonic(); requestNum ++; // use GetSyncMLBuffer()/RetSyncMLBuffer() to access the data to be // sent or have it copied into caller's buffer using // ReadSyncMLBuffer(), then send it to the server sendBuffer = m_engine.GetSyncMLBuffer(session, true); m_agent->send(sendBuffer.get(), sendBuffer.size()); stepCmd = sysync::STEPCMD_SENTDATA; // we have sent the data break; } case sysync::STEPCMD_RESENDDATA: { SE_LOG_INFO(NULL, "resend previous message, retry #%d", m_retries); resendStart = Timespec::monotonic(); /* We are resending previous message, just read from the * previous buffer */ m_agent->send(sendBuffer.get(), sendBuffer.size()); stepCmd = sysync::STEPCMD_SENTDATA; // we have sent the data break; } case sysync::STEPCMD_NEEDDATA: if (!sendStart) { // no message sent yet, record start of wait for data sendStart = Timespec::monotonic(); } switch (m_agent->wait()) { case TransportAgent::ACTIVE: // Still sending the data?! Don't change anything, // skip SessionStep() above. break; case TransportAgent::TIME_OUT: { double duration = (Timespec::monotonic() - sendStart).duration(); // HTTP SyncML servers cannot resend a HTTP POST // reply. Other server transports could in theory // resend, but don't have the necessary D-Bus APIs // (MB #6370). // Same if() as below for FAILED. if (m_serverMode || !m_retryInterval || duration + 0.9 >= m_retryDuration || requestNum == 1) { SE_LOG_INFO(NULL, "Transport giving up after %d retries and %ld:%02ldmin", m_retries, (long)duration / 60, (long)duration % 60); SE_THROW_EXCEPTION(TransportException, "timeout, retry period exceeded"); }else { // Timeout must have been due to retryInterval having passed, resend // immediately. m_retries ++; stepCmd = sysync::STEPCMD_RESENDDATA; } break; } case TransportAgent::GOT_REPLY: { const char *reply; size_t replylen; string contentType; m_agent->getReply(reply, replylen, contentType); // sanity check for reply: if known at all, it must be either XML or WBXML if (contentType.empty() || contentType.find("application/vnd.syncml+wbxml") != contentType.npos || contentType.find("application/vnd.syncml+xml") != contentType.npos) { // put answer received earlier into SyncML engine's buffer m_retries = 0; sendBuffer.reset(); m_engine.WriteSyncMLBuffer(session, reply, replylen); if (m_serverMode) { SharedKey sessionKey = m_engine.OpenSessionKey(session); m_engine.SetStrValue(sessionKey, "contenttype", contentType); } stepCmd = sysync::STEPCMD_GOTDATA; // we have received response data break; } else { SE_LOG_DEBUG(NULL, "unexpected content type '%s' in reply, %d bytes:\n%.*s", contentType.c_str(), (int)replylen, (int)replylen, reply); SE_LOG_ERROR(NULL, "unexpected reply from peer; might be a temporary problem, try again later"); } //fall through to network failure case } /* If this is a network error, it usually failed quickly, retry * immediately has likely no effect. Manually sleep here to wait a while * before retry. Sleep time will be calculated so that the * message sending interval equals m_retryInterval. */ case TransportAgent::FAILED: { // Send might have failed because of abort or // suspend request. if (flags.isSuspended()) { SE_LOG_DEBUG(NULL, "suspending after TransportAgent::FAILED as requested by user"); stepCmd = sysync::STEPCMD_SUSPEND; break; } else if (flags.isAborted()) { SE_LOG_DEBUG(NULL, "aborting after TransportAgent::FAILED as requested by user"); stepCmd = sysync::STEPCMD_ABORT; break; } Timespec curTime = Timespec::monotonic(); double duration = (curTime - sendStart).duration(); double resendDelay = m_retryInterval - (curTime - resendStart).duration(); if (resendDelay < 0) { resendDelay = 0; } // Similar if() as above for TIME_OUT. In addition, we must check that // the next resend won't happen after the retryDuration, because then // we might as well give up now immediately. Include some fuzz factor // in case we woke up slightly too early. if (m_serverMode || !m_retryInterval || duration + resendDelay + 0.9 >= m_retryDuration || requestNum == 1) { SE_LOG_INFO(NULL, "Transport giving up after %d retries and %ld:%02ldmin", m_retries, (long)duration / 60, (long)duration % 60); SE_THROW_EXCEPTION(TransportException, "transport failed, retry period exceeded"); } else { // Resend after having ensured that the retryInterval is over. if (resendDelay > 0) { if (Sleep(resendDelay) > 0) { if (flags.isSuspended()) { SE_LOG_DEBUG(NULL, "suspending after premature exit from sleep() caused by user suspend"); stepCmd = sysync::STEPCMD_SUSPEND; } else { SE_LOG_DEBUG(NULL, "aborting after premature exit from sleep() caused by user abort"); stepCmd = sysync::STEPCMD_ABORT; } break; } } m_retries ++; stepCmd = sysync::STEPCMD_RESENDDATA; } break; } case TransportAgent::CANCELED: // Send might have failed because of abort or // suspend request. if (flags.isSuspended()) { SE_LOG_DEBUG(NULL, "suspending after TransportAgent::CANCELED as requested by user"); stepCmd = sysync::STEPCMD_SUSPEND; break; } else if (flags.isAborted()) { SE_LOG_DEBUG(NULL, "aborting after TransportAgent::CANCELED as requested by user"); stepCmd = sysync::STEPCMD_ABORT; break; } // not sure exactly why it is canceled SE_THROW_EXCEPTION_STATUS(BadSynthesisResult, "transport canceled", sysync::LOCERR_USERABORT); break; default: stepCmd = sysync::STEPCMD_TRANSPFAIL; // communication with server failed break; } } // Don't tell engine to abort when it already did. if (aborting && stepCmd == sysync::STEPCMD_ABORT) { stepCmd = sysync::STEPCMD_DONE; } previousStepCmd = stepCmd; // loop until session done or aborted with error } catch (const BadSynthesisResult &result) { if (result.result() == sysync::LOCERR_USERABORT && aborting) { SE_LOG_INFO(NULL, "Aborted as requested."); stepCmd = sysync::STEPCMD_DONE; } else if (result.result() == sysync::LOCERR_USERSUSPEND && suspending) { SE_LOG_INFO(NULL, "Suspended as requested."); stepCmd = sysync::STEPCMD_DONE; } else if (aborting) { // aborting very early can lead to results different from LOCERR_USERABORT // => don't treat this as error SE_LOG_INFO(NULL, "Aborted with unexpected result (%d)", static_cast(result.result())); stepCmd = sysync::STEPCMD_DONE; } else { Exception::handle(&status); SE_LOG_DEBUG(NULL, "aborting after catching fatal error"); // Don't tell engine to abort when it already did. stepCmd = aborting ? sysync::STEPCMD_DONE : sysync::STEPCMD_ABORT; } } catch (...) { Exception::handle(&status); SE_LOG_DEBUG(NULL, "aborting after catching fatal error"); // Don't tell engine to abort when it already did. stepCmd = aborting ? sysync::STEPCMD_DONE : sysync::STEPCMD_ABORT; } } while (stepCmd != sysync::STEPCMD_DONE && stepCmd != sysync::STEPCMD_ERROR); // If we get here without error, then close down connection normally. // Otherwise destruct the agent without further communication. if (!status && !flags.isAborted()) { try { m_agent->shutdown(); // TODO: implement timeout for peers which fail to respond while (!flags.isAborted() && m_agent->wait(true) == TransportAgent::ACTIVE) { // TODO: allow aborting the sync here } } catch (...) { status = handleException(); } } // Let session shut down before auto-destructing anything else // (like our signal blocker). This may take a while, because it // may involve shutting down the helper background thread which // opened our local datastore. SE_LOG_DEBUG(NULL, "closing session"); sessionSentinel.reset(); session.reset(); SE_LOG_DEBUG(NULL, "session closed"); return status; } string SyncContext::getSynthesisDatadir() { if (isEphemeral() && m_sourceListPtr) { return m_sourceListPtr->getLogdir() + "/synthesis"; } else if (m_localSync && !m_serverMode) { return m_localClientRootPath + "/.synthesis"; } else { return getRootPath() + "/.synthesis"; } } SyncMLStatus SyncContext::handleException() { SyncMLStatus res = Exception::handle(); return res; } void SyncContext::status() { checkConfig("status check"); SourceList sourceList(*this, false); initSources(sourceList); PasswordConfigProperty::checkPasswords(getUserInterfaceNonNull(), *this, // Don't need sync passwords. PasswordConfigProperty::CHECK_PASSWORD_ALL & ~PasswordConfigProperty::CHECK_PASSWORD_SYNC, sourceList.getSourceNames()); BOOST_FOREACH(SyncSource *source, sourceList) { source->open(); } SyncReport changes; checkSourceChanges(sourceList, changes); stringstream out; changes.prettyPrint(out, SyncReport::WITHOUT_SERVER| SyncReport::WITHOUT_CONFLICTS| SyncReport::WITHOUT_REJECTS| SyncReport::WITH_TOTAL); SE_LOG_INFO(NULL, "Local item changes:\n%s", out.str().c_str()); sourceList.accessSession(getLogDir()); Logger::instance().setLevel(Logger::INFO); string prevLogdir = sourceList.getPrevLogdir(); bool found = access(prevLogdir.c_str(), R_OK|X_OK) == 0; if (found) { if (!m_quiet && getPrintChanges()) { try { sourceList.setPath(prevLogdir); sourceList.dumpDatabases("current", NULL); sourceList.dumpLocalChanges("", "after", "current", ""); } catch(...) { Exception::handle(); } } } else { SE_LOG_SHOW(NULL, "Previous log directory not found."); if (getLogDir().empty()) { SE_LOG_SHOW(NULL, "Enable the 'logdir' option and synchronize to use this feature."); } } } void SyncContext::checkStatus(SyncReport &report) { checkConfig("status check"); SourceList sourceList(*this, false); initSources(sourceList); PasswordConfigProperty::checkPasswords(getUserInterfaceNonNull(), *this, // Don't need sync passwords. PasswordConfigProperty::CHECK_PASSWORD_ALL & ~PasswordConfigProperty::CHECK_PASSWORD_SYNC, sourceList.getSourceNames()); BOOST_FOREACH(SyncSource *source, sourceList) { source->open(); } checkSourceChanges(sourceList, report); } static void logRestoreReport(const SyncReport &report, bool dryrun) { if (!report.empty()) { stringstream out; report.prettyPrint(out, SyncReport::WITHOUT_SERVER|SyncReport::WITHOUT_CONFLICTS|SyncReport::WITH_TOTAL); SE_LOG_INFO(NULL, "Item changes %s applied locally during restore:\n%s", dryrun ? "to be" : "that were", out.str().c_str()); SE_LOG_INFO(NULL, "The same incremental changes will be applied to the server during the next sync."); SE_LOG_INFO(NULL, "Use -sync refresh-from-client to replace the complete data on the server."); } } void SyncContext::checkSourceChanges(SourceList &sourceList, SyncReport &changes) { changes.setStart(time(NULL)); BOOST_FOREACH(SyncSource *source, sourceList) { SyncSourceReport local; if (source->getOperations().m_checkStatus) { source->getOperations().m_checkStatus(local); } else { // no information available local.setItemStat(SyncSourceReport::ITEM_LOCAL, SyncSourceReport::ITEM_ADDED, SyncSourceReport::ITEM_TOTAL, -1); local.setItemStat(SyncSourceReport::ITEM_LOCAL, SyncSourceReport::ITEM_UPDATED, SyncSourceReport::ITEM_TOTAL, -1); local.setItemStat(SyncSourceReport::ITEM_LOCAL, SyncSourceReport::ITEM_REMOVED, SyncSourceReport::ITEM_TOTAL, -1); local.setItemStat(SyncSourceReport::ITEM_LOCAL, SyncSourceReport::ITEM_ANY, SyncSourceReport::ITEM_TOTAL, -1); } changes.addSyncSourceReport(source->getName(), local); } changes.setEnd(time(NULL)); } bool SyncContext::checkForScriptAbort(SharedSession session) { try { SharedKey sessionKey = m_engine.OpenSessionKey(session); SharedKey contextKey = m_engine.OpenKeyByPath(sessionKey, "/sessionvars"); bool abort = m_engine.GetInt32Value(contextKey, "delayedabort"); return abort; } catch (NoSuchKey) { // this is necessary because the session might already have // been closed, which removes the variable return false; } catch (BadSynthesisResult) { return false; } } void SyncContext::restore(const string &dirname, RestoreDatabase database) { checkConfig("restore"); SourceList sourceList(*this, false); sourceList.accessSession(dirname.c_str()); Logger::instance().setLevel(Logger::INFO); initSources(sourceList); PasswordConfigProperty::checkPasswords(getUserInterfaceNonNull(), *this, // Don't need sync passwords. PasswordConfigProperty::CHECK_PASSWORD_ALL & ~PasswordConfigProperty::CHECK_PASSWORD_SYNC, sourceList.getSourceNames()); string datadump = database == DATABASE_BEFORE_SYNC ? "before" : "after"; BOOST_FOREACH(SyncSource *source, sourceList) { // fake a source alert event displaySourceProgress(*source, SyncSourceEvent(sysync::PEV_ALERTED, -1, 0, 0), true); source->open(); } if (!m_quiet && getPrintChanges()) { sourceList.dumpDatabases("current", NULL); sourceList.dumpLocalChanges(dirname, "current", datadump, "", "Data changes to be applied locally during restore:\n", "CLIENT_TEST_LEFT_NAME='current data' " "CLIENT_TEST_REMOVED='after restore' " "CLIENT_TEST_REMOVED='to be removed' " "CLIENT_TEST_ADDED='to be added'"); } SyncReport report; try { BOOST_FOREACH(SyncSource *source, sourceList) { SyncSourceReport sourcereport; try { displaySourceProgress(*source, SyncSourceEvent(sysync::PEV_SYNCSTART, 0, 0, 0), true); sourceList.restoreDatabase(*source, datadump, m_dryrun, sourcereport); displaySourceProgress(*source, SyncSourceEvent(sysync::PEV_SYNCEND, 0, 0, 0), true); report.addSyncSourceReport(source->getName(), sourcereport); } catch (...) { sourcereport.recordStatus(STATUS_FATAL); report.addSyncSourceReport(source->getName(), sourcereport); throw; } } } catch (...) { logRestoreReport(report, m_dryrun); throw; } logRestoreReport(report, m_dryrun); } void SyncContext::getSessions(vector &dirs) { LogDir::create(*this)->previousLogdirs(dirs); } string SyncContext::readSessionInfo(const string &dir, SyncReport &report) { boost::shared_ptr logging(LogDir::create(*this)); logging->openLogdir(dir); logging->readReport(report); return logging->getPeerNameFromLogdir(dir); } #ifdef ENABLE_UNIT_TESTS /** * This class works LogDirTest as scratch directory. * LogDirTest/[file_event|file_contact]_[one|two|empty] contain different * sets of items for use in a FileSyncSource. * * With that setup and a fake SyncContext it is possible to simulate * sessions and test the resulting logdirs. */ class LogDirTest : public CppUnit::TestFixture { class LogContext : public SyncContext, public Logger { public: LogContext() : SyncContext("nosuchconfig@nosuchcontext") {} ostringstream m_out; /** capture output produced while test ran */ void messagev(const MessageOptions &options, const char *format, va_list args) { std::string str = StringPrintfV(format, args); m_out << '[' << levelToStr(options.m_level) << ']' << str; if (!boost::ends_with(str, "\n")) { m_out << std::endl; } } }; boost::shared_ptr m_logContext; public: LogDirTest() : m_maxLogDirs(10) { } ~LogDirTest() { } void setUp() { static const char *vcard_1 = "BEGIN:VCARD\n" "VERSION:2.1\n" "TITLE:tester\n" "FN:John Doe\n" "N:Doe;John;;;\n" "X-MOZILLA-HTML:FALSE\n" "TEL;TYPE=WORK;TYPE=VOICE:business 1\n" "EMAIL:john.doe@work.com\n" "X-AIM:AIM JOHN\n" "END:VCARD\n"; static const char *vcard_2 = "BEGIN:VCARD\n" "VERSION:2.1\n" "TITLE:developer\n" "FN:John Doe\n" "N:Doe;John;;;\n" "X-MOZILLA-HTML:TRUE\n" "BDAY:2006-01-08\n" "END:VCARD\n"; static const char *ical_1 = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "METHOD:PUBLISH\n" "BEGIN:VEVENT\n" "SUMMARY:phone meeting\n" "DTEND:20060406T163000Z\n" "DTSTART:20060406T160000Z\n" "UID:1234567890!@#$%^&*()<>@dummy\n" "DTSTAMP:20060406T211449Z\n" "LAST-MODIFIED:20060409T213201\n" "CREATED:20060409T213201\n" "LOCATION:calling from home\n" "DESCRIPTION:let's talk\n" "CLASS:PUBLIC\n" "TRANSP:OPAQUE\n" "SEQUENCE:1\n" "BEGIN:VALARM\n" "DESCRIPTION:alarm\n" "ACTION:DISPLAY\n" "TRIGGER;VALUE=DURATION;RELATED=START:-PT15M\n" "END:VALARM\n" "END:VEVENT\n" "END:VCALENDAR\n"; static const char *ical_2 = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "METHOD:PUBLISH\n" "BEGIN:VEVENT\n" "SUMMARY:phone meeting\n" "DTEND:20060406T163000Z\n" "DTSTART:20060406T160000Z\n" "UID:1234567890!@#$%^&*()<>@dummy\n" "DTSTAMP:20060406T211449Z\n" "LAST-MODIFIED:20060409T213201\n" "CREATED:20060409T213201\n" "LOCATION:my office\n" "CATEGORIES:WORK\n" "DESCRIPTION:what the heck\\, let's even shout a bit\n" "CLASS:PUBLIC\n" "TRANSP:OPAQUE\n" "SEQUENCE:1\n" "END:VEVENT\n" "END:VCALENDAR\n"; rm_r("LogDirTest"); dump("file_event.one", "1", ical_1); dump("file_event.two", "1", ical_1); dump("file_event.two", "2", ical_2); mkdir_p(getLogData() + "/file_event.empty"); dump("file_contact.one", "1", vcard_1); dump("file_contact.two", "1", vcard_1); dump("file_contact.two", "2", vcard_2); mkdir_p(getLogData() + "/file_contact.empty"); mkdir_p(getLogDir()); m_maxLogDirs = 0; // Suppress output by redirecting into LogContext::m_out. // It's not tested at the moment. m_logContext.reset(new LogContext); Logger::addLogger(m_logContext); } void tearDown() { Logger::removeLogger(m_logContext.get()); m_logContext.reset(); } private: string getLogData() { return "LogDirTest/data"; } virtual InitStateString getLogDir() const { return "LogDirTest/cache/syncevolution"; } int m_maxLogDirs; void dump(const char *dir, const char *file, const char *data) { string name = getLogData(); name += "/"; name += dir; mkdir_p(name); name += "/"; name += file; ofstream out(name.c_str()); out << data; } CPPUNIT_TEST_SUITE(LogDirTest); CPPUNIT_TEST(testQuickCompare); CPPUNIT_TEST(testSessionNoChanges); CPPUNIT_TEST(testSessionChanges); CPPUNIT_TEST(testMultipleSessions); CPPUNIT_TEST(testExpire); CPPUNIT_TEST_SUITE_END(); /** * Simulate a session involving one or more sources. * * @param changeServer pretend that peer got changed * @param status result of session * @param varargs sourcename ("file_event"), * statebefore (NULL for no dump, or suffix like "_one"), * stateafter (NULL for same as before), ..., NULL * @return logdir created for the session */ string session(bool changeServer, SyncMLStatus status, ...) { Logger::Level level = Logger::instance().getLevel(); SourceList list(*m_logContext, true); list.setLogLevel(SourceList::LOGGING_QUIET); SyncReport report; list.startSession("", m_maxLogDirs, 0, &report); va_list ap; va_start(ap, status); while (true) { const char *sourcename = va_arg(ap, const char *); if (!sourcename) { break; } const char *type = NULL; if (!strcmp(sourcename, "file_event")) { type = "file:text/calendar:2.0"; } else if (!strcmp(sourcename, "file_contact")) { type = "file:text/vcard:3.0"; } CPPUNIT_ASSERT(type); string datadir = getLogData() + "/"; cxxptr source(SyncSource::createTestingSource(sourcename, type, true, (string("file://") + datadir).c_str())); datadir += sourcename; datadir += "_1"; source->open(); if (changeServer) { // fake one added item on server source->setItemStat(SyncSourceReport::ITEM_REMOTE, SyncSourceReport::ITEM_ADDED, SyncSourceReport::ITEM_TOTAL, 1); } list.addSource(source); const char *before = va_arg(ap, const char *); const char *after = va_arg(ap, const char *); if (before) { // do a "before" dump after directing the source towards the desired data rm_r(datadir); CPPUNIT_ASSERT_EQUAL(0, symlink((string(sourcename) + before).c_str(), datadir.c_str())); list.syncPrepare(sourcename); if (after) { rm_r(datadir); CPPUNIT_ASSERT_EQUAL(0, symlink((string(sourcename) + after).c_str(), datadir.c_str())); } } } list.syncDone(status, &report); Logger::instance().setLevel(level); return list.getLogdir(); } typedef vector Sessions_t; // full paths to all sessions, sorted Sessions_t listSessions() { Sessions_t sessions; string logdir = getLogDir(); ReadDir dirs(logdir); BOOST_FOREACH(const string &dir, dirs) { sessions.push_back(logdir + "/" + dir); } sort(sessions.begin(), sessions.end()); return sessions; } void testQuickCompare() { // identical dirs => identical files CPPUNIT_ASSERT(!LogDir::haveDifferentContent("file_event", getLogData(), "empty", getLogData(), "empty")); CPPUNIT_ASSERT(!LogDir::haveDifferentContent("file_event", getLogData(), "one", getLogData(), "one")); CPPUNIT_ASSERT(!LogDir::haveDifferentContent("file_event", getLogData(), "two", getLogData(), "two")); // some files shared CPPUNIT_ASSERT(!system("cp -l -r LogDirTest/data/file_event.two LogDirTest/data/file_event.copy && rm LogDirTest/data/file_event.copy/2")); CPPUNIT_ASSERT(LogDir::haveDifferentContent("file_event", getLogData(), "two", getLogData(), "copy")); CPPUNIT_ASSERT(LogDir::haveDifferentContent("file_event", getLogData(), "copy", getLogData(), "one")); } void testSessionNoChanges() { ScopedEnvChange config("XDG_CONFIG_HOME", "LogDirTest/config"); ScopedEnvChange cache("XDG_CACHE_HOME", "LogDirTest/cache"); // simple session with no changes string dir = session(false, STATUS_OK, "file_event", ".one", ".one", (char *)0); Sessions_t sessions = listSessions(); CPPUNIT_ASSERT_EQUAL((size_t)1, sessions.size()); CPPUNIT_ASSERT_EQUAL(dir, sessions[0]); IniFileConfigNode status(dir, "status.ini", true); CPPUNIT_ASSERT(status.exists()); CPPUNIT_ASSERT_EQUAL(string("1"), status.readProperty("source-file__event-backup-before").get()); CPPUNIT_ASSERT_EQUAL(string("1"), status.readProperty("source-file__event-backup-after").get()); CPPUNIT_ASSERT_EQUAL(string("200"), status.readProperty("status").get()); CPPUNIT_ASSERT(!LogDir::haveDifferentContent("file_event", dir, "before", dir, "after")); } void testSessionChanges() { ScopedEnvChange config("XDG_CONFIG_HOME", "LogDirTest/config"); ScopedEnvChange cache("XDG_CACHE_HOME", "LogDirTest/cache"); // session with local changes string dir = session(false, STATUS_OK, "file_event", ".one", ".two", (char *)0); Sessions_t sessions = listSessions(); CPPUNIT_ASSERT_EQUAL((size_t)1, sessions.size()); CPPUNIT_ASSERT_EQUAL(dir, sessions[0]); IniFileConfigNode status(dir, "status.ini", true); CPPUNIT_ASSERT(status.exists()); CPPUNIT_ASSERT_EQUAL(string("1"), status.readProperty("source-file__event-backup-before").get()); CPPUNIT_ASSERT_EQUAL(string("2"), status.readProperty("source-file__event-backup-after").get()); CPPUNIT_ASSERT_EQUAL(string("200"), status.readProperty("status").get()); CPPUNIT_ASSERT(LogDir::haveDifferentContent("file_event", dir, "before", dir, "after")); } void testMultipleSessions() { ScopedEnvChange config("XDG_CONFIG_HOME", "LogDirTest/config"); ScopedEnvChange cache("XDG_CACHE_HOME", "LogDirTest/cache"); // two sessions, starting with 1 item, adding 1 during the sync, then // removing it again during the second string dir = session(false, STATUS_OK, "file_event", ".one", ".two", "file_contact", ".one", ".two", (char *)0); { Sessions_t sessions = listSessions(); CPPUNIT_ASSERT_EQUAL((size_t)1, sessions.size()); CPPUNIT_ASSERT_EQUAL(dir, sessions[0]); IniFileConfigNode status(dir, "status.ini", true); CPPUNIT_ASSERT(status.exists()); CPPUNIT_ASSERT_EQUAL(string("1"), status.readProperty("source-file__event-backup-before").get()); CPPUNIT_ASSERT_EQUAL(string("2"), status.readProperty("source-file__event-backup-after").get()); CPPUNIT_ASSERT_EQUAL(string("1"), status.readProperty("source-file__contact-backup-before").get()); CPPUNIT_ASSERT_EQUAL(string("2"), status.readProperty("source-file__contact-backup-after").get()); CPPUNIT_ASSERT_EQUAL(string("200"), status.readProperty("status").get()); CPPUNIT_ASSERT(LogDir::haveDifferentContent("file_event", dir, "before", dir, "after")); CPPUNIT_ASSERT(LogDir::haveDifferentContent("file_contact", dir, "before", dir, "after")); } string seconddir = session(false, STATUS_OK, "file_event", ".two", ".one", "file_contact", ".two", ".one", (char *)0); { Sessions_t sessions = listSessions(); CPPUNIT_ASSERT_EQUAL((size_t)2, sessions.size()); CPPUNIT_ASSERT_EQUAL(dir, sessions[0]); CPPUNIT_ASSERT_EQUAL(seconddir, sessions[1]); IniFileConfigNode status(seconddir, "status.ini", true); CPPUNIT_ASSERT(status.exists()); CPPUNIT_ASSERT_EQUAL(string("2"), status.readProperty("source-file__event-backup-before").get()); CPPUNIT_ASSERT_EQUAL(string("1"), status.readProperty("source-file__event-backup-after").get()); CPPUNIT_ASSERT_EQUAL(string("2"), status.readProperty("source-file__contact-backup-before").get()); CPPUNIT_ASSERT_EQUAL(string("1"), status.readProperty("source-file__contact-backup-after").get()); CPPUNIT_ASSERT_EQUAL(string("200"), status.readProperty("status").get()); CPPUNIT_ASSERT(LogDir::haveDifferentContent("file_event", seconddir, "before", seconddir, "after")); CPPUNIT_ASSERT(LogDir::haveDifferentContent("file_contact", seconddir, "before", seconddir, "after")); } CPPUNIT_ASSERT(!LogDir::haveDifferentContent("file_event", dir, "after", seconddir, "before")); CPPUNIT_ASSERT(!LogDir::haveDifferentContent("file_contact", dir, "after", seconddir, "before")); } void testExpire() { ScopedEnvChange config("XDG_CONFIG_HOME", "LogDirTest/config"); ScopedEnvChange cache("XDG_CACHE_HOME", "LogDirTest/cache"); string dirs[5]; Sessions_t sessions; m_maxLogDirs = 1; // The latest session always must be preserved, even if it // is normally considered less important (no error in this case). dirs[0] = session(false, STATUS_FATAL, (char *)0); dirs[0] = session(false, STATUS_OK, (char *)0); sessions = listSessions(); CPPUNIT_ASSERT_EQUAL((size_t)1, sessions.size()); CPPUNIT_ASSERT_EQUAL(dirs[0], sessions[0]); // all things being equal, then expire the oldest session, // leaving us with two here m_maxLogDirs = 2; dirs[0] = session(false, STATUS_OK, (char *)0); dirs[1] = session(false, STATUS_OK, (char *)0); sessions = listSessions(); CPPUNIT_ASSERT_EQUAL((size_t)2, sessions.size()); CPPUNIT_ASSERT_EQUAL(dirs[0], sessions[0]); CPPUNIT_ASSERT_EQUAL(dirs[1], sessions[1]); // When syncing first file_event, then file_contact, both sessions // must be preserved despite m_maxLogDirs = 1, otherwise // we would loose the only existent backup of file_event. dirs[0] = session(false, STATUS_OK, "file_event", ".two", ".one", (char *)0); dirs[1] = session(false, STATUS_OK, "file_contact", ".two", ".one", (char *)0); sessions = listSessions(); CPPUNIT_ASSERT_EQUAL((size_t)2, sessions.size()); CPPUNIT_ASSERT_EQUAL(dirs[0], sessions[0]); CPPUNIT_ASSERT_EQUAL(dirs[1], sessions[1]); // after synchronizing both, we can expire both the old sessions m_maxLogDirs = 1; dirs[0] = session(false, STATUS_OK, "file_event", ".two", ".one", "file_contact", ".two", ".one", (char *)0); sessions = listSessions(); CPPUNIT_ASSERT_EQUAL((size_t)1, sessions.size()); CPPUNIT_ASSERT_EQUAL(dirs[0], sessions[0]); // when doing multiple failed syncs without dumps, keep the sessions // which have database dumps m_maxLogDirs = 2; dirs[1] = session(false, STATUS_FATAL, (char *)0); dirs[1] = session(false, STATUS_FATAL, (char *)0); sessions = listSessions(); CPPUNIT_ASSERT_EQUAL((size_t)2, sessions.size()); CPPUNIT_ASSERT_EQUAL(dirs[0], sessions[0]); CPPUNIT_ASSERT_EQUAL(dirs[1], sessions[1]); // when doing syncs which don't change data, keep the sessions which // did change something: keep oldest backup because it created the // backups for the first time dirs[1] = session(false, STATUS_OK, "file_event", ".one", ".one", "file_contact", ".one", ".one", (char *)0); dirs[1] = session(false, STATUS_OK, "file_event", ".one", ".one", "file_contact", ".one", ".one", (char *)0); sessions = listSessions(); CPPUNIT_ASSERT_EQUAL((size_t)2, sessions.size()); CPPUNIT_ASSERT_EQUAL(dirs[0], sessions[0]); CPPUNIT_ASSERT_EQUAL(dirs[1], sessions[1]); // when making a change in each sync, we end up with the two // most recent sessions eventually: first change server, // then local dirs[1] = session(true, STATUS_OK, "file_event", ".one", ".one", "file_contact", ".one", ".one", (char *)0); sessions = listSessions(); CPPUNIT_ASSERT_EQUAL((size_t)2, sessions.size()); CPPUNIT_ASSERT_EQUAL(dirs[0], sessions[0]); CPPUNIT_ASSERT_EQUAL(dirs[1], sessions[1]); dirs[0] = dirs[1]; dirs[1] = session(false, STATUS_OK, "file_event", ".one", ".two", "file_contact", ".one", ".two", (char *)0); sessions = listSessions(); CPPUNIT_ASSERT_EQUAL((size_t)2, sessions.size()); CPPUNIT_ASSERT_EQUAL(dirs[0], sessions[0]); CPPUNIT_ASSERT_EQUAL(dirs[1], sessions[1]); } }; SYNCEVOLUTION_TEST_SUITE_REGISTRATION(LogDirTest); #endif // ENABLE_UNIT_TESTS SE_END_CXX syncevolution_1.4/src/syncevo/SyncContext.h000066400000000000000000000631551230021373600212770ustar00rootroot00000000000000/* * Copyright (C) 2005-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_EVOLUTIONSYNCCLIENT #define INCL_EVOLUTIONSYNCCLIENT #include #include #include #include #include #include #include #include #include #include #include #include SE_BEGIN_CXX class TransportAgent; class SourceList; class SyncSource; class SyncSourceEvent; /** * This is the main class inside SyncEvolution which * looks at the configuration, activates all enabled * sources and executes the synchronization. * * All interaction with the user (reporting progress, asking for * passwords, ...) is done via virtual methods. The default * implementation of those uses stdin/out. * */ class SyncContext : public SyncConfig { /** * the string used to request a config, * *not* the normalized config name itself; * for that use SyncConfig::getConfigName() */ const string m_server; bool m_doLogging; bool m_quiet; bool m_dryrun; bool m_localSync; string m_localPeerContext; /**< context name (including @) if doing local sync */ string m_localClientRootPath; bool m_serverMode; bool m_serverAlerted; /**< sync was initiated by server (applies to client and server mode) */ bool m_configNeeded; std::string m_sessionID; SharedBuffer m_initialMessage; string m_initialMessageType; string m_syncDeviceID; FullProps m_configFilters; boost::shared_ptr m_agent; boost::shared_ptr m_userInterface; /** * a pointer to the active SourceList instance for this context if one exists; * used for error handling in throwError() on the iPhone */ SourceList *m_sourceListPtr; /** * a pointer to the active SyncContext instance if one exists; * set by sync() and/or SwapContext */ static SyncContext *m_activeContext; class SwapContext { SyncContext *m_oldContext; public: SwapContext(SyncContext *newContext) : m_oldContext(SyncContext::m_activeContext) { SyncContext::m_activeContext = newContext; } ~SwapContext() { SyncContext::m_activeContext = m_oldContext; } }; /** * Connection to the Synthesis engine. Always valid in a * constructed SyncContext. Use getEngine() to reference * it. */ SharedEngine m_engine; /** * Synthesis session handle. Only valid while sync is running. */ SharedSession m_session; /** * installs session in SyncContext and removes it again * when going out of scope */ class SessionSentinel { SyncContext &m_client; public: SessionSentinel(SyncContext &client, SharedSession &session) : m_client(client) { m_client.m_session = session; } ~SessionSentinel() { m_client.m_session.reset(); } }; /* * The URL this SyncContext is actually using, since we may support multiple * urls in the configuration. * */ string m_usedSyncURL; /* True iff current sync session was triggered by us * (such as in server alerted sync). */ bool m_remoteInitiated; public: /** * Common initialization code which needs to be done once * at the start of main() in any application using SyncEvolution. * For example, initializes (if applicable) glib and EDS. * * @param appname static string, must remain valid, defines name of executable (see g_set_prgname()) */ static void initMain(const char *appname); /** * A signal invoked as part of initMain(). * Backends can connect to it to extend initMain(). */ typedef boost::signals2::signal InitMainSignal; static InitMainSignal &GetInitMainSignal(); /** * A signal invoked each time a source has gone through a sync cycle. */ typedef boost::signals2::signal SourceSyncedSignal; SourceSyncedSignal m_sourceSyncedSignal; /** * true if binary was compiled as stable release * (see gen-autotools.sh) */ static bool isStableRelease(); /** * override stable release mode (for testing purposes) */ static void setStableRelease(bool isStableRelease); /** * SyncContext using a volatile config * and no logging. */ SyncContext(); /** * Constructor for syncing with a SyncML peer. * * @param peer identifies the client or server config to be used * @param doLogging write additional log and datatbase files about the sync; * true for regular syncs, false for debugging */ SyncContext(const string &server, bool doLogging = false); /** * Constructor for client in a local sync. * * @param client identifies the client context to be used (@foobar) * @param server identifies the server peer (foo@bar) * @param rootPath use this directory as config directory for the * peer-specific files (located inside peer directory * of server config) * @param agent transport agent, ready for communication with server * @param doLogging write additional log and datatbase files about the sync */ SyncContext(const string &client, const string &server, const string &rootPath, const boost::shared_ptr &agent, bool doLogging = false); virtual ~SyncContext(); bool getQuiet() { return m_quiet; } void setQuiet(bool quiet) { m_quiet = quiet; } bool getDryRun() { return m_dryrun; } void setDryRun(bool dryrun) { m_dryrun = dryrun; } bool isLocalSync() const { return m_localSync; } bool isServerAlerted() const { return m_serverAlerted; } void setServerAlerted(bool serverAlerted) { m_serverAlerted = serverAlerted; } boost::shared_ptr getUserInterface() { return m_userInterface; } void setUserInterface(const boost::shared_ptr &userInterface) { m_userInterface = userInterface; } /** use config UI owned by caller, without reference counting */ void setUserInterface(UserInterface *userInterface) { m_userInterface = boost::shared_ptr(userInterface, NopDestructor()); } /** * In contrast to getUserInterface(), this call here never returns NULL. * If no UserInterface is currently set, then it returns * a reference to a dummy instance which doesn't do anything. */ UserInterface &getUserInterfaceNonNull(); /** * Running operations typically checks that a config really exists * on disk. Setting false disables the check. */ bool isConfigNeeded() const { return m_configNeeded; } void setConfigNeeded(bool configNeeded) { m_configNeeded = configNeeded; } /** * throws error if config is needed and not available * * @param operation a noun describing what is to be done next ("proceed with %s", operation) */ void checkConfig(const std::string &operation) const; /** * Sets configuration filters. Currently only used in local sync * to configure the sync client. */ void setConfigProps(const FullProps &props) { m_configFilters = props; } const FullProps &getConfigProps() const { return m_configFilters; } /** only for server: device ID of peer */ void setSyncDeviceID(const std::string &deviceID) { m_syncDeviceID = deviceID; } std::string getSyncDeviceID() const { return m_syncDeviceID; } /* * Use sendSAN as the first step is sync() if this is a server alerted sync. * Prepare the san package and send the SAN request to the peer. * Returns false if failed to get a valid client sync request * otherwise put the client sync request into m_initialMessage which will * be used to initalze the server via initServer(), then continue sync() to * start the real sync serssion. * @version indicates the SAN protocal version used (1.2 or 1.1/1.0) */ bool sendSAN(uint16_t version); /** * Initializes the session so that it runs as SyncML server once * sync() is called. For this to work the first client message * must be available already. * * @param sessionID session ID to be used by server * @param data content of initial message sent by the client * @param messageType content type set by the client */ void initServer(const std::string &sessionID, SharedBuffer data, const std::string &messageType); /** * Executes the sync, throws an exception in case of failure. * Handles automatic backups and report generation. * * @retval complete sync report, skipped if NULL * @return overall sync status, for individual sources see report */ SyncMLStatus sync(SyncReport *report = NULL); /** result of analyzeSyncMLMessage() */ struct SyncMLMessageInfo { std::string m_deviceID; /** a string representation of the whole structure for debugging */ std::string toString() { return std::string("deviceID ") + m_deviceID; } }; /** * Instead or executing a sync, analyze the initial message * without changing any local data. Returns once the LocURI = * device ID of the client is known. * * @return device ID, empty if not in data */ static SyncMLMessageInfo analyzeSyncMLMessage(const char *data, size_t len, const std::string &messageType); /** * Convenience function, to be called inside a catch() block of * (or for) the sync. * * Rethrows the exception to determine what it is, then logs it * as an error and returns a suitable error code (usually a general * STATUS_DATASTORE_FAILURE). */ SyncMLStatus handleException(); /** * Determines the log directory of the previous sync (either in * temp or logdir) and shows changes since then. */ void status(); enum RestoreDatabase { DATABASE_BEFORE_SYNC, DATABASE_AFTER_SYNC }; /** * Restore data of selected sources from before or after the given * sync session, identified by absolute path to the log dir. */ void restore(const string &dirname, RestoreDatabase database); /** * fills vector with absolute path to information about previous * sync sessions, oldest one first */ void getSessions(vector &dirs); /** * fills report with information about previous session * @return the peer name from the dir. */ string readSessionInfo(const string &dir, SyncReport &report); /** * fills report with information about local changes * * Only sync sources selected in the SyncContext * constructor are checked. The local item changes will be set in * the SyncReport's ITEM_LOCAL ITEM_ADDED/UPDATED/REMOVED. * * Some sync sources might not be able to report this * information outside of a regular sync, in which case * these fields are set to -1. * * Start and end times of the check are also reported. */ void checkStatus(SyncReport &report); /** * throws a StatusException with a local, fatal error with the given string * or (on the iPhone, where exception handling is not * supported by the toolchain) prints an error directly * and aborts * * output format: * * @param error a string describing the error */ static void throwError(const string &error) SE_NORETURN; /** * throw an exception with a specific status code after an operation failed and * remember that this instance has failed * * output format: * * @param status a more specific status code; other throwError() variants * use STATUS_FATAL + sysync::LOCAL_STATUS_CODE, which is interpreted * as a fatal local error * @param action a string describing what was attempted *and* how it failed */ static void throwError(SyncMLStatus status, const string &failure) SE_NORETURN; /** * throw an exception after an operation failed and * remember that this instance has failed * * output format: : * * @Param action a string describing the operation or object involved * @param error the errno error code for the failure */ static void throwError(const string &action, int error) SE_NORETURN; /** * An error handler which prints the error message and then * stops the program. Never returns. * * The API was chosen so that it can be used as libebook/libecal * "backend-dies" signal handler. */ static void fatalError(void *object, const char *error) SE_NORETURN; /** * When using Evolution this function starts a background thread * which drives the default event loop. Without that loop * "backend-died" signals are not delivered. The problem with * the thread is that it seems to interfere with gconf startup * when added to the main() function of syncevolution. Therefore * it is started by SyncSource::beginSync() (for unit * testing of sync sources) and SyncContext::sync() (for * normal operation). */ static void startLoopThread(); /** * Finds activated sync source by name. May return NULL * if no such sync source was defined or is not currently * instantiated. Pointer remains valid throughout the sync * session. Called by Synthesis DB plugin to find active * sources. * * @param name can be both as well as _ * (necessary when renaming sources in the Synthesis XML config) * * @TODO: roll SourceList into SyncContext and * make this non-static */ static SyncSource *findSource(const std::string &name); static const char m_findSourceSeparator = '@'; /** * Find the active sync context for the given session. * * @param sessionName chosen by SyncEvolution and passed to * Synthesis engine, which calls us back * with it in SyncEvolution_Session_CreateContext() * @return context or NULL if not found */ static SyncContext *findContext(const char *sessionName); SharedEngine getEngine() { return m_engine; } const SharedEngine getEngine() const { return m_engine; } bool getDoLogging() { return m_doLogging; } /** * Returns the string used to select the peer config * used by this instance. * * Note that this is not the same as a valid configuration * name. For example "foo" might be matched against a * "foo@bar" config by SyncConfig. Use SyncConfig::getConfigName() * to get the underlying config. */ std::string getPeer() { return m_server; } /** * Handle for active session, may be NULL. */ SharedSession getSession() { return m_session; } bool getRemoteInitiated() {return m_remoteInitiated;} void setRemoteInitiated(bool remote) {m_remoteInitiated = remote;} /** * If called while a sync session runs, * the engine will finish the session and then * immediately try to run another one with * the same sources. * * Does nothing when called at the wrong time. * There's no guarantee either that restarting is * possible. */ static void requestAnotherSync(); /** * access to current set of sync sources, NULL if not instantiated yet */ const std::vector *getSources() const; protected: /** exchange active Synthesis engine */ SharedEngine swapEngine(SharedEngine newengine) { SharedEngine oldengine = m_engine; m_engine = newengine; return oldengine; } /** sentinel class which creates, installs and removes a new Synthesis engine for the duration of its own life time */ class SwapEngine { SyncContext &m_client; SharedEngine m_oldengine; public: SwapEngine(SyncContext &client) : m_client(client) { SharedEngine syncengine(m_client.createEngine()); m_oldengine = m_client.swapEngine(syncengine); } ~SwapEngine() { m_client.swapEngine(m_oldengine); } }; /** * Create a Synthesis engine for the currently active * sources (might be empty!) and settings. */ SharedEngine createEngine(); /** * Return skeleton Synthesis client XML configuration. * * The , , elements (if * present) are replaced by the caller with fragments found in the * file system. When already has content, that content * may contain , , , which * will be replaced by definitions gathered from backends. * * The default implementation of this function takes the configuration from * (in this order): * - $(XDG_CONFIG_HOME)/syncevolution-xml * - $(datadir)/syncevolution/xml * Files with identical names are read from the first location where they * are found. If $(SYNCEVOLUTION_XML_CONFIG_DIR) is set, then it overrides * the previous two locations. * * The syncevolution.xml file is read from the first place where it is found. * In addition, further .xml files in sub-directories are gathered and get * inserted into the syncevolution.xml template. * * If none of these locations has XML configs, then builtin strings are * used as fallback. This only works for mode == "client". Otherwise an * error is thrown. * * @param mode "client" or "server" * @retval xml is filled with Synthesis client config which may hav * @retval rules remote rules which the caller needs for * @retval configname a string describing where the config came from */ virtual void getConfigTemplateXML(const string &mode, string &xml, string &rules, string &configname); /** * Return complete Synthesis XML configuration. * * Calls getConfigTemplateXML(), then fills in * sync source XML fragments if necessary. * * @retval xml is filled with complete Synthesis client config * @retval configname a string describing where the config came from */ virtual void getConfigXML(string &xml, string &configname); /** * Callback for derived classes: called after initializing the * client, but before doing anything with its configuration. * Can be used to override the client configuration. */ virtual void prepare() {} /** * instantiate transport agent * * Called by engine when it needs to exchange messages. The * transport agent will be used throughout the sync session and * unref'ed when no longer needed. At most one agent will be * requested at a time. The transport agent is intentionally * returned as a Boost shared pointer so that a pointer to a * class with a different life cycle is possible, either by * keeping a reference or by returning a shared_ptr where the * destructor doesn't do anything. * * The agent must be ready for use: * - HTTP specific settings must have been applied * - the current SyncContext's timeout must have been * installed via TransportAgent::setTimeout() * * The default implementation instantiates one of the builtin * transport agents, depending on how it was compiled. * * @param gmainloop the GMainLoop to be used by transports, if not NULL; * transports not supporting that should not be created; * transports will increase the reference count for the loop * @return transport agent */ virtual boost::shared_ptr createTransportAgent(void *gmainloop); virtual boost::shared_ptr createTransportAgent() { return createTransportAgent(NULL); } /** * display a text message from the server * * Not really used by SyncML servers. Could be displayed in a * modal dialog. * * @param message string with local encoding, possibly with line breaks */ virtual void displayServerMessage(const string &message); /** * display general sync session progress * * @param type PEV_*, see * @param extra1 extra information depending on type * @param extra2 extra information depending on type * @param extra3 extra information depending on type */ virtual void displaySyncProgress(sysync::TProgressEventEnum type, int32_t extra1, int32_t extra2, int32_t extra3); /** * An event plus its parameters, see Synthesis engine. */ class SyncSourceEvent { public: sysync::TProgressEventEnum m_type; int32_t m_extra1, m_extra2, m_extra3; SyncSourceEvent() : m_type(sysync::PEV_NOP) {} SyncSourceEvent(sysync::TProgressEventEnum type, int32_t extra1, int32_t extra2, int32_t extra3) { m_type = type; m_extra1 = extra1; m_extra2 = extra2; m_extra3 = extra3; } }; /** * display sync source specific progress * * @param source source which is the target of the event * @param event contains PEV_* and extra parameters, see * @param flush if true, then bypass caching events and print directly * @return true if the event was cached */ virtual bool displaySourceProgress(SyncSource &source, const SyncSourceEvent &event, bool flush); /** * report step command info * * Will be called after each step in step loop in SyncContext::doSync(). * This reports step command info. * @param stepCmd step command enum value */ virtual void reportStepCmd(sysync::uInt16 stepCmd) {} private: /** initialize members as part of constructors */ void init(); /** * generate XML configuration and (re)initialize engine with it */ void initEngine(bool logXML); /** * the code common to init() and status(): * populate source list with active sources and open */ void initSources(SourceList &sourceList); /** * set m_localSync and m_localPeerContext * @param config config name of peer */ void initLocalSync(const string &config); /** * called via pre-signal of m_startDataRead */ void startSourceAccess(SyncSource *source); /** * utility function for status() and getChanges(): * iterate over sources, check for changes and copy result */ void checkSourceChanges(SourceList &sourceList, SyncReport &changes); /** * A method to report sync is really successfully started. * It happens at the same time SynthesDBPlugin starts to access source. * For each sync, it is only called at most one time. * The default action is nothing. */ virtual void syncSuccessStart() { } /** * sets up Synthesis session and executes it */ SyncMLStatus doSync(); /** * directory for Synthesis client binfiles or * Synthesis server textdb files, unique for each * peer */ string getSynthesisDatadir(); /** * return true if "delayedabort" session variable is true */ bool checkForScriptAbort(SharedSession session); // total retry duration int m_retryDuration; // message resend interval int m_retryInterval; // Current retry count int m_retries; //a flag indicating whether it is the first time to start source access. //It can be used to report infomation about a sync is successfully started. bool m_firstSourceAccess; // Cache for use in displaySourceProgress(). SyncSource *m_sourceProgress; SyncSourceEvent m_sourceEvent; public: /** * Returns the URL in the getSyncURL() list which is to be used * for sync. The long term goal is to pick the first URL which * uses a currently available transport; right now it simply picks * the first supported one. */ string getUsedSyncURL(); }; SE_END_CXX #endif // INCL_EVOLUTIONSYNCCLIENT syncevolution_1.4/src/syncevo/SyncML.cpp000066400000000000000000001267631230021373600205230ustar00rootroot00000000000000/* * Copyright (C) 2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; SE_BEGIN_CXX SimpleSyncMode SimplifySyncMode(SyncMode mode, bool peerIsClient) { switch (mode) { case SYNC_NONE: case SYNC_TWO_WAY: case SYNC_SLOW: case SYNC_RESTORE_FROM_BACKUP: case SYNC_ONE_WAY_FROM_LOCAL: case SYNC_REFRESH_FROM_LOCAL: case SYNC_ONE_WAY_FROM_REMOTE: case SYNC_REFRESH_FROM_REMOTE: return static_cast(mode); case SA_SYNC_ONE_WAY_FROM_CLIENT: case SYNC_ONE_WAY_FROM_CLIENT: return peerIsClient ? SIMPLE_SYNC_ONE_WAY_FROM_REMOTE : SIMPLE_SYNC_ONE_WAY_FROM_LOCAL; case SA_SYNC_REFRESH_FROM_CLIENT: case SYNC_REFRESH_FROM_CLIENT: return peerIsClient ? SIMPLE_SYNC_REFRESH_FROM_REMOTE : SIMPLE_SYNC_REFRESH_FROM_LOCAL; case SA_SYNC_ONE_WAY_FROM_SERVER: case SYNC_ONE_WAY_FROM_SERVER: return peerIsClient ? SIMPLE_SYNC_ONE_WAY_FROM_LOCAL : SIMPLE_SYNC_ONE_WAY_FROM_REMOTE; case SA_SYNC_REFRESH_FROM_SERVER: case SYNC_REFRESH_FROM_SERVER: return peerIsClient ? SIMPLE_SYNC_REFRESH_FROM_LOCAL : SIMPLE_SYNC_REFRESH_FROM_REMOTE; case SA_SYNC_TWO_WAY: return SIMPLE_SYNC_TWO_WAY; case SYNC_LOCAL_CACHE_SLOW: return SIMPLE_SYNC_LOCAL_CACHE_SLOW; case SYNC_LOCAL_CACHE_INCREMENTAL: return SIMPLE_SYNC_LOCAL_CACHE_INCREMENTAL; case SYNC_LAST: case SYNC_INVALID: return SIMPLE_SYNC_INVALID; } return SIMPLE_SYNC_INVALID; } SANSyncMode AlertSyncMode(SyncMode mode, bool peerIsClient) { switch(mode) { case SYNC_RESTORE_FROM_BACKUP: case SYNC_NONE: case SYNC_LAST: case SYNC_INVALID: return SA_INVALID; case SYNC_SLOW: case SYNC_LOCAL_CACHE_SLOW: return SA_SLOW; case SYNC_TWO_WAY: case SA_SYNC_TWO_WAY: case SYNC_LOCAL_CACHE_INCREMENTAL: // use two-way because it is more likely to be implemented return SA_TWO_WAY; case SYNC_ONE_WAY_FROM_CLIENT: case SA_SYNC_ONE_WAY_FROM_CLIENT: return SA_ONE_WAY_FROM_CLIENT; case SYNC_REFRESH_FROM_CLIENT: case SA_SYNC_REFRESH_FROM_CLIENT: return SA_REFRESH_FROM_CLIENT; case SYNC_ONE_WAY_FROM_SERVER: case SA_SYNC_ONE_WAY_FROM_SERVER: return SA_ONE_WAY_FROM_SERVER; case SYNC_REFRESH_FROM_SERVER: case SA_SYNC_REFRESH_FROM_SERVER: return SA_REFRESH_FROM_SERVER; case SYNC_ONE_WAY_FROM_LOCAL: return peerIsClient ? SA_ONE_WAY_FROM_SERVER : SA_ONE_WAY_FROM_CLIENT; case SYNC_REFRESH_FROM_LOCAL: return peerIsClient ? SA_REFRESH_FROM_SERVER : SA_REFRESH_FROM_CLIENT; case SYNC_ONE_WAY_FROM_REMOTE: return peerIsClient ? SA_ONE_WAY_FROM_CLIENT : SA_ONE_WAY_FROM_SERVER; case SYNC_REFRESH_FROM_REMOTE: return peerIsClient ? SA_REFRESH_FROM_CLIENT : SA_REFRESH_FROM_SERVER; } return SA_INVALID; } std::string PrettyPrintSyncMode(SyncMode mode, bool userVisible) { switch (mode) { case SYNC_NONE: return userVisible ? "disabled" : "SYNC_NONE"; case SYNC_TWO_WAY: case SA_SYNC_TWO_WAY: return userVisible ? "two-way" : "SYNC_TWO_WAY"; case SYNC_SLOW: return userVisible ? "slow" : "SYNC_SLOW"; case SYNC_ONE_WAY_FROM_CLIENT: case SA_SYNC_ONE_WAY_FROM_CLIENT: return userVisible ? "one-way-from-client" : "SYNC_ONE_WAY_FROM_CLIENT"; case SYNC_REFRESH_FROM_CLIENT: case SA_SYNC_REFRESH_FROM_CLIENT: return userVisible ? "refresh-from-client" : "SYNC_REFRESH_FROM_CLIENT"; case SYNC_ONE_WAY_FROM_SERVER: case SA_SYNC_ONE_WAY_FROM_SERVER: return userVisible ? "one-way-from-server" : "SYNC_ONE_WAY_FROM_SERVER"; case SYNC_REFRESH_FROM_SERVER: case SA_SYNC_REFRESH_FROM_SERVER: return userVisible ? "refresh-from-server" : "SYNC_REFRESH_FROM_SERVER"; case SYNC_RESTORE_FROM_BACKUP: return userVisible ? "restore-from-backup" : "SYNC_RESTORE_FROM_BACKUP"; case SYNC_ONE_WAY_FROM_LOCAL: return userVisible ? "one-way-from-local" : "SYNC_REFRESH_FROM_LOCAL"; case SYNC_REFRESH_FROM_LOCAL: return userVisible ? "refresh-from-local" : "SYNC_REFRESH_FROM_LOCAL"; case SYNC_ONE_WAY_FROM_REMOTE: return userVisible ? "one-way-from-remote" : "SYNC_ONE_WAY_FROM_REMOTE"; case SYNC_REFRESH_FROM_REMOTE: return userVisible ? "refresh-from-remote" : "SYNC_REFRESH_FROM_REMOTE"; case SYNC_LOCAL_CACHE_SLOW: return userVisible ? "local-cache-slow" : "SYNC_LOCAL_CACHE_SLOW"; case SYNC_LOCAL_CACHE_INCREMENTAL: return userVisible ? "local-cache-incremental" : "SYNC_LOCAL_CACHE_INCREMENTAL"; default: std::stringstream res; res << (userVisible ? "sync-mode-" : "SYNC_") << int(mode); return res.str(); } } SyncMode StringToSyncMode(const std::string &mode, bool serverAlerted) { if (boost::iequals(mode, "slow") || boost::iequals(mode, "SYNC_SLOW")) { return SYNC_SLOW; } else if (boost::iequals(mode, "two-way") || boost::iequals(mode, "SYNC_TWO_WAY")) { return serverAlerted ?SA_SYNC_TWO_WAY: SYNC_TWO_WAY; } else if (boost::iequals(mode, "refresh-from-server") || boost::iequals(mode, "SYNC_REFRESH_FROM_SERVER")) { return serverAlerted? SA_SYNC_REFRESH_FROM_SERVER: SYNC_REFRESH_FROM_SERVER; } else if (boost::iequals(mode, "refresh-from-client") || boost::iequals(mode, "SYNC_REFRESH_FROM_CLIENT")) { return serverAlerted? SA_SYNC_REFRESH_FROM_CLIENT: SYNC_REFRESH_FROM_CLIENT; } else if (boost::iequals(mode, "one-way-from-server") || boost::iequals(mode, "SYNC_ONE_WAY_FROM_SERVER")) { return serverAlerted? SA_SYNC_ONE_WAY_FROM_SERVER: SYNC_ONE_WAY_FROM_SERVER; } else if (boost::iequals(mode, "one-way-from-client") || boost::iequals(mode, "SYNC_ONE_WAY_FROM_CLIENT")) { return serverAlerted? SA_SYNC_ONE_WAY_FROM_CLIENT: SYNC_ONE_WAY_FROM_CLIENT; } else if (boost::iequals(mode, "refresh-from-remote") || boost::iequals(mode, "SYNC_REFRESH_FROM_REMOTE")) { return SYNC_REFRESH_FROM_REMOTE; } else if (boost::iequals(mode, "refresh-from-local") || boost::iequals(mode, "SYNC_REFRESH_FROM_LOCAL")) { return SYNC_REFRESH_FROM_LOCAL; } else if (boost::iequals(mode, "one-way-from-remote") || boost::iequals(mode, "SYNC_ONE_WAY_FROM_REMOTE")) { return SYNC_ONE_WAY_FROM_REMOTE; } else if (boost::iequals(mode, "one-way-from-local") || boost::iequals(mode, "SYNC_ONE_WAY_FROM_LOCAL")) { return SYNC_ONE_WAY_FROM_LOCAL; } else if (boost::iequals(mode, "disabled") || boost::iequals(mode, "SYNC_NONE")) { return SYNC_NONE; } else if (boost::iequals(mode, "local-cache-slow") || boost::iequals(mode, "SYNC_LOCAL_CACHE_SLOW")) { return SYNC_LOCAL_CACHE_SLOW; } else if (boost::iequals(mode, "local-cache-incremental") || boost::iequals(mode, "SYNC_LOCAL_CACHE_INCREMENTAL")) { return SYNC_LOCAL_CACHE_INCREMENTAL; } else { return SYNC_INVALID; } } ContentType StringToContentType(const std::string &type, bool force) { if (boost::iequals (type, "text/x-vcard") || boost::iequals (type, "text/x-vcard:2.1")) { return WSPCTC_XVCARD; } else if (boost::iequals (type, "text/vcard") ||boost::iequals (type, "text/vcard:3.0")) { return force ? WSPCTC_VCARD : WSPCTC_XVCARD; } else if (boost::iequals (type, "text/x-vcalendar") ||boost::iequals (type, "text/x-vcalendar:1.0") ||boost::iequals (type, "text/x-calendar") || boost::iequals (type, "text/x-calendar:1.0")) { return WSPCTC_XVCALENDAR; } else if (boost::iequals (type, "text/calendar") ||boost::iequals (type, "text/calendar:2.0")) { return force ? WSPCTC_ICALENDAR : WSPCTC_XVCALENDAR; } else if (boost::iequals (type, "text/plain") ||boost::iequals (type, "text/plain:1.0")) { return WSPCTC_TEXT_PLAIN; } else { return WSPCTC_UNKNOWN; } } std::string GetLegacyMIMEType (const std::string &type, bool force) { if (boost::iequals (type, "text/x-vcard") || boost::iequals (type, "text/x-vcard:2.1")) { return "text/x-vcard"; } else if (boost::iequals (type, "text/vcard") ||boost::iequals (type, "text/vcard:3.0")) { return force ? "text/vcard" : "text/x-vcard"; } else if (boost::iequals (type, "text/x-vcalendar") ||boost::iequals (type, "text/x-vcalendar:1.0") ||boost::iequals (type, "text/x-calendar") || boost::iequals (type, "text/x-calendar:1.0")) { return "text/x-vcalendar"; } else if (boost::iequals (type, "text/calendar") ||boost::iequals (type, "text/calendar:2.0")) { return force ? "text/vcalendar" : "text/x-vcalendar"; } else if (boost::iequals (type, "text/plain") ||boost::iequals (type, "text/plain:1.0")) { return "text/plain"; } else { return ""; } } std::string Status2String(SyncMLStatus status) { string error; bool local; int code = status; local = status >= static_cast(sysync::LOCAL_STATUS_CODE); if (local && status <= static_cast(sysync::LOCAL_STATUS_CODE_END)) { code = status - static_cast(sysync::LOCAL_STATUS_CODE); } else { code = status; } switch (code) { case STATUS_OK: case STATUS_HTTP_OK: error = "no error"; break; case STATUS_NO_CONTENT: error = "no content/end of data"; break; case STATUS_DATA_MERGED: error = "data merged"; break; case STATUS_UNAUTHORIZED: error = "authorization failed"; break; case STATUS_FORBIDDEN: error = "access denied"; break; case STATUS_NOT_FOUND: error = "object not found"; break; case STATUS_COMMAND_NOT_ALLOWED: error = "operation not allowed"; break; case STATUS_OPTIONAL_FEATURE_NOT_SUPPORTED: error = "optional feature not supported"; break; case STATUS_AUTHORIZATION_REQUIRED: error = "authorization required"; break; case STATUS_COMMAND_GONE: error = "command gone"; break; case STATUS_SIZE_REQUIRED: error = "size required"; break; case STATUS_INCOMPLETE_COMMAND: error = "incomplete command"; break; case STATUS_REQUEST_ENTITY_TOO_LARGE: error = "request entity too large"; break; case STATUS_UNSUPPORTED_MEDIA_TYPE_OR_FORMAT: error = "unsupported media type or format"; break; case STATUS_REQUESTED_SIZE_TOO_BIG: error = "requested size too big"; break; case STATUS_RETRY_LATER: error = "retry later"; break; case STATUS_ALREADY_EXISTS: error = "object exists already"; break; case STATUS_UNKNOWN_SEARCH_GRAMMAR: error = "unknown search grammar"; break; case STATUS_BAD_CGI_OR_FILTER_QUERY: error = "bad CGI or filter query"; break; case STATUS_SOFT_DELETE_CONFLICT: error = "soft-delete conflict"; break; case STATUS_PARTIAL_ITEM_NOT_ACCEPTED: error = "partial item not accepted"; break; case STATUS_ITEM_NOT_EMPTY: error = "item not empty"; break; case STATUS_MOVE_FAILED: error = "move failed"; break; case STATUS_FATAL: error = "fatal error"; break; case STATUS_DATASTORE_FAILURE: error = "database failure"; break; case STATUS_FULL: error = "out of space"; break; case STATUS_UNEXPECTED_SLOW_SYNC: error = "unexpected slow sync"; break; case STATUS_PARTIAL_FAILURE: error = "some changes could not be transferred"; break; case STATUS_PASSWORD_TIMEOUT: error = "password request timed out"; break; case STATUS_RELEASE_TOO_OLD: error = "SyncEvolution binary V" VERSION " too old to use configuration"; break; case STATUS_MIGRATION_NEEDED: error = "proceeding would make backward incompatible changes, aborted"; break; case sysync::LOCERR_BADPROTO: error = "bad or unknown protocol"; break; case sysync::LOCERR_SMLFATAL: error = "fatal problem with SML"; break; case sysync::LOCERR_COMMOPEN: error = "cannot open communication"; break; case sysync::LOCERR_SENDDATA: error = "cannot send data"; break; case sysync::LOCERR_RECVDATA: error = "cannot receive data"; break; case sysync::LOCERR_BADCONTENT: error = "bad content in response"; break; case sysync::LOCERR_PROCESSMSG: error = "SML (or SAN) error processing incoming message"; break; case sysync::LOCERR_COMMCLOSE: error = "cannot close communication"; break; case sysync::LOCERR_AUTHFAIL: error = "transport layer authorisation failed"; break; case sysync::LOCERR_CFGPARSE: error = "error parsing config file"; break; case sysync::LOCERR_CFGREAD: error = "error reading config file"; break; case sysync::LOCERR_NOCFG: error = "no config found"; break; case sysync::LOCERR_NOCFGFILE: error = "config file could not be found"; break; case sysync::LOCERR_EXPIRED: error = "expired"; break; case sysync::LOCERR_WRONGUSAGE: error = "bad usage"; break; case sysync::LOCERR_BADHANDLE: error = "bad handle"; break; case sysync::LOCERR_USERABORT: error = "aborted on behalf of user"; break; case sysync::LOCERR_BADREG: error = "bad registration"; break; case sysync::LOCERR_LIMITED: error = "limited trial version"; break; case sysync::LOCERR_TIMEOUT: error = "connection timeout"; break; case sysync::LOCERR_CERT_EXPIRED: error = "connection SSL certificate expired"; break; case sysync::LOCERR_CERT_INVALID: error = "connection SSL certificate invalid"; break; case sysync::LOCERR_INCOMPLETE: error = "incomplete sync session"; break; case sysync::LOCERR_RETRYMSG: error = "retry sending message"; break; case sysync::LOCERR_OUTOFMEM: error = "out of memory"; break; case sysync::LOCERR_NOCONN: error = "no means to open a connection"; break; case sysync::LOCERR_CONN: error = "connection cannot be established"; break; case sysync::LOCERR_ALREADY: error = "element is already installed"; break; case sysync::LOCERR_TOONEW: error = "this build is too new for this license"; break; case sysync::LOCERR_NOTIMP: error = "function not implemented"; break; case sysync::LOCERR_WRONGPROD: error = "this license code is valid, but not for this product"; break; case sysync::LOCERR_USERSUSPEND: error = "explicitly suspended on behalf of user"; break; case sysync::LOCERR_TOOOLD: error = "this build is too old for this SDK/plugin"; break; case sysync::LOCERR_UNKSUBSYSTEM: error = "unknown subsystem"; break; case sysync::LOCERR_SESSIONRST: error = "next message will be a session restart"; break; case sysync::LOCERR_LOCDBNOTRDY: error = "local datastore is not ready"; break; case sysync::LOCERR_RESTART: error = "session should be restarted from scratch"; break; case sysync::LOCERR_PIPECOMM: error = "internal pipe communication problem"; break; case sysync::LOCERR_BUFTOOSMALL: error = "buffer too small for requested value"; break; case sysync::LOCERR_TRUNCATED: error = "value truncated to fit into field or buffer"; break; case sysync::LOCERR_BADPARAM: error = "bad parameter"; break; case sysync::LOCERR_OUTOFRANGE: error = "out of range"; break; case sysync::LOCERR_TRANSPFAIL: error = "external transport failure"; break; case sysync::LOCERR_CLASSNOTREG: error = "class not registered"; break; case sysync::LOCERR_IIDNOTREG: error = "interface not registered"; break; case sysync::LOCERR_BADURL: error = "bad URL"; break; case sysync::LOCERR_SRVNOTFOUND: error = "server not found"; break; case STATUS_MAX: break; } string statusstr = StringPrintf("%s, status %d", local ? "local" : "remote", status); string description; if (error.empty()) { description = statusstr; } else { description = StringPrintf("%s (%s)", error.c_str(), statusstr.c_str()); } return description; } namespace { const char * const locNames[] = { "local", "remote", NULL }; const char * const stateNames[] = { "added", "updated", "removed", "any", NULL }; const char * const resultNames[] = { "total", "reject", "match", "conflict_server_won", "conflict_client_won", "conflict_duplicated", "sent", "received", NULL }; int toIndex(const char * const names[], const std::string &name) { int i; for (i = 0; names[i] && name != names[i]; i++) {} return i; } std::string toString(const char * const names[], int index) { for (int i = 0; names[i]; i++) { if (i == index) { return names[i]; } } return "unknown"; } } std::string SyncSourceReport::LocationToString(ItemLocation location) { return toString(locNames, location); } SyncSourceReport::ItemLocation SyncSourceReport::StringToLocation(const std::string &location) { return static_cast(toIndex(locNames, location)); } std::string SyncSourceReport::StateToString(ItemState state) { return toString(stateNames, state); } SyncSourceReport::ItemState SyncSourceReport::StringToState(const std::string &state) { return static_cast(toIndex(stateNames, state)); } std::string SyncSourceReport::ResultToString(ItemResult result) { return toString(resultNames, result); } SyncSourceReport::ItemResult SyncSourceReport::StringToResult(const std::string &result) { return static_cast(toIndex(resultNames, result)); } std::string SyncSourceReport::StatTupleToString(ItemLocation location, ItemState state, ItemResult result) { return std::string("") + LocationToString(location) + "-" + StateToString(state) + "-" + ResultToString(result); } void SyncSourceReport::StringToStatTuple(const std::string &str, ItemLocation &location, ItemState &state, ItemResult &result) { std::vector< std::string > tokens; boost::split(tokens, str, boost::is_any_of("-")); location = tokens.size() > 0 ? StringToLocation(tokens[0]) : ITEM_LOCATION_MAX; state = tokens.size() > 1 ? StringToState(tokens[1]) : ITEM_STATE_MAX; result = tokens.size() > 2 ? StringToResult(tokens[2]) : ITEM_RESULT_MAX; } bool SyncSourceReport::wasChanged(ItemLocation location) { for (int i = ITEM_ADDED; i < ITEM_ANY; i++) { if (getItemStat(location, (ItemState)i, ITEM_TOTAL) > 0) { return true; } } return false; } std::ostream &operator << (std::ostream &out, const SyncReport &report) { report.prettyPrint(out, 0); return out; } namespace { string fill(char sep, size_t width) { string res; res.resize(width - 1, sep); return res; } string center(char sep, const string &str, size_t width) { if (str.size() + 1 >= width) { return str; } else { string res; res.resize(width - 1, sep); res.replace((width - 1 - str.size()) / 2, str.size(), str); return res; } } string right(char sep, const string &str, size_t width) { if (str.size() + 1 >= width) { return str; } else { string res; res.resize(width - 1, sep); res.replace(width - 2 - str.size(), str.size(), str); return res; } } #if 0 string left(char sep, const string &str, size_t width) { if (str.size() + 1 >= width) { return str; } else { string res; res.resize(width - 1, sep); res.replace(1, str.size(), str); return res; } } #endif // insert string at column if it fits, otherwise flush right string align(char sep, const string &str, size_t width, size_t column) { if (column + str.size() + 1 >= width) { return right(sep, str, width); } else { string res; res.resize(width - 1, sep); res.replace(column, str.size(), str); return res; } } } SyncReport::SyncReport(const std::string &dump) { boost::shared_ptr data(new std::string(dump)); boost::shared_ptr blob(new StringDataBlob("sync report", data, true)); IniHashConfigNode node(blob); node >> *this; } std::string SyncReport::toString() const { boost::shared_ptr data(new std::string); boost::shared_ptr blob(new StringDataBlob("sync report", data, false)); IniHashConfigNode node(blob); node << *this; node.flush(); return *data; } void SyncReport::prettyPrint(std::ostream &out, int flags) const { // table looks like this: // +-------------------+-------------------------------+-------------------------------|-CON-+ // | | LOCAL | REMOTE | FLI | // | Source | NEW | MOD | DEL | ERR | TOTAL | NEW | MOD | DEL | ERR | TOTAL | CTS | // +-------------------+-----+-----+-----+-----+-------+-----+-----+-----+-----+-------+-----+ // // Most of the columns can be turned on or off dynamically. // Their width is calculated once (including right separators and spaces): // | name_width |count_width| | |conflict_width| // |client_width | server_width | // | text_width | // name column is sized dynamically based on column header and actual names size_t name_width = strlen("Source"); BOOST_FOREACH(const SyncReport::value_type &entry, *this) { const std::string &name = entry.first; if (name_width < name.size()) { name_width = name.size(); } } name_width += 1; // separator if (name_width < 20) { // enough room for spaces name_width += 2; } int count_width = 6; int num_counts = 3; if (flags & WITH_TOTAL) { num_counts++; } if (!(flags & WITHOUT_REJECTS)) { num_counts++; } int client_width = (flags & WITHOUT_CLIENT) ? 0 : num_counts * count_width; int server_width = (flags & WITHOUT_SERVER) ? 0 : num_counts * count_width; int conflict_width = (flags & WITHOUT_CONFLICTS) ? 0 : 6; int text_width = name_width + client_width + server_width + conflict_width; if (text_width < 70) { // enlarge name column to make room for long lines of text name_width += 70 - text_width; text_width = 70; } out << "+" << fill('-', name_width); if (!(flags & WITHOUT_CLIENT)) { out << '|' << center('-', "", client_width); } if (!(flags & WITHOUT_SERVER)) { out << '|' << center('-', "", server_width); } if (!(flags & WITHOUT_CONFLICTS)) { out << '|' << center('-', "CON", conflict_width); } out << "+\n"; if (!(flags & WITHOUT_REJECTS) || !(flags & WITHOUT_CONFLICTS)) { out << "|" << fill(' ', name_width); if (!(flags & WITHOUT_CLIENT)) { out << '|' << center(' ', m_localName, client_width); } if (!(flags & WITHOUT_SERVER)) { out << '|' << center(' ', m_remoteName, server_width); } if (!(flags & WITHOUT_CONFLICTS)) { out << '|' << center(' ', "FLI", conflict_width); } out << "|\n"; } out << '|' << right(' ', "Source", name_width); if (!(flags & WITHOUT_CLIENT)) { out << '|' << center(' ', "NEW", count_width); out << '|' << center(' ', "MOD", count_width); out << '|' << center(' ', "DEL", count_width); if (!(flags & WITHOUT_REJECTS)) { out << '|' << center(' ', "ERR", count_width); } if (flags & WITH_TOTAL) { out << '|' << center(' ', "TOTAL", count_width); } } if (!(flags & WITHOUT_SERVER)) { out << '|' << center(' ', "NEW", count_width); out << '|' << center(' ', "MOD", count_width); out << '|' << center(' ', "DEL", count_width); if (!(flags & WITHOUT_REJECTS)) { out << '|' << center(' ', "ERR", count_width); } if (flags & WITH_TOTAL) { out << '|' << center(' ', "TOTAL", count_width); } } if (!(flags & WITHOUT_CONFLICTS)) { out << '|' << center(' ', "CTS", conflict_width); } out << "|\n"; stringstream sepstream; sepstream << '+' << fill('-', name_width); if (!(flags & WITHOUT_CLIENT)) { sepstream << '+' << fill('-', count_width); sepstream << '+' << fill('-', count_width); sepstream << '+' << fill('-', count_width); if (!(flags & WITHOUT_REJECTS)) { sepstream << '+' << fill('-', count_width); } if (flags & WITH_TOTAL) { sepstream << '+' << fill('-', count_width); } } if (!(flags & WITHOUT_SERVER)) { sepstream << '+' << fill('-', count_width); sepstream << '+' << fill('-', count_width); sepstream << '+' << fill('-', count_width); if (!(flags & WITHOUT_REJECTS)) { sepstream << '+' << fill('-', count_width); } if (flags & WITH_TOTAL) { sepstream << '+' << fill('-', count_width); } } if (!(flags & WITHOUT_CONFLICTS)) { sepstream << '+' << fill('-', conflict_width); } sepstream << "+\n"; string sep = sepstream.str(); out << sep; BOOST_FOREACH(const SyncReport::value_type &entry, *this) { const std::string &name = entry.first; const SyncSourceReport &source = entry.second; out << '|' << right(' ', name, name_width); ssize_t name_column = name_width - 2 - name.size(); if (name_column < 0) { name_column = 0; } for (SyncSourceReport::ItemLocation location = ((flags & WITHOUT_CLIENT) ? SyncSourceReport::ITEM_REMOTE : SyncSourceReport::ITEM_LOCAL); location <= ((flags & WITHOUT_SERVER) ? SyncSourceReport::ITEM_LOCAL : SyncSourceReport::ITEM_REMOTE); location = SyncSourceReport::ItemLocation(int(location) + 1)) { for (SyncSourceReport::ItemState state = SyncSourceReport::ITEM_ADDED; state <= SyncSourceReport::ITEM_REMOVED; state = SyncSourceReport::ItemState(int(state) + 1)) { stringstream count; count << source.getItemStat(location, state, SyncSourceReport::ITEM_TOTAL); out << '|' << center(' ', count.str(), count_width); } if (!(flags & WITHOUT_REJECTS)) { stringstream count; count << source.getItemStat(location, SyncSourceReport::ITEM_ANY, SyncSourceReport::ITEM_REJECT); out << '|' << center(' ', count.str(), count_width); } if (flags & WITH_TOTAL) { stringstream count; count << source.getItemStat(location, SyncSourceReport::ITEM_ANY, SyncSourceReport::ITEM_TOTAL); out << '|' << center(' ', count.str(), count_width); } } int total_conflicts = 0; if (!(flags & WITHOUT_CONFLICTS)) { total_conflicts = source.getItemStat(SyncSourceReport::ITEM_REMOTE, SyncSourceReport::ITEM_ANY, SyncSourceReport::ITEM_CONFLICT_SERVER_WON) + source.getItemStat(SyncSourceReport::ITEM_REMOTE, SyncSourceReport::ITEM_ANY, SyncSourceReport::ITEM_CONFLICT_CLIENT_WON) + source.getItemStat(SyncSourceReport::ITEM_REMOTE, SyncSourceReport::ITEM_ANY, SyncSourceReport::ITEM_CONFLICT_DUPLICATED); stringstream conflicts; conflicts << total_conflicts; out << '|' << center(' ', conflicts.str(), conflict_width); } out << "|\n"; std::stringstream line; if (source.getFinalSyncMode() != SYNC_NONE || source.getItemStat(SyncSourceReport::ITEM_LOCAL, SyncSourceReport::ITEM_ANY, SyncSourceReport::ITEM_SENT_BYTES) || source.getItemStat(SyncSourceReport::ITEM_LOCAL, SyncSourceReport::ITEM_ANY, SyncSourceReport::ITEM_RECEIVED_BYTES)) { line << PrettyPrintSyncMode(source.getFinalSyncMode()) << ", "; if (source.getRestarts()) { line << source.getRestarts() + 1 << " cycles, "; } line << source.getItemStat(SyncSourceReport::ITEM_LOCAL, SyncSourceReport::ITEM_ANY, SyncSourceReport::ITEM_SENT_BYTES) / 1024 << " KB sent by client, " << source.getItemStat(SyncSourceReport::ITEM_LOCAL, SyncSourceReport::ITEM_ANY, SyncSourceReport::ITEM_RECEIVED_BYTES) / 1024 << " KB received"; out << '|' << align(' ', line.str(), text_width, name_column) << "|\n"; } if (total_conflicts > 0) { for (SyncSourceReport::ItemResult result = SyncSourceReport::ITEM_CONFLICT_SERVER_WON; result <= SyncSourceReport::ITEM_CONFLICT_DUPLICATED; result = SyncSourceReport::ItemResult(int(result) + 1)) { int count; if ((count = source.getItemStat(SyncSourceReport::ITEM_REMOTE, SyncSourceReport::ITEM_ANY, result)) != 0 || true) { std::stringstream line; line << count << " " << (result == SyncSourceReport::ITEM_CONFLICT_SERVER_WON ? "client item(s) discarded" : result == SyncSourceReport::ITEM_CONFLICT_CLIENT_WON ? "server item(s) discarded" : "item(s) duplicated"); out << '|' << align(' ', line.str(), text_width, name_column) << "|\n"; } } } int total_matched = source.getItemStat(SyncSourceReport::ITEM_REMOTE, SyncSourceReport::ITEM_ANY, SyncSourceReport::ITEM_MATCH); if (total_matched) { stringstream line; line << total_matched << " item(s) matched"; out << '|' << align(' ', line.str(), text_width, name_column) << "|\n"; } if (source.m_backupBefore.isAvailable() || source.m_backupAfter.isAvailable()) { std::stringstream backup; backup << "item(s) in database backup: "; if (source.m_backupBefore.isAvailable()) { backup << source.m_backupBefore.getNumItems() << " before sync, "; } else { backup << "no backup before sync, "; } if (source.m_backupAfter.isAvailable()) { backup << source.m_backupAfter.getNumItems() << " after it"; } else { backup << "no backup after it"; } out << '|' << align(' ', backup.str(), text_width, name_column) << "|\n"; } if (source.getStatus()) { out << '|' << align(' ', Status2String(source.getStatus()), text_width, name_column) << "|\n"; } out << sep; } if (getStart()) { out << '|' << center(' ', formatSyncTimes(), text_width) << "|\n"; } if (getStatus()) { out << '|' << center(' ', getStatus() != STATUS_HTTP_OK ? Status2String(getStatus()) : "synchronization completed successfully", text_width) << "|\n"; } if (getStatus() || getStart()) { out << sep; } if (!getError().empty()) { out << "First ERROR encountered: " << getError() << endl; } } std::string SyncReport::formatSyncTimes() const { std::stringstream out; time_t duration = m_end - m_start; out << "start "; if (!m_start) { out << "unknown"; } else { struct tm tmbuffer, *tm = localtime_r(&m_start, &tmbuffer); if (tm) { char buffer[160]; strftime(buffer, sizeof(buffer), "%c", tm); out << buffer; } else { out << "???"; } if (!m_end) { out << ", unknown duration (crashed?!)"; } else { out << ", duration " << duration / 60 << ":" << std::setw(2) << std::setfill('0') << duration % 60 << "min"; } } return out.str(); } std::string SyncReport::slowSyncExplanation(const std::string &peer, const std::set &sources) { if (sources.empty()) { return ""; } string sourceparam = boost::join(sources, " "); std::string explanation = StringPrintf("Doing a slow synchronization may lead to duplicated items or\n" "lost data when the server merges items incorrectly. Choosing\n" "a different synchronization mode may be the better alternative.\n" "Restart synchronization of affected source(s) with one of the\n" "following sync modes to recover from this problem:\n" " slow, refresh-from-server, refresh-from-client\n\n" "Analyzing the current state:\n" " syncevolution --status %s %s\n\n" "Running with one of the three modes:\n" " syncevolution --sync [slow|refresh-from-remote|refresh-from-local] %s %s\n", peer.c_str(), sourceparam.c_str(), peer.c_str(), sourceparam.c_str()); return explanation; } std::string SyncReport::slowSyncExplanation(const std::string &peer) const { std::set sources; BOOST_FOREACH(const SyncReport::value_type &entry, *this) { const std::string &name = entry.first; const SyncSourceReport &source = entry.second; if (source.getStatus() == STATUS_UNEXPECTED_SLOW_SYNC) { string virtualsource = source.getVirtualSource(); sources.insert(virtualsource.empty() ? name : virtualsource); } } return slowSyncExplanation(peer, sources); } ConfigNode &operator << (ConfigNode &node, const SyncReport &report) { node.setProperty("start", static_cast(report.getStart())); node.setProperty("end", static_cast(report.getEnd())); node.setProperty("status", static_cast(report.getStatus())); string error = report.getError(); if (!error.empty()) { node.setProperty("error", error); } else { node.removeProperty("error"); } BOOST_FOREACH(const SyncReport::value_type &entry, report) { const std::string &name = entry.first; const SyncSourceReport &source = entry.second; string prefix = name; boost::replace_all(prefix, "_", "__"); boost::replace_all(prefix, "-", "_+"); prefix = "source-" + prefix; string key; key = prefix + "-mode"; node.setProperty(key, PrettyPrintSyncMode(source.getFinalSyncMode())); if (source.getRestarts()) { key = prefix + "-restarts"; node.setProperty(key, source.getRestarts()); } key = prefix + "-first"; node.setProperty(key, source.isFirstSync()); key = prefix + "-resume"; node.setProperty(key, source.isResumeSync()); key = prefix + "-status"; node.setProperty(key, static_cast(source.getStatus())); string virtualsource = source.getVirtualSource(); if (!virtualsource.empty()) { key = prefix + "-virtualsource"; node.setProperty(key, virtualsource); } key = prefix + "-backup-before"; node.setProperty(key, source.m_backupBefore.getNumItems()); key = prefix + "-backup-after"; node.setProperty(key, source.m_backupAfter.getNumItems()); for (int location = 0; location < SyncSourceReport::ITEM_LOCATION_MAX; location++) { for (int state = 0; state < SyncSourceReport::ITEM_STATE_MAX; state++) { for (int result = 0; result < SyncSourceReport::ITEM_RESULT_MAX; result++) { int intval = source.getItemStat(SyncSourceReport::ItemLocation(location), SyncSourceReport::ItemState(state), SyncSourceReport::ItemResult(result)); if (intval) { key = prefix + "-stat-" + SyncSourceReport::StatTupleToString(SyncSourceReport::ItemLocation(location), SyncSourceReport::ItemState(state), SyncSourceReport::ItemResult(result)); node.setProperty(key, intval); } } } } } return node; } ConfigNode &operator >> (ConfigNode &node, SyncReport &report) { long ts; if (node.getProperty("start", ts)) { report.setStart(ts); } if (node.getProperty("end", ts)) { report.setEnd(ts); } int status; if (node.getProperty("status", status)) { report.setStatus(static_cast(status)); } string error; if (node.getProperty("error", error)) { report.setError(error); } ConfigNode::PropsType props; node.readProperties(props); BOOST_FOREACH(const ConfigNode::PropsType::value_type &prop, props) { string key = prop.first; if (boost::starts_with(key, "source-")) { key.erase(0, strlen("source-")); size_t off = key.find('-'); if (off != key.npos) { string sourcename = key.substr(0, off); boost::replace_all(sourcename, "_+", "-"); boost::replace_all(sourcename, "__", "_"); SyncSourceReport &source = report.getSyncSourceReport(sourcename); key.erase(0, off + 1); if (boost::starts_with(key, "stat-")) { key.erase(0, strlen("stat-")); SyncSourceReport::ItemLocation location; SyncSourceReport::ItemState state; SyncSourceReport::ItemResult result; SyncSourceReport::StringToStatTuple(key, location, state, result); stringstream in(prop.second); int intval; in >> intval; source.setItemStat(location, state, result, intval); } else if (key == "mode") { source.recordFinalSyncMode(StringToSyncMode(prop.second)); } else if (key == "restarts") { int value; if (node.getProperty(prop.first, value)) { source.setRestarts(value); } } else if (key == "first") { bool value = false; if (node.getProperty(prop.first, value)) { source.recordFirstSync(value); } } else if (key == "resume") { bool value = false; if (node.getProperty(prop.first, value)) { source.recordResumeSync(value); } } else if (key == "status") { long value; if (node.getProperty(prop.first, value)) { source.recordStatus(static_cast(value)); } } else if (key == "virtualsource") { source.recordVirtualSource(node.readProperty(prop.first)); } else if (key == "backup-before") { long value; if (node.getProperty(prop.first, value)) { source.m_backupBefore.setNumItems(value); } } else if (key == "backup-after") { long value; if (node.getProperty(prop.first, value)) { source.m_backupAfter.setNumItems(value); } } } } } return node; } SE_END_CXX syncevolution_1.4/src/syncevo/SyncML.h000066400000000000000000000434221230021373600201560ustar00rootroot00000000000000/* * Copyright (C) 2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_SYNCML #define INCL_SYNCML #include #include #include #include #include #include #include SE_BEGIN_CXX /** * only the codes which are valid for a server alerted sync (SAN) message */ enum SANSyncMode { SA_SLOW = 201, SA_FIRST = SA_SLOW, SA_TWO_WAY = 206, SA_ONE_WAY_FROM_CLIENT = 207, SA_REFRESH_FROM_CLIENT = 208, SA_ONE_WAY_FROM_SERVER = 209, SA_REFRESH_FROM_SERVER = 210, SA_LAST = SA_REFRESH_FROM_SERVER, SA_INVALID = 255 }; /** * unambiguous sync modes: * - data direction is independent of client/server role * - no server alerted variants */ enum SimpleSyncMode { SIMPLE_SYNC_NONE, SIMPLE_SYNC_TWO_WAY = 200, SIMPLE_SYNC_SLOW = 201, SIMPLE_SYNC_RESTORE_FROM_BACKUP = 211, SIMPLE_SYNC_ONE_WAY_FROM_LOCAL = 212, SIMPLE_SYNC_REFRESH_FROM_LOCAL = 213, SIMPLE_SYNC_ONE_WAY_FROM_REMOTE = 214, SIMPLE_SYNC_REFRESH_FROM_REMOTE = 215, // custom modes in SyncEvolution /** mirror data on server side, slow variant (client sends all items) */ SIMPLE_SYNC_LOCAL_CACHE_SLOW = 218, /** mirror data on server side, incremental variant (client sends only changes) */ SIMPLE_SYNC_LOCAL_CACHE_INCREMENTAL = 219, SIMPLE_SYNC_INVALID = 255 }; /** all kinds of sync modes */ enum SyncMode { /** unset or disabled */ SYNC_NONE, SYNC_FIRST = 200, SYNC_TWO_WAY = 200, SYNC_SLOW = 201, SYNC_ONE_WAY_FROM_CLIENT = 202, SYNC_REFRESH_FROM_CLIENT = 203, SYNC_ONE_WAY_FROM_SERVER = 204, SYNC_REFRESH_FROM_SERVER = 205, // used by Server Alerted Sync, same as SA_ in SANSyncMode SA_SYNC_TWO_WAY = 206, SA_SYNC_ONE_WAY_FROM_CLIENT = 207, SA_SYNC_REFRESH_FROM_CLIENT = 208, SA_SYNC_ONE_WAY_FROM_SERVER = 209, SA_SYNC_REFRESH_FROM_SERVER = 210, // used by restore backend with backup data, a pseudo mode SYNC_RESTORE_FROM_BACKUP = 211, // modes which always transfer in the same direction, // regardless which side acts as client or server SYNC_ONE_WAY_FROM_LOCAL = 212, SYNC_REFRESH_FROM_LOCAL = 213, SYNC_ONE_WAY_FROM_REMOTE = 214, SYNC_REFRESH_FROM_REMOTE = 215, // custom mode SYNC_LOCAL_CACHE_SLOW = 218, SYNC_LOCAL_CACHE_INCREMENTAL = 219, SYNC_LAST = 220, /** error situation (in contrast to SYNC_NONE) */ SYNC_INVALID = 255 }; /** * maps to normal SYNC_ variants (no SA_*) and unambiguous direction * (LOCAL/REMOTE instead of CLIENT/SERVER) */ SimpleSyncMode SimplifySyncMode(SyncMode mode, bool peerIsClient); /** * maps to the server alerted variants */ SANSyncMode AlertSyncMode(SyncMode mode, bool peerIsClient); /* According to OMNA WSP Content Type Numbers*/ enum ContentType { WSPCTC_TEXT_PLAIN = 0x03, WSPCTC_XVCALENDAR = 0x06, WSPCTC_XVCARD = 0x07, WSPCTC_ICALENDAR = 0x0305, WSPCTC_VCARD = 0x0309, WSPCTC_UNKNOWN =0xFFFFFF }; /** * Return string for sync mode. User-visible strings are the ones used * in a sync source config ("two-way", "refresh-from-server", etc.). * Otherwise the constants above are returned ("SYNC_NONE"). */ std::string PrettyPrintSyncMode(SyncMode mode, bool userVisible = true); /** * Parse user-visible mode names. */ SyncMode StringToSyncMode(const std::string &str, bool serverAlerted = false); /* * Parse string based content type to WSPCTC encoded binary code * Always use older type unless forceType is true. */ ContentType StringToContentType (const std::string &str, bool forceType); /* * return string based MIME Type for PIM, always use legacy type unless * forceType is set. * */ std::string GetLegacyMIMEType (const std::string &str, bool forceType); /** * result of SyncML operations, same codes as in HTTP and the Synthesis engine */ enum SyncMLStatus { /** ok */ STATUS_OK = 0, /** more explicit ok status in cases where 0 might mean "unknown" (SyncReport) */ STATUS_HTTP_OK = 200, /** no content / end of file / end of iteration / empty/NULL value */ STATUS_NO_CONTENT = 204, /** external data has been merged */ STATUS_DATA_MERGED = 207, /** The request requires user authentication. */ STATUS_UNAUTHORIZED = 401, /** forbidden / access denied */ STATUS_FORBIDDEN = 403, /** object not found / unassigned field */ STATUS_NOT_FOUND = 404, /** command not allowed */ STATUS_COMMAND_NOT_ALLOWED = 405, STATUS_OPTIONAL_FEATURE_NOT_SUPPORTED = 406, STATUS_AUTHORIZATION_REQUIRED = 407, /** * "If the specified remote procedure call no longer exists on * the recipient, then the (410) Gone exception condition is * created by the command." */ STATUS_COMMAND_GONE = 410, /** * "If the Put command did not include the size of the data item * to be transferred (i.e., in the Meta element type), then the * (411) Size required exception condition is created by the * command. */ STATUS_SIZE_REQUIRED = 411, /** * "If the Alert command didn’t include all the correct parameters * in the Item element type, then the (412) Incomplete command * exception condition is created by the command." */ STATUS_INCOMPLETE_COMMAND = 412, /** * "If the requested data item is too large to be transferred at * this time, then the (413) Request entity too large exception * condition is created by the command. */ STATUS_REQUEST_ENTITY_TOO_LARGE = 413, STATUS_UNSUPPORTED_MEDIA_TYPE_OR_FORMAT = 415, /** * "If the Size specified in the Meta element type was too large * for the recipient (e.g., the recipient does not have sufficient * input buffer for the data), then the (416) Requested size too * big exception condition is created by the command." */ STATUS_REQUESTED_SIZE_TOO_BIG = 416, STATUS_RETRY_LATER = 417, /** object exists already */ STATUS_ALREADY_EXISTS = 418, /** database / memory full error */ STATUS_FULL = 420, STATUS_UNKNOWN_SEARCH_GRAMMAR = 421, STATUS_BAD_CGI_OR_FILTER_QUERY = 422, /** * "In a two-way synchronization, if the OMA DS client specifies a * "Soft Delete" for an item that has already been "Hard Deleted" * on the OMA DS server, then a (423) Soft-delete conflict MUST be * returned in the Status command." */ STATUS_SOFT_DELETE_CONFLICT = 423, STATUS_PARTIAL_ITEM_NOT_ACCEPTED = 426, STATUS_ITEM_NOT_EMPTY = 427, STATUS_MOVE_FAILED = 428, /** command failed / fatal DB error */ STATUS_FATAL = 500, /** in Synthesis StartDataRead: slow sync forced by backend */ STATUS_SLOW_SYNC_508 = 508, /** general DB error */ STATUS_DATASTORE_FAILURE = 510, /* sysync error codes also used by SyncEvolution */ /** transport failure, sysync::LOCERR_TRANSPFAIL */ STATUS_TRANSPORT_FAILURE = 20043, /* error codes in the range reserved by Synthesis for the application follow */ /** ran into an unexpected slow sync, refused to execute it */ STATUS_UNEXPECTED_SLOW_SYNC = 22000, /** no error at the SyncML level, but some items did not transfer correctly */ STATUS_PARTIAL_FAILURE = 22001, /** * Set by SyncEvolution in status.ini before starting an sync. * Replaced with the final status code if the sync completes. * Finding this code here in a session report implies that * the process responsible for the session died unexpectedly, * for unknown reasons. */ STATUS_DIED_PREMATURELY = 22002, /** * Set by dbus server when it asks password from dbus clients * and no responsble is gotten in a specific time. */ STATUS_PASSWORD_TIMEOUT = 22003, /** * On-disk files (config, Synthesis binfiles) are too recent * to be read and/or used by this SyncEvolution release. * User must upgrade. */ STATUS_RELEASE_TOO_OLD = 22004, /** * On-disk files would be changed in such a way that downgrading * becomes impossible. User must explicitly migrate config if * he wants to proceed. */ STATUS_MIGRATION_NEEDED = 22005, STATUS_MAX = 0x7FFFFFF }; /** * short (in the range of 80 characters or less) description of the status code, * followed by "(status xxxx)" because the mapping of description to code * might be ambiguous */ std::string Status2String(SyncMLStatus status); /** * Information about a database dump. * Currently only records the number of items. * A negative number of items means no backup * available. */ class BackupReport { public: BackupReport() { clear(); } bool isAvailable() const { return m_numItems >= 0; } long getNumItems() const { return m_numItems; } void setNumItems(long numItems) { m_numItems = numItems; } void clear() { m_numItems = -1; } private: long m_numItems; }; class SyncSourceReport { public: SyncSourceReport() { memset(m_stat, 0, sizeof(m_stat)); m_received = 0; m_first = m_resume = false; m_mode = SYNC_NONE; m_status = STATUS_OK; m_restarts = 0; } enum ItemLocation { ITEM_LOCAL, ITEM_REMOTE, ITEM_LOCATION_MAX }; static std::string LocationToString(ItemLocation location); static ItemLocation StringToLocation(const std::string &location); enum ItemState { ITEM_ADDED, ITEM_UPDATED, ITEM_REMOVED, ITEM_ANY, ITEM_STATE_MAX }; static std::string StateToString(ItemState state); static ItemState StringToState(const std::string &state); enum ItemResult { ITEM_TOTAL, /**< total number ADDED/UPDATED/REMOVED */ ITEM_REJECT, /**< number of rejected items, ANY state */ ITEM_MATCH, /**< number of matched items, ANY state, REMOTE */ ITEM_CONFLICT_SERVER_WON, /**< conflicts resolved by using server item, ANY state, REMOTE */ ITEM_CONFLICT_CLIENT_WON, /**< conflicts resolved by using client item, ANY state, REMOTE */ ITEM_CONFLICT_DUPLICATED, /**< conflicts resolved by duplicating item, ANY state, REMOTE */ ITEM_SENT_BYTES, /**< number of sent bytes, ANY, LOCAL */ ITEM_RECEIVED_BYTES, /**< number of received bytes, ANY, LOCAL */ ITEM_RESULT_MAX }; static std::string ResultToString(ItemResult result); static ItemResult StringToResult(const std::string &result); static std::string StatTupleToString(ItemLocation location, ItemState state, ItemResult result); static void StringToStatTuple(const std::string &str, ItemLocation &location, ItemState &state, ItemResult &result); /** * get item statistics * * @param location either local or remote * @param state added, updated or removed * @param success either okay or failed */ int getItemStat(ItemLocation location, ItemState state, ItemResult success) const { return m_stat[location][state][success]; } void setItemStat(ItemLocation location, ItemState state, ItemResult success, int count) { m_stat[location][state][success] = count; } void incrementItemStat(ItemLocation location, ItemState state, ItemResult success) { m_stat[location][state][success]++; } /** true if statistics indicate that peer or local was modified during sync */ bool wasChanged(ItemLocation location); void recordFinalSyncMode(SyncMode mode) { m_mode = mode; } SyncMode getFinalSyncMode() const { return m_mode; } void recordRestart() { m_restarts++; } void setRestarts(int restarts) { m_restarts = restarts; } /** * number of times that the sync session was restarted * involving the source, usually zero */ int getRestarts() const { return m_restarts; } void recordFirstSync(bool isFirstSync) { m_first = isFirstSync; } bool isFirstSync() const { return m_first; } void recordResumeSync(bool isResumeSync) { m_resume = isResumeSync; } bool isResumeSync() const { return m_resume; } void recordStatus(SyncMLStatus status ) { m_status = status; } SyncMLStatus getStatus() const { return m_status; } /** counts each received add/update/delete */ void recordTotalNumItemsReceived(int received) { m_received = received; } int getTotalNumItemsReceived() const { return m_received; } /** * if not empty, then this was the virtual source which cause the * current one to be included in the sync */ void recordVirtualSource(const std::string &virtualsource) { m_virtualSource = virtualsource; } std::string getVirtualSource() const { return m_virtualSource; } /** information about database dump before and after session */ BackupReport m_backupBefore, m_backupAfter; private: /** storage for getItemStat(): allow access with _MAX as index */ int m_stat[ITEM_LOCATION_MAX + 1][ITEM_STATE_MAX + 1][ITEM_RESULT_MAX + 1]; int m_received; SyncMode m_mode; int m_restarts; bool m_first; bool m_resume; SyncMLStatus m_status; std::string m_virtualSource; }; class SyncReport : public std::map { time_t m_start, m_end; SyncMLStatus m_status; std::string m_error; std::string m_localName, m_remoteName; public: SyncReport() : m_start(0), m_end(0), m_status(STATUS_OK), m_localName("LOCAL"), m_remoteName("REMOTE") {} /** construct from text dump */ SyncReport(const std::string &dump); /** convert to text dump */ std::string toString() const; void setLocalName(const std::string &name) { m_localName = name; } void setRemoteName(const std::string &name) { m_remoteName = name; } typedef std::pair SourceReport_t; void addSyncSourceReport(const std::string &name, const SyncSourceReport &report) { (*this)[name] = report; } SyncSourceReport &getSyncSourceReport(const std::string &name) { return (*this)[name]; } const SyncSourceReport *findSyncSourceReport(const std::string &name) const { const_iterator it = find(name); return it == end() ? NULL : &it->second; } /** start time of sync, 0 if unknown */ time_t getStart() const { return m_start; } void setStart(time_t start) { m_start = start; } /** end time of sync, 0 if unknown (indicates a crash) */ time_t getEnd() const { return m_end; } void setEnd(time_t end) { m_end = end; } /** * overall sync result * * STATUS_OK = 0 means unknown status (might have aborted prematurely), * STATUS_HTTP_OK = 200 means successful completion */ SyncMLStatus getStatus() const { return m_status; } void setStatus(SyncMLStatus status) { m_status = status; } /** * Initial ERROR description as seen by SyncEvolution, * typically via the logging infrastructure. Not localized. */ std::string getError() const { return m_error; } void setError(const std::string &error) { m_error = error; } void clear() { std::map::clear(); m_start = m_end = 0; m_error = ""; m_status = STATUS_OK; } /** generate short string representing start and duration of sync */ std::string formatSyncTimes() const; /** pretty-print with options */ void prettyPrint(std::ostream &out, int flags) const; enum { WITHOUT_CLIENT = 1 << 1, WITHOUT_SERVER = 1 << 2, WITHOUT_CONFLICTS = 1 << 3, WITHOUT_REJECTS = 1 << 4, WITH_TOTAL = 1 << 5 }; /** * Produces a non-localized explanation for recovering from unexpected * slow syncs, targeted towards command line users. * * @param peer config name used to select the affected peer (nor necessarily normalized) * @param sources list of affected sources * @return explanation, empty string if list of sources is empty */ static std::string slowSyncExplanation(const std::string &peer, const std::set &sources); /** * Produces a non-localized explanation for recovering from unexpected * slow syncs, targeted towards command line users. Uses the information * about sources stored in the report. * * @param peer config name used to select the affected peer (nor necessarily normalized) * @return explanation, empty string if list of sources is empty */ std::string slowSyncExplanation(const std::string &peer) const; }; /** pretty-print the report as an ASCII table */ std::ostream &operator << (std::ostream &out, const SyncReport &report); class ConfigNode; /** write report into a ConfigNode */ ConfigNode &operator << (ConfigNode &node, const SyncReport &report); /** read report from a ConfigNode */ ConfigNode &operator >> (ConfigNode &node, SyncReport &report); SE_END_CXX #endif // INCL_SYNCML syncevolution_1.4/src/syncevo/SyncSource.cpp000066400000000000000000001634621230021373600214500ustar00rootroot00000000000000/* * Copyright (C) 2005-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef _GNU_SOURCE # define _GNU_SOURCE 1 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef ENABLE_UNIT_TESTS #include "test.h" #endif #include SE_BEGIN_CXX void SyncSourceBase::throwError(const string &action, int error) { std::string what = action + ": " + strerror(error); // be as specific if we can be: relevant for the file backend, // which is expected to return STATUS_NOT_FOUND == 404 for "file // not found" if (error == ENOENT) { throwError(STATUS_NOT_FOUND, what); } else { throwError(what); } } void SyncSourceBase::throwError(const string &failure) { SyncContext::throwError(string(getDisplayName()) + ": " + failure); } void SyncSourceBase::throwError(SyncMLStatus status, const string &failure) { SyncContext::throwError(status, getDisplayName() + ": " + failure); } SyncMLStatus SyncSourceBase::handleException(HandleExceptionFlags flags) { SyncMLStatus res = Exception::handle(getDisplayName(), flags); return res == STATUS_FATAL ? STATUS_DATASTORE_FAILURE : res; } void SyncSourceBase::getDatastoreXML(string &xml, XMLConfigFragments &fragments) { stringstream xmlstream; SynthesisInfo info; getSynthesisInfo(info, fragments); xmlstream << " SyncEvolution\n"; if (info.m_earlyStartDataRead) { xmlstream << " yes\n"; } if (info.m_readOnly) { xmlstream << " \n" " yes\n"; } xmlstream << " " << (serverModeEnabled() ? "yes" : "no") << "\n" " yes \n" " yes\n"; if (info.m_globalIDs) { xmlstream << " 1122583000"; } xmlstream << "\n" " \n" " newer-wins\n" "\n" " \n" " newer-wins\n" "\n" " \n" " UTF-8\n" " \n" " unix\n" "\n" " \n" " SYSTEM\n" "\n" " \n" " yes\n" "\n"; xmlstream << " \n" " \n"; if (!info.m_profile.empty()) { xmlstream << " \n" " \n" " \n" " \n"; } xmlstream << " \n" " \n" "\n"; xmlstream << " \n" " \n" << info.m_datatypes << " \n"; // arbitrary configuration options, can override the ones above xmlstream << info.m_datastoreOptions; xml = xmlstream.str(); } string SyncSourceBase::getNativeDatatypeName() { SynthesisInfo info; XMLConfigFragments fragments; getSynthesisInfo(info, fragments); return info.m_native; } SyncSource::SyncSource(const SyncSourceParams ¶ms) : SyncSourceConfig(params.m_name, params.m_nodes), m_numDeleted(0), m_forceSlowSync(false), m_database("", ""), m_name(params.getDisplayName()), m_needChanges(true) { } SDKInterface *SyncSource::getSynthesisAPI() const { return m_synthesisAPI.empty() ? NULL : static_cast(m_synthesisAPI[m_synthesisAPI.size() - 1]); } void SyncSource::pushSynthesisAPI(sysync::SDK_InterfaceType *synthesisAPI) { m_synthesisAPI.push_back(synthesisAPI); } void SyncSource::popSynthesisAPI() { m_synthesisAPI.pop_back(); } SourceRegistry &SyncSource::getSourceRegistry() { static SourceRegistry sourceRegistry; return sourceRegistry; } RegisterSyncSource::RegisterSyncSource(const string &shortDescr, bool enabled, Create_t create, const string &typeDescr, const Values &typeValues) : m_shortDescr(shortDescr), m_enabled(enabled), m_create(create), m_typeDescr(typeDescr), m_typeValues(typeValues) { SourceRegistry ®istry(SyncSource::getSourceRegistry()); // insert sorted by description to have deterministic ordering for(SourceRegistry::iterator it = registry.begin(); it != registry.end(); ++it) { if ((*it)->m_shortDescr > shortDescr) { registry.insert(it, this); return; } } registry.push_back(this); } class InactiveSyncSource : public SyncSource { public: InactiveSyncSource(const SyncSourceParams ¶ms) : SyncSource(params) {} virtual bool isInactive() const { return true; } virtual void enableServerMode() {} virtual bool serverModeEnabled() const { return false; } virtual void getSynthesisInfo(SyncEvo::SyncSourceBase::SynthesisInfo&, SyncEvo::XMLConfigFragments&) { throwError("inactive"); } virtual Databases getDatabases() { throwError("inactive"); return Databases(); } virtual void open() { throwError("inactive"); } virtual void close() { throwError("inactive"); } virtual std::string getPeerMimeType() const { return ""; } }; SyncSource *RegisterSyncSource::InactiveSource(const SyncSourceParams ¶ms) { return new InactiveSyncSource(params); } TestRegistry &SyncSource::getTestRegistry() { static TestRegistry testRegistry; return testRegistry; } RegisterSyncSourceTest::RegisterSyncSourceTest(const string &configName, const string &testCaseName) : m_configName(configName), m_testCaseName(testCaseName) { SyncSource::getTestRegistry().push_back(this); } static class ScannedModules { public: ScannedModules() { #ifdef ENABLE_MODULES list > > dirs; /* If enviroment variable SYNCEVOLUTION_BACKEND_DIR is set, will search backends from this path instead. */ string backend_dir (SYNCEVO_BACKEND); char *backend_env = getenv("SYNCEVOLUTION_BACKEND_DIR"); if (backend_env && strlen(backend_env)){ backend_dir = backend_env; } boost::shared_ptr dir (new ReadDir (backend_dir, false)); string dirpath (backend_dir); // Base name (= no dir, no .so suffix) mapping to full file // name (including .so). std::map candidates; // scan directories for matching module names do { debug<<"Scanning backend libraries in " < subdir (new ReadDir (path, false)); dirs.push_back (make_pair(path, subdir)); } continue; } if (boost::ends_with(entry, ".so")) { string fullpath = dirpath + '/' + entry; fullpath = normalizePath(fullpath); candidates[entry.substr(0, entry.size() - 3)] = fullpath; } } if (!dirs.empty()){ dirpath = dirs.front().first; dir = dirs.front().second; dirs.pop_front(); } else { break; } } while (true); // Look at foo- before foo. If there is more than // one version and the version sorts lexically, the "highest" // one will be checked first, too. // // The intention is to try loading syncebook-2 (with explicit // library dependencies) first, then skip syncebook if loading // of syncebook-2 succeeded. If loading of syncebook-2 fails // due to missing libraries, we proceed to use syncebook. BOOST_REVERSE_FOREACH (const StringPair &entry, candidates) { const std::string &basename = entry.first; const std::string &fullpath = entry.second; std::string replacement; std::string modname; size_t offset = basename.rfind('-'); if (offset != basename.npos) { modname = basename.substr(offset); } else { modname = basename; } BOOST_FOREACH (const std::string &l, m_available) { if (boost::starts_with(l, modname)) { replacement = l; break; } } if (!replacement.empty()) { debug << "Skipping " << basename << " = " << fullpath << " because a more recent version of it was already loaded: " << replacement; continue; } // Open the shared object so that backend can register // itself. We keep that pointer, so never close the // module! // RTLD_LAZY is needed for the WebDAV backend, which // needs to do an explicit dlopen() of libneon in compatibility // mode before any of the neon functions can be resolved. void *dlhandle = dlopen(fullpath.c_str(), RTLD_LAZY|RTLD_GLOBAL); // remember which modules were found and which were not if (dlhandle) { debug<<"Loading backend library "< m_available; std::ostringstream debug, info; } scannedModules; string SyncSource::backendsInfo() { return scannedModules.info.str(); } string SyncSource::backendsDebug() { return scannedModules.debug.str(); } void SyncSource::requestAnotherSync() { // At the moment the per-source request to restart cannot be // stored; instead only a per-session request is set. That's okay // for now because restarting is limited to sessions with only // one source active (intentional simplification). SE_LOG_DEBUG(getDisplayName(), "requesting another sync"); SyncContext::requestAnotherSync(); } SyncSource *SyncSource::createSource(const SyncSourceParams ¶ms, bool error, SyncConfig *config) { SourceType sourceType = getSourceType(params.m_nodes); if (sourceType.m_backend == "virtual") { SyncSource *source = NULL; source = new VirtualSyncSource(params, config); if (error && !source) { SyncContext::throwError(params.getDisplayName() + ": virtual source cannot be instantiated"); } return source; } const SourceRegistry ®istry(getSourceRegistry()); auto_ptr source; BOOST_FOREACH(const RegisterSyncSource *sourceInfos, registry) { auto_ptr nextSource(sourceInfos->m_create(params)); if (nextSource.get()) { if (source.get()) { SyncContext::throwError(params.getDisplayName() + ": backend " + sourceType.m_backend + " is ambiguous, avoid the alias and pick a specific backend instead directly"); } source = nextSource; } } if (source.get()) { return source.release(); } if (error) { string backends; if (!scannedModules.m_available.empty()) { backends += "by any of the backend modules ("; backends += boost::join(scannedModules.m_available, ", "); backends += ") "; } string problem = StringPrintf("%s%sbackend not supported %sor not correctly configured (backend=%s databaseFormat=%s syncFormat=%s)", params.m_name.c_str(), params.m_name.empty() ? "" : ": ", backends.c_str(), sourceType.m_backend.c_str(), sourceType.m_localFormat.c_str(), sourceType.m_format.c_str()); SyncContext::throwError(SyncMLStatus(sysync::LOCERR_CFGPARSE), problem); } return NULL; } SyncSource *SyncSource::createTestingSource(const string &name, const string &type, bool error, const char *prefix) { std::string config = "target-config@client-test"; const char *server = getenv("CLIENT_TEST_SERVER"); if (server) { config += "-"; config += server; } boost::shared_ptr context(new SyncConfig(config)); SyncSourceNodes nodes = context->getSyncSourceNodes(name); SyncSourceParams params(name, nodes, context); PersistentSyncSourceConfig sourceconfig(name, nodes); sourceconfig.setSourceType(type); if (prefix) { sourceconfig.setDatabaseID(string(prefix) + name + "_1"); } return createSource(params, error); } VirtualSyncSource::VirtualSyncSource(const SyncSourceParams ¶ms, SyncConfig *config) : DummySyncSource(params) { if (config) { std::string evoSyncSource = getDatabaseID(); BOOST_FOREACH(std::string name, getMappedSources()) { if (name.empty()) { throwError(StringPrintf("configuration of underlying sources contains empty source name: database = '%s'", evoSyncSource.c_str())); } SyncSourceNodes source = config->getSyncSourceNodes(name); SyncSourceParams params(name, source, boost::shared_ptr(config, SyncConfigNOP())); boost::shared_ptr syncSource(createSource(params, true, config)); m_sources.push_back(syncSource); } if (m_sources.size() != 2) { throwError(StringPrintf("configuration of underlying sources must contain exactly one calendar and one todo source (like calendar+todo): database = '%s'", evoSyncSource.c_str())); } } } void VirtualSyncSource::open() { getDataTypeSupport(); BOOST_FOREACH(boost::shared_ptr &source, m_sources) { source->open(); } } void VirtualSyncSource::close() { BOOST_FOREACH(boost::shared_ptr &source, m_sources) { source->close(); } } std::vector VirtualSyncSource::getMappedSources() { std::string evoSyncSource = getDatabaseID(); std::vector mappedSources = unescapeJoinedString (evoSyncSource, ','); return mappedSources; } std::string VirtualSyncSource::getDataTypeSupport() { string datatypes; SourceType sourceType = getSourceType(); string type = sourceType.m_format; datatypes = getDataTypeSupport(type, sourceType.m_forceFormat); return datatypes; } SyncSource::Databases VirtualSyncSource::getDatabases() { SyncSource::Databases dbs; BOOST_FOREACH (boost::shared_ptr &source, m_sources) { SyncSource::Databases sub = source->getDatabases(); if (sub.empty()) { return dbs; } } Database db ("calendar+todo", ""); dbs.push_back (db); return dbs; } void SyncSourceSession::init(SyncSource::Operations &ops) { ops.m_startDataRead = boost::bind(&SyncSourceSession::startDataRead, this, _1, _2); ops.m_endDataRead = boost::lambda::constant(sysync::LOCERR_OK); ops.m_startDataWrite = boost::lambda::constant(sysync::LOCERR_OK); ops.m_endDataWrite = boost::bind(&SyncSourceSession::endDataWrite, this, _1, _2); } sysync::TSyError SyncSourceSession::startDataRead(const char *lastToken, const char *resumeToken) { try { beginSync(lastToken ? lastToken : "", resumeToken ? resumeToken : ""); } catch (const StatusException &ex) { SyncMLStatus status = ex.syncMLStatus(); if (status == STATUS_SLOW_SYNC_508) { // Not an error. Return it normally, without ERROR logging // in our caller. return status; } else { throw; } } return sysync::LOCERR_OK; } sysync::TSyError SyncSourceSession::endDataWrite(bool success, char **newToken) { std::string token = endSync(success); *newToken = StrAlloc(token.c_str()); return sysync::LOCERR_OK; } void SyncSourceChanges::init(SyncSource::Operations &ops) { ops.m_readNextItem = boost::bind(&SyncSourceChanges::iterate, this, _1, _2, _3); } SyncSourceChanges::SyncSourceChanges() : m_first(true) { } bool SyncSourceChanges::addItem(const string &luid, State state) { pair res = m_items[state].insert(luid); return res.second; } bool SyncSourceChanges::reset() { bool removed = false; for (int i = 0; i < MAX; i++) { if (!m_items[i].empty()) { m_items[i].clear(); removed = true; } } m_first = true; return removed; } sysync::TSyError SyncSourceChanges::iterate(sysync::ItemID aID, sysync::sInt32 *aStatus, bool aFirst) { aID->item = NULL; aID->parent = NULL; if (m_first || aFirst) { m_it = m_items[ANY].begin(); m_first = false; } if (m_it == m_items[ANY].end()) { *aStatus = sysync::ReadNextItem_EOF; } else { const string &luid = *m_it; if (m_items[NEW].find(luid) != m_items[NEW].end() || m_items[UPDATED].find(luid) != m_items[UPDATED].end()) { *aStatus = sysync::ReadNextItem_Changed; } else { *aStatus = sysync::ReadNextItem_Unchanged; } aID->item = StrAlloc(luid.c_str()); ++m_it; } return sysync::LOCERR_OK; } void SyncSourceDelete::init(SyncSource::Operations &ops) { ops.m_deleteItem = boost::bind(&SyncSourceDelete::deleteItemSynthesis, this, _1); } sysync::TSyError SyncSourceDelete::deleteItemSynthesis(sysync::cItemID aID) { deleteItem(aID->item); incrementNumDeleted(); return sysync::LOCERR_OK; } void SyncSourceSerialize::getSynthesisInfo(SynthesisInfo &info, XMLConfigFragments &fragments) { string type = getMimeType(); // default remote rule (local-storage.xml): suppresses empty properties info.m_backendRule = "LOCALSTORAGE"; // We store entire items locally and thus have to make sure that // they are complete by having the engine merge incoming and local // data. info.m_datastoreOptions += " true\n"; if (type == "text/x-vcard") { info.m_native = "vCard21"; info.m_fieldlist = "contacts"; info.m_profile = "\"vCard\", 1"; info.m_datatypes = " \n" " \n"; } else if (type == "text/vcard") { info.m_native = "vCard30"; info.m_fieldlist = "contacts"; info.m_profile = "\"vCard\", 2"; info.m_datatypes = " \n" " \n"; // If a backend overwrites the m_beforeWriteScript, then it must // include $VCARD_OUTGOING_PHOTO_VALUE_SCRIPT in its own script, // otherwise it will be sent invalid, empty PHOTO;TYPE=unknown;VALUE=binary: // properties. info.m_beforeWriteScript = "$VCARD_OUTGOING_PHOTO_VALUE_SCRIPT;\n"; // Likewise for reading. This is needed to ensure proper merging // of contact data. info.m_afterReadScript = "$VCARD_INCOMING_PHOTO_VALUE_SCRIPT;\n"; } else if (type == "text/x-calendar" || type == "text/x-vcalendar") { info.m_native = "vCalendar10"; info.m_fieldlist = "calendar"; info.m_profile = "\"vCalendar\", 1"; info.m_datatypes = " \n" " \n"; /** * here are two default implementations. If user wants to reset it, * just implement its own getSynthesisInfo. If user wants to use this default * implementations and its new scripts, it is possible to append its implementations * to info.m_afterReadScript and info.m_beforeWriteScript. */ info.m_afterReadScript = "$VCALENDAR10_AFTERREAD_SCRIPT;\n"; info.m_beforeWriteScript = "$VCALENDAR10_BEFOREWRITE_SCRIPT;\n" "$CALENDAR_BEFOREWRITE_SCRIPT;\n"; } else if (type == "text/calendar" || boost::starts_with(type, "text/calendar+")) { info.m_native = "iCalendar20"; info.m_fieldlist = "calendar"; info.m_profile = "\"vCalendar\", 2"; info.m_datatypes = " \n" " \n"; info.m_beforeWriteScript = "$CALENDAR_BEFOREWRITE_SCRIPT;\n"; } else if (type == "text/plain") { info.m_fieldlist = "Note"; info.m_profile = "\"Note\", 2"; } else { throwError(string("default MIME type not supported: ") + type); } SourceType sourceType = getSourceType(); if (!sourceType.m_format.empty()) { type = sourceType.m_format; } info.m_datatypes = getDataTypeSupport(type, sourceType.m_forceFormat); } std::string SyncSourceBase::getDataTypeSupport(const std::string &type, bool forceFormat) { std::string datatypes; if (type == "text/x-vcard:2.1" || type == "text/x-vcard") { datatypes = " \n"; if (!forceFormat) { datatypes += " \n"; } } else if (type == "text/vcard:3.0" || type == "text/vcard") { datatypes = " \n"; if (!forceFormat) { datatypes += " \n"; } } else if (type == "text/x-vcalendar:1.0" || type == "text/x-vcalendar" || type == "text/x-calendar:1.0" || type == "text/x-calendar") { datatypes = " \n"; if (!forceFormat) { datatypes += " \n"; } } else if (type == "text/calendar:2.0" || type == "text/calendar") { datatypes = " \n"; if (!forceFormat) { datatypes += " \n"; } } else if (type == "text/calendar+plain") { datatypes = " \n" " \n" " \n"; } else if (type == "text/plain:1.0" || type == "text/plain") { // note10 are the same as note11, so ignore force format datatypes = " \n" " \n"; } else if (type.empty()) { throwError("no MIME type configured"); } else { throwError(string("configured MIME type not supported: ") + type); } return datatypes; } sysync::TSyError SyncSourceSerialize::readItemAsKey(sysync::cItemID aID, sysync::KeyH aItemKey) { std::string item; readItem(aID->item, item); TSyError res = getSynthesisAPI()->setValue(aItemKey, "data", item.c_str(), item.size()); return res; } SyncSource::Operations::InsertItemAsKeyResult_t SyncSourceSerialize::insertItemAsKey(sysync::KeyH aItemKey, sysync::ItemID newID) { SharedBuffer data; TSyError res = getSynthesisAPI()->getValue(aItemKey, "data", data); if (!res) { InsertItemResult inserted = insertItem("", data.get()); switch (inserted.m_state) { case ITEM_OKAY: break; case ITEM_AGAIN: // Skip setting the newID. return Operations::InsertItemAsKeyContinue_t(boost::bind(&SyncSourceSerialize::insertContinue, this, _2, inserted.m_continue)); break; case ITEM_REPLACED: res = sysync::DB_DataReplaced; break; case ITEM_MERGED: res = sysync::DB_DataMerged; break; case ITEM_NEEDS_MERGE: res = sysync::DB_Conflict; break; } newID->item = StrAlloc(inserted.m_luid.c_str()); } return res; } SyncSource::Operations::UpdateItemAsKeyResult_t SyncSourceSerialize::updateItemAsKey(sysync::KeyH aItemKey, sysync::cItemID aID, sysync::ItemID newID) { SharedBuffer data; TSyError res = getSynthesisAPI()->getValue(aItemKey, "data", data); if (!res) { InsertItemResult inserted = insertItem(aID->item, data.get()); switch (inserted.m_state) { case ITEM_OKAY: break; case ITEM_AGAIN: // Skip setting the newID. return Operations::UpdateItemAsKeyContinue_t(boost::bind(&SyncSourceSerialize::insertContinue, this, _3, inserted.m_continue)); break; case ITEM_REPLACED: res = sysync::DB_DataReplaced; break; case ITEM_MERGED: res = sysync::DB_DataMerged; break; case ITEM_NEEDS_MERGE: res = sysync::DB_Conflict; break; } newID->item = StrAlloc(inserted.m_luid.c_str()); } return res; } sysync::TSyError SyncSourceSerialize::insertContinue(sysync::ItemID newID, const InsertItemResult::Continue_t &cont) { // The engine cannot tell us when it needs results (for example, // in the "final message received from peer" case in // TSyncSession::EndMessage(), so assume that it does whenever it // calls us again => flush and wait. flushItemChanges(); finishItemChanges(); InsertItemResult inserted = cont(); TSyError res = sysync::LOCERR_OK; switch (inserted.m_state) { case ITEM_OKAY: break; case ITEM_AGAIN: // Skip setting the newID. return sysync::LOCERR_AGAIN; break; case ITEM_REPLACED: res = sysync::DB_DataReplaced; break; case ITEM_MERGED: res = sysync::DB_DataMerged; break; case ITEM_NEEDS_MERGE: res = sysync::DB_Conflict; break; } newID->item = StrAlloc(inserted.m_luid.c_str()); return res; } SyncSourceSerialize::InsertItemResult SyncSourceSerialize::insertItemRaw(const std::string &luid, const std::string &item) { InsertItemResult result = insertItem(luid, item); while (result.m_state == ITEM_AGAIN) { // Flush and wait, because caller (command line, restore) is // not prepared to deal with asynchronous execution. flushItemChanges(); finishItemChanges(); result = result.m_continue(); } return result; } void SyncSourceSerialize::readItemRaw(const std::string &luid, std::string &item) { return readItem(luid, item); } void SyncSourceSerialize::init(SyncSource::Operations &ops) { ops.m_readItemAsKey = boost::bind(&SyncSourceSerialize::readItemAsKey, this, _1, _2); ops.m_insertItemAsKey = boost::bind(&SyncSourceSerialize::insertItemAsKey, this, _1, _2); ops.m_updateItemAsKey = boost::bind(&SyncSourceSerialize::updateItemAsKey, this, _1, _2, _3); } void ItemCache::init(const SyncSource::Operations::ConstBackupInfo &oldBackup, const SyncSource::Operations::BackupInfo &newBackup, bool legacy) { m_counter = 1; m_legacy = legacy; m_backup = newBackup; m_hash2counter.clear(); m_dirname = oldBackup.m_dirname; if (m_dirname.empty() || !oldBackup.m_node) { return; } long numitems; if (!oldBackup.m_node->getProperty("numitems", numitems)) { return; } for (long counter = 1; counter <= numitems; counter++) { stringstream key; key << counter << m_hashSuffix; Hash_t hash; if (oldBackup.m_node->getProperty(key.str(), hash)) { m_hash2counter[hash] = counter; } } } void ItemCache::reset() { // clean directory and start counting at 1 again m_counter = 1; rm_r(m_backup.m_dirname); mkdir_p(m_backup.m_dirname); m_backup.m_node->clear(); } string ItemCache::getFilename(Hash_t hash) { Map_t::const_iterator it = m_hash2counter.find(hash); if (it != m_hash2counter.end()) { stringstream dirname; dirname << m_dirname << "/" << it->second; return dirname.str(); } else { return ""; } } const char *ItemCache::m_hashSuffix = #ifdef USE_SHA256 "-sha256" #else "-hash" #endif ; void ItemCache::backupItem(const std::string &item, const std::string &uid, const std::string &rev) { stringstream filename; filename << m_backup.m_dirname << "/" << m_counter; ItemCache::Hash_t hash = hashFunc(item); string oldfilename = getFilename(hash); if (!oldfilename.empty()) { // found old file with same content, reuse it via hardlink if (link(oldfilename.c_str(), filename.str().c_str())) { // Hard linking failed. Record this, then continue // by ignoring the old file. SE_LOG_DEBUG(NULL, "hard linking old %s new %s: %s", oldfilename.c_str(), filename.str().c_str(), strerror(errno)); oldfilename.clear(); } } if (oldfilename.empty()) { // write new file instead of reusing old one ofstream out(filename.str().c_str()); out.write(item.c_str(), item.size()); out.close(); if (out.fail()) { SE_THROW(string("error writing ") + filename.str() + ": " + strerror(errno)); } } stringstream key; key << m_counter << "-uid"; m_backup.m_node->setProperty(key.str(), uid); if (m_legacy) { // clear() does not remove the existing content, which was // intended here. This should have been key.str(""). As a // result, keys for -rev are longer than intended because they // start with the -uid part. We cannot change it now, because // that would break compatibility with nodes that use the // older, longer keys for -rev. // key.clear(); } else { key.str(""); } key << m_counter << "-rev"; m_backup.m_node->setProperty(key.str(), rev); key.str(""); key << m_counter << ItemCache::m_hashSuffix; m_backup.m_node->setProperty(key.str(), hash); m_counter++; } void ItemCache::finalize(BackupReport &report) { stringstream value; value << m_counter - 1; m_backup.m_node->setProperty("numitems", value.str()); m_backup.m_node->flush(); report.setNumItems(m_counter - 1); } void SyncSourceRevisions::initRevisions() { if (!m_revisionsSet) { // might still be filled with garbage from previous run m_revisions.clear(); listAllItems(m_revisions); m_revisionsSet = true; } } void SyncSourceRevisions::backupData(const SyncSource::Operations::ConstBackupInfo &oldBackup, const SyncSource::Operations::BackupInfo &newBackup, BackupReport &report) { ItemCache cache; cache.init(oldBackup, newBackup, true); bool startOfSync = newBackup.m_mode == SyncSource::Operations::BackupInfo::BACKUP_BEFORE; RevisionMap_t buffer; RevisionMap_t *revisions; if (startOfSync) { initRevisions(); revisions = &m_revisions; } else { listAllItems(buffer); revisions = &buffer; } // Ensure that source knows what we are going to read. std::vector uids; uids.reserve(revisions->size()); BOOST_FOREACH(const StringPair &mapping, *revisions) { uids.push_back(mapping.first); } // We may dump after a hint was already set when starting the // sync. Remember that and restore it when done. If we fail, we // don't need to restore, because then syncing will abort or skip // the source. ReadAheadOrder oldOrder; ReadAheadItems oldLUIDs; getReadAheadOrder(oldOrder, oldLUIDs); setReadAheadOrder(READ_SELECTED_ITEMS, uids); string item; errno = 0; BOOST_FOREACH(const StringPair &mapping, *revisions) { const string &uid = mapping.first; const string &rev = mapping.second; m_raw->readItemRaw(uid, item); cache.backupItem(item, uid, rev); } setReadAheadOrder(oldOrder, oldLUIDs); cache.finalize(report); } void SyncSourceRevisions::restoreData(const SyncSource::Operations::ConstBackupInfo &oldBackup, bool dryrun, SyncSourceReport &report) { RevisionMap_t revisions; listAllItems(revisions); long numitems; string strval; strval = oldBackup.m_node->readProperty("numitems"); stringstream stream(strval); stream >> numitems; for (long counter = 1; counter <= numitems; counter++) { stringstream key; key << counter << "-uid"; string uid = oldBackup.m_node->readProperty(key.str()); key.clear(); key << counter << "-rev"; string rev = oldBackup.m_node->readProperty(key.str()); RevisionMap_t::iterator it = revisions.find(uid); report.incrementItemStat(report.ITEM_LOCAL, report.ITEM_ANY, report.ITEM_TOTAL); if (it != revisions.end() && it->second == rev) { // item exists in backup and database with same revision: // nothing to do } else { // add or update, so need item stringstream filename; filename << oldBackup.m_dirname << "/" << counter; string data; if (!ReadFile(filename.str(), data)) { throwError(StringPrintf("restoring %s from %s failed: could not read file", uid.c_str(), filename.str().c_str())); } // TODO: it would be nicer to recreate the item // with the original revision. If multiple peers // synchronize against us, then some of them // might still be in sync with that revision. By // updating the revision here we force them to // needlessly receive an update. // // For the current peer for which we restore this is // avoided by the revision check above: unchanged // items aren't touched. SyncSourceReport::ItemState state = it == revisions.end() ? SyncSourceReport::ITEM_ADDED : // not found in database, create anew SyncSourceReport::ITEM_UPDATED; // found, update existing item try { report.incrementItemStat(report.ITEM_LOCAL, state, report.ITEM_TOTAL); if (!dryrun) { m_raw->insertItemRaw(it == revisions.end() ? "" : uid, data); } } catch (...) { report.incrementItemStat(report.ITEM_LOCAL, state, report.ITEM_REJECT); throw; } } // remove handled item from revision list so // that when we are done, the only remaining // items listed there are the ones which did // no exist in the backup if (it != revisions.end()) { revisions.erase(it); } } // now remove items that were not in the backup BOOST_FOREACH(const StringPair &mapping, revisions) { try { report.incrementItemStat(report.ITEM_LOCAL, report.ITEM_REMOVED, report.ITEM_TOTAL); if (!dryrun) { m_del->deleteItem(mapping.first); } } catch(...) { report.incrementItemStat(report.ITEM_LOCAL, report.ITEM_REMOVED, report.ITEM_REJECT); throw; } } } bool SyncSourceRevisions::detectChanges(ConfigNode &trackingNode, ChangeMode mode) { bool forceSlowSync = false; // erase content which might have been set in a previous call reset(); if (!m_firstCycle) { // detectChanges() must have been called before; // don't trust our cached revisions in that case (not updated during sync!) // TODO: keep the revision map up-to-date as part of a sync and reuse it m_revisionsSet = false; } else { m_firstCycle = false; } if (mode == CHANGES_NONE) { // shortcut because nothing changed: just copy our known item list ConfigProps props; trackingNode.readProperties(props); RevisionMap_t revisions; BOOST_FOREACH(const StringPair &mapping, props) { const string &uid = mapping.first; const string &revision = mapping.second; addItem(uid); revisions[uid] = revision; } setAllItems(revisions); return false; } if (!m_revisionsSet && mode == CHANGES_FULL) { ConfigProps props; trackingNode.readProperties(props); if (!props.empty()) { // We were not asked to throw away all old information and // there is some that may be worth salvaging, so let's give // our derived class a chance to update it instead of having // to reread everything. // // The exact number of items at which the update method is // more efficient depends on the derived class; here we assume // that even a single item makes it worthwhile. The derived // class can always ignore the information if it has different // tradeoffs. // // TODO (?): an API which only provides the information // on demand... m_revisions.clear(); m_revisions.insert(props.begin(), props.end()); updateAllItems(m_revisions); // continue with m_revisions initialized below m_revisionsSet = true; } } // traditional, slow fallback follows... initRevisions(); // Check whether we have valid revision information. If not, then // we need to do a slow sync. The assumption here is that an empty // revision string marks missing information. When we don't need // change information, not having a revision string is okay. if (needChanges() && !m_revisions.empty() && m_revisions.begin()->second.empty()) { forceSlowSync = true; mode = CHANGES_SLOW; } // If we don't need changes, then override the mode so that // we don't compute them below. if (!needChanges()) { mode = CHANGES_SLOW; } // Delay setProperty calls until after checking all uids. // Necessary for MapSyncSource, which shares the revision among // several uids. Another advantage is that we can do the "find // deleted items" check with less entries (new items not added // yet). StringMap revUpdates; if (mode == CHANGES_SLOW) { // Make tracking node identical to current set of items // by re-adding them below. trackingNode.clear(); } BOOST_FOREACH(const StringPair &mapping, m_revisions) { const string &uid = mapping.first; const string &revision = mapping.second; // always remember the item, need full list addItem(uid); // avoid unnecessary work in CHANGES_SLOW mode if (mode == CHANGES_SLOW) { trackingNode.setProperty(uid, revision); } else { // detect changes string serverRevision(trackingNode.readProperty(uid)); if (!serverRevision.size()) { addItem(uid, NEW); revUpdates[uid] = revision; } else { if (revision != serverRevision) { addItem(uid, UPDATED); revUpdates[uid] = revision; } } } } if (mode != CHANGES_SLOW) { // clear information about all items that we recognized as deleted ConfigProps props; trackingNode.readProperties(props); BOOST_FOREACH(const StringPair &mapping, props) { const string &uid(mapping.first); if (getAllItems().find(uid) == getAllItems().end()) { addItem(uid, DELETED); trackingNode.removeProperty(uid); } } // now update tracking node BOOST_FOREACH(const StringPair &update, revUpdates) { trackingNode.setProperty(update.first, update.second); } } return forceSlowSync; } void SyncSourceRevisions::updateRevision(ConfigNode &trackingNode, const std::string &old_luid, const std::string &new_luid, const std::string &revision) { if (!needChanges()) { return; } databaseModified(); if (old_luid != new_luid) { trackingNode.removeProperty(old_luid); } if (new_luid.empty() || revision.empty()) { throwError("need non-empty LUID and revision string"); } trackingNode.setProperty(new_luid, revision); } void SyncSourceRevisions::deleteRevision(ConfigNode &trackingNode, const std::string &luid) { if (!needChanges()) { return; } databaseModified(); trackingNode.removeProperty(luid); } void SyncSourceRevisions::sleepSinceModification() { Timespec current = Timespec::monotonic(); // Don't let this get interrupted by user abort. // It is needed for correct change tracking. while ((current - m_modTimeStamp).duration() < m_revisionAccuracySeconds) { Sleep(m_revisionAccuracySeconds - (current - m_modTimeStamp).duration()); current = Timespec::monotonic(); } } void SyncSourceRevisions::databaseModified() { m_modTimeStamp = Timespec::monotonic(); } void SyncSourceRevisions::init(SyncSourceRaw *raw, SyncSourceDelete *del, int granularity, SyncSource::Operations &ops) { m_raw = raw; m_del = del; m_revisionAccuracySeconds = granularity; m_revisionsSet = false; m_firstCycle = false; if (raw) { ops.m_backupData = boost::bind(&SyncSourceRevisions::backupData, this, _1, _2, _3); } if (raw && del) { ops.m_restoreData = boost::bind(&SyncSourceRevisions::restoreData, this, _1, _2, _3); } ops.m_endDataWrite.getPostSignal().connect(boost::bind(&SyncSourceRevisions::sleepSinceModification, this)); } std::string SyncSourceLogging::getDescription(sysync::KeyH aItemKey) { try { std::list values; BOOST_FOREACH(const std::string &field, m_fields) { SharedBuffer value; if (!getSynthesisAPI()->getValue(aItemKey, field, value) && value.size()) { values.push_back(std::string(value.get())); } } std::string description = boost::join(values, m_sep); return description; } catch (...) { // Instead of failing we log the error and ask // the caller to log the UID. That way transient // errors or errors in the logging code don't // prevent syncs. handleException(); return ""; } } std::string SyncSourceLogging::getDescription(const string &luid) { return ""; } void SyncSourceLogging::insertItemAsKey(sysync::KeyH aItemKey, sysync::ItemID newID) { std::string description = getDescription(aItemKey); SE_LOG_INFO(getDisplayName(), description.empty() ? "%s <%s>" : "%s \"%s\"", "adding", !description.empty() ? description.c_str() : "???"); } void SyncSourceLogging::updateItemAsKey(sysync::KeyH aItemKey, sysync::cItemID aID, sysync::ItemID newID) { std::string description = getDescription(aItemKey); SE_LOG_INFO(getDisplayName(), description.empty() ? "%s <%s>" : "%s \"%s\"", "updating", !description.empty() ? description.c_str() : aID ? aID->item : "???"); } void SyncSourceLogging::deleteItem(sysync::cItemID aID) { std::string description = getDescription(aID->item); SE_LOG_INFO(getDisplayName(), description.empty() ? "%s <%s>" : "%s \"%s\"", "deleting", !description.empty() ? description.c_str() : aID->item); } void SyncSourceLogging::init(const std::list &fields, const std::string &sep, SyncSource::Operations &ops) { m_fields = fields; m_sep = sep; ops.m_insertItemAsKey.getPreSignal().connect(boost::bind(&SyncSourceLogging::insertItemAsKey, this, _2, _3)); ops.m_updateItemAsKey.getPreSignal().connect(boost::bind(&SyncSourceLogging::updateItemAsKey, this, _2, _3, _4)); ops.m_deleteItem.getPreSignal().connect(boost::bind(&SyncSourceLogging::deleteItem, this, _2)); } sysync::TSyError SyncSourceAdmin::loadAdminData(const char *aLocDB, const char *aRemDB, char **adminData) { std::string data = m_configNode->readProperty(m_adminPropertyName); *adminData = StrAlloc(StringEscape::unescape(data, '!').c_str()); resetMap(); return sysync::LOCERR_OK; } sysync::TSyError SyncSourceAdmin::saveAdminData(const char *adminData) { m_configNode->setProperty(m_adminPropertyName, StringEscape::escape(adminData, '!', StringEscape::INI_VALUE)); // Flush here, because some calls to saveAdminData() happend // after SyncSourceAdmin::flush() (= session end). m_configNode->flush(); return sysync::LOCERR_OK; } bool SyncSourceAdmin::readNextMapItem(sysync::MapID mID, bool aFirst) { if (aFirst) { resetMap(); } if (m_mappingIterator != m_mapping.end()) { entry2mapid(m_mappingIterator->first, m_mappingIterator->second, mID); ++m_mappingIterator; return true; } else { return false; } } sysync::TSyError SyncSourceAdmin::insertMapItem(sysync::cMapID mID) { string key, value; mapid2entry(mID, key, value); #if 0 StringMap::iterator it = m_mapping.find(key); if (it != m_mapping.end()) { // error, exists already return sysync::DB_Forbidden; } else { m_mapping[key] = value; return sysync::LOCERR_OK; } #else m_mapping[key] = value; m_mappingNode->clear(); m_mappingNode->writeProperties(m_mapping); m_mappingNode->flush(); return sysync::LOCERR_OK; #endif } sysync::TSyError SyncSourceAdmin::updateMapItem(sysync::cMapID mID) { string key, value; mapid2entry(mID, key, value); ConfigProps::iterator it = m_mapping.find(key); if (it == m_mapping.end()) { // error, does not exist return sysync::DB_Forbidden; } else { m_mapping[key] = value; m_mappingNode->clear(); m_mappingNode->writeProperties(m_mapping); m_mappingNode->flush(); return sysync::LOCERR_OK; } } sysync::TSyError SyncSourceAdmin::deleteMapItem(sysync::cMapID mID) { string key, value; mapid2entry(mID, key, value); ConfigProps::iterator it = m_mapping.find(key); if (it == m_mapping.end()) { // error, does not exist return sysync::DB_Forbidden; } else { m_mapping.erase(it); m_mappingNode->clear(); m_mappingNode->writeProperties(m_mapping); m_mappingNode->flush(); return sysync::LOCERR_OK; } } void SyncSourceAdmin::flush() { m_configNode->flush(); if (m_mappingLoaded) { m_mappingNode->clear(); m_mappingNode->writeProperties(m_mapping); m_mappingNode->flush(); } } void SyncSourceAdmin::resetMap() { m_mapping.clear(); m_mappingNode->readProperties(m_mapping); m_mappingIterator = m_mapping.begin(); m_mappingLoaded = true; } void SyncSourceAdmin::mapid2entry(sysync::cMapID mID, string &key, string &value) { key = StringPrintf("%s-%x", StringEscape::escape(mID->localID ? mID->localID : "", '!', StringEscape::INI_WORD).c_str(), mID->ident); if (mID->remoteID && mID->remoteID[0]) { value = StringPrintf("%s %x", StringEscape::escape(mID->remoteID ? mID->remoteID : "", '!', StringEscape::INI_WORD).c_str(), mID->flags); } else { value = StringPrintf("%x", mID->flags); } } void SyncSourceAdmin::entry2mapid(const string &key, const string &value, sysync::MapID mID) { size_t found = key.rfind('-'); mID->localID = StrAlloc(StringEscape::unescape(key.substr(0,found), '!').c_str()); if (found != key.npos) { mID->ident = strtol(key.substr(found+1).c_str(), NULL, 16); } else { mID->ident = 0; } std::vector< std::string > tokens; boost::split(tokens, value, boost::is_from_range(' ', ' ')); if (tokens.size() >= 2) { // if branch from mapid2entry above mID->remoteID = StrAlloc(StringEscape::unescape(tokens[0], '!').c_str()); mID->flags = strtol(tokens[1].c_str(), NULL, 16); } else { // else branch from above mID->remoteID = NULL; mID->flags = strtol(tokens[0].c_str(), NULL, 16); } } void SyncSourceAdmin::init(SyncSource::Operations &ops, const boost::shared_ptr &config, const std::string adminPropertyName, const boost::shared_ptr &mapping) { m_configNode = config; m_adminPropertyName = adminPropertyName; m_mappingNode = mapping; m_mappingLoaded = false; ops.m_loadAdminData = boost::bind(&SyncSourceAdmin::loadAdminData, this, _1, _2, _3); ops.m_saveAdminData = boost::bind(&SyncSourceAdmin::saveAdminData, this, _1); if (mapping->isVolatile()) { // Don't provide map item operations. SynthesisDBPlugin will // tell the Synthesis engine not to call these (normally needed // for suspend/resume, which we don't support in volatile mode // because we don't store any meta data persistently). // // ops.m_readNextMapItem = boost::lambda::constant(false); // ops.m_insertMapItem = boost::lambda::constant(sysync::LOCERR_OK); // ops.m_updateMapItem = boost::lambda::constant(sysync::LOCERR_OK); // ops.m_deleteMapItem = boost::lambda::constant(sysync::LOCERR_OK); } else { ops.m_readNextMapItem = boost::bind(&SyncSourceAdmin::readNextMapItem, this, _1, _2); ops.m_insertMapItem = boost::bind(&SyncSourceAdmin::insertMapItem, this, _1); ops.m_updateMapItem = boost::bind(&SyncSourceAdmin::updateMapItem, this, _1); ops.m_deleteMapItem = boost::bind(&SyncSourceAdmin::deleteMapItem, this, _1); } ops.m_endDataWrite.getPostSignal().connect(boost::bind(&SyncSourceAdmin::flush, this)); } void SyncSourceAdmin::init(SyncSource::Operations &ops, SyncSource *source) { init(ops, source->getProperties(true), SourceAdminDataName, source->getServerNode()); } void SyncSourceBlob::init(SyncSource::Operations &ops, const std::string &dir) { m_blob.Init(getSynthesisAPI(), getName().c_str(), dir, "", "", ""); ops.m_readBlob = boost::bind(&SyncSourceBlob::readBlob, this, _1, _2, _3, _4, _5, _6, _7); ops.m_writeBlob = boost::bind(&SyncSourceBlob::writeBlob, this, _1, _2, _3, _4, _5, _6, _7); ops.m_deleteBlob = boost::bind(&SyncSourceBlob::deleteBlob, this, _1, _2); } void TestingSyncSource::removeAllItems() { // remove longest luids first: // for luid=UID[+RECURRENCE-ID] that will // remove children from a merged event first, // which is better supported by certain servers Items_t items = getAllItems(); for (Items_t::reverse_iterator it = items.rbegin(); it != items.rend(); ++it) { deleteItem(*it); } } #ifdef ENABLE_UNIT_TESTS class SyncSourceTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(SyncSourceTest); CPPUNIT_TEST(backendsAvailable); CPPUNIT_TEST_SUITE_END(); void backendsAvailable() { //We expect backendsInfo() to be empty if !ENABLE_MODULES //Otherwise, there should be at least some backends. #ifdef ENABLE_MODULES CPPUNIT_ASSERT( !SyncSource::backendsInfo().empty() ); #endif } }; SYNCEVOLUTION_TEST_SUITE_REGISTRATION(SyncSourceTest); #endif // ENABLE_UNIT_TESTS SE_END_CXX syncevolution_1.4/src/syncevo/SyncSource.h000066400000000000000000003421741230021373600211140ustar00rootroot00000000000000/* * Copyright (C) 2005-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_SYNCSOURCE #define INCL_SYNCSOURCE #include #include #include #include #include #include #include #include #include #include SE_BEGIN_CXX class SyncSource; struct SDKInterface; /** * This set of parameters always has to be passed when constructing * SyncSource instances. */ struct SyncSourceParams { /** * @param name the name needed by SyncSource * @param nodes a set of config nodes to be used by this source * @param context Additional non-source config settings. * When running as part of a normal sync, these are the * settings for the peer. When running in a local sync, * these settings come from the "target-config" peer * config inside the config context of the source. * Testing uses "target-config@client-test". On the * command line, this is the config chosen by the * user, which may or may not have peer-specific settings! * @param contextName optional name of context in which the source is defined, * needed to disambiguates "name" when sources from * different contexts are active in a sync */ SyncSourceParams(const string &name, const SyncSourceNodes &nodes, const boost::shared_ptr &context, const string &contextName = "") : m_name(name), m_nodes(nodes), m_context(context), m_contextName(contextName) {} std::string getDisplayName() const { return m_contextName.empty() ? m_name : m_contextName + "/" + m_name; } string m_name; SyncSourceNodes m_nodes; boost::shared_ptr m_context; string m_contextName; }; /** * The SyncEvolution core has no knowledge of existing SyncSource * implementations. Implementations have to register themselves * by instantiating this class exactly once with information * about themselves. * * It is also possible to add configuration options. For that define a * derived class. In its constructor use * SyncSourceConfig::getRegistry() resp. SyncConfig::getRegistry() to * define new configuration properties. The advantage of registering * them is that the user interface will automatically handle them like * the predefined ones. The namespace of these configuration options * is shared by all sources and the core. * * For properties with arbitrary names use the * SyncSourceNodes::m_trackingNode. */ class RegisterSyncSource { public: /** * Users select a SyncSource and its data format via the "type" * config property. Backends have to add this kind of function to * the SourceRegistry_t in order to be considered by the * SyncSource creation mechanism. * * The function will be called to check whether the backend was * meant by the user. It should return a new instance which will * be freed by the caller or NULL if it does not support the * selected type. * * Inactive sources should return the special InactiveSource * instance (created with InactiveSource() below) if they * recognize without a doubt that the user wanted to instantiate * them: for example, an inactive EvolutionContactSource will * return NULL for "addressbook" but InactiveSource for * "evolution-contacts". */ typedef SyncSource *(*Create_t)(const SyncSourceParams ¶ms); /** create special result of Create_t: a source which just throws errors when used */ static SyncSource *InactiveSource(const SyncSourceParams ¶ms); /** * @param shortDescr a few words identifying the data to be synchronized, * e.g. "Evolution Calendar" * @param enabled true if the sync source can be instantiated, * false if it was not enabled during compilation or is * otherwise not functional * @param create factory function for sync sources of this type * @param typeDescr multiple lines separated by \n which get appended to * the the description of the type property, e.g. * "Evolution Memos = memo = evolution-memo\n" * " plain text in UTF-8 (default) = text/plain\n" * " iCalendar 2.0 = text/calendar\n" * " The later format is not tested because none of the\n" * " supported SyncML servers accepts it.\n" * @param typeValues the config accepts multiple names for the same internal * type string; this list here is added to that list of * aliases. It should contain at least one unique string * the can be used to pick this sync source among all * SyncEvolution sync sources (testing, listing backends, ...). * Example: Values() + (Aliases("Evolution Memos") + "evolution-memo") */ RegisterSyncSource(const string &shortDescr, bool enabled, Create_t create, const string &typeDescr, const Values &typeValues); public: const string m_shortDescr; const bool m_enabled; const Create_t m_create; const string m_typeDescr; const Values m_typeValues; }; typedef list SourceRegistry; class ClientTest; class TestingSyncSource; /** * Information about a data source. For the sake of simplicity all * items pointed to are owned by the ClientTest and must * remain valid throughout a test session. Not setting a pointer * is okay, but it will disable all tests that need the * information. */ struct ClientTestConfig { /** * The name is used in test names and has to be set. */ std::string m_sourceName; /** * A default URI to be used when creating a client config. */ std::string m_uri; /** * A corresponding source name in the default server template, * this is used to copy corresponding uri set in the server template * instead of the uri field above (which is the same for all servers). */ std::string m_sourceNameServerTemplate; /** * A member function of a subclass which is called to create a * sync source referencing the data. This is used in tests of * the SyncSource API itself as well as in tests which need to * modify or check the data sources used during synchronization. * * The test framework will call beginSync() and then some of * the functions it wants to test. After a successful test it * will call endSync() which is then expected to store all * changes persistently. Creating a sync source again * with the same call should not report any * new/updated/deleted items until such changes are made via * another sync source. * * The instance will be deleted by the caller. Because this * may be in the error case or in an exception handler, * the sync source's desctructor should not thow exceptions. * * @param client the same instance to which this config belongs * @param clientID the unique ID of the client, "1" resp. "2" in practice (can also be obtained as * client->getClientID(), but not all implementers have (or want) access to the * class definition) * @param source index of the data source (from 0 to ClientTest::getNumSources() - 1) * @param isSourceA true if the requested SyncSource is the first one accessing that * data, otherwise the second */ typedef boost::function createsource_t; /** * Creates a sync source which references the primary database; * it may report the same changes as the sync source used during * sync tests. */ createsource_t m_createSourceA; /** * A second sync source also referencing the primary data * source, but configured so that it tracks changes * independently from the the primary sync source. * * In local tests the usage is like this: * - add item via first SyncSource * - iterate over new items in second SyncSource * - check that it lists the added item * * In tests with a server the usage is: * - do a synchronization with the server * - iterate over items in second SyncSource * - check that the total number and number of * added/updated/deleted items is as expected */ createsource_t m_createSourceB; /** * The framework can generate vCard and vCalendar/iCalendar items * automatically by copying a template item and modifying certain * properties. * * This is the template for these automatically generated items. * It must contain the string <> which will be replaced * with the revision parameter of the createItem() method. */ std::string m_templateItem; /** * This is a colon (:) separated list of properties which need * to be modified in templateItem. */ std::string m_uniqueProperties; /** * This is a single property in templateItem which can be extended * to increase the size of generated items. */ std::string m_sizeProperty; /** * Type to be set when importing any of the items into the * corresponding sync sources. Use "" if sync source doesn't * need this information. * * Not currently used! All items are assumed to be in the raw, * internal format (see SyncSourceRaw and SyncSourceSerialize). */ std::string m_itemType; /** * callback which is invoked with a specific item as paramter * to do data type specific conversions before actually * using the test item; default is a NOP function * * @param update modify item content so that it can be * used as an update of the old data * @param uniqueUIDSuffix beyond not reusing UIDs between tests, also embed this suffix * in test items inside the running test to avoid reuse * if necessary (Google CalDAV + UID/SEQUENCE => 409 error) */ boost::function m_mangleItem; /** * A very simple item that is inserted during basic tests. Ideally * it only contains properties supported by all servers. */ std::string m_insertItem; /** * A slightly modified version of insertItem. If the source has UIDs * embedded into the item data, then both must have the same UID. * Again all servers should better support these modified properties. */ std::string m_updateItem; /** * A more heavily modified version of insertItem. Same UID if necessary, * but can test changes to items only supported by more advanced * servers. */ std::string m_complexUpdateItem; /** * To test merge conflicts two different updates of insertItem are * needed. This is the first such update. */ std::string m_mergeItem1; /** * The second merge update item. To avoid true conflicts it should * update different properties than mergeItem1, but even then servers * usually have problems perfectly merging items. Therefore the * test is run without expecting a certain merge result. */ std::string m_mergeItem2; /** * The items in the inner vector are related: the first one the is * main one, the other(s) is/are a subordinate ones. The semantic * is that the main item is complete on it its own, while the * other normally should only be used in combination with the main * one. * * Because SyncML cannot express such dependencies between items, * a SyncSource has to be able to insert, updated and remove * both items independently. However, operations which violate * the semantic of the related items (like deleting the parent, but * not the child) may have unspecified results (like also deleting * the child). See linkedItemsRelaxedSemantic and sourceKnowsItemSemantic. * * One example for main and subordinate items are a recurring * iCalendar 2.0 event and a detached recurrence. */ typedef class LinkedItems : public std::vector { public: std::string m_name; /**< used as Client::Source::LinkedItems */ StringMap m_options; /**< used to pass additional parameters to the test */ /** for testLinkedItemsSubset: create the additional VEVENT that is added when talking to Exchange; parameters are start, skip, index and total number of items in that test */ boost::function m_testLinkedItemsSubsetAdditional; } LinkedItems_t; /** * The linked items may exist in different variations (outer vector). */ typedef std::vector MultipleLinkedItems_t; MultipleLinkedItems_t m_linkedItems; /** * Another set of linked items for the LinkedItems*::testItemsAll/Second/Third/... tests. */ MultipleLinkedItems_t m_linkedItemsSubset; /** * Backends atomic modification tests */ Bool m_atomicModification; /** * set to false to disable tests which slightly violate the * semantic of linked items by inserting children * before/without their parent */ Bool m_linkedItemsRelaxedSemantic; /** * setting this to false disables tests which depend * on the source's support for linked item semantic * (testLinkedItemsInsertParentTwice, testLinkedItemsInsertChildTwice) * * Matches SynthesisInfo::m_globalIDs. */ Bool m_sourceKnowsItemSemantic; /** * Set this to true if the backend does not have IDs which are the * same for all clients and across slow syncs. For example, when * testing the ActiveSync backend this field needs to be true, * because items are renumbered as 1:x with x = 1, 2, ... for each * clients when a sync anchor is assigned to it. */ Bool m_sourceLUIDsAreVolatile; /** * Set this to true if the backend supports * X-SYNCEVOLUTION-EXDATE-DETACHED, see CalDAVSource.cpp * CalDAVSource::readSubItem(). */ Bool m_supportsReccurenceEXDates; /** * called to dump all items into a file, required by tests which need * to compare items * * ClientTest::dump can be used: it will simply dump all items of the source * with a blank line as separator. * * @param source sync source A already created and with beginSync() called * @param file a file name * @return error code, 0 for success */ boost::function m_dump; /** * import test items: which these are is determined entirely by * the implementor, but tests work best if several complex items are * imported * * ClientTest::import can be used if the file contains items separated by * empty lines. * * @param source sync source A already created and with beginSync() called * @param file the name of the file to import * @retval realfile the name of the file that was really imported; * this may depend on the current server that is being tested * @param luids optional; if empty, then fill with luids (empty string for failed items); * if not empty, then update instead of adding the items * @return error string, empty for success */ boost::function *)> m_import; /** * a function which compares two files with items in the format used by "dump" * * @param fileA first file name * @param fileB second file name * @return true if the content of the files is considered equal */ boost::function m_compare; /** * A file with test cases in the format expected by import and compare. * The file should contain data as supported by the local storage. * * It is used in "Source::*::testImport" test, which verifies that * the backend can import and export that data. * * It is also used in "Sync::*::testItems", which verifies that * the peer can store and export it. Often local extensions are * not supported by peers. This can be handled in different ways: * - Patch synccompare to ignore such changes on a per-peer basis. * - Create a ..tem file in the src/testcases * build directory where is the string here ("eds_event.ics"), * and the value of CLIENT_TEST_SERVER ("funambol"). * That file then will be used in testItems instead of the base * version. See the src/Makefile.am for rules that maintain such files. */ std::string m_testcases; /** * the item type normally used by the source (not used by the tests * themselves; client-test.cpp uses it to initialize source configs) */ std::string m_type; /** * a list of sub configs separated via , if this is a super datastore */ std::string m_subConfigs; /** * TRUE if the source supports recovery from an interrupted * synchronization. Enables the Client::Sync::*::Retry group * of tests. */ Bool m_retrySync; Bool m_suspendSync; Bool m_resendSync; /** * Set this to a list of properties which must *not* be removed * from the test items. Leave empty to disable the testRemoveProperties * test. Test items must be in vCard 3.0/iCalendar 2.0 format. */ std::set m_essentialProperties; /** * Set this to test if the source supports preserving local data extensions. * Uses the "testcases" data. See Sync::*::testExtensions. * * The function must modify a single item such that re-importing * it locally will be seen as updating it. It is empty by default * because not all backends necessarily pass this test. * * genericUpdate works for vCard and iCalendar by updating FN, N, resp. SUMMARY * and can be used as implementation of update. */ boost::function m_update; boost::function m_genericUpdate; /** * A list of m_sourceName values of other ClientTestConfigs * which share the same database. Normally, sources are tested in * isolation, but for such linked sources we also need to test * interdependencies, in particular regarding change tracking and * item listing. */ std::list m_linkedSources; }; /** * In addition to registering the sync source itself by creating an * instance of RegisterSyncSource, configurations for testing it can * also be registered. A sync source which supports more than one data * exchange format can register one configuration for each format, but * not registering any configuration is also okay. * * *Using* the registered tests depends on the CPPUnit test framework. * *Registering* does not. Therefore backends should always register * * *themselves for testing and leave it to the test runner * "client-test" whether tests are really executed. * * Unit tests are different. They create hard dependencies on CPPUnit * inside the code that contains them, and thus should be encapsulated * inside #ifdef ENABLE_UNIT_TESTS checks. * * Sync sources have to work stand-alone without a full SyncClient * configuration for all local tests. The minimal configuration prepared * for the source includes: * - a tracking node (as used f.i. by TrackingSyncSource) which * points towards "~/.config/syncevolution/client-test-changes" * - a unique change ID (as used f.i. by EvolutionContactSource) * - a valid "evolutionsource" property in the config node, starting * with the CLIENT_TEST_EVOLUTION_PREFIX env variable or (if that * wasn't set) the "SyncEvolution_Test_" prefix * - "evolutionuser/password" if CLIENT_TEST_EVOLUTION_USER/PASSWORD * are set * * No other properties are set, which implies that currently sync sources * which require further parameters cannot be tested. * * @warning There is a potential problem with the registration * mechanism. Both the sync source tests as well as the CPPUnit tests * derived from them are registrered when global class instances are * initialized. If the RegisterTestEvolution instance in * client-test-app.cpp is initialized *before* the sync source tests, * then those won't show up in the test list. Currently the right * order seems to be used, so everything works as expected. */ class RegisterSyncSourceTest { public: /** * Invoked after all global constructors are run. * May add further RegisterSyncSourceTests to the * global registry. */ virtual void init() const {} /** * This call is invoked after setting up the config with default * values for the test cases selected via the constructor's * testCaseName parameter (one of eds_contact, eds_contact, eds_event, eds_task; * see ClientTest in the Funambol client library for the current * list). * * This call can then override any of the values or (if there * are no predefined test cases) add them. * * The "type" property must select your sync source and the * data format for the test. * * @retval config change any field whose default is not suitable */ virtual void updateConfig(ClientTestConfig &config) const = 0; /** * @param configName a unique string: the predefined names known by * ClientTest::getTestData() are already used for the initial * set of Evolution sync sources, for new sync sources * build a string by combining them with the sync source name * (e.g., "sqlite_eds_contact") * @param testCaseName a string recognized by ClientTest::getTestData() or an * empty string if there are no predefined test cases */ RegisterSyncSourceTest(const string &configName, const string &testCaseName); virtual ~RegisterSyncSourceTest() {} const string m_configName; const string m_testCaseName; /** * A list of m_configName values of other RegisterSyncSourceTest * which share the same database. Normally, sources are tested in * isolation, but for such linked sources we also need to test * interdependencies, in particular regarding change tracking and * item listing. */ std::list m_linkedSources; }; class TestRegistry : public vector { public: // TODO: using const RegisterSyncSourceTest * operator [] (int); const RegisterSyncSourceTest * operator [] (const string &configName) const { BOOST_FOREACH(const RegisterSyncSourceTest *test, *this) { if (test->m_configName == configName) { return test; } } throw out_of_range(string("test config registry: ") + configName); return NULL; } }; /** * a container for Synthesis XML config fragments * * Backends can define their own field lists, profiles, datatypes and * remote rules. The name of each of these entities have to be unique: * either prefix each name with the name of the backend or coordinate * with other developers (e.g. regarding shared field lists). * * To add new items, add them to the respective hash in your backend's * getDatastoreXML() or getSynthesisInfo() implementation. Both * methods have default implementations: getSynthesisInfo() is called * by the default getDatastoreXML() to provide some details and * provides them based on the "type" configuration option. * * The default config XML contains several predefined items: * - field lists: contacts, calendar, Note, bookmarks * - profiles: vCard, vCalendar, Note, vBookmark * - datatypes: vCard21, vCard30, vCalendar10, iCalendar20, * note10/11 (no difference except the versioning!), * vBookmark10 * - remote rule: EVOLUTION * * These items do not appear in the hashes, so avoid picking the same * names. The entries of each hash has to be a well-formed XML * element, their keys the name encoded in each XML element. */ struct XMLConfigFragments { class mapping : public std::map { public: string join() { string res; size_t len = 0; BOOST_FOREACH(const value_type &entry, *this) { len += entry.second.size() + 1; } res.reserve(len); BOOST_FOREACH(const value_type &entry, *this) { res += entry.second; res += "\n"; } return res; } } m_fieldlists, m_profiles, m_datatypes, m_remoterules; }; /** * used in SyncSource::Operations post-operation signal */ enum OperationExecution { OPERATION_SKIPPED, /**< operation was skipped because pre-operation slot threw an exception */ OPERATION_EXCEPTION, /**< operation itself failed with an exception (may also return error code) */ OPERATION_FINISHED, /**< operation finished normally (but might have returned an error code) */ OPERATION_EMPTY /**< operation not implemented */ }; /** * Implements the "call all slots, error if any failed" semantic of * the pre- and post-signals described below. */ class OperationSlotInvoker { public: typedef sysync::TSyError result_type; template result_type operator() (InputIterator first, InputIterator last) const { result_type res = sysync::LOCERR_OK; while (first != last) { try { *first; } catch (...) { SyncMLStatus status = Exception::handle(); if (res == sysync::LOCERR_OK) { res = static_cast(status); } } ++first; } return res; } }; /** * Helper class for looking up a pending operation by a Synthesis parameter. * KeyH (add, replace) and item ID (delete) are supported. */ template struct KeyConverter; /** * For KeyH we make the assumption that the key exists as long * as the pending operation, and thus its address can be used as * unique identifier for the operation. */ template<> struct KeyConverter { typedef void * key_type; static key_type toKey(sysync::KeyH key) { return static_cast(key); } }; /** * For cItemID we just use the item ID as string. */ template<> struct KeyConverter { typedef std::string key_type; static key_type toKey(sysync::cItemID id) { return id->item; } }; /** * To be returned by a function wrapped by OperationWrapper * when the function is not done yet and wants to be called again * for the same item. */ template class ContinueOperation : public boost::function { public: ContinueOperation() {} ContinueOperation(const boost::function &callback) : boost::function(callback) {} }; /** * Helper class, needs to be specialized based on number of parameters * and return type. */ template class OperationWrapperSwitch; /** one parameter, sysync::TSyError type */ template class OperationWrapperSwitch { public: typedef sysync::TSyError result_type; typedef boost::function OperationType; /** * The pre-signal is invoked with the same parameters as * the operations, plus a reference to the sync source as * initial parameter. Slots may throw exceptions, which * will skip the actual implementation. However, all slots * will be invoked exactly once even if one of them throws * an exception. The result of the operation then is the * error code extracted from the first exception (see * OperationSlotInvoker). */ typedef boost::signals2::signal PreSignal; /** * The post-signal is invoked exactly once, regardless * whether the implementation was skipped, executed or * doesn't exist at all. This information is passed as the * second parameter, followed by the result of the * operation or the pre-signals, followed by the * parameters of the operation. * * As with the pre-signal, any slot may throw an exception * to override the final result, but this won't interrupt * calling the rest of the slots. */ typedef boost::signals2::signal PostSignal; /** * invokes signals and implementation of operation, * combines all return codes into one */ sysync::TSyError operator () (SyncSource &source) const throw () { sysync::TSyError res; OperationExecution exec; res = m_pre(source); if (res != sysync::LOCERR_OK) { exec = OPERATION_SKIPPED; } else { if (m_operation) { try { res = m_operation(); exec = OPERATION_FINISHED; } catch (...) { res = Exception::handle(/* source */); exec = OPERATION_EXCEPTION; } } else { res = sysync::LOCERR_NOTIMP; exec = OPERATION_EMPTY; } } sysync::TSyError newres = m_post(source, exec, res); if (newres != sysync::LOCERR_OK) { res = newres; } return res == STATUS_FATAL ? STATUS_DATASTORE_FAILURE : res; } /** * Anyone may connect code to the signals via * getOperations().getPre/PostSignal(), although strictly * speaking this modifies the behavior of the * implementation. */ PreSignal &getPreSignal() const { return const_cast(m_pre); } PostSignal &getPostSignal() const { return const_cast(m_post); } protected: OperationType m_operation; private: PreSignal m_pre; PostSignal m_post; }; template class OperationWrapperSwitch { public: typedef sysync::TSyError result_type; typedef boost::function OperationType; typedef typename boost::function::arg1_type arg1_type; typedef boost::signals2::signal PreSignal; typedef boost::signals2::signal PostSignal; sysync::TSyError operator () (SyncSource &source, arg1_type a1) const throw () { sysync::TSyError res; OperationExecution exec; res = m_pre(source, a1); if (res != sysync::LOCERR_OK) { exec = OPERATION_SKIPPED; } else { if (m_operation) { try { res = m_operation(a1); exec = OPERATION_FINISHED; } catch (...) { res = Exception::handle(/* source */); exec = OPERATION_EXCEPTION; } } else { res = sysync::LOCERR_NOTIMP; exec = OPERATION_EMPTY; } } sysync::TSyError newres = m_post(source, exec, res, a1); if (newres != sysync::LOCERR_OK) { res = newres; } return res == STATUS_FATAL ? STATUS_DATASTORE_FAILURE : res; } PreSignal &getPreSignal() const { return const_cast(m_pre); } PostSignal &getPostSignal() const { return const_cast(m_post); } protected: OperationType m_operation; private: PreSignal m_pre; PostSignal m_post; }; template class OperationWrapperSwitch { public: typedef sysync::TSyError result_type; typedef boost::function OperationType; typedef typename boost::function::arg1_type arg1_type; typedef boost::signals2::signal PreSignal; typedef boost::signals2::signal PostSignal; typedef KeyConverter Converter; typedef ContinueOperation Continue; typedef std::map Pending; sysync::TSyError operator () (SyncSource &source, arg1_type a1) const throw () { sysync::TSyError res; OperationExecution exec; // Marking m_pending "volatile" didn't work, find() not defined for that. typename Pending::iterator it = const_cast(m_pending).find(Converter::toKey(a1)); bool continuing = it != m_pending.end(); res = continuing ? sysync::LOCERR_OK : m_pre(source, a1); if (res != sysync::LOCERR_OK) { exec = OPERATION_SKIPPED; } else { if (continuing) { res = it->second(a1); if (res != sysync::LOCERR_AGAIN) { const_cast(m_pending).erase(it); } } else if (m_operation) { try { V newres = m_operation(a1); sysync::TSyError *completed = boost::get(&newres); if (completed) { res = *completed; } else { res = sysync::LOCERR_AGAIN; Continue cont = boost::get(newres); const_cast(m_pending).insert(std::make_pair(Converter::toKey(a1), cont)); } exec = OPERATION_FINISHED; } catch (...) { res = Exception::handle(/* source */); exec = OPERATION_EXCEPTION; } } else { res = sysync::LOCERR_NOTIMP; exec = OPERATION_EMPTY; } } if (res != sysync::LOCERR_AGAIN) { sysync::TSyError newres = m_post(source, exec, res, a1); if (newres != sysync::LOCERR_OK) { res = newres; } } return res == STATUS_FATAL ? STATUS_DATASTORE_FAILURE : res; } PreSignal &getPreSignal() const { return const_cast(m_pre); } PostSignal &getPostSignal() const { return const_cast(m_post); } protected: OperationType m_operation; private: PreSignal m_pre; PostSignal m_post; Pending m_pending; }; template class OperationWrapperSwitch { public: typedef sysync::TSyError result_type; typedef boost::function OperationType; typedef typename boost::function::arg1_type arg1_type; typedef typename boost::function::arg2_type arg2_type; typedef boost::signals2::signal PreSignal; typedef boost::signals2::signal PostSignal; sysync::TSyError operator () (SyncSource &source, arg1_type a1, arg2_type a2) const throw () { sysync::TSyError res; OperationExecution exec; res = m_pre(source, a1, a2); if (res != sysync::LOCERR_OK) { exec = OPERATION_SKIPPED; } else { if (m_operation) { try { res = m_operation(a1, a2); exec = OPERATION_FINISHED; } catch (...) { res = Exception::handle(/* source */); exec = OPERATION_EXCEPTION; } } else { res = sysync::LOCERR_NOTIMP; exec = OPERATION_EMPTY; } } sysync::TSyError newres = m_post(source, exec, res, a1, a2); if (newres != sysync::LOCERR_OK) { res = newres; } return res == STATUS_FATAL ? STATUS_DATASTORE_FAILURE : res; } PreSignal &getPreSignal() const { return const_cast(m_pre); } PostSignal &getPostSignal() const { return const_cast(m_post); } protected: OperationType m_operation; private: PreSignal m_pre; PostSignal m_post; }; template class OperationWrapperSwitch { public: typedef sysync::TSyError result_type; typedef boost::function OperationType; typedef typename boost::function::arg1_type arg1_type; typedef typename boost::function::arg2_type arg2_type; typedef boost::signals2::signal PreSignal; typedef boost::signals2::signal PostSignal; typedef KeyConverter Converter; typedef ContinueOperation Continue; typedef std::map Pending; sysync::TSyError operator () (SyncSource &source, arg1_type a1, arg2_type a2) const throw () { sysync::TSyError res; OperationExecution exec; // Marking m_pending "volatile" didn't work, find() not defined for that. typename Pending::iterator it = const_cast(m_pending).find(Converter::toKey(a1)); bool continuing = it != m_pending.end(); res = continuing ? sysync::LOCERR_OK : m_pre(source, a1, a2); if (res != sysync::LOCERR_OK) { exec = OPERATION_SKIPPED; } else { if (continuing) { res = it->second(a1, a2); if (res != sysync::LOCERR_AGAIN) { const_cast(m_pending).erase(it); } } else if (m_operation) { try { V newres = m_operation(a1, a2); sysync::TSyError *completed = boost::get(&newres); if (completed) { res = *completed; } else { res = sysync::LOCERR_AGAIN; Continue cont = boost::get(newres); const_cast(m_pending).insert(std::make_pair(Converter::toKey(a1), cont)); } exec = OPERATION_FINISHED; } catch (...) { res = Exception::handle(/* source */); exec = OPERATION_EXCEPTION; } } else { res = sysync::LOCERR_NOTIMP; exec = OPERATION_EMPTY; } } if (res != sysync::LOCERR_AGAIN) { sysync::TSyError newres = m_post(source, exec, res, a1, a2); if (newres != sysync::LOCERR_OK) { res = newres; } } return res == STATUS_FATAL ? STATUS_DATASTORE_FAILURE : res; } PreSignal &getPreSignal() const { return const_cast(m_pre); } PostSignal &getPostSignal() const { return const_cast(m_post); } protected: OperationType m_operation; private: PreSignal m_pre; PostSignal m_post; Pending m_pending; }; template class OperationWrapperSwitch { public: typedef sysync::TSyError result_type; typedef boost::function OperationType; typedef typename boost::function::arg1_type arg1_type; typedef typename boost::function::arg2_type arg2_type; typedef typename boost::function::arg3_type arg3_type; typedef boost::signals2::signal PreSignal; typedef boost::signals2::signal PostSignal; sysync::TSyError operator () (SyncSource &source, arg1_type a1, arg2_type a2, arg3_type a3) const throw () { sysync::TSyError res; OperationExecution exec; res = m_pre(source, a1, a2, a3); if (res != sysync::LOCERR_OK) { exec = OPERATION_SKIPPED; } else { if (m_operation) { try { res = m_operation(a1, a2, a3); exec = OPERATION_FINISHED; } catch (...) { res = Exception::handle(/* source */); exec = OPERATION_EXCEPTION; } } else { res = sysync::LOCERR_NOTIMP; exec = OPERATION_EMPTY; } } sysync::TSyError newres = m_post(source, exec, res, a1, a2, a3); if (newres != sysync::LOCERR_OK) { res = newres; } return res == STATUS_FATAL ? STATUS_DATASTORE_FAILURE : res; } PreSignal &getPreSignal() const { return const_cast(m_pre); } PostSignal &getPostSignal() const { return const_cast(m_post); } protected: OperationType m_operation; private: PreSignal m_pre; PostSignal m_post; }; template class OperationWrapperSwitch { public: typedef sysync::TSyError result_type; typedef boost::function OperationType; typedef typename boost::function::arg1_type arg1_type; typedef typename boost::function::arg2_type arg2_type; typedef typename boost::function::arg3_type arg3_type; typedef boost::signals2::signal PreSignal; typedef boost::signals2::signal PostSignal; typedef KeyConverter Converter; typedef ContinueOperation Continue; typedef std::map Pending; sysync::TSyError operator () (SyncSource &source, arg1_type a1, arg2_type a2, arg3_type a3) const throw () { sysync::TSyError res; OperationExecution exec; // Marking m_pending "volatile" didn't work, find() not defined for that. typename Pending::iterator it = const_cast(m_pending).find(Converter::toKey(a1)); bool continuing = it != m_pending.end(); res = continuing ? sysync::LOCERR_OK : m_pre(source, a1, a2, a3); if (res != sysync::LOCERR_OK) { exec = OPERATION_SKIPPED; } else { if (continuing) { res = it->second(a1, a2, a3); if (res != sysync::LOCERR_AGAIN) { const_cast(m_pending).erase(it); } } else if (m_operation) { try { V newres = m_operation(a1, a2, a3); sysync::TSyError *completed = boost::get(&newres); if (completed) { res = *completed; } else { res = sysync::LOCERR_AGAIN; Continue cont = boost::get(newres); const_cast(m_pending).insert(std::make_pair(Converter::toKey(a1), cont)); } exec = OPERATION_FINISHED; } catch (...) { res = Exception::handle(/* source */); exec = OPERATION_EXCEPTION; } } else { res = sysync::LOCERR_NOTIMP; exec = OPERATION_EMPTY; } } if (res != sysync::LOCERR_AGAIN) { sysync::TSyError newres = m_post(source, exec, res, a1, a2, a3); if (newres != sysync::LOCERR_OK) { res = newres; } } return res == STATUS_FATAL ? STATUS_DATASTORE_FAILURE : res; } PreSignal &getPreSignal() const { return const_cast(m_pre); } PostSignal &getPostSignal() const { return const_cast(m_post); } protected: OperationType m_operation; private: PreSignal m_pre; PostSignal m_post; Pending m_pending; }; /** * This mimics a boost::function with the same signature. The function * signature F must have a sysync::TSyError error return code, as in most * of the Synthesis DB API, or a boost::variant of sysync::TSyError and * ContinueOperation. * * Specializations of this class for operations with different number * of parameters provide a call operator which invokes a pre- and * post-signal around the actual implementation. See * OperationWrapperSwitch for details. * * If the function returns a variant with a ContinueOperation inside, * the OperationWrapper will store that callback for the current * set of parameters (currently using only the first one as key), * then when called again, skip the pre-signal and invoke the callback * instead of the original operation. That callback may return LOCERR_AGAIN * to request being called again the same way. The post-signal is * called when the operation finally completes. * * Additional operations could be wrapped by providing more * specializations (different return code, more parameters). The * number or parameters in the operation cannot exceed six, because * adding three more parameters in the post-signal would push the * total number of parameters in that signal beyond the limit of nine * supported arguments in boost::signals2/boost::function. */ template class OperationWrapper : public OperationWrapperSwitch::arity, typename boost::function::result_type> { typedef OperationWrapperSwitch::arity, typename boost::function::result_type> inherited; public: /** operation implemented? */ operator bool () const { return inherited::m_operation; } /** * Only usable by derived classes via read/write m_operations: * sets the actual implementation of the operation. */ void operator = (const boost::function &operation) { inherited::m_operation = operation; } }; /** * abstract base class for SyncSource with some common functionality * and no data * * Used to implement and call that functionality in multiple derived * classes, including situations where a derived class is derived from * this base via different intermediate classes, therefore the * need to keep it abstract. */ class SyncSourceBase { public: virtual ~SyncSourceBase() {} /** * the name of the sync source (for example, "addressbook"), * unique in the context of its own configuration **/ virtual std::string getName() const { return "uninitialized SyncSourceBase"; } /** * the name of the sync source as it should be displayed to users * in debug messages; typically the same as getName(), but may * also include a context ("@foobar/addressbook") to disambiguate * the name when "addressbook" is used multiple times in a sync (as * with local sync) */ virtual std::string getDisplayName() const { return "uninitialized SyncSourceBase"; } /** * Convenience function, to be called inside a catch() block of * (or for) the sync source. * * Rethrows the exception to determine what it is, then logs it * as an error and returns a suitable error code (usually a general * STATUS_DATASTORE_FAILURE). * * @param flags influence behavior of the method */ SyncMLStatus handleException(HandleExceptionFlags flags = HANDLE_EXCEPTION_FLAGS_NONE); /** * throw an exception after an operation failed * * output format: : : * * @param action a string describing the operation or object involved * @param error the errno error code for the failure */ void throwError(const string &action, int error) SE_NORETURN; /** * throw an exception after an operation failed and * remember that this instance has failed * * output format: : * * @param action a string describing what was attempted *and* how it failed */ void throwError(const string &failure) SE_NORETURN; /** * throw an exception with a specific status code after an operation failed and * remember that this instance has failed * * output format: : * * @param status a more specific status code; other throwError() variants use STATUS_FATAL * @param action a string describing what was attempted *and* how it failed */ void throwError(SyncMLStatus status, const string &failure) SE_NORETURN; /** * The Synthesis engine only counts items which are deleted by the * peer. Items deleted locally at the start of a * refresh-from-server sync are not counted (and cannot be counted * in all cases). * * Sync sources which want to have those items included in the * sync statistics should count *all* deleted items using these * methods. SyncContext will use this number for * refresh-from-server syncs. */ /**@{*/ virtual long getNumDeleted() const = 0; virtual void setNumDeleted(long num) = 0; virtual void incrementNumDeleted() = 0; /**@}*/ /** * Return Synthesis XML fragment for this sync source. * Must *not* include the element; it is created by * the caller. * * The default implementation returns a configuration for the * SynthesisDBPlugin, which invokes SyncSource::Operations. Items * are exchanged with the SyncsSource in the format defined by * getSynthesisInfo(). The format used with the SyncML side is * negotiated via the peer's capabilities, with the type defined * in the configuration being the preferred one of the data store. * * See SyncContext::getConfigXML() for details about * predefined entries that can be referenced here. * * @retval xml put content of ... here * @retval fragments the necessary definitions for the datastore have to be added here */ virtual void getDatastoreXML(string &xml, XMLConfigFragments &fragments); /** * Synthesis name which matches the format used * for importing and exporting items (exportData()). * This is not necessarily the same format that is given * to the Synthesis engine. If this internal format doesn't * have a in the engine, then an empty string is * returned. */ virtual string getNativeDatatypeName(); /** * return Synthesis API pointer, if one currently is available * (between SyncEvolution_Module_CreateContext() and * SyncEvolution_Module_DeleteContext()) */ virtual SDKInterface *getSynthesisAPI() const = 0; /** * Prepare the sync source for usage inside a SyncML server. To * be called directly after creating the source, if at all. */ virtual void enableServerMode() = 0; virtual bool serverModeEnabled() const = 0; /** * The optional operations. * * All of them are guaranteed to happen between open() and * close(). * * They are all allowed to throw exceptions: the operations called * by SyncEvolution then abort whatever SyncEvolution was doing * and end in the normal exception handling. For the Synthesis * operations, the bridge code in SynthesisDBPlugin code catches * exceptions, logs them and translates them into Synthesis error * codes, which are returned to the Synthesis engine. * * Monitoring of most DB operations is possible via the pre- and * post-signals managed by OperationWrapper. */ struct Operations { /** * The caller determines where item data is stored (m_dirname) * and where meta information about them (m_node). The callee * then can use both arbitrarily. As an additional hint, * m_mode specifies why and when the backup is made, which * is useful to determine whether information can be reused. */ struct BackupInfo { enum Mode { BACKUP_BEFORE, /**< directly at start of sync */ BACKUP_AFTER, /**< directly after sync */ BACKUP_OTHER } m_mode; string m_dirname; boost::shared_ptr m_node; BackupInfo() {} BackupInfo(Mode mode, const string &dirname, const boost::shared_ptr &node) : m_mode(mode), m_dirname(dirname), m_node(node) {} }; struct ConstBackupInfo { BackupInfo::Mode m_mode; string m_dirname; boost::shared_ptr m_node; ConstBackupInfo() {} ConstBackupInfo(BackupInfo::Mode mode, const string &dirname, const boost::shared_ptr &node) : m_mode(mode), m_dirname(dirname), m_node(node) {} }; /** * Dump all data from source unmodified into the given backup location. * Information about the created backup is added to the * report. * * Required for the backup/restore functionality in * SyncEvolution, not for syncing itself. But typically it is * called before syncing (can be turned off by users), so * implementations can reuse the information gathered while * making a backup in later operations. * * @param previous the most recent backup, empty m_dirname if none * @param next the backup which is to be created, directory and node are empty * @param report to be filled with information about backup (number of items, etc.) */ typedef void (BackupData_t)(const ConstBackupInfo &oldBackup, const BackupInfo &newBackup, BackupReport &report); boost::function m_backupData; /** * Restore database from data stored in backupData(). * If possible don't touch items which are the same as in the * backup, to mimimize impact on future incremental syncs. * * @param oldBackup the backup which is to be restored * @param dryrun pretend to restore and fill in report, without * actually touching backend data * @param report to be filled with information about restore * (number of total items and changes) */ typedef void (RestoreData_t)(const ConstBackupInfo &oldBackup, bool dryrun, SyncSourceReport &report); boost::function m_restoreData; /** * initialize information about local changes and items * as in beginSync() with all parameters set to true, * but without changing the state of the underlying database * * This method will be called to check for local changes without * actually running a sync, so there is no matching end call. * * There might be sources which don't support non-destructive * change tracking (in other words, checking changes permanently * modifies the state of the source and cannot be repeated). * Such sources should leave the functor empty. */ typedef void (CheckStatus_t)(SyncSourceReport &local); boost::function m_checkStatus; /** * A quick check whether the source currently has data. * * If this cannot be determined easily, don't provide the * operation. The information is currently only used to * determine whether a slow sync should be allowed. If * the operation is not provided, the assumption is that * there is local data, which disables the "allow slow * sync for empty databases" heuristic and forces the user * to choose. */ typedef bool (IsEmpty_t)(); boost::function m_isEmpty; /** * Synthesis DB API callbacks. For documentation see the * Synthesis API specification (PDF and/or sync_dbapi.h). * * Implementing this is necessary for SyncSources which want * to be part of a sync session. */ /**@{*/ typedef OperationWrapper StartDataRead_t; StartDataRead_t m_startDataRead; typedef OperationWrapper EndDataRead_t; EndDataRead_t m_endDataRead; typedef OperationWrapper StartDataWrite_t; StartDataWrite_t m_startDataWrite; typedef OperationWrapper FinalizeLocalID_t; FinalizeLocalID_t m_finalizeLocalID; typedef OperationWrapper EndDataWrite_t; EndDataWrite_t m_endDataWrite; /** the SynthesisDBPlugin is configured so that this operation doesn't have to (and cannot) return the item data */ typedef OperationWrapper ReadNextItem_t; ReadNextItem_t m_readNextItem; typedef OperationWrapper ReadItemAsKey_t; ReadItemAsKey_t m_readItemAsKey; typedef ContinueOperation InsertItemAsKeyContinue_t; typedef boost::variant InsertItemAsKeyResult_t; typedef OperationWrapper InsertItemAsKey_t; InsertItemAsKey_t m_insertItemAsKey; typedef ContinueOperation UpdateItemAsKeyContinue_t; typedef boost::variant UpdateItemAsKeyResult_t; typedef OperationWrapper UpdateItemAsKey_t; UpdateItemAsKey_t m_updateItemAsKey; typedef ContinueOperation DeleteItemContinue_t; typedef boost::variant DeleteItemResult_t; typedef OperationWrapper DeleteItem_t; DeleteItem_t m_deleteItem; /**@}*/ /** * Synthesis administration callbacks. For documentation see the * Synthesis API specification (PDF and/or sync_dbapi.h). * * Implementing this is *optional* in clients. In the Synthesis client * engine, the "binfiles" module provides these calls without SyncEvolution * doing anything. * * In the Synthesis server engine, the * SyncSource::enableServerMode() call must install an * implementation, like the one from SyncSourceAdmin. */ /**@{*/ typedef OperationWrapper LoadAdminData_t; LoadAdminData_t m_loadAdminData; typedef OperationWrapper SaveAdminData_t; SaveAdminData_t m_saveAdminData; // not currently wrapped because it has a different return type; // templates could be adapted to handle that typedef bool (ReadNextMapItem_t)(sysync::MapID mID, bool aFirst); boost::function m_readNextMapItem; typedef OperationWrapper InsertMapItem_t; InsertMapItem_t m_insertMapItem; typedef OperationWrapper UpdateMapItem_t; UpdateMapItem_t m_updateMapItem; typedef OperationWrapper DeleteMapItem_t; DeleteMapItem_t m_deleteMapItem; // not wrapped, too many parameters typedef boost::function ReadBlob_t; ReadBlob_t m_readBlob; typedef boost::function WriteBlob_t; WriteBlob_t m_writeBlob; typedef OperationWrapper DeleteBlob_t; DeleteBlob_t m_deleteBlob; /**@}*/ }; /** * Read-only access to operations. */ virtual const Operations &getOperations() const = 0; /** * Start flushing item modifications which were not executed right * away. Item modifications (add/update/delete) can be delayed by * returning LOCERR_AGAIN or, when using for example * SyncSourceSerialize aka TrackingSyncSource, by returning a "check" * function instead of the final result. * * The sync engine calls this method after processing each incoming * SyncML message. */ virtual void flushItemChanges() {} /** * Called after flush() to ensure that all pending modifications * have completed. Called when the engine needs the results. * * Called by the sync engine when the SyncML peer ran out of new * item changes. At that time we would start sending back and forth * empty messages, unless we can provide results. */ virtual void finishItemChanges() {} /** * In some usage scenarios, change tracking is not necessary. * This includes local caching (where local data is not expected * to changed outside of sync) or item manipulation. * * The user of a source must explicitly disable change tracking, * see SyncSource. */ virtual bool needChanges() { return true; } enum ReadAheadOrder { READ_ALL_ITEMS, /** all items as reported by the ReadNextItem operation */ READ_CHANGED_ITEMS, /** read updated or added items as reported by the ReadNextItem operation */ READ_SELECTED_ITEMS, /** read items as given in an explicit list */ READ_NONE /** remove previous hint */ }; typedef std::vector ReadAheadItems; /** * Provides a hint to the source which items are going to be read * next. A source may use this to implement read-ahead. This * is just a hint, the source must also work if reads turn out to * ask for other items. */ virtual void setReadAheadOrder(ReadAheadOrder order, const ReadAheadItems &luids = ReadAheadItems()) { } virtual void getReadAheadOrder(ReadAheadOrder &order, ReadAheadItems &luids) { order = READ_NONE; luids.clear(); } protected: struct SynthesisInfo { /** * name to use for MAKE/PARSETEXTWITHPROFILE, * leave empty when acessing the field list directly */ std::string m_profile; /** * the second parameter for MAKE/PARSETEXTWITHPROFILE * which specifies a remote rule to be applied when * converting to and from the backend */ std::string m_backendRule; /** list of supported datatypes in "" format */ std::string m_datatypes; /** native datatype (see getNativeDatatypeName()) */ std::string m_native; /** name of the field list used by the datatypes */ std::string m_fieldlist; /** * One or more Synthesis script statements, separated * and terminated with a semicolon. Can be left empty. * * If not empty, then these statements are executed directly * before converting the current item fields into * a single string with MAKETEXTWITHPROFILE() in the sync source's * (see SyncSourceBase::getDatastoreXML()). * * This value is currently only used by sync sources which * set m_profile. */ std::string m_beforeWriteScript; /** * Same as m_beforeWriteScript, but used directly after * converting a string into fields with PARSETEXTWITHPROFILE() * in . */ std::string m_afterReadScript; /** * Arbitrary configuration options, can override the ones above * because they are added to the * XML configuration directly before the closing element. * * One example is adding : this is necessary * in backends which depend on getting complete items (= for example, * vCard 3.0 strings) from the engine. Note that any source * derived from SyncSourceSerialize (= the majority of the backends) * have this set by default. */ std::string m_datastoreOptions; /** * If true, then the StartDataRead call (aka SyncSourceSession::beginSync) * is invoked before the first message exchange with the peer. Otherwise * it is invoked only if the peer could be reached and accepts the credentials. * * See SyncSourceSession::beginSync for further comments. */ Bool m_earlyStartDataRead; /** * If true, then the storage is considered read-only by the * engine. All write requests by the peer will be silently * discarded. This is necessary for slow syncs, where the peer * might send back modified items. */ Bool m_readOnly; /** * If true, then the storage preserves and supports UID and * (in iCalendar 2.0) RECURRENCE-ID with the "globally unique" * semantic from iCalendar 2.0 (id assigned once when item is * created). If both sides in a sync support this, then the * engine can rely on these properties to find matching items * during a slow sync. * * Matches ClientTestConfig::m_sourceKnowsItemSemantic. */ Bool m_globalIDs; }; /** * helper function for getDatastoreXML(): fill in information * as necessary * * @retval fragments the necessary definitions for the other * return values have to be added here */ virtual void getSynthesisInfo(SynthesisInfo &info, XMLConfigFragments &fragments) = 0; /** * utility code: creates Synthesis * statements, using the predefined vCard21/vCard30/vcalendar10/icalendar20 * types. Throws an error if no suitable result can be returned (empty or invalid type) * * @param type the format specifier as used in SyncEvolution configs, with and without version * (text/x-vcard:2.1, text/x-vcard, text/x-vcalendar, text/calendar, text/plain, ...); * see SourceType::m_format * @param forceFormat if true, then don't allow alternative formats (like vCard 3.0 in addition to 2.1); * see SourceType::m_force * @return generated XML fragment */ std::string getDataTypeSupport(const std::string &type, bool forceFormat); }; /** * SyncEvolution accesses all sources through this interface. * * Certain functionality is optional or can be implemented in * different ways. These methods are accessed through functors * (function objects) which may be unset. The expected usage is that * derived classes fill in the pieces that they provide by binding the * functors to normal methods. For example, TrackingSyncSource * provides a normal base class with pure virtual functions which have * to be provided by users of that class. * * Error reporting is done via the Log class. */ class SyncSource : virtual public SyncSourceBase, public SyncSourceConfig, public SyncSourceReport { public: SyncSource(const SyncSourceParams ¶ms); virtual ~SyncSource() {} /** true in sources which are not meant to be used, see RegisterSyncSource::InactiveSource() */ virtual bool isInactive() const { return false; } /** * SyncSource implementations must register themselves here via * RegisterSyncSource */ static SourceRegistry &getSourceRegistry(); /** * SyncSource tests are registered here by the constructor of * RegisterSyncSourceTest */ static TestRegistry &getTestRegistry(); struct Database { Database(const string &name, const string &uri, bool isDefault = false) : m_name( name ), m_uri( uri ), m_isDefault(isDefault) {} string m_name; string m_uri; bool m_isDefault; }; typedef vector Databases; /** * returns a list of all know data sources for the kind of items * supported by this sync source */ virtual Databases getDatabases() = 0; /** * Creates a new database. * The default implementation just throws an error. * * @param database At least the name should be set. Some backends * may also be able to create the database with * a specific URI. * @return description of the new database */ virtual Database createDatabase(const Database &database) { throwError("creating databases is not supported by backend " + getBackend()); return Database("", ""); } /** * Removing a database primarily removes the meta data about the * database. The data itself may still exist in a trash folder. * The enum tells the deleteDatabase() call what the intention of * the caller is. */ enum RemoveData { REMOVE_DATA_DEFAULT, /**< do whatever makes most sense for the backend */ REMOVE_DATA_FORCE, /**< force immediate purging of the data, fail if not possible */ REMOVE_DATA_KEEP /**< keep data, only remove access to it */ }; /** * Removes a database. To map a "database" property to a uri, * instantiate the source with the desired config, open() it and * then call getDatabase(). * * @param uri unique identifier for the database * @param removeData describes what to do about the database content */ virtual void deleteDatabase(const std::string &uri, RemoveData removeData) { throwError("deleting databases is not supported by backend " + getBackend()); } /** * Actually opens the data source specified in the constructor, * will throw the normal exceptions if that fails. Should * not modify the state of the sync source. * * The expectation is that this call is fairly light-weight, but * does enough checking to determine whether the source is * usable. More expensive operations (like determining changes) * should be done in the m_startDataRead callback (bound to * beginSync() in some of the utility classes). * * In clients, it will be called for all sources before * the sync starts. In servers, it is called for each source once * the client asks for it, but not sooner. */ virtual void open() = 0; /** * Returns the actual database that is in use. open() must * have been called first. * * Useful because the "database" property might be empty or * be interpreted in different ways by different backends. * * Needed for deleting databases. Not implemented in all * backends. The default implementation returns an empty * structure. * * @return Database structure with at least m_uri set if * the actual database is known. */ Database getDatabase() const { return m_database; } /** * To be called by derived implementation of open(). */ void setDatabase(const Database &database) { m_database = database; } /** * Read-only access to operations. Derived classes can modify * them via m_operations. */ virtual const Operations &getOperations() const { return m_operations; } /** * closes the data source so that it can be reopened * * Just as open() it should not affect the state of * the database unless some previous action requires * it. */ virtual void close() = 0; /** * return Synthesis API pointer, if one currently is available * (between SyncEvolution_Module_CreateContext() and * SyncEvolution_Module_DeleteContext()) */ virtual SDKInterface *getSynthesisAPI() const; /** * change the Synthesis API that is used by the source */ void pushSynthesisAPI(sysync::SDK_InterfaceType *synthesisAPI); /** * remove latest Synthesis API and return to previous one (if any) */ void popSynthesisAPI(); /** * If called while a sync session runs (i.e. after m_startDataRead * (aka beginSync()) and before m_endDataWrite (aka endSync())), * the engine will finish the session and then immediately try to * run another session where any source in which requestAnotherSync() * was called is active again. There is no guarantee that this * will be possible. * * The source must be prepared to correctly handle another sync * session. m_endDataWrite will be called and then the sequence * of calls starts again at m_startDataRead. * * The sync mode will switch to an incremental sync in the same * direction as the initial sync (one-way to client or server, * two-way). * * Does nothing when called at the wrong time. There's no * guarantee either that restarting is possible. * * Currently only supported when a single source is active in * the initial sync. */ void requestAnotherSync(); /** * factory function for a SyncSource that provides the * source type specified in the params.m_nodes.m_configNode * * @param error throw a runtime error describing what the problem is if no matching source is found * @param config optional, needed for intantiating virtual sources * @return valid instance, NULL if no source can handle the given type (only when error==false) */ static SyncSource *createSource(const SyncSourceParams ¶ms, bool error = true, SyncConfig *config = NULL); /** * Factory function for a SyncSource with the given name * and handling the kind of data specified by "type" (e.g. * "Evolution Contacts:text/x-vcard"). * * The source is instantiated with dummy configuration nodes under * the pseudo server name "testing". This function is used for * testing sync sources, not for real syncs. If the prefix is set, * then __1 is used as database, just as in the * Client::Sync and Client::Source tests. Otherwise the default * database is used. * * @param error throw a runtime error describing what the problem is if no matching source is found * @return NULL if no source can handle the given type */ static SyncSource *createTestingSource(const string &name, const string &type, bool error, const char *prefix = getenv("CLIENT_TEST_EVOLUTION_PREFIX")); /** * Some information about available backends. * Multiple lines, formatted for users of the * command line. */ static string backendsInfo(); /** * Debug information about backends. */ static string backendsDebug(); /** * Mime type a backend communicates with the remote peer by default, * this is used to alert the remote peer in SAN during server alerted sync. */ virtual std::string getPeerMimeType() const =0; /* implementation of SyncSourceBase */ virtual std::string getName() const { return SyncSourceConfig::getName(); } virtual std::string getDisplayName() const { return m_name.c_str(); } virtual void setDisplayName(const std::string &name) { m_name = name; } virtual long getNumDeleted() const { return m_numDeleted; } virtual void setNumDeleted(long num) { m_numDeleted = num; } virtual void incrementNumDeleted() { m_numDeleted++; } virtual bool needChanges() { return m_needChanges; } void setNeedChanges(bool needChanges) { m_needChanges = needChanges; } /** * Set to true in SyncContext::initSAN() when a SyncML server has * to force a client into slow sync mode. This is necessary because * the server cannot request that mode (missing in the standard). * Forcing the slow sync mode is done via a FORCESLOWSYNC() macro * call in an . */ void setForceSlowSync(bool forceSlowSync) { m_forceSlowSync = forceSlowSync; } bool getForceSlowSync() const { return m_forceSlowSync; } protected: Operations m_operations; private: /** * Counter for items deleted in the source. Has to be incremented * by RemoveAllItems() and DeleteItem(). This counter is used to * update the Synthesis engine counter in those cases where the * engine does not (refresh from server) or cannot * (RemoveAllItems()) count the removals itself. */ long m_numDeleted; bool m_forceSlowSync; /** * Interface pointer for this sync source, allocated for us by the * Synthesis engine and registered here by * SyncEvolution_Module_CreateContext(). Only valid until * SyncEvolution_Module_DeleteContext(), in other words, while * the engine is running. */ std::vector m_synthesisAPI; /** database in use after open(), to be set via setDatabase() by derived class */ Database m_database; /** actual name of the source */ std::string m_name; /** change tracking enabled? */ bool m_needChanges; }; /** * A SyncSource with no pure virtual functions. */ class DummySyncSource : public SyncSource { public: DummySyncSource(const SyncSourceParams ¶ms) : SyncSource(params) {} DummySyncSource(const std::string &name, const std::string &contextName) : SyncSource(SyncSourceParams(name, SyncSourceNodes(), boost::shared_ptr(), contextName)) {} virtual Databases getDatabases() { return Databases(); } virtual void open() {} virtual void close() {} virtual void getSynthesisInfo(SynthesisInfo &info, XMLConfigFragments &fragments) {} virtual void enableServerMode() {} virtual bool serverModeEnabled() const { return false; } virtual std::string getPeerMimeType() const {return "";} }; /** * A special source which combines one or more real sources. * Most of the special handling for that is in SyncContext.cpp. * * This class can be instantiated, opened and closed if and only if * the underlying sources also support that. */ class VirtualSyncSource : public DummySyncSource { std::vector< boost::shared_ptr > m_sources; public: /** * @param config optional: when given, the constructor will instantiate the * referenced underlying sources and check them in open() */ VirtualSyncSource(const SyncSourceParams ¶ms, SyncConfig *config = NULL); /** opens underlying sources and checks config by calling getDataTypeSupport() */ virtual void open(); virtual void close(); /** * returns array with source names that are referenced by this * virtual source */ std::vector getMappedSources(); /** * returns statements for XML config, * throws error if not configured correctly */ std::string getDataTypeSupport(); using SyncSourceBase::getDataTypeSupport; /* * If any of the sub datasource has no databases associated, return an empty * database list to indicate a possibly error condition; otherwise return a * dummy database to identify "calendar+todo" combined datasource. **/ virtual Databases getDatabases(); }; /** * Hooks up the Synthesis DB Interface start sync (BeginDataRead) and * end sync (EndDataWrite) calls with virtual methods. Ensures that * sleepSinceModification() is called. * * Inherit from this class in your sync source and call the init() * method to use it. */ class SyncSourceSession : virtual public SyncSourceBase { public: /** * called before Synthesis engine starts to ask for changes and item data * * If SynthesisInfo::m_earlyStartDataRead is true, then this call is * invoked before the first message exchange with a peer and it * may throw a STATUS_SLOW_SYNC_508 StatusException if an * incremental sync is not possible. In that case, preparations * for a slow sync must have completed successfully inside the * beginSync() call. It is not going to get called again. * * If SynthesisInfo::m_earlyStartDataRead is false (the default), * then this is called only if the peer was reachable and accepted * the credentials. This mode of operation is preferred if a fallback * to slow sync is not needed, because it allows deferring expensive * operations until really needed. For example, the engine does * database dumps at the time when StartDataRead is called. * * See StartDataRead for details. * * @param lastToken identifies the last completed sync * @param resumeToken identifies a more recent sync which needs to be resumed; * if not empty, then report changes made after that sync * instead of the last completed sync */ virtual void beginSync(const std::string &lastToken, const std::string &resumeToken) = 0; /** * called after completing or suspending the current sync * * See EndDataWrite for details. * * @return a token identifying this sync session for a future beginSync() */ virtual std::string endSync(bool success) = 0; /** set Synthesis DB Interface operations */ void init(SyncSource::Operations &ops); private: sysync::TSyError startDataRead(const char *lastToken, const char *resumeToken); sysync::TSyError endDataWrite(bool success, char **newToken); }; /** * Implements the Synthesis DB Interface for reporting item changes * (ReadNextItemAsKey) *without* actually delivering the item data. */ class SyncSourceChanges : virtual public SyncSourceBase { public: SyncSourceChanges(); enum State { ANY, NEW, UPDATED, DELETED, MAX }; /** * Add the LUID of a NEW/UPDATED/DELETED item. * If unspecified, the luid is added to the list of * all items. This must be done *in addition* to adding * the luid with a specific state. * * For example, the luid of an updated item should be added with * addItem(luid [, ANY]) and again with addItem(luid, DELETED). * * The Synthesis engine does not need the list of deleted items * and does not distinguish between added and updated items, so * for syncing, adding DELETED items is optional and all items * which are different from the last sync can be added as * UPDATED. The client-test program expects that the informationb * is provided precisely. * * @return true if the luid was already listed */ bool addItem(const string &luid, State state = ANY); /** * Wipe out all added items, returning true if any were found. */ bool reset(); typedef std::set Items_t; const Items_t &getItems(State state) { return m_items[state]; } const Items_t &getAllItems() const { return m_items[ANY]; } const Items_t &getNewItems() const { return m_items[NEW]; } const Items_t &getUpdatedItems() const { return m_items[UPDATED]; } const Items_t &getDeletedItems() const { return m_items[DELETED]; } /** set Synthesis DB Interface operations */ void init(SyncSource::Operations &ops); private: Items_t m_items[MAX]; bool m_first; Items_t::const_iterator m_it; sysync::TSyError iterate(sysync::ItemID aID, sysync::sInt32 *aStatus, bool aFirst); }; /** * Implements the Synthesis DB Interface for deleting an item * (DeleteItem). Increments number of deleted items in * SyncSourceBase. */ class SyncSourceDelete : virtual public SyncSourceBase { public: virtual void deleteItem(const string &luid) = 0; /** set Synthesis DB Interface operations */ void init(SyncSource::Operations &ops); private: sysync::TSyError deleteItemSynthesis(sysync::cItemID aID); }; enum InsertItemResultState { /** * Operation not complete, invoke callback in ItemResult to check * for progress. */ ITEM_AGAIN, /** * item added or updated as requested */ ITEM_OKAY, /** * When a backend is asked to add an item and recognizes * that the item matches an already existing item, it may * replace that item instead of creating a duplicate. In this * case it must return ITEM_REPLACED and set the luid/revision * of that updated item. * * This can happen when such an item was added concurrently to * the running sync or, more likely, was reported as new by * the backend and the engine failed to find the match because * it doesn't know about some special semantic, like iCalendar * 2.0 UID). * * Note that depending on the age of the items, the older data * will replace the more recent one when always using item * replacement. */ ITEM_REPLACED, /** * Same as ITEM_REPLACED, except that the backend did some * modifications to the data that was sent to it before * storing it, like merging it with the existing item. The * engine will treat the updated item as modified and send * back the update to the peer as soon as possible. In server * mode that will be in the same sync session, in a client in * the next session (client cannot send changes after having * received data from the server). */ ITEM_MERGED, /** * As before, a match against an existing item was detected. * By returning this state and the luid of the matched item * (revision not needed) the engine is instructed to do the * necessary data comparison and merging itself. Useful when a * backend can't do the necessary merging itself. */ ITEM_NEEDS_MERGE }; /** * an interface for reading and writing items in the internal * format; see SyncSourceSerialize for an explanation */ class SyncSourceRaw : virtual public SyncSourceBase { public: class InsertItemResult { public: InsertItemResult() : m_state(ITEM_OKAY) {} /** * @param luid the LUID after the operation; during an update the LUID must * not be changed, so return the original one here * @param revision the revision string after the operation; leave empty if not used * @param state report about what was done with the data */ InsertItemResult(const string &luid, const string &revision, InsertItemResultState state) : m_luid(luid), m_revision(revision), m_state(state) {} /** * Constructor for the case where the final result is not available yet. * * @param check will be called again later to poll for completion */ InsertItemResult(const boost::function &check) : m_state(ITEM_AGAIN), m_continue(check) {} string m_luid; string m_revision; InsertItemResultState m_state; typedef ContinueOperation Continue_t; Continue_t m_continue; }; /** same as SyncSourceSerialize::insertItem(), but with internal format */ virtual InsertItemResult insertItemRaw(const std::string &luid, const std::string &item) = 0; /** same as SyncSourceSerialize::readItem(), but with internal format */ virtual void readItemRaw(const std::string &luid, std::string &item) = 0; }; /** * Implements the Synthesis DB Interface for importing/exporting item * data (ReadItemAsKey, InsertItemAsKey, UpdateItemAsKey) in such a * way that the sync source only has to deal with a text * representation of an item. * * There may be two such representations: * - "engine format" is the one exchanged with the Synthesis engine * - "internal or raw format" is a format that might better capture * the internal representation and can be used for backup/restore * and testing * * To give an example, the EvolutionMemoSource uses plain text as * engine format and iCalendar 2.0 as raw format. * * The BackupData_t and RestoreData_t operations are implemented by * this class using the internal format. * * The engine format must be something that the Synthesis engine can * parse and generate, in other words, there must be a corresponding * profile in the XML configuration. This class uses information * provided by the sync source (mime type and version) and from the * configuration (format selected by user) to generate the required * XML configuration parts for common configurations (vCard, * vCalendar, iCalendar, text). Special representations can be added * to the global XML configuration by overriding default * implementations provided in this class. * * InsertItemAsKey and UpdateItemAsKey are mapped to the same * insertItem() call because in practice it can happen that a request * to add an item must be turned into an update. For example, a * meeting was imported both into the server and the client. A request * to add the item again should be treated as an update, based on the * unique iCalendar 2.0 LUID. */ class SyncSourceSerialize : virtual public SyncSourceBase, virtual public SyncSourceRaw { public: /** * Returns the preferred mime type of the items handled by the sync source. * Example: "text/x-vcard" */ virtual std::string getMimeType() const = 0; /** * Returns the version of the mime type used by client. * Example: "2.1" */ virtual std::string getMimeVersion() const = 0; /** * returns the backend selection and configuration */ virtual InitState getSourceType() const = 0; /** * Create or modify an item. * * The sync source should be flexible: if the LUID is non-empty, it * shall modify the item referenced by the LUID. If the LUID is * empty, the normal operation is to add it. But if the item * already exists (e.g., a calendar event which was imported * by the user manually), then the existing item should be * updated also in the second case. * * Passing a LUID of an item which does not exist is an error. * This error should be reported instead of covering it up by * (re)creating the item. * * Errors are signaled by throwing an exception. Returning empty * strings in the result is an error which triggers an "item could * not be stored" error. * * @param luid identifies the item to be modified, empty for creating * @param item contains the new content of the item, using the engine format * @return the result of inserting the item */ virtual InsertItemResult insertItem(const std::string &luid, const std::string &item) = 0; /** * Return item data in engine format. * * @param luid identifies the item * @retval item item data */ virtual void readItem(const std::string &luid, std::string &item) = 0; /* implement SyncSourceRaw under the assumption that the internal and engine format are identical */ virtual InsertItemResult insertItemRaw(const std::string &luid, const std::string &item); virtual void readItemRaw(const std::string &luid, std::string &item); /** set Synthesis DB Interface operations */ void init(SyncSource::Operations &ops); protected: /** * used getMimeType(), getMimeVersion() and getSourceType() * to provide the information necessary for automatic * conversion to the sync source's internal item representation */ virtual void getSynthesisInfo(SynthesisInfo &info, XMLConfigFragments &fragments); private: sysync::TSyError readItemAsKey(sysync::cItemID aID, sysync::KeyH aItemKey); SyncSource::Operations::InsertItemAsKeyResult_t insertItemAsKey(sysync::KeyH aItemKey, sysync::ItemID newID); SyncSource::Operations::UpdateItemAsKeyResult_t updateItemAsKey(sysync::KeyH aItemKey, sysync::cItemID aID, sysync::ItemID newID); sysync::TSyError insertContinue(sysync::ItemID newID, const InsertItemResult::Continue_t &cont); }; /** * Mapping from Hash() value to file. * Used by SyncSourceRevisions, but may be of use for * other backup implementations. */ class ItemCache { public: #ifdef USE_SHA256 typedef std::string Hash_t; Hash_t hashFunc(const std::string &data) { return SHA_256(data); } #else typedef unsigned long Hash_t; Hash_t hashFunc(const std::string &data) { return Hash(data); } #endif typedef unsigned long Counter_t; /** mark the algorithm used for the hash via different suffices */ static const char *m_hashSuffix; /** * Collect information about stored hashes. Provides * access to file name via hash. * * If no hashes were written (as in an old SyncEvoltion * version), we could read the files to recreate the * hashes. This is not done because it won't occur * often enough. * * Hashes are also not verified. Users should better * not edit them or file contents... * * @param oldBackup existing backup to read; may be empty * @param newBackup new backup to be created * @param legacy legacy mode includes a bug * which cannot be fixed without breaking on-disk format */ void init(const SyncSource::Operations::ConstBackupInfo &oldBackup, const SyncSource::Operations::BackupInfo &newBackup, bool legacy); /** * create file name for a specific hash, empty if no such hash */ string getFilename(Hash_t hash); /** * add a new item, reusing old one if possible * * @param item new item data * @param uid its unique ID * @param rev revision string */ void backupItem(const std::string &item, const std::string &uid, const std::string &rev); /** to be called after init() and all backupItem() calls */ void finalize(BackupReport &report); /** can be used to restart creating the backup after an intermediate failure */ void reset(); private: typedef std::map Map_t; Map_t m_hash2counter; string m_dirname; SyncSource::Operations::BackupInfo m_backup; bool m_legacy; unsigned long m_counter; }; /** * Implements change tracking based on a "revision" string, a string * which is guaranteed to change automatically each time an item is * modified. Backup/restore is optionally implemented by this class if * pointers to SyncSourceRaw and SyncSourceDelete interfaces are * passed to init(). For backup only the former is needed, for restore * both. * * Potential implementations of the revision string are: * - a modification time stamp * - a hash value of a textual representation of the item * (beware, such a hash might change as the textual representation * changes even though the item is unchanged) * * Sync sources which want to use this functionality have to provide * the following functionality by implementing the pure virtual * functions below: * - enumerate all existing items * - provide LUID and the revision string * The LUID must remain *constant* when making changes to an item, * whereas the revision string must *change* each time the item is * changed by anyone. * Both can be arbitrary strings, but keeping them simple (printable * ASCII, no white spaces, no equal sign) makes debugging simpler * because they can be stored as they are as key/value pairs in the * sync source's change tracking config node (the .other.ini files when * using file-based configuration). More complex strings use escape * sequences introduced with an exclamation mark for unsafe characters. * * Most of the functionality of this class must be activated * explicitly as part of the life cycle of the sync source instance by * calling detectChanges(), updateRevision() and deleteRevision(). * * If the required interfaces are provided to init(), then backup/restore * operations are set. init() also hooks into the session life cycle * with an end callback that ensures that enough time passes at the end * of the sync. This is important for sync sources which use time stamps * as revision string. "enough time" is defined by a parameter to the * init call. */ class SyncSourceRevisions : virtual public SyncSourceChanges, virtual public SyncSourceBase { public: typedef map RevisionMap_t; /** * Fills the complete mapping from UID to revision string of all * currently existing items. * * Usually both UID and revision string must be non-empty. The * only exception is a refresh-from-client: in that case the * revision string may be empty. The implementor of this call * cannot know whether empty strings are allowed, therefore it * should not throw errors when it cannot create a non-empty * string. The caller of this method will detect situations where * a non-empty string is necessary and none was provided. * * An source must set the revision string for all items or * none at all. * * This call is typically only invoked only once during the * lifetime of a source, at the time when detectChanges() needs * the information. The result returned in that invocation is * used throught the session. * * When detectChanges() is called with CHANGES_NONE, listAllItems() * is avoided. Instead the cached information is used. Sources * may need to know that information, so in this case setAllItems() * is called as part of detectChanges(). */ virtual void listAllItems(RevisionMap_t &revisions) = 0; /** * Called by SyncSourceRevisions::detectChanges() to tell * the derived class about the cached information if (and only * if) listAllItems() and updateAllItems() were not called. The derived class * might not need this information, so the default implementation * simply ignores. * * A more complex API could have been defined to only prepare the * information when needed, but that seemed unnecessarily complex. */ virtual void setAllItems(const RevisionMap_t &revisions) {} /** * updates the revision map to reflect the current state * * May be called instead of listAllItems() if the caller has * a valid list to start from. If the implementor * cannot update the list, it must start from scratch by * reseting the list and calling listAllItems(). The default * implementation of this method does that. */ virtual void updateAllItems(SyncSourceRevisions::RevisionMap_t &revisions) { revisions.clear(); listAllItems(revisions); } /** * Tells detectChanges() how to do its job. */ enum ChangeMode { /** * Call listAllItems() and use the list of previous items * to calculate changes. */ CHANGES_FULL, /** * Don't rely on previous information. Will call * listAllItems() and generate a full list of items based on * the result. */ CHANGES_SLOW, /** * Caller has already determined that a) no items have changed * and that b) the list of previous items is valid. For example, * some backends have a way of getting a revision string for * the whole database and can compare that against the value * from the end of the previous sync. * * In this mode, listAllItems() doesn't have to be called. * A list of all items will be created, with no items marked * as added/updated/deleted. */ CHANGES_NONE }; /** * calculate changes, call when sync source is ready for * listAllItems() and before changes are needed * * The trackingNode must be provided by the caller. It will * be updated by each of the calls and must be stored by * the caller. * * @param trackingNode a config node for exclusive use by this class * @param mode determines how changes are detected; if unsure, * use CHANGES_FULL, which will always produce * the required information, albeit more slowly * than the other modes * @return true if change detection could only provide a list of currently * existing items, but not the list of added/updated/deleted items */ bool detectChanges(ConfigNode &trackingNode, ChangeMode mode); /** * record that an item was added or updated * * @param old_luid empty for add, old LUID otherwise * @param new_luid normally LUIDs must not change, but this call allows it * @param revision revision string after change */ void updateRevision(ConfigNode &trackingNode, const std::string &old_luid, const std::string &new_luid, const std::string &revision); /** * record that we deleted an item * * @param luid the obsolete LUID */ void deleteRevision(ConfigNode &trackingNode, const std::string &luid); /** * set Synthesis DB Interface and backup/restore operations * @param raw needed for backups; if NULL, no backups are made * @param del needed for restores; if NULL, only backups are possible * @param granularity time that has to pass between making a modification * and checking for changes; this class ensures that * at least this amount of time has passed before letting * the session terminate. Delays in different source do * not add up. */ void init(SyncSourceRaw *raw, SyncSourceDelete *del, int granularity, SyncSource::Operations &ops); private: SyncSourceRaw *m_raw; SyncSourceDelete *m_del; int m_revisionAccuracySeconds; /** buffers the result of the initial listAllItems() call */ RevisionMap_t m_revisions; bool m_revisionsSet; bool m_firstCycle; void initRevisions(); /** * Dump all data from source unmodified into the given directory. * The ConfigNode can be used to store meta information needed for * restoring that state. Both directory and node are empty. */ void backupData(const SyncSource::Operations::ConstBackupInfo &oldBackup, const SyncSource::Operations::BackupInfo &newBackup, BackupReport &report); /** * Restore database from data stored in backupData(). Will be * called inside open()/close() pair. beginSync() is *not* called. */ void restoreData(const SyncSource::Operations::ConstBackupInfo &oldBackup, bool dryrun, SyncSourceReport &report); /** * Increments the time stamp of the latest database modification, * called automatically whenever revisions change. */ void databaseModified(); /** time stamp of latest database modification, for sleepSinceModification() */ Timespec m_modTimeStamp; void sleepSinceModification(); }; /** * Common logging for sync sources. * * This class wraps the Synthesis DB functors that were set before * calling its init() method. The wrappers then log a single line * describing what is happening (adding/updating/removing) * to which item (with a short item specific description extracted * from the incoming item data or the backend). */ class SyncSourceLogging : public virtual SyncSourceBase { public: /** * wrap Synthesis DB Interface operations * * @param fields list of fields to read in getDescription() * @param sep separator between non-empty fields */ void init(const std::list &fields, const std::string &sep, SyncSource::Operations &ops); /** * Extract short description from Synthesis item data. * The default implementation reads a list of fields * as strings and concatenates the non-empty ones * with a separator. * * @param aItemKey key for reading fields * @return description, empty string will cause the ID of the item to be printed */ virtual std::string getDescription(sysync::KeyH aItemKey); /** * Extract short description from backend. * Necessary for deleted items. The default implementation * returns an empty string, so that implementing this * is optional. * * @param luid LUID of the item to be deleted in the backend * @return description, empty string will cause the ID of the item to be printed */ virtual std::string getDescription(const string &luid); private: std::list m_fields; std::string m_sep; void insertItemAsKey(sysync::KeyH aItemKey, sysync::ItemID newID); void updateItemAsKey(sysync::KeyH aItemKey, sysync::cItemID aID, sysync::ItemID newID); void deleteItem(sysync::cItemID aID); }; /** * Implements Load/SaveAdminData and MapItem handling in a SyncML * server. Uses a single property for the admin data in the "internal" * node and a complete node for the map items. */ class SyncSourceAdmin : public virtual SyncSourceBase { boost::shared_ptr m_configNode; std::string m_adminPropertyName; boost::shared_ptr m_mappingNode; bool m_mappingLoaded; ConfigProps m_mapping; ConfigProps::const_iterator m_mappingIterator; sysync::TSyError loadAdminData(const char *aLocDB, const char *aRemDB, char **adminData); sysync::TSyError saveAdminData(const char *adminData); bool readNextMapItem(sysync::MapID mID, bool aFirst); sysync::TSyError insertMapItem(sysync::cMapID mID); sysync::TSyError updateMapItem(sysync::cMapID mID); sysync::TSyError deleteMapItem(sysync::cMapID mID); void flush(); void resetMap(); void mapid2entry(sysync::cMapID mID, string &key, string &value); void entry2mapid(const string &key, const string &value, sysync::MapID mID); public: /** flexible initialization */ void init(SyncSource::Operations &ops, const boost::shared_ptr &config, const std::string adminPropertyName, const boost::shared_ptr &mapping); /** * simpler initialization, using the default placement of data * inside the SyncSourceConfig base class */ void init(SyncSource::Operations &ops, SyncSource *source); }; /** * Implements Read/Write/DeleteBlob. Blobs are stored inside a * configurable directory, which has to be unique for the current * peer. */ class SyncSourceBlob : public virtual SyncSourceBase { /** * Only one blob is active at a time. * This utility class provides the actual implementation. */ sysync::TBlob m_blob; sysync::TSyError readBlob(sysync::cItemID aID, const char *aBlobID, void **aBlkPtr, size_t *aBlkSize, size_t *aTotSize, bool aFirst, bool *aLast) { // Translate between sysync::memSize and size_t, which // is different on s390 (or at least the compiler complains...). sysync::memSize blksize = aBlkSize ? static_cast(*aBlkSize) : 0, totsize = aTotSize ? static_cast(*aTotSize) : 0; sysync::TSyError err = m_blob.ReadBlob(aID, aBlobID, aBlkPtr, &blksize, &totsize, aFirst, aLast); if (aBlkSize) { *aBlkSize = blksize; } if (aTotSize) { *aTotSize = totsize; } return err; } sysync::TSyError writeBlob(sysync::cItemID aID, const char *aBlobID, void *aBlkPtr, size_t aBlkSize, size_t aTotSize, bool aFirst, bool aLast) { mkdir_p(m_blob.getBlobPath()); return m_blob.WriteBlob(aID, aBlobID, aBlkPtr, aBlkSize, aTotSize, aFirst, aLast); } sysync::TSyError deleteBlob(sysync::cItemID aID, const char *aBlobID) { return m_blob.DeleteBlob(aID, aBlobID); } sysync::TSyError loadAdminData(sysync::cItemID aID, const char *aBlobID, void **aBlkPtr, size_t *aBlkSize, size_t *aTotSize, bool aFirst, bool *aLast); public: void init(SyncSource::Operations &ops, const std::string &dir); }; /** * This is an interface definition that is expected by the client-test * program. Part of the reason for this requirement is that the test * program was originally written for the Funambol SyncSource API. * The other reason is that the testing is based on importing/exporting * items in the internal format of the sync source, which has to be * text based or even MIMEDIR based (for tests involving synccompare). */ class TestingSyncSource : public SyncSource, virtual public SyncSourceSession, virtual public SyncSourceChanges, virtual public SyncSourceDelete, virtual public SyncSourceSerialize { public: TestingSyncSource(const SyncSourceParams ¶ms) : SyncSource(params) { SyncSourceSession::init(m_operations); SyncSourceChanges::init(m_operations); SyncSourceDelete::init(m_operations); SyncSourceSerialize::init(m_operations); } ~TestingSyncSource() {} virtual InitState getSourceType() const { return SyncSourceConfig::getSourceType(); } virtual void removeAllItems(); }; SE_END_CXX #endif // INCL_SYNCSOURCE syncevolution_1.4/src/syncevo/SynthesisDBPlugin.cpp000066400000000000000000000637731230021373600227350ustar00rootroot00000000000000/* * This file implements the Synthesis DB API and maps it to * EvolutionSyncSources. It was derived from the Synthesis * sync_dbapi_demo.c file. * * The external API of this file are the globally visible * C functions defined by sync_dbapidef.h. * * Copyright (c) 2004-2008 by Synthesis AG * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include /* include general SDK definitions */ #include /* include the interface file and utilities */ #include /* include SDK utilities */ using namespace sysync; #include #include #include #include #include SE_BEGIN_CXX #define BuildNumber 0 /* User defined build number, can be 0..255 */ #define MyDB "SyncEvolution" /* example debug name */ #define MY_ID 42 /* example datastore context */ #define STRLEN 80 /* Max length of local string copies */ /* -- MODULE -------------------------------------------------------------------- */ /* will be casted to the SyncSource * structure */ static SyncSource *MoC(CContext mContext) { return (SyncSource *)mContext; } /** * looks up datasource and uses pointer to it as context * * @param mContextName name of previously instantiated SyncSource, "" when used as session module * @retval mContext the corresponding SyncSource */ extern "C" TSyError SyncEvolution_Module_CreateContext( CContext *mContext, cAppCharP moduleName, cAppCharP subName, cAppCharP mContextName, DB_Callback mCB ) { TSyError err = LOCERR_WRONGUSAGE; if (!mContextName[0]) { *mContext = NULL; err = LOCERR_OK; } else { SyncSource *source = SyncContext::findSource(mContextName); if (source) { source->pushSynthesisAPI(mCB); *mContext = (CContext)source; err = LOCERR_OK; } } SE_LOG_DEBUG(NULL, "CreateContext %s/%s/%s => %d", moduleName, subName, mContextName, err); return err; } /** * @TODO: introduce and return some kind of SyncEvolution build number */ extern "C" CVersion SyncEvolution_Module_Version(CContext mContext) { CVersion v = Plugin_Version(BuildNumber); if (mContext) { SE_LOG_DEBUG(NULL, "Module_Version = %08lx", (long)v); } return v; } /* Get the plug-in's capabilities */ extern "C" TSyError SyncEvolution_Module_Capabilities( CContext mContext, appCharP *mCapabilities ) { SyncSource *source = MoC(mContext); std::stringstream s; s << MyPlatform() << "\n" << DLL_Info << "\n" << CA_MinVersion << ":V1.0.6.0\n" /* must not be changed */ << CA_Manufacturer << ":SyncEvolution\n" << CA_Description << ":SyncEvolution Synthesis DB Plugin\n" << Plugin_DS_Data_Str << ":no\n" << Plugin_DS_Data_Key << ":yes\n" << CA_ItemAsKey << ":yes\n" << Plugin_DS_Blob << ((source && source->getOperations().m_readBlob) ? ":yes\n" : ":no\n"); if (source && source->getOperations().m_loadAdminData) { s << Plugin_DS_Admin << ":yes\n"; if (source && !source->getOperations().m_readNextMapItem) { s << CA_ResumeSupported << ":no\n"; } } *mCapabilities= StrAlloc(s.str().c_str()); SE_LOG_DEBUG(NULL, "Module_Capabilities:\n%s", *mCapabilities); return LOCERR_OK; } /* Module_Capabilities */ extern "C" TSyError SyncEvolution_Module_PluginParams( CContext mContext, cAppCharP mConfigParams, CVersion engineVersion ) { SyncSource *source = MoC(mContext); SE_LOG_DEBUG(source ? source->getDisplayName() : "", "Module_PluginParams\n Engine=%08lX\n %s", (long)engineVersion, mConfigParams); /*return LOCERR_CFGPARSE;*/ /* if there are unsupported params */ return LOCERR_OK; } /* Module_PluginParams */ /* Dispose the memory of the module context */ extern "C" void SyncEvolution_Module_DisposeObj( CContext mContext, void* memory ) { StrDispose(memory); } extern "C" TSyError SyncEvolution_Module_DeleteContext( CContext mContext ) { SyncSource *source = MoC(mContext); SE_LOG_DEBUG(NULL, "Module_DeleteContext %s", source ? source->getName().c_str() : "'session'"); if (source) { source->popSynthesisAPI(); } return LOCERR_OK; } /* will be casted to the SyncContext* structure */ static SyncContext* SeC( CContext sContext ) { return (SyncContext*)sContext; } /* Create a context for a new session. Maps to the existing SyncContext. */ extern "C" TSyError SyncEvolution_Session_CreateContext( CContext *sContext, cAppCharP sessionName, DB_Callback sCB ) { *sContext = (CContext)SyncContext::findContext(sessionName); SE_LOG_DEBUG(NULL, "Session_CreateContext '%s' %s", sessionName, *sContext ? "found" : "not found"); if (*sContext) { return LOCERR_OK; } else { return DB_NotFound; } } /* Session_CreateContext */ /* ----- "script-like" ADAPT --------- */ extern "C" TSyError SyncEvolution_Session_AdaptItem( CContext sContext, appCharP *sItemData1, appCharP *sItemData2, appCharP *sLocalVars, uInt32 sIdentifier ) { /**** CAN BE ADAPTED BY USER ****/ // SyncContext* sc= SeC( sContext ); SE_LOG_DEBUG(NULL, "Session_AdaptItem '%s' '%s' '%s' id=%d", *sItemData1,*sItemData2,*sLocalVars, sIdentifier); return LOCERR_OK; } /* Session_AdaptItem */ /* Check the database entry of and return its nonce string */ extern "C" TSyError SyncEvolution_Session_CheckDevice( CContext sContext, cAppCharP aDeviceID, appCharP *sDevKey, appCharP *nonce ) { SyncContext *sc = SeC(sContext); if (!sc) { return LOCERR_WRONGUSAGE; } TSyError res = LOCERR_OK; sc->setSyncDeviceID(aDeviceID); string id = sc->getRemoteDevID(); if (id.empty()) { sc->setRemoteDevID(aDeviceID); sc->flush(); } else if (id != aDeviceID) { // We are using the wrong configuration?! Refuse to continue. SE_LOG_ERROR(NULL, "remote device ID '%s' in config does not match the one from the peer '%s' - incorrect configuration?!", id.c_str(), aDeviceID); res = DB_Forbidden; } *sDevKey= StrAlloc(aDeviceID); *nonce = StrAlloc(sc->getNonce().c_str()); SE_LOG_DEBUG(NULL, "Session_CheckDevice dev='%s' nonce='%s' res=%d", *sDevKey, *nonce, res); return res; } /* Session_CheckDevice */ /* Get a new nonce from the database. If this returns an error, the SyncML engine * will create its own nonce. */ extern "C" TSyError SyncEvolution_Session_GetNonce( CContext sContext, appCharP *nonce ) { return DB_NotFound; } /* Session_GetNonce */ /* Save the new nonce (which will be expected to be returned * in the next session for this device */ extern "C" TSyError SyncEvolution_Session_SaveNonce( CContext sContext, cAppCharP nonce ) { SyncContext *sc = SeC(sContext); if (!sc) { return LOCERR_WRONGUSAGE; } SE_LOG_DEBUG(NULL, "Session_SaveNonce nonce='%s'", nonce); sc->setNonce(nonce); sc->flush(); return LOCERR_OK; } /* Session_SaveNonce */ /* Save the device info of */ extern "C" TSyError SyncEvolution_Session_SaveDeviceInfo( CContext sContext, cAppCharP aDeviceInfo ) { SyncContext *sc = SeC(sContext); if (!sc) { return LOCERR_WRONGUSAGE; } SE_LOG_DEBUG(NULL, "Session_SaveDeviceInfo info='%s'", aDeviceInfo ); sc->setDeviceData(aDeviceInfo); sc->flush(); return LOCERR_OK; } /* Session_SaveDeviceInfo */ /* Get the plugin's DB time */ extern "C" TSyError SyncEvolution_Session_GetDBTime( CContext sContext, appCharP *currentDBTime ) { return DB_NotFound; } /* Session_GetDBTime */ /* Return: Password_ClrText_IN 'SessionLogin' will get clear text password * Password_ClrText_OUT " must return clear text password * Password_MD5_OUT " must return MD5 coded password * Password_MD5_Nonce_IN " will get MD5B64(MD5B64(user:pwd):nonce) */ extern "C" sInt32 SyncEvolution_Session_PasswordMode( CContext sContext ) { return Password_ClrText_OUT; } /* Session_PasswordMode */ /* Make login */ extern "C" TSyError SyncEvolution_Session_Login( CContext sContext, cAppCharP sUsername, appCharP *sPassword, appCharP *sUsrKey ) { SyncContext *sc = SeC(sContext); if (!sc) { return LOCERR_WRONGUSAGE; } TSyError res = DB_Forbidden; UserIdentity id = sc->getSyncUser(); Credentials cred = IdentityProviderCredentials(id, sc->getSyncPassword()); const std::string &user = cred.m_username; const std::string &password = cred.m_password; if (user.empty() && password.empty()) { // nothing to check, accept peer res = LOCERR_OK; } else if (user == sUsername) { *sPassword=StrAlloc(password.c_str()); res = LOCERR_OK; } SE_LOG_DEBUG(NULL, "Session_Login usr='%s' expected user='%s' res=%d", sUsername, user.c_str(), res); return res; } /* Session_Login */ /* Make logout */ extern "C" TSyError SyncEvolution_Session_Logout( CContext sContext ) { return LOCERR_OK; } /* Session_Logout */ extern "C" void SyncEvolution_Session_DisposeObj( CContext sContext, void* memory ) { StrDispose ( memory ); } /* Session_DisposeObj */ /* Can be implemented empty, if no action is required */ extern "C" void SyncEvolution_Session_ThreadMayChangeNow( CContext sContext ) { } /* Session_ThreadMayChangeNow */ /* This routine is implemented for debug purposes only and will NOT BE CALLED by the * SyncML engine. Can be implemented empty, if not needed */ extern "C" void SyncEvolution_Session_DispItems( CContext sContext, bool allFields, cAppCharP specificItem ) { } /* Session_DispItems */ /* Delete a session context */ extern "C" TSyError SyncEvolution_Session_DeleteContext( CContext sContext ) { return LOCERR_OK; } /* Session_DeleteContext */ /* ----------------------------------------------------------------- */ /* This is an example, how a context could be structured */ /* will be casted to the SyncSource structure */ static SyncSource *DBC( CContext aContext ) { return (SyncSource *)aContext; } /* -- OPEN ----------------------------------------------------------------------- */ /** * looks up datasource and uses pointer to it as context * * @param aContextName name of previously instantiated SyncSource * @retval aContext the corresponding SyncSource */ extern "C" TSyError SyncEvolution_CreateContext( CContext *aContext, cAppCharP aContextName, DB_Callback aCB, cAppCharP sDevKey, cAppCharP sUsrKey ) { TSyError err = LOCERR_WRONGUSAGE; SyncSource *source = SyncContext::findSource(aContextName); if (source) { source->pushSynthesisAPI(aCB); *aContext = (CContext)source; err = LOCERR_OK; } SE_LOG_DEBUG(source ? source->getDisplayName() : "", "'%s' dev='%s' usr='%s' err=%d", aContextName, sDevKey, sUsrKey, err); return err; } extern "C" uInt32 SyncEvolution_ContextSupport( CContext aContext, cAppCharP aContextRules ) { SyncSource *source = DBC( aContext ); if (!source) { return LOCERR_WRONGUSAGE; } SE_LOG_DEBUG(source->getDisplayName(), "ContextSupport %s", aContextRules); return 0; } extern "C" uInt32 SyncEvolution_FilterSupport( CContext aContext, cAppCharP aFilterRules ) { /**** CAN BE ADAPTED BY USER ****/ SyncSource *source = DBC( aContext ); if (!source) { return LOCERR_WRONGUSAGE; } SE_LOG_DEBUG(source->getDisplayName(), "FilterSupport %s", aFilterRules); return 0; } /* FilterSupport */ /* -- ADMINISTRATION ------------------------------------------------------------ */ extern "C" TSyError SyncEvolution_LoadAdminData( CContext aContext, cAppCharP aLocDB, cAppCharP aRemDB, appCharP *adminData ) { SyncSource *source = DBC( aContext ); if (!source) { return LOCERR_WRONGUSAGE; } TSyError res = source->getOperations().m_loadAdminData(*source, aLocDB, aRemDB, adminData); SE_LOG_DEBUG(source->getDisplayName(), "LoadAdminData '%s' '%s', '%s' res=%d", aLocDB, aRemDB, *adminData ? *adminData : "", res); return res; } /* LoadAdminData */ extern "C" TSyError SyncEvolution_SaveAdminData( CContext aContext, cAppCharP adminData ) { SyncSource *source = DBC( aContext ); if (!source) { return LOCERR_WRONGUSAGE; } TSyError res = source->getOperations().m_saveAdminData(*source, adminData); SE_LOG_DEBUG(source->getDisplayName(), "SaveAdminData '%s' res=%d", adminData, res); return res; } /* SaveAdminData */ extern "C" bool SyncEvolution_ReadNextMapItem( CContext aContext, MapID mID, bool aFirst ) { SyncSource *source = DBC( aContext ); if (!source) { return LOCERR_WRONGUSAGE; } bool res = false; try { // always reset mID, just in case that caller expects it or // operation doesn't do it correctly mID->localID = NULL; mID->remoteID = NULL; mID->ident = 0; mID->flags = 0; if (source->getOperations().m_readNextMapItem) { res = source->getOperations().m_readNextMapItem(mID, aFirst); } } catch (...) { res = source->handleException(); } SE_LOG_DEBUG(source->getDisplayName(), "ReadNextMapItem '%s' + %x = '%s' + %d first=%s res=%d", res ? NullPtrCheck(mID->localID) : "(none)", res ? mID->ident : 0, res ? NullPtrCheck(mID->remoteID) : "(none)", res ? mID->flags : 0, aFirst ? "yes" : "no", res); return res; } /* ReadNextMapItem */ extern "C" TSyError SyncEvolution_InsertMapItem( CContext aContext, cMapID mID ) { SyncSource *source = DBC( aContext ); if (!source) { return LOCERR_WRONGUSAGE; } TSyError res = source->getOperations().m_insertMapItem(*source, mID); SE_LOG_DEBUG(source->getDisplayName(), "InsertMapItem '%s' + %x = '%s' + %x res=%d", NullPtrCheck(mID->localID), mID->ident, NullPtrCheck(mID->remoteID), mID->flags, res); return res; } /* InsertMapItem */ extern "C" TSyError SyncEvolution_UpdateMapItem( CContext aContext, cMapID mID ) { SyncSource *source = DBC( aContext ); if (!source) { return LOCERR_WRONGUSAGE; } TSyError res = source->getOperations().m_updateMapItem(*source, mID); SE_LOG_DEBUG(source->getDisplayName(), "UpdateMapItem '%s' + %x = '%s' + %x, res=%d", mID->localID, mID->ident, mID->remoteID, mID->flags, res); return res; } /* UpdateMapItem */ extern "C" TSyError SyncEvolution_DeleteMapItem( CContext aContext, cMapID mID ) { SyncSource *source = DBC( aContext ); if (!source) { return LOCERR_WRONGUSAGE; } TSyError res = source->getOperations().m_deleteMapItem(*source, mID); SE_LOG_DEBUG(source->getDisplayName(), "DeleteMapItem '%s' + %x = '%s' + %x res=%d", mID->localID, mID->ident, mID->remoteID, mID->flags, res); return res; } /* DeleteMapItem */ /* -- GENERAL -------------------------------------------------------------------- */ extern "C" void SyncEvolution_DisposeObj( CContext aContext, void* memory ) { free( memory ); } /* DisposeObj */ extern "C" void SyncEvolution_ThreadMayChangeNow( CContext aContext ) { } /* ThreadMayChangeNow */ extern "C" void SyncEvolution_WriteLogData( CContext aContext, cAppCharP logData ) { } /* WriteLogData */ /* This routine is implemented for debug purposes only and will NOT BE CALLED by the * SyncML engine. Can be implemented empty, if not needed */ extern "C" void SyncEvolution_DispItems( CContext aContext, bool allFields, cAppCharP specificItem ) { } /* DispItems */ /* ----- "script-like" ADAPT --------- */ extern "C" TSyError SyncEvolution_AdaptItem( CContext aContext, appCharP *aItemData1, appCharP *aItemData2, appCharP *aLocalVars, uInt32 aIdentifier ) { /**** CAN BE ADAPTED BY USER ****/ SyncSource *source = DBC( aContext ); if (!source) { return LOCERR_WRONGUSAGE; } SE_LOG_DEBUG(source->getDisplayName(), "AdaptItem '%s' '%s' '%s' id=%d", *aItemData1, *aItemData2, *aLocalVars, aIdentifier); return LOCERR_OK; } /* AdaptItem */ /* -- READ ---------------------------------------------------------------------- */ /** * Start data access here and complete it in SyncEvolution_EndDataWrite(). */ extern "C" TSyError SyncEvolution_StartDataRead( CContext aContext, cAppCharP lastToken, cAppCharP resumeToken ) { SyncSource *source = DBC( aContext ); if (!source) { return LOCERR_WRONGUSAGE; } TSyError res = source->getOperations().m_startDataRead(*source, lastToken, resumeToken); SE_LOG_DEBUG(source->getDisplayName(), "StartDataRead last='%s' resume='%s' res=%d", lastToken, resumeToken, res); return res; } extern "C" TSyError SyncEvolution_ReadNextItemAsKey( CContext aContext, ItemID aID, KeyH aItemKey, sInt32* aStatus, bool aFirst ) { /**** CAN BE ADAPTED BY USER ****/ SyncSource *source = DBC( aContext ); if (!source) { return LOCERR_WRONGUSAGE; } *aStatus = 0; memset(aID, 0, sizeof(*aID)); TSyError res = source->getOperations().m_readNextItem(*source, aID, aStatus, aFirst); SE_LOG_DEBUG(source->getDisplayName(), "ReadNextItemAsKey aStatus=%d aID=(%s,%s) res=%d", *aStatus, aID->item, aID->parent, res); return res; } extern "C" TSyError SyncEvolution_ReadItemAsKey( CContext aContext, cItemID aID, KeyH aItemKey ) { SyncSource *source = DBC( aContext ); if (!source) { return LOCERR_WRONGUSAGE; } TSyError res = source->getOperations().m_readItemAsKey(*source, aID, aItemKey); SE_LOG_DEBUG(source->getDisplayName(), "ReadItemAsKey aID=(%s,%s) res=%d", aID->item, aID->parent, res); return res; } extern "C" sysync::TSyError SyncEvolution_ReadBlob(CContext aContext, cItemID aID, cAppCharP aBlobID, appPointer *aBlkPtr, memSize *aBlkSize, memSize *aTotSize, bool aFirst, bool *aLast) { SyncSource *source = DBC( aContext ); if (!source) { return LOCERR_WRONGUSAGE; } TSyError res; if (source->getOperations().m_readBlob) { try { size_t blksize = aBlkSize ? static_cast(*aBlkSize) : 0, totsize = aTotSize ? static_cast(*aTotSize) : 0; /* Another conversion between memSize and size_t to make s390 happy */ res = source->getOperations().m_readBlob(aID, aBlobID, (void **)aBlkPtr, aBlkSize ? &blksize : NULL, aTotSize ? &totsize : NULL, aFirst, aLast); if (aBlkSize) { *aBlkSize = blksize; } if (aTotSize) { *aTotSize = totsize; } } catch (...) { res = source->handleException(); } } else { res = LOCERR_NOTIMP; } SE_LOG_DEBUG(source->getDisplayName(), "ReadBlob aID=(%s,%s) aBlobID=(%s) aBlkPtr=%p aBlkSize=%lu aTotSize=%lu aFirst=%s aLast=%s res=%d", aID->item,aID->parent, aBlobID, aBlkPtr, aBlkSize ? (unsigned long)*aBlkSize : 0, aTotSize ? (unsigned long)*aTotSize : 0, aFirst? "true" : "false", *aLast ? "true" : "false", res); return res; } /* ReadBlob */ extern "C" TSyError SyncEvolution_EndDataRead( CContext aContext ) { SyncSource *source = DBC( aContext ); if (!source) { return LOCERR_WRONGUSAGE; } TSyError res = source->getOperations().m_endDataRead(*source); SE_LOG_DEBUG(source->getDisplayName(), "EndDataRead res=%d", res); return res; } /* -- WRITE --------------------------------------------------------------------- */ extern "C" TSyError SyncEvolution_StartDataWrite( CContext aContext ) { SyncSource *source = DBC( aContext ); if (!source) { return LOCERR_WRONGUSAGE; } SE_LOG_DEBUG(source->getDisplayName(), "StartDataWrite"); return LOCERR_OK; } extern "C" TSyError SyncEvolution_InsertItemAsKey( CContext aContext, KeyH aItemKey, ItemID newID ) { /**** CAN BE ADAPTED BY USER ****/ SyncSource *source = DBC( aContext ); if (!source) { return LOCERR_WRONGUSAGE; } TSyError res = source->getOperations().m_insertItemAsKey(*source, aItemKey, newID); SE_LOG_DEBUG(source->getDisplayName(), "InsertItemAsKey res=%d\n", res); return res; } extern "C" TSyError SyncEvolution_UpdateItemAsKey( CContext aContext, KeyH aItemKey, cItemID aID, ItemID updID ) { SyncSource *source = DBC( aContext ); if (!source) { return LOCERR_WRONGUSAGE; } TSyError res = source->getOperations().m_updateItemAsKey(*source, aItemKey, aID, updID); SE_LOG_DEBUG(source->getDisplayName(), "aID=(%s,%s) res=%d", aID->item,aID->parent, res); return res; } extern "C" TSyError SyncEvolution_MoveItem( CContext aContext, cItemID aID, cAppCharP newParID ) { SyncSource *source = DBC( aContext ); if (!source) { return LOCERR_WRONGUSAGE; } SE_LOG_DEBUG(source->getDisplayName(), "MoveItem aID=(%s,%s) => (%s,%s)", aID->item,aID->parent, aID->item,newParID); return LOCERR_NOTIMP; } extern "C" TSyError SyncEvolution_DeleteItem( CContext aContext, cItemID aID ) { SyncSource *source = DBC( aContext ); if (!source) { return LOCERR_WRONGUSAGE; } TSyError res = source->getOperations().m_deleteItem(*source, aID); SE_LOG_DEBUG(source->getDisplayName(), "DeleteItem aID=(%s,%s) res=%d", aID->item, aID->parent, res); return res; } extern "C" TSyError SyncEvolution_FinalizeLocalID( CContext aContext, cItemID aID, ItemID updID ) { return LOCERR_NOTIMP; } extern "C" TSyError SyncEvolution_DeleteSyncSet( CContext aContext ) { SyncSource *source = DBC( aContext ); if (!source) { return LOCERR_WRONGUSAGE; } SE_LOG_DEBUG(source->getDisplayName(), "DeleteSyncSet not implemented"); return LOCERR_NOTIMP; } extern "C" TSyError SyncEvolution_WriteBlob(CContext aContext, cItemID aID, cAppCharP aBlobID, appPointer aBlkPtr, memSize aBlkSize, memSize aTotSize, bool aFirst, bool aLast) { SyncSource *source = DBC( aContext ); if (!source) { return LOCERR_WRONGUSAGE; } TSyError res; if (source->getOperations().m_writeBlob) { try { res = source->getOperations().m_writeBlob(aID, aBlobID, aBlkPtr, aBlkSize, aTotSize, aFirst, aLast); } catch (...) { res = source->handleException(); } } else { res = LOCERR_NOTIMP; } SE_LOG_DEBUG(source->getDisplayName(), "WriteBlob aID=(%s,%s) aBlobID=(%s) aBlkPtr=%p aBlkSize=%lu aTotSize=%lu aFirst=%s aLast=%s res=%d", aID->item,aID->parent, aBlobID, aBlkPtr, (unsigned long)aBlkSize, (unsigned long)aTotSize, aFirst ? "true" : "false", aLast ? "true" : "false", res); return res; } /* WriteBlob */ extern "C" TSyError SyncEvolution_DeleteBlob( CContext aContext, cItemID aID, cAppCharP aBlobID ) { SyncSource *source = DBC( aContext ); if (!source) { return LOCERR_WRONGUSAGE; } TSyError res = source->getOperations().m_deleteBlob(*source, aID, aBlobID); SE_LOG_DEBUG(source->getDisplayName(), "DeleteBlob aID=(%s,%s) aBlobID=(%s) res=%d", aID->item,aID->parent, aBlobID, res); return res; } /* DeleteBlob */ extern "C" TSyError SyncEvolution_EndDataWrite( CContext aContext, bool success, appCharP *newToken ) { SyncSource *source = DBC( aContext ); if (!source) { return LOCERR_WRONGUSAGE; } TSyError res = source->getOperations().m_endDataWrite(*source, success, newToken); SE_LOG_DEBUG(source->getDisplayName(), "EndDataWrite %s '%s' res=%d", success ? "COMMIT":"ROLLBACK", *newToken, res); return res; } /* ----------------------------------- */ extern "C" TSyError SyncEvolution_DeleteContext( CContext aContext ) { /**** CAN BE ADAPTED BY USER ****/ SyncSource *source = DBC( aContext ); if (!source) { return LOCERR_WRONGUSAGE; } SE_LOG_DEBUG(source->getDisplayName(), "DeleteContext"); source->popSynthesisAPI(); return LOCERR_OK; } SE_END_CXX syncevolution_1.4/src/syncevo/SynthesisEngine.cpp000066400000000000000000000233131230021373600224600ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include SE_BEGIN_CXX void SharedEngine::Connect(const string &aEngineName, sysync::CVersion aPrgVersion, sysync::uInt16 aDebugFlags) { sysync::TSyError err = m_engine->Connect(aEngineName, aPrgVersion, aDebugFlags); if (err) { SE_THROW_EXCEPTION_STATUS(BadSynthesisResult, std::string("cannot connect to engine '") + aEngineName + "'", static_cast(err)); } } void SharedEngine::Disconnect() { sysync::TSyError err = m_engine->Disconnect(); if (err) { SE_THROW_EXCEPTION_STATUS(BadSynthesisResult, "cannot disconnect engine", static_cast(err)); } } void SharedEngine::InitEngineXML(const string &aConfigXML) { sysync::TSyError err = m_engine->InitEngineXML(aConfigXML.c_str()); if (err) { SE_THROW_EXCEPTION_STATUS(BadSynthesisResult, "Synthesis XML config parser error", static_cast(err)); } } class FreeEngineItem { SharedEngine m_engine; public: FreeEngineItem(const SharedEngine &engine) : m_engine(engine) {} void operator () (sysync::KeyH key) { m_engine.get()->CloseKey(key); } void operator () (sysync::SessionH session) { m_engine.get()->CloseSession(session); } }; SharedSession SharedEngine::OpenSession(const string &aSessionID) { sysync::SessionH sessionH = NULL; sysync::TSyError err = m_engine->OpenSession(sessionH, 0, aSessionID.empty() ? NULL : aSessionID.c_str()); if (err) { SE_THROW_EXCEPTION_STATUS(BadSynthesisResult, "opening session failed", static_cast(err)); } return SharedSession(sessionH, FreeEngineItem(*this)); } SharedKey SharedEngine::OpenSessionKey(SharedSession &aSessionH) { sysync::KeyH key; sysync::TSyError err = m_engine->OpenSessionKey(aSessionH.get(), key, 0); if (err) { SE_THROW_EXCEPTION_STATUS(BadSynthesisResult, "opening session key failed", static_cast(err)); } return SharedKey(key, FreeEngineItem(*this)); } void SharedEngine::SessionStep(const SharedSession &aSessionH, sysync::uInt16 &aStepCmd, sysync::TEngineProgressInfo *aInfoP) { sysync::TSyError err = m_engine->SessionStep(aSessionH.get(), aStepCmd, aInfoP); if (err) { SE_THROW_EXCEPTION_STATUS(BadSynthesisResult, "proceeding with session failed", static_cast(err)); } } class FreeSyncMLBuffer { SharedEngine m_engine; SharedSession m_session; bool m_forSend; size_t m_size; public: FreeSyncMLBuffer(const SharedEngine &engine, const SharedSession &session, bool forSend, size_t size) : m_engine(engine), m_session(session), m_forSend(forSend), m_size(size) {} void operator () (char *buffer) { m_engine.get()->RetSyncMLBuffer(m_session.get(), m_forSend, m_size); } }; SharedBuffer SharedEngine::GetSyncMLBuffer(const SharedSession &aSessionH, bool aForSend) { sysync::appPointer buffer; sysync::memSize bufSize; sysync::TSyError err = m_engine->GetSyncMLBuffer(aSessionH.get(), aForSend, buffer, bufSize); if (err) { SE_THROW_EXCEPTION_STATUS(BadSynthesisResult,"acquiring SyncML buffer failed", static_cast(err)); } return SharedBuffer((char *)buffer, (size_t)bufSize, FreeSyncMLBuffer(*this, aSessionH, aForSend, bufSize)); } void SharedEngine::WriteSyncMLBuffer(const SharedSession &aSessionH, const char *data, size_t len) { sysync::TSyError err = m_engine->WriteSyncMLBuffer(aSessionH.get(), const_cast(data), len); if (err) { SE_THROW_EXCEPTION_STATUS(BadSynthesisResult, "writing SyncML buffer failed", static_cast(err)); } } SharedKey SharedEngine::OpenKeyByPath(const SharedKey &aParentKeyH, const string &aPath, bool noThrow) { sysync::KeyH key = NULL; sysync::TSyError err = m_engine->OpenKeyByPath(key, aParentKeyH.get(), aPath.c_str(), 0); if (err && noThrow) { return SharedKey(); } if (err) { string what = "opening key "; what += aPath; if (err == sysync::DB_NoContent) { SE_THROW_EXCEPTION(NoSuchKey, what); } else { SE_THROW_EXCEPTION_STATUS(BadSynthesisResult, what, static_cast(err)); } } return SharedKey(key, FreeEngineItem(*this)); } SharedKey SharedEngine::OpenSubkey(const SharedKey &aParentKeyH, sysync::sInt32 aID, bool noThrow) { sysync::KeyH key = NULL; sysync::TSyError err = m_engine->OpenSubkey(key, aParentKeyH.get(), aID, 0); if (err && noThrow) { return SharedKey(); } if (err) { string what = "opening sub key"; if (err == sysync::DB_NoContent) { SE_THROW_EXCEPTION(NoSuchKey, what); } else { SE_THROW_EXCEPTION_STATUS(BadSynthesisResult, what, static_cast(err)); } } return SharedKey(key, FreeEngineItem(*this)); } string SharedEngine::GetStrValue(const SharedKey &aKeyH, const string &aValName) { std::string s; sysync::TSyError err = m_engine->GetStrValue(aKeyH.get(), aValName.c_str(), s); if (err) { SE_THROW_EXCEPTION_STATUS(BadSynthesisResult, string("error reading value ") + aValName, static_cast(err)); } return s; } void SharedEngine::SetStrValue(const SharedKey &aKeyH, const string &aValName, const string &aValue) { sysync::TSyError err = m_engine->SetStrValue(aKeyH.get(), aValName.c_str(), aValue); if (err) { SE_THROW_EXCEPTION_STATUS(BadSynthesisResult, string("error writing value ") + aValName, static_cast(err)); } } sysync::sInt32 SharedEngine::GetInt32Value(const SharedKey &aKeyH, const string &aValName) { sysync::sInt32 v; sysync::TSyError err = m_engine->GetInt32Value(aKeyH.get(), aValName.c_str(), v); if (err) { SE_THROW_EXCEPTION_STATUS(BadSynthesisResult, string("error reading value ") + aValName, static_cast(err)); } return v; } void SharedEngine::SetInt32Value(const SharedKey &aKeyH, const string &aValName, sysync::sInt32 aValue) { sysync::TSyError err = m_engine->SetInt32Value(aKeyH.get(), aValName.c_str(), aValue); if (err) { SE_THROW_EXCEPTION_STATUS(BadSynthesisResult, string("error writing value ") + aValName, static_cast(err)); } } void SharedEngine::doDebug(Logger::Level level, const char *prefix, const char *file, int line, const char *function, const char *format, va_list args) { std::string str = StringPrintfV(format, args); SySyncDebugPuts(m_engine->fCI, file, line, function, level <= Logger::ERROR ? DBG_ERROR : level <= Logger::INFO ? DBG_HOT : 0, prefix, str.c_str()); } sysync::TSyError SDKInterface::setValue(sysync::KeyH aItemKey, const std::string &field, const char *data, size_t datalen) { return this->ui.SetValue(this, aItemKey, field.c_str(), sysync::VALTYPE_TEXT, data, datalen); } sysync::TSyError SDKInterface::getValue(sysync::KeyH aItemKey, const std::string &field, SharedBuffer &data) { sysync::memSize len; TSyError res = this->ui.GetValue(this, aItemKey, field.c_str(), sysync::VALTYPE_TEXT, NULL, 0, &len); if (!res) { data = SharedBuffer(new char[len + 1], len); data[len] = 0; res = this->ui.GetValue(this, aItemKey, field.c_str(), sysync::VALTYPE_TEXT, data.get(), len + 1, &len); } return res; } SE_END_CXX syncevolution_1.4/src/syncevo/SynthesisEngine.h000066400000000000000000000131471230021373600221310ustar00rootroot00000000000000/* * Copyright (C) 2005-2008 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_SYNTHESISENGINE #define INCL_SYNTHESISENGINE #include #include #include #include #include // TODO: remove dependency on header file. // Currently required because shared_ptr // checks that type is completely defined. #include #include #include #include #include #include #include SE_BEGIN_CXX typedef boost::shared_ptr SharedSession; typedef boost::shared_ptr SharedKey; class SharedBuffer : public boost::shared_array { size_t m_size; public: SharedBuffer() : m_size(0) {} /** transfers ownership */ explicit SharedBuffer(char *p, size_t size): boost::shared_array(p), m_size(size) {} /** transfers ownership with custom destructor */ template SharedBuffer(char *p, size_t size, const D &d) : boost::shared_array(p, d), m_size(size) {} /** copies memory */ explicit SharedBuffer(const char *p, size_t size): boost::shared_array(new char [size]), m_size(size) { memcpy(get(), p, size); } size_t size() const { return m_size; } }; /** * Wrapper around a class which is derived from * TEngineModuleBase. Provides a C++ * interface which uses Boost smart pointers and * exceptions derived from std::runtime_error to track * resources/report errors. */ class SharedEngine { boost::shared_ptr m_engine; public: SharedEngine(sysync::TEngineModuleBase *engine = NULL): m_engine(engine) {} sysync::TEngineModuleBase *get() { return m_engine.get(); } void Connect(const string &aEngineName, sysync::CVersion aPrgVersion = 0, sysync::uInt16 aDebugFlags = 0); void Disconnect(); void InitEngineXML(const string &aConfigXML); SharedSession OpenSession(const string &aSessionID); SharedKey OpenSessionKey(SharedSession &aSessionH); void SessionStep(const SharedSession &aSessionH, sysync::uInt16 &aStepCmd, sysync::TEngineProgressInfo *aInfoP = NULL); SharedBuffer GetSyncMLBuffer(const SharedSession &aSessionH, bool aForSend); void WriteSyncMLBuffer(const SharedSession &aSessionH, const char *data, size_t len); SharedKey OpenKeyByPath(const SharedKey &aParentKeyH, const string &aPath, bool noThrow = false); SharedKey OpenSubkey(const SharedKey &aParentKeyH, sysync::sInt32 aID, bool noThrow = false); string GetStrValue(const SharedKey &aKeyH, const string &aValName); void SetStrValue(const SharedKey &aKeyH, const string &aValName, const string &aValue); sysync::sInt32 GetInt32Value(const SharedKey &aKeyH, const string &aValName); void SetInt32Value(const SharedKey &aKeyH, const string &aValName, sysync::sInt32 aValue); void doDebug(Logger::Level level, const char *prefix, const char *file, int line, const char *function, const char *format, va_list args); }; /** * thrown when a function returns a non-okay error code */ class BadSynthesisResult : public StatusException { public: BadSynthesisResult(const std::string &file, int line, const string &what, sysync::TSyErrorEnum result) : StatusException(file, line, what, SyncMLStatus(result)) {} sysync::TSyErrorEnum result() const { return sysync::TSyErrorEnum(syncMLStatus()); } }; /** * thrown when a key cannot be opened because it doesn't exist */ class NoSuchKey : public BadSynthesisResult { public: NoSuchKey(const std::string &file, int line, const string &what) : BadSynthesisResult(file, line, what, sysync::DB_NoContent) {} }; /** * A class which wraps the underlying sysync::SDK_InterfaceType * methods. Any sysync::SDK_InterfaceType pointer can be casted * into this class because this class doesn't add any virtual * functions or data. */ struct SDKInterface : public sysync::SDK_InterfaceType { sysync::TSyError setValue(sysync::KeyH aItemKey, const std::string &field, const char *data, size_t datalen); sysync::TSyError getValue(sysync::KeyH aItemKey, const std::string &field, SharedBuffer &data); }; SE_END_CXX #endif // INCL_SYNTHESISENGINE syncevolution_1.4/src/syncevo/ThreadSupport.h000066400000000000000000000075211230021373600216150ustar00rootroot00000000000000/* * Copyright (C) 2013 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_THREAD_SUPPORT # define INCL_THREAD_SUPPORT #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifdef HAVE_GLIB # include #else # define GLIB_CHECK_VERSION(major, minor, revision) 0 #endif #include #include SE_BEGIN_CXX // The revised threading API we use below was introduced in glib 2.3.2 // The fallback for older glib is to not offer thread support. // The classes are still defined, they just don't do anything. #if GLIB_CHECK_VERSION(2, 32, 0) # define HAVE_THREAD_SUPPORT /** * Core building block for mutices. */ template class MutexTemplate { protected: M m_mutex; public: /** * Created when locking the mutex. When the last copy of it * gets destroyed, the mutex gets unlocked again. */ class Guard : private boost::shared_ptr { Guard(M *mutex) throw () : boost::shared_ptr(mutex, _unlock) {} friend class MutexTemplate; public: Guard() throw() {} void unlock() throw() { boost::shared_ptr::reset(); } }; /** * Lock the mutex and return a handle that'll automatically * unlock the mutex when the last copy gets destroyed. */ Guard lock() throw () { _lock(&m_mutex); return Guard(&m_mutex); } M *get() { return &m_mutex; } operator M * () { return &m_mutex; } }; /** * Initializes a mutex which was allocated dynamically * on the heap or stack and frees allocated resources * when done. It's an error to free a locked mutex. */ template class DynMutexTemplate : public MutexTemplate { public: DynMutexTemplate() { _init(MutexTemplate::get()); } ~DynMutexTemplate() { _clear(MutexTemplate::get()); } }; typedef MutexTemplate Mutex; typedef DynMutexTemplate DynMutex; typedef MutexTemplate RecMutex; typedef DynMutexTemplate DynRecMutex; class Cond { GCond m_cond; public: Cond() { g_cond_init(&m_cond); } ~Cond() { g_cond_clear(&m_cond); } void signal() { g_cond_signal(&m_cond); } template void wait(M &m) { g_cond_wait(&m_cond, m); } }; #else # undef HAVE_THREAD_SUPPORT /** * Fallback just to get code compiled. */ class DummyMutex { public: class Guard { public: void unlock() throw() {} }; Guard lock() throw() { return Guard(); } }; typedef DummyMutex Mutex; typedef DummyMutex DynMutex; typedef DummyMutex RecMutex; typedef DummyMutex RecDynMutex; class Cond { public: void signal() {} template void wait(M &m) {} }; #endif SE_END_CXX #endif // INCL_THREAD_SUPPORT syncevolution_1.4/src/syncevo/Timespec.h000066400000000000000000000060331230021373600205570ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_SYNCEVOLUTION_TIME # define INCL_SYNCEVOLUTION_TIME #include #include #include #include #include SE_BEGIN_CXX /** * Sub-second time stamps. Thin wrapper around timespec * and clock_gettime() (for monotonic time). Comparisons * assume normalized values (tv_nsec >= 0, < 1e9). Addition * and substraction produce normalized values, as long * as the result is positive. Substracting a - b where a < b * leads to an undefined result. */ class Timespec : public timespec { public: Timespec() { tv_sec = 0; tv_nsec = 0; } Timespec(time_t sec, long nsec) { tv_sec = sec; tv_nsec = nsec; } bool operator < (const Timespec &other) const { return tv_sec < other.tv_sec || (tv_sec == other.tv_sec && tv_nsec < other.tv_nsec); } bool operator > (const Timespec &other) const { return tv_sec > other.tv_sec || (tv_sec == other.tv_sec && tv_nsec > other.tv_nsec); } bool operator <= (const Timespec &other) const { return !(*this > other); } bool operator >= (const Timespec &other) const { return !(*this < other); } operator bool () const { return tv_sec || tv_nsec; } Timespec operator + (int seconds) const { return Timespec(tv_sec + seconds, tv_nsec); } Timespec operator - (int seconds) const { return Timespec(tv_sec - seconds, tv_nsec); } Timespec operator + (unsigned seconds) const { return Timespec(tv_sec + seconds, tv_nsec); } Timespec operator - (unsigned seconds) const { return Timespec(tv_sec - seconds, tv_nsec); } Timespec operator + (const Timespec &other) const; Timespec operator - (const Timespec &other) const; operator timeval () const { timeval res; res.tv_sec = tv_sec; res.tv_usec = tv_nsec / 1000; return res; } time_t seconds() const { return tv_sec; } long nsecs() const { return tv_nsec; } double duration() const { return (double)tv_sec + ((double)tv_nsec) / 1e9; } static Timespec monotonic() { Timespec res; clock_gettime(CLOCK_MONOTONIC, &res); return res; } static Timespec system() { Timespec res; clock_gettime(CLOCK_REALTIME, &res); return res; } }; SE_END_CXX #endif // INCL_SYNCEVOLUTION_TIME syncevolution_1.4/src/syncevo/TmpFile.cpp000066400000000000000000000065501230021373600207050ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include #include #include #include "TmpFile.h" TmpFile::TmpFile() : m_fd(-1), m_mapptr(0), m_mapsize(0) { } TmpFile::~TmpFile() { try { unmap(); close(); } catch (std::exception &x) { fprintf(stderr, "TmpFile::~TmpFile(): %s\n", x.what()); } catch (...) { fputs("TmpFile::~TmpFile(): unknown exception\n", stderr); } } void TmpFile::create() { gchar *filename = NULL; GError *error = NULL; if (m_fd >= 0 || m_mapptr || m_mapsize) { throw TmpFileException("TmpFile::create(): busy"); } m_fd = g_file_open_tmp(NULL, &filename, &error); if (error != NULL) { throw TmpFileException( std::string("TmpFile::create(): g_file_open_tmp(): ") + std::string(error->message)); } m_filename = filename; g_free(filename); } void TmpFile::map(void **mapptr, size_t *mapsize) { struct stat sb; if (m_mapptr || m_mapsize) { throw TmpFileException("TmpFile::map(): busy"); } if (m_fd < 0) { throw TmpFileException("TmpFile::map(): m_fd < 0"); } if (fstat(m_fd, &sb) != 0) { throw TmpFileException("TmpFile::map(): fstat()"); } m_mapptr = mmap(NULL, sb.st_size, PROT_READ|PROT_WRITE, MAP_PRIVATE, m_fd, 0); if (m_mapptr == MAP_FAILED) { m_mapptr = 0; throw TmpFileException("TmpFile::map(): mmap()"); } m_mapsize = sb.st_size; if (mapptr != NULL) { *mapptr = m_mapptr; } if (mapsize != NULL) { *mapsize = m_mapsize; } } void TmpFile::unmap() { if (m_mapptr && m_mapsize) { munmap(m_mapptr, m_mapsize); } m_mapsize = 0; m_mapptr = 0; } size_t TmpFile::moreData() const { if (m_fd >= 0) { struct stat sb; if (fstat(m_fd, &sb) != 0) { throw TmpFileException("TmpFile::map(): fstat()"); } if ((!m_mapptr && sb.st_size) || (sb.st_size > 0 && m_mapsize < (size_t)sb.st_size)) { return sb.st_size - m_mapsize; } } return 0; } void TmpFile::remove() { if (!m_filename.empty()) { unlink(m_filename.c_str()); m_filename.clear(); } } void TmpFile::close() { remove(); if (m_fd >= 0) { ::close(m_fd); m_fd = -1; } } pcrecpp::StringPiece TmpFile::stringPiece() { pcrecpp::StringPiece sp; if (!(m_mapptr && m_mapsize)) { map(); } sp.set(m_mapptr, static_cast (m_mapsize)); return sp; } syncevolution_1.4/src/syncevo/TmpFile.h000066400000000000000000000072031230021373600203460ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_SYNCEVOLUTION_TMPFILE #define INCL_SYNCEVOLUTION_TMPFILE #include #include #include /** * Exception class for TmpFile. */ class TmpFileException : public std::runtime_error { public: TmpFileException(const std::string &what) : std::runtime_error(what) { } }; /** * Class for handling temporary files, either read/write access * or memory mapped. * * Closing and removing a mapped file is supported by calling close() * after map(). */ class TmpFile { protected: int m_fd; void *m_mapptr; size_t m_mapsize; std::string m_filename; public: TmpFile(); virtual ~TmpFile(); /** * Create a temporary file. */ void create(); /** * Map a view of file and optionally return pointer and/or size. * * File should already have a correct size. * * @param mapptr Pointer to variable for mapped pointer. (can be NULL) * @param mapsize Pointer to variable for mapped size. (can be NULL) */ void map(void **mapptr = 0, size_t *mapsize = 0); /** * Unmap a view of file. */ void unmap(); /** * Returns amount of bytes not mapped into memory yet, zero if none. */ size_t moreData() const; /** * Remove the file. If the process crashes, the file will be removed, * but the process itself can still map and use the file content. */ void remove(); /** * Remove and close the file. * * Calling this after map() will make the file disappear from * filesystem but the mapping will be valid until unmapped or * instance of this class is destroyed. */ void close(); /** * Retrieve file name of the file. * * @return file name */ const std::string & filename() const { return m_filename; } /** * Retrieve descriptor of the file. * * @return descriptor */ int fd() { return m_fd; } /** * Size of the mapping. * * @return mapped size */ size_t size() const { return m_mapsize; } /** * Pointer to the mapping. * * @return pointer to the mapping */ operator void *() { return m_mapptr; } /** * @overload */ operator const void *() const { return m_mapptr; } /** * Retrieve pcrecpp::StringPiece object for the mapped view. * * @return pcrecpp::StringPiece of the mapped view */ pcrecpp::StringPiece stringPiece(); }; #endif // INCL_SYNCEVOLUTION_TMPFILE syncevolution_1.4/src/syncevo/TrackingSyncSource.cpp000066400000000000000000000200011230021373600231100ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include #include SE_BEGIN_CXX TrackingSyncSource::TrackingSyncSource(const SyncSourceParams ¶ms, int granularitySeconds) : TestingSyncSource(params) { boost::shared_ptr safeNode(new SafeConfigNode(params.m_nodes.getTrackingNode())); m_trackingNode.reset(new PrefixConfigNode("item-", safeNode)); m_metaNode = safeNode; m_operations.m_checkStatus = boost::bind(&TrackingSyncSource::checkStatus, this, _1); m_operations.m_isEmpty = boost::bind(&TrackingSyncSource::isEmpty, this); SyncSourceRevisions::init(this, this, granularitySeconds, m_operations); } void TrackingSyncSource::checkStatus(SyncSourceReport &changes) { // use the most reliable (and most expensive) method by default ChangeMode mode = CHANGES_FULL; // assume that we do a regular sync, with reusing stored information // if possible string oldRevision = m_metaNode->readProperty("databaseRevision"); if (!oldRevision.empty()) { string newRevision = databaseRevision(); SE_LOG_DEBUG(getDisplayName(), "old database revision '%s', new revision '%s'", oldRevision.c_str(), newRevision.c_str()); if (newRevision == oldRevision) { SE_LOG_DEBUG(getDisplayName(), "revisions match, no item changes"); mode = CHANGES_NONE; } } if (mode == CHANGES_FULL) { SE_LOG_DEBUG(getDisplayName(), "using full item scan to detect changes"); } detectChanges(*m_trackingNode, mode); // copy our item counts into the report changes.setItemStat(ITEM_LOCAL, ITEM_ADDED, ITEM_TOTAL, getNewItems().size()); changes.setItemStat(ITEM_LOCAL, ITEM_UPDATED, ITEM_TOTAL, getUpdatedItems().size()); changes.setItemStat(ITEM_LOCAL, ITEM_REMOVED, ITEM_TOTAL, getDeletedItems().size()); changes.setItemStat(ITEM_LOCAL, ITEM_ANY, ITEM_TOTAL, getAllItems().size()); } void TrackingSyncSource::beginSync(const std::string &lastToken, const std::string &resumeToken) { // use the most reliable (and most expensive) method by default ChangeMode mode = CHANGES_FULL; // resume token overrides the normal token; safe to ignore in most // cases and this detectChanges() is done independently of the // token, but let's do it right here anyway string token; if (!resumeToken.empty()) { token = resumeToken; } else { token = lastToken; } // slow sync if token is empty if (token.empty()) { SE_LOG_DEBUG(getDisplayName(), "slow sync or testing, do full item scan to detect changes"); mode = CHANGES_SLOW; } else { string oldRevision = m_metaNode->readProperty("databaseRevision"); if (!oldRevision.empty()) { string newRevision = databaseRevision(); SE_LOG_DEBUG(getDisplayName(), "old database revision '%s', new revision '%s'", oldRevision.c_str(), newRevision.c_str()); if (newRevision == oldRevision) { SE_LOG_DEBUG(getDisplayName(), "revisions match, no item changes"); mode = CHANGES_NONE; } // Reset old revision. If anything goes wrong, then we // don't want to rely on a possibly incorrect optimization. m_metaNode->setProperty("databaseRevision", ""); m_metaNode->flush(); } } if (mode == CHANGES_FULL) { SE_LOG_DEBUG(getDisplayName(), "using full item scan to detect changes"); } bool forceSlowSync = detectChanges(*m_trackingNode, mode); if (forceSlowSync) { // tell engine that we need a slow sync SE_THROW_EXCEPTION_STATUS(StatusException, "change detection incomplete, must do slow sync", STATUS_SLOW_SYNC_508); } } std::string TrackingSyncSource::endSync(bool success) { // store changes persistently flush(); if (success) { string updatedRevision = databaseRevision(); m_metaNode->setProperty("databaseRevision", updatedRevision); // flush both nodes, just in case; in practice, the properties // end up in the same file and only get flushed once m_trackingNode->flush(); m_metaNode->flush(); } else { // The Synthesis docs say that we should rollback in case of // failure. Cannot do that for data, so lets at least keep // the revision map unchanged. } // no token handling at the moment (not needed for clients): // return a non-empty token to distinguish an incremental // sync from a slow sync in beginSync() return "1"; } TrackingSyncSource::InsertItemResult TrackingSyncSource::continueInsertItem(const boost::function &check, const std::string &luid) { InsertItemResult res = check(); if (res.m_state == ITEM_AGAIN) { // Delay updating the revision. res.m_continue = InsertItemResult::Continue_t(boost::bind(&TrackingSyncSource::continueInsertItem, this, res.m_continue, luid)); } else if (res.m_state != ITEM_NEEDS_MERGE) { updateRevision(*m_trackingNode, luid, res.m_luid, res.m_revision); } return res; } TrackingSyncSource::InsertItemResult TrackingSyncSource::doInsertItem(const std::string &luid, const std::string &item, bool raw) { // insertItem() is overloaded, need to disambiguate here. return continueInsertItem(boost::bind(static_cast(&TrackingSyncSource::insertItem), this, luid, item, raw), luid); } TrackingSyncSource::InsertItemResult TrackingSyncSource::insertItem(const std::string &luid, const std::string &item) { return doInsertItem(luid, item, false); } TrackingSyncSource::InsertItemResult TrackingSyncSource::insertItemRaw(const std::string &luid, const std::string &item) { InsertItemResult res = doInsertItem(luid, item, true); while (res.m_state == ITEM_AGAIN) { // Flush and wait, because caller (command line, restore) is // not prepared to deal with asynchronous execution. flushItemChanges(); finishItemChanges(); res = res.m_continue(); } return res; } void TrackingSyncSource::readItem(const std::string &luid, std::string &item) { readItem(luid, item, false); } void TrackingSyncSource::readItemRaw(const std::string &luid, std::string &item) { readItem(luid, item, true); } void TrackingSyncSource::deleteItem(const std::string &luid) { removeItem(luid); deleteRevision(*m_trackingNode, luid); } void TrackingSyncSource::enableServerMode() { SyncSourceAdmin::init(m_operations, this); SyncSourceBlob::init(m_operations, getCacheDir()); } bool TrackingSyncSource::serverModeEnabled() const { return m_operations.m_loadAdminData; } std::string TrackingSyncSource::getPeerMimeType() const { return getMimeType(); } SE_END_CXX syncevolution_1.4/src/syncevo/TrackingSyncSource.h000066400000000000000000000261121230021373600225660ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_TRACKINGSYNCSOURCE #define INCL_TRACKINGSYNCSOURCE #include #include #include #include #include #include SE_BEGIN_CXX /** * This class implements change tracking. Data sources which want to use * this functionality have to provide the following functionality * by implementing the pure virtual functions below: * - open() the data * - enumerate all existing items * - provide LUID and "revision string": * The LUID must remain *constant* when the user edits an item (it * may change when SyncEvolution changes an item), whereas the * revision string must *change* each time the item is changed * by anyone. * Both can be arbitrary strings, but keeping them simple (printable * ASCII, no white spaces, no equal sign) makes debugging simpler * because they can be stored as they are as key/value pairs in the * sync source's change tracking config node (the .other.ini files when * using file-based configuration). More complex strings use escape * sequences introduced with an exclamation mark for unsafe characters. * - import/export/update single items * - persistently store all changes in flush() * - clean up in close() * * A derived class may (but doesn't have to) override additional * functions to modify or replace the default implementations, e.g.: * - dumping the complete database (export()) * * Potential implementations of the revision string are: * - a modification time stamp * - a hash value of a textual representation of the item * (beware, such a hash might change as the textual representation * changes even though the item is unchanged) */ class TrackingSyncSource : public TestingSyncSource, virtual public SyncSourceRevisions, virtual public SyncSourceBlob, virtual public SyncSourceAdmin { public: /** * Creates a new tracking sync source. * * @param granularity sync sources whose revision string * is based on time should specify the number * of seconds which has to pass before changes * are detected reliably (see SyncSourceRevisions * for details), otherwise pass 0 */ TrackingSyncSource(const SyncSourceParams ¶ms, int granularitySeconds = 1); ~TrackingSyncSource() {} /** * ConfigNode used for change tracking in SyncSourceRevisions. * Derived classes might need that when implementing operations * which have side effects on other items (for example, * EvolutionCalendarSource::removeItem()). */ ConfigNode &getTrackingNode() { return *m_trackingNode; } /** * returns a list of all know sources for the kind of items * supported by this sync source */ virtual Databases getDatabases() = 0; /** * Actually opens the data source specified in the constructor, * will throw the normal exceptions if that fails. Should * not modify the state of the sync source. * * The expectation is that this call is fairly light-weight, but * does enough checking to determine whether the source is * usable. More expensive operations (like determining changes) * should be done in the beginSync() callback. * * In clients, it will be called for all sources before * the sync starts. In servers, it is called for each source once * the client asks for it, but not sooner. */ virtual void open() = 0; /** * A quick check whether the source currently has data. Currently * used as part of the "allow slow sync" checking after open() and * before beginSync(). Returning false is acceptable when it is * uncertain and too expensive to check. */ virtual bool isEmpty() = 0; /** * A unique identifier for the current state of the complete database. * The semantic is the following: * - empty string implies "state unknown" or "identifier not supported" (the default implementation) * - id not empty and id_1 == id_2 implies "nothing has changed"; * the inverse is not true (ids may be different although nothing has changed) */ virtual std::string databaseRevision() { return ""; } /** * fills the complete mapping from LUID to revision string of all * currently existing items * * Usually both LUID and revision string must be non-empty. The * only exception is a refresh-from-client: in that case the * revision string may be empty. The implementor of this call * cannot know whether empty strings are allowed, therefore it * should not throw errors when it cannot create a non-empty * string. The caller of this method will detect situations where * a non-empty string is necessary and none was provided. */ virtual void listAllItems(SyncSourceRevisions::RevisionMap_t &revisions) = 0; /** * Called at the start of the sync session to tell * the derived class about the cached information if (and only * if) listAllItems() and updateAllItems() were not called. The derived class * might not need this information, so the default implementation * simply ignores. * * A more complex API could have been defined to only prepare the * information when needed, but that seemed unnecessarily complex. */ virtual void setAllItems(const RevisionMap_t &revisions) {} /** * updates the revision map to reflect the current state * * May be called instead of listAllItems() if the caller has * a valid list to start from. If the implementor * cannot update the list, it must start from scratch by * reseting the list and calling listAllItems(). The default * implementation of this method does that. */ virtual void updateAllItems(SyncSourceRevisions::RevisionMap_t &revisions) { revisions.clear(); listAllItems(revisions); } /** * Create or modify an item. * * The sync source should be flexible: if the LUID is non-empty, it * shall modify the item referenced by the LUID. If the LUID is * empty, the normal operation is to add it. But if the item * already exists (e.g., a calendar event which was imported * by the user manually), then the existing item should be * updated also in the second case. * * Passing a LUID of an item which does not exist is an error. * This error should be reported instead of covering it up by * (re)creating the item. * * Errors are signaled by throwing an exception. Returning empty * strings in the result is an error which triggers an "item could * not be stored" error. * * @param luid identifies the item to be modified, empty for creating * @param item contains the new content of the item * @param raw item has internal format instead of engine format; * testing and backup/restore might use such an internal format * which may be different (more complete!) than the * format when talking to the sync engine * @return the result of inserting the item */ virtual InsertItemResult insertItem(const std::string &luid, const std::string &item, bool raw) = 0; /** * Return item data in engine format. * * Must throw a STATUS_NOT_FOUND (= 404) StatusException when the * item does not exist. * * @param luid identifies the item * @param raw return item in internal format instead of engine format * @retval item item data */ virtual void readItem(const std::string &luid, std::string &item, bool raw) = 0; /** * delete the item (renamed so that it can be wrapped by deleteItem()) * * Must throw a STATUS_NOT_FOUND (= 404) StatusException when the * item does not exist. */ virtual void removeItem(const string &luid) = 0; /** * optional: write all changes, throw error if that fails * * This is called while the sync is still active whereas * close() is called afterwards. Reporting problems * as early as possible may be useful at some point, * but currently doesn't make a relevant difference. */ virtual void flush() {} /** * closes the data source so that it can be reopened * * Just as open() it should not affect the state of * the database unless some previous action requires * it. */ virtual void close() = 0; /** * Returns the preferred mime type of the items handled by the sync source. * Example: "text/x-vcard" */ virtual std::string getMimeType() const = 0; /** * Returns the version of the mime type used by client. * Example: "2.1" */ virtual std::string getMimeVersion() const = 0; using SyncSource::getName; private: void checkStatus(SyncSourceReport &changes); boost::shared_ptr m_trackingNode; /** * Stores meta information besides the item list: * - "databaseRevision" = result of databaseRevision() at end of last sync * * Shares the same key/value store as m_trackingNode, * which uses the "item-" prefix in its keys to * avoid name clashes. */ boost::shared_ptr m_metaNode; protected: /* implementations of SyncSource callbacks */ virtual void beginSync(const std::string &lastToken, const std::string &resumeToken); virtual std::string endSync(bool success); virtual void deleteItem(const string &luid); virtual InsertItemResult insertItem(const std::string &luid, const std::string &item); virtual void readItem(const std::string &luid, std::string &item); virtual InsertItemResult insertItemRaw(const std::string &luid, const std::string &item); virtual void readItemRaw(const std::string &luid, std::string &item); virtual void enableServerMode(); virtual bool serverModeEnabled() const; virtual std::string getPeerMimeType() const; private: InsertItemResult doInsertItem(const std::string &luid, const std::string &item, bool raw); InsertItemResult continueInsertItem(const boost::function &check, const std::string &luid); }; SE_END_CXX #endif // INCL_TRACKINGSYNCSOURCE syncevolution_1.4/src/syncevo/TransportAgent.cpp000066400000000000000000000035031230021373600223130ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include SE_BEGIN_CXX const char * const TransportAgent::m_contentTypeSyncML = "application/vnd.syncml+xml"; const char * const TransportAgent::m_contentTypeSyncWBXML = "application/vnd.syncml+wbxml"; const char * const TransportAgent::m_contentTypeURLEncoded = "application/x-www-form-urlencoded"; const char * const TransportAgent::m_contentTypeServerAlertedNotificationDS = "application/vnd.syncml.ds.notification"; void HTTPTransportAgent::setConfig(SyncConfig &config) { if (config.getUseProxy()) { setProxy(config.getProxyHost()); UserIdentity identity = config.getProxyUser(); Credentials cred = IdentityProviderCredentials(identity, config.getProxyPassword()); setProxyAuth(cred.m_username, cred.m_password); } setUserAgent(config.getUserAgent()); setSSL(config.findSSLServerCertificate(), config.getSSLVerifyServer(), config.getSSLVerifyHost()); } SE_END_CXX syncevolution_1.4/src/syncevo/TransportAgent.h000066400000000000000000000137721230021373600217710ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_TRANSPORTAGENT #define INCL_TRANSPORTAGENT #include #include #include SE_BEGIN_CXX class SyncConfig; /** * Abstract API for a message send/receive agent. * * The calling sequence is as follows: * - set parameters for next message * - start message send * - optional: cancel transmission * - wait for completion and the optional reply * - close * - wait for completion of the shutdown * * Data to be sent is owned by caller. Data received as reply is * allocated and owned by agent. Errors are reported via * TransportAgentException. */ class TransportAgent { public: virtual ~TransportAgent() {} /** * set transport specific URL of next message */ virtual void setURL(const std::string &url) = 0; /** * define content type for post, see content type constants */ virtual void setContentType(const std::string &type) = 0; /** * Requests an normal shutdown of the transport. This can take a * while, for example if communication is still pending. * Therefore wait() has to be called to ensure that the * shutdown is complete and that no error occurred. * * Simply deleting the transport is an *unnormal* shutdown that * does not communicate with the peer. */ virtual void shutdown() = 0; /** * start sending message * * Memory must remain valid until reply is received or * message transmission is canceled. * * @param data start address of data to send * @param len number of bytes */ virtual void send(const char *data, size_t len) = 0; /** * cancel an active message transmission * * Blocks until send buffer is no longer in use. * Returns immediately if nothing pending. */ virtual void cancel() = 0; enum Status { /** * operation is on-going, check again with wait() */ ACTIVE, /** * received and buffered complete reply, * get access to it with getReponse() */ GOT_REPLY, /** * message wasn't sent, try again with send() */ CANCELED, /** * sending message has failed, transport should not throw an exception * if the error is recoverable (such as a temporary network error) */ FAILED, /** * transport was closed normally without error */ CLOSED, /** * transport timeout */ TIME_OUT, /** * unused transport, configure and use send() */ INACTIVE }; /** * Wait for completion of an operation initiated earlier. * The operation can be a send with optional reply or * a close request. * * Returns immediately if no operations is pending. * * @param noReply true if no reply is required for a running send; * only relevant for transports used by a SyncML server */ virtual Status wait(bool noReply = false) = 0; /** * Tells the transport agent to stop the transmission the given * amount of seconds after send() was called. The transport agent * will then stop the message transmission and return a TIME_OUT * status in wait(). * * @param seconds number of seconds to wait before timing out, zero for no timeout */ virtual void setTimeout(int seconds) = 0; /** * provides access to reply data * * Memory pointer remains valid as long as * transport agent is not deleted and no other * message is sent. */ virtual void getReply(const char *&data, size_t &len, std::string &contentType) = 0; /** SyncML in XML format */ static const char * const m_contentTypeSyncML; /** SyncML in WBXML format */ static const char * const m_contentTypeSyncWBXML; /** normal HTTP URL encoded */ static const char * const m_contentTypeURLEncoded; /** binary Server Alerted Notification (SAN) for data sync */ static const char * const m_contentTypeServerAlertedNotificationDS; }; class HTTPTransportAgent : public TransportAgent { public: /** * set proxy for transport, in protocol://[user@]host[:port] format */ virtual void setProxy(const std::string &proxy) = 0; /** * set proxy user name (if not specified in proxy string) * and password */ virtual void setProxyAuth(const std::string &user, const std::string &password) = 0; /** * control how SSL certificates are checked * * @param cacerts path to a single CA certificate file * @param verifyServer enable server verification (should always be on) * @param verifyHost do strict hostname checking in the certificate */ virtual void setSSL(const std::string &cacerts, bool verifyServer, bool verifyHost) = 0; /** * override default user agent string */ virtual void setUserAgent(const std::string &agent) = 0; /** * convenience method which copies the HTTP settings from * SyncConfig */ void setConfig(SyncConfig &config); }; SE_END_CXX #endif // INCL_TRANSPORTAGENT syncevolution_1.4/src/syncevo/UserInterface.cpp000066400000000000000000000103201230021373600220720ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include SE_BEGIN_CXX std::string ConfigPasswordKey::toString() const { std::vector props; props.reserve(7); #define PUSH_PROP(_x) if (!_x.empty()) { props.push_back(StringPrintf("%s=%s", #_x, _x.c_str())); } PUSH_PROP(user); PUSH_PROP(server); PUSH_PROP(domain); PUSH_PROP(object); PUSH_PROP(protocol); PUSH_PROP(authtype); #undef PUSH_PROP if (port) { props.push_back(StringPrintf("port=%d", port)); } return boost::join(props, " "); } static bool CheckKeyring(const InitStateTri &keyring) { // Default slot, registered with higher priority // than any other keyring backend. If we get here // no other backend was chosen by the keyring // property. If it is a string, then the string // must have been invalid or unsupported. if (keyring.wasSet() && keyring.getValue() == InitStateTri::VALUE_STRING && !keyring.get().empty()) { SE_THROW("Unsupported value for the \"keyring\" property, no such keyring found: " + keyring.get()); } return false; } static bool PreventPlainText(const InitStateTri &keyring, const std::string &passwordName) { // Another slot, called after CheckKeyring when saving. // Ensures that if keyring was meant to be used and // couldn't be used, an error is throw instead of // silently storing as plain text password. if (keyring.getValue() != InitStateTri::VALUE_FALSE && !keyring.get().empty()) { SE_THROW(StringPrintf("Cannot save %s as requested in %s." "This SyncEvolution binary was compiled without support for storing " "passwords in a keyring or wallet, or none of the backends providing that " "functionality were usable. Either store passwords in your configuration " "files or enter them interactively on each program run.\n", passwordName.c_str(), (keyring.getValue() == InitStateTri::VALUE_TRUE || keyring.get().empty()) ? "a secure keyring" : keyring.get().c_str())); } return false; } LoadPasswordSignal &GetLoadPasswordSignal() { static class Signal : public LoadPasswordSignal { public: Signal() { connect(100, boost::bind(CheckKeyring, _1)); } } loadPasswordSignal; return loadPasswordSignal; } SavePasswordSignal &GetSavePasswordSignal() { static class Signal : public SavePasswordSignal { public: Signal() { connect(100, boost::bind(CheckKeyring, _1)); connect(101, boost::bind(PreventPlainText, _1, _2)); } } savePasswordSignal; return savePasswordSignal; } void UserInterface::askPasswordAsync(const std::string &passwordName, const std::string &descr, const ConfigPasswordKey &key, const boost::function &success, const boost::function &failureException) { try { success(askPassword(passwordName, descr, key)); } catch (...) { failureException(); } } SE_END_CXX syncevolution_1.4/src/syncevo/UserInterface.h000066400000000000000000000211011230021373600215360ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * Copyright (C) 2009-12 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_USERINTERFACE # define INCL_USERINTERFACE #include #include #include #include SE_BEGIN_CXX /** * This struct wraps keys for storing passwords * in configuration system. Some fields might be empty * for some passwords. Each field might have different * meaning for each password. Fields using depends on * what user actually wants. */ struct ConfigPasswordKey { public: ConfigPasswordKey() : port(0) {} std::string toString() const; /** the user for the password */ std::string user; /** the server for the password */ std::string server; /** the domain name */ std::string domain; /** the remote object */ std::string object; /** the network protocol */ std::string protocol; /** the authentication type */ std::string authtype; /** the network port */ unsigned int port; /** a description of the password, for error messages */ std::string description; }; /** * This interface has to be provided to let SyncEvolution interact * with the user. Possible implementations are: * - command line: use platform secret storage, ask for password via stdin * - D-Bus server: use platform secret storage, relay password requests via D-Bus to UIs */ class UserInterface { public: virtual ~UserInterface() {} /** * A helper function which interactively asks the user for * a certain password. May throw errors. * * @param passwordName the name of the password in the config file, such as 'proxyPassword' * @param descr A simple string explaining what the password is needed for, * e.g. "SyncML server". This string alone has to be enough * for the user to know what the password is for, i.e. the * string has to be unique. * @param key the key used to retrieve password. Using this instead of ConfigNode is * to make user interface independent on Configuration Tree * @return entered password */ virtual std::string askPassword(const std::string &passwordName, const std::string &descr, const ConfigPasswordKey &key) = 0; /** * A helper function which interactively asks the user for * a certain password. May throw errors. Final result will * be returned via callbacks. Use this function in code which * is currently inside glib event processing, because askPassword() * might only work when it is allowed to invoke glib. * * @param passwordName the name of the password in the config file, such as 'proxyPassword' * @param descr A simple string explaining what the password is needed for, * e.g. "SyncML server". This string alone has to be enough * for the user to know what the password is for, i.e. the * string has to be unique. * @param key the key used to retrieve password. Using this instead of ConfigNode is * to make user interface independent on Configuration Tree */ virtual void askPasswordAsync(const std::string &passwordName, const std::string &descr, const ConfigPasswordKey &key, const boost::function &success, const boost::function &failureException); /** * A helper function which is used for user interface to save * a certain password. Currently possibly syncml server. May * throw errors. * @param passwordName the name of the password in the config file, such as 'proxyPassword' * @param password password to be saved * @param key the key used to store password * @return true if ui saves the password and false if not */ virtual bool savePassword(const std::string &passwordName, const std::string &password, const ConfigPasswordKey &key) = 0; /** * Read from stdin until end of stream. Must be connected to * stdin as seen by user (different in command line client * and D-Bus server). */ virtual void readStdin(std::string &content) = 0; }; /** * Some ConfigUserInterface implementations check in the system's * password manager before asking the user. Backends provide optional * access to GNOME keyring (maps to freedesktop.org Secrets D-Bus API) * and KWallet (custom protocol in KDE < 4.8, same Secrets API >= * 4.8). * * The following signals are to be invoked by ConfigUserInterface * implementations which want to use these extensions. They return * true if some backend implemented the request, false otherwise. */ /** * call one slot after the other, return as soon as the first one * returns true */ struct TrySlots { typedef bool result_type; template bool operator()(InputIterator first, InputIterator last) const { while (first != last) { if (*first) { return true; } ++first; } return false; } }; /** * Same as ConfigUserInterface::askPassword(), except that the * password is returned as retval. If it was set, then the * password was found in the keyring. The return value indicates * whether any slot matched the configured keyring. * * Backends need to be sure that the user wants them to handle * the request before doing the work and returning true. They * should check the first parameter, the value of the "keyring" * configuration option, to determine that. * * GNOME keyring and KWallet add themselves here and in * SavePasswordSignal. KWallet adds itself with priority 0 * and GNOME keyring with 1, which means that KWallet is called * first. It checks whether KDE really is the preferred * storage, otherwise defers to GNOME keyring (or any other * slot) by returning false. */ typedef boost::signals2::signal LoadPasswordSignal; LoadPasswordSignal &GetLoadPasswordSignal(); static const int INTERNAL_LOAD_PASSWORD_SLOTS = 1; /** * Same as AskPasswordSignal for saving. */ typedef boost::signals2::signal SavePasswordSignal; SavePasswordSignal &GetSavePasswordSignal(); static const int INTERNAL_SAVE_PASSWORD_SLOTS = 2; class SimpleUserInterface : public UserInterface { InitStateTri m_useKeyring; public: SimpleUserInterface(InitStateTri useKeyring) : m_useKeyring(useKeyring) {} virtual std::string askPassword(const std::string &passwordName, const std::string &descr, const ConfigPasswordKey &key) { InitStateString password; // Try to use keyring, if allowed. GetLoadPasswordSignal()(m_useKeyring, passwordName, descr, key, password); return password; } virtual bool savePassword(const std::string &passwordName, const std::string &password, const ConfigPasswordKey &key) { return false; } virtual void readStdin(std::string &content) { content.clear(); } }; SE_END_CXX #endif // INCL_USERINTERFACE syncevolution_1.4/src/syncevo/VolatileConfigNode.h000066400000000000000000000032021230021373600225140ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_EVOLUTION_VOLATILE_CONFIG_NODE # define INCL_EVOLUTION_VOLATILE_CONFIG_NODE #include #include #include SE_BEGIN_CXX /** * This class can store properties while in memory, but will never * save them persistently. Implemented by instantiating an IniHashConfigNode * (because order of entries doesn't matter) * with invalid path and never calling its flush() method. */ class VolatileConfigNode : public FilterConfigNode { public: VolatileConfigNode() : FilterConfigNode(boost::shared_ptr(new IniHashConfigNode("/dev/null", "dummy.ini", true))) {} virtual std::string getName() const { return "intermediate configuration"; } virtual bool isVolatile() const { return true; } virtual void flush() {} }; SE_END_CXX #endif syncevolution_1.4/src/syncevo/VolatileConfigTree.h000066400000000000000000000025631230021373600225370ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_EVOLUTION_VOLATILE_CONFIG_TREE # define INCL_EVOLUTION_VOLATILE_CONFIG_TREE #include #include SE_BEGIN_CXX /** * This class can store properties while in memory, but will never * save them persistently. Implemented by instantiating a FileConfigTree * with invalid path and intercepting its flush() method. */ class VolatileConfigTree : public FileConfigTree { public: VolatileConfigTree() : FileConfigTree("/dev/null", SyncConfig::SHARED_LAYOUT) {} virtual void flush() {} }; SE_END_CXX #endif syncevolution_1.4/src/syncevo/configs/000077500000000000000000000000001230021373600202635ustar00rootroot00000000000000syncevolution_1.4/src/syncevo/configs/README000066400000000000000000000022711230021373600211450ustar00rootroot00000000000000The sample configs contain common elements (datatypes, scripts, remote rules, debug settings) which are maintained as separate files in the corresponding directories. When modifying those common elements, run "update-samples.pl" in this directory to update the sample configs. The complete samples are under version control for several reasons: 1. avoid dependency on Perl unless common elements need to be updated 2. effect of changes on complete config show up in patches 3. the file layout and unshared parts ( and ) are determined by the sample configs The naming of common elements determines the order in which they get inserted. Files not ending in .xml are ignored. Elements that only apply to a client or server are stored in the corresponding sub directories, while the shared elements are in the "debug/scripting/datatypes/remoterules". It is a somewhat subjective choice which elements are stored in one file and which ones are split up. The three elements of a datatype definition (field list, profile, datatype) where split up because there might be multiple different profiles using the same field list and some users of these files might want to replace the default one. syncevolution_1.4/src/syncevo/configs/configs.am000066400000000000000000000003651230021373600222360ustar00rootroot00000000000000src_syncevo_configs_xmldir = $(datadir)/syncevolution/xml dist_src_syncevo_configs_xml_SCRIPTS = src/syncevo/configs/update-samples.pl dist_noinst_DATA += \ src/syncevo/configs/README include $(top_srcdir)/src/syncevo/configs/configs_xml.am syncevolution_1.4/src/syncevo/configs/configs_xml.am000066400000000000000000000044321230021373600231150ustar00rootroot00000000000000# TODO: preferably generate this list instead #./*.xml src_syncevo_configsdir = $(datadir)/syncevolution/xml dist_src_syncevo_configs_DATA = $(subst $(srcdir)/,,$(wildcard $(srcdir)/src/syncevo/configs/*.xml)) # ./datatypes/*.xml src_syncevo_configs_datatypesdir = $(datadir)/syncevolution/xml/datatypes dist_src_syncevo_configs_datatypes_DATA = $(subst $(srcdir)/,,$(wildcard $(srcdir)/src/syncevo/configs/datatypes/*.xml)) # ./datatypes/server/*.xml src_syncevo_configs_datatypes_serverdir = $(datadir)/syncevolution/xml/datatypes/server dist_src_syncevo_configs_datatypes_server_DATA = $(subst $(srcdir)/,,$(wildcard $(srcdir)/src/syncevo/configs/datatypes/server/*.xml)) # ./debug/*.xml src_syncevo_configs_debugdir = $(datadir)/syncevolution/xml/debug dist_src_syncevo_configs_debug_DATA = $(subst $(srcdir)/,,$(wildcard $(srcdir)/src/syncevo/configs/debug/*.xml)) # ./remoterules/*.xml src_syncevo_configs_remoterulesdir = $(datadir)/syncevolution/xml/remoterules dist_src_syncevo_configs_remoterules_DATA = $(subst $(srcdir)/,,$(wildcard $(srcdir)/src/syncevo/configs/remoterules/*.xml)) # ./remoterules/client/*.xml src_syncevo_configs_remoterules_clientdir = $(datadir)/syncevolution/xml/remoterules/client dist_src_syncevo_configs_remoterules_client_DATA = $(subst $(srcdir)/,,$(wildcard $(srcdir)/src/syncevo/configs/remoterules/client/*.xml)) # ./remoterules/server/*.xml src_syncevo_configs_remoterules_serverdir = $(datadir)/syncevolution/xml/remoterules/server dist_src_syncevo_configs_remoterules_server_DATA = $(subst $(srcdir)/,,$(wildcard $(srcdir)/src/syncevo/configs/remoterules/server/*.xml)) # ./scripting/*.xml src_syncevo_configs_scriptingdir = $(datadir)/syncevolution/xml/scripting dist_src_syncevo_configs_scripting_DATA = $(subst $(srcdir)/,,$(wildcard $(srcdir)/src/syncevo/configs/scripting/*.xml)) # ./scripting/client/*.xml src_syncevo_configs_scripting_clientdir = $(datadir)/syncevolution/xml/scripting/client dist_src_syncevo_configs_scripting_client_DATA = $(subst $(srcdir)/,,$(wildcard $(srcdir)/src/syncevo/configs/scripting/client/*.xml)) # ./scripting/server/*.xml src_syncevo_configs_scripting_serverdir = $(datadir)/syncevolution/xml/scripting/server dist_src_syncevo_configs_scripting_server_DATA = $(subst $(srcdir)/,,$(wildcard $(srcdir)/src/syncevo/configs/scripting/server/*.xml)) syncevolution_1.4/src/syncevo/configs/datatypes/000077500000000000000000000000001230021373600222615ustar00rootroot00000000000000syncevolution_1.4/src/syncevo/configs/datatypes/00vcard-fieldlist.xml000066400000000000000000000176731230021373600262350ustar00rootroot00000000000000 syncevolution_1.4/src/syncevo/configs/datatypes/01vcard-profile.xml000066400000000000000000000475751230021373600257230ustar00rootroot00000000000000 syncevolution_1.4/src/syncevo/configs/datatypes/02vcard-types.xml000066400000000000000000000053001230021373600254040ustar00rootroot00000000000000 2.1 3.0 2.1 3.0 syncevolution_1.4/src/syncevo/configs/datatypes/10calendar-fieldlist.xml000066400000000000000000000110301230021373600266650ustar00rootroot00000000000000 syncevolution_1.4/src/syncevo/configs/datatypes/11calendar-profile.xml000066400000000000000000000757521230021373600263740ustar00rootroot00000000000000 current olson 1 false true SUMMARY 0 false true syncevolution_1.4/src/syncevo/configs/datatypes/12calendar-types.xml000066400000000000000000000032631230021373600260650ustar00rootroot00000000000000 1.0 2.0 text/plain 1.0 text/plain 1.1 syncevolution_1.4/src/syncevo/configs/datatypes/20note-fieldlist.xml000066400000000000000000000004771230021373600260770ustar00rootroot00000000000000 syncevolution_1.4/src/syncevo/configs/datatypes/21note-profile.xml000066400000000000000000000006411230021373600255520ustar00rootroot00000000000000 1 false true SUBJECT 0 false true syncevolution_1.4/src/syncevo/configs/datatypes/22notes-types.xml000066400000000000000000000005321230021373600254410ustar00rootroot00000000000000 text/plain 1.0 text/plain 1.1 syncevolution_1.4/src/syncevo/configs/datatypes/30bookmark-fieldlist.xml000066400000000000000000000013421230021373600267300ustar00rootroot00000000000000 syncevolution_1.4/src/syncevo/configs/datatypes/31bookmark-profile.xml000066400000000000000000000017601230021373600264160ustar00rootroot00000000000000 syncevolution_1.4/src/syncevo/configs/datatypes/32bookmark-type.xml000066400000000000000000000004071230021373600257350ustar00rootroot00000000000000 text/x-vbookmark 1.0 syncevolution_1.4/src/syncevo/configs/datatypes/server/000077500000000000000000000000001230021373600235675ustar00rootroot00000000000000syncevolution_1.4/src/syncevo/configs/datatypes/server/40email-fieldlist.xml000066400000000000000000000026241230021373600275250ustar00rootroot00000000000000 syncevolution_1.4/src/syncevo/configs/datatypes/server/41email-profile.xml000066400000000000000000000055361230021373600272140ustar00rootroot00000000000000 true 100 ATT_COUNT ATT_MIMETYPES ATT_CONTENTS ATT_SIZES ATT_NAMES LIMIT From: rfc2047 true FROM To: rfc2047 true TO Cc: rfc2047 true CC Bcc: rfc2047 true BCC Reply-To: rfc2047 true Subject: rfc2047 true SUBJECT X-Priority: true date Date: true Status: true X-Sync-Parent-Folder: true X-Sync-Message-Read: true X-Sync-Lastmodified: true body 0 false true syncevolution_1.4/src/syncevo/configs/datatypes/server/42email-type-zipped.xml000066400000000000000000000027311230021373600300210ustar00rootroot00000000000000 application/x-zip-message 1.1 yes yes 9 syncevolution_1.4/src/syncevo/configs/datatypes/server/42email-type.xml000066400000000000000000000013541230021373600265300ustar00rootroot00000000000000 text/message 1.0 syncevolution_1.4/src/syncevo/configs/datatypes/server/43email-sonyericsson.xml000066400000000000000000000014011230021373600302770ustar00rootroot00000000000000 message/rfc822 1.0 syncevolution_1.4/src/syncevo/configs/datatypes/server/44email-nokia9500.xml000066400000000000000000000117201230021373600271660ustar00rootroot00000000000000 message/x-rfc822 1.0 SIZELIMIT())) { // force conflict only if this is a reload FORCECONFLICT(); } // make sure we never overwrite a body in the inbox BODY = UNASSIGNED; // delete always wins over replace in inbox (to avoid adds to inbox) DELETEWINS(); } } } else if (UPPERCASE(FOLDER)=="OUTBOX") { // never try to change something in outbox IGNOREUPDATE(); if (SYNCOP()!="delete") { // - date of mail is NOW, set it such that a correct date is written to the DB MAILDATE = DBNOW(); // MAILDATE = (INTEGER)DBNOW() - TIMEUNITS(120); // %%% backdate it 2 mins to make sure it does not get retransmitted // - echo item as delete (this causes that it is moved to the "sent" folder in the 9500) ECHOITEM("delete"); } CONFLICTSTRATEGY("client-wins"); } else { // Other folder // - silently discard incoming item for other folder than the above // except if it is a delete if (SYNCOP()!="delete") REJECTITEM(0); } ]]> =STARTDATE(); RETURN PASSES; ]]> syncevolution_1.4/src/syncevo/configs/debug/000077500000000000000000000000001230021373600213515ustar00rootroot00000000000000syncevolution_1.4/src/syncevo/configs/debug/00default.xml000066400000000000000000000063141230021373600236630ustar00rootroot00000000000000 buffered yes html auto yes no yes separate no no no no syncevolution_1.4/src/syncevo/configs/remoterules/000077500000000000000000000000001230021373600226315ustar00rootroot00000000000000syncevolution_1.4/src/syncevo/configs/remoterules/00_have_evolution_ui_slot.xml000066400000000000000000000002741230021373600304420ustar00rootroot00000000000000 syncevolution_1.4/src/syncevo/configs/remoterules/00_have_exdate_detached.xml000066400000000000000000000001361230021373600277500ustar00rootroot00000000000000 syncevolution_1.4/src/syncevo/configs/remoterules/00_have_syncevolution_exdate_detached.xml000066400000000000000000000000711230021373600327470ustar00rootroot00000000000000 syncevolution_1.4/src/syncevo/configs/remoterules/00_need_tz_in_event.xml000066400000000000000000000000531230021373600271670ustar00rootroot00000000000000 syncevolution_1.4/src/syncevo/configs/remoterules/00_simple_exdate.xml000066400000000000000000000000511230021373600264710ustar00rootroot00000000000000 syncevolution_1.4/src/syncevo/configs/remoterules/00_syncevolution.xml000066400000000000000000000023061230021373600265740ustar00rootroot00000000000000 Patrick Ohly SyncEvolution no yes yes yes syncevolution_1.4/src/syncevo/configs/remoterules/10_maemo_calendar.xml000066400000000000000000000004051230021373600266010ustar00rootroot00000000000000 none - this rule is activated via its name in MAKE/PARSETEXTWITHPROFILE() macro calls syncevolution_1.4/src/syncevo/configs/remoterules/all.xml000066400000000000000000000004251230021373600241240ustar00rootroot00000000000000 none - this rule is activated via its name in MAKE/PARSETEXTWITHPROFILE() macro calls syncevolution_1.4/src/syncevo/configs/remoterules/client/000077500000000000000000000000001230021373600241075ustar00rootroot00000000000000syncevolution_1.4/src/syncevo/configs/remoterules/client/00zyb.xml000066400000000000000000000003371230021373600256000ustar00rootroot00000000000000 ZYB ZYB yes syncevolution_1.4/src/syncevo/configs/remoterules/client/01mobical.xml000066400000000000000000000003711230021373600264010ustar00rootroot00000000000000 Tactel AB Mobical Sync Server syncevolution_1.4/src/syncevo/configs/remoterules/client/02google-contacts.xml000066400000000000000000000154751230021373600300770ustar00rootroot00000000000000 Google sync 1.2 Google Sync - 0.01 - Google server contacts contacts 32 text/x-vcard 2.1 text/x-vcard 2.1 text/x-vcard 2.1 BEGINVCARD ENDVCARD VERSION 2.1 1 REV1 N1 FN1 TITLE1 ORG1 TEL TYPE HOME WORK VOICE CELL FAX PAGER PREF CAR EMAIL TYPE HOME WORK INTERNET ADR TYPE HOME WORK NOTE1 PHOTO 1 TYPE JPEG 1 2 ]]> syncevolution_1.4/src/syncevo/configs/remoterules/client/03funambol.xml000066400000000000000000000003441230021373600266000ustar00rootroot00000000000000 Funambol DS Server* syncevolution_1.4/src/syncevo/configs/remoterules/evolution.xml000066400000000000000000000005421230021373600254000ustar00rootroot00000000000000 none - this rule is activated via its name in MAKE/PARSETEXTWITHPROFILE() macro calls yes syncevolution_1.4/src/syncevo/configs/remoterules/extended-date-format.xml000066400000000000000000000002231230021373600273510ustar00rootroot00000000000000 syncevolution_1.4/src/syncevo/configs/remoterules/kde.xml000066400000000000000000000002371230021373600241200ustar00rootroot00000000000000 none - this rule is activated via its name in MAKE/PARSETEXTWITHPROFILE() macro calls syncevolution_1.4/src/syncevo/configs/remoterules/local-storage.xml000066400000000000000000000006451230021373600261140ustar00rootroot00000000000000 none - this rule is activated by default via its name in MAKE/PARSETEXTWITHPROFILE() macro calls yes syncevolution_1.4/src/syncevo/configs/remoterules/server/000077500000000000000000000000001230021373600241375ustar00rootroot00000000000000syncevolution_1.4/src/syncevo/configs/remoterules/server/00_nokia.xml000066400000000000000000000017371230021373600262710ustar00rootroot00000000000000 NOKIA no yes syncevolution_1.4/src/syncevo/configs/remoterules/server/00_sony_ericsson.xml000066400000000000000000000010651230021373600300570ustar00rootroot00000000000000 SonyEricsson no ISO-8859-1 ISO-8859-1 syncevolution_1.4/src/syncevo/configs/remoterules/server/00_t39m.xml000066400000000000000000000004761230021373600257630ustar00rootroot00000000000000 Ericsson R1A yes ANSI Ericsson T39m syncevolution_1.4/src/syncevo/configs/remoterules/server/01_t68.xml000066400000000000000000000004731230021373600256060ustar00rootroot00000000000000 Ericsson R1B yes ANSI Ericsson T68 syncevolution_1.4/src/syncevo/configs/remoterules/server/02_V3.xml000066400000000000000000000006631230021373600254570ustar00rootroot00000000000000 Motorola* V3 yes yes yes ANSI ANSI Motorola V3 syncevolution_1.4/src/syncevo/configs/remoterules/server/03_V3i.xml000066400000000000000000000006171230021373600256300ustar00rootroot00000000000000 Motorola* V3i yes yes yes ANSI Motorola V3i syncevolution_1.4/src/syncevo/configs/remoterules/server/04_6230.xml000066400000000000000000000004111230021373600255520ustar00rootroot00000000000000 NOKIA 6230 yes yes Nokia 6230 syncevolution_1.4/src/syncevo/configs/remoterules/server/05_9210.xml000066400000000000000000000005271230021373600255640ustar00rootroot00000000000000 NOKIA 9210 yes Nokia 9210 syncevolution_1.4/src/syncevo/configs/remoterules/server/06_9210i.xml000066400000000000000000000005311230021373600257310ustar00rootroot00000000000000 NOKIA 9210i yes Nokia 9210 syncevolution_1.4/src/syncevo/configs/remoterules/server/07_3220.xml000066400000000000000000000006141230021373600255560ustar00rootroot00000000000000 NOKIA 3220 yes yes Nokia 3220 syncevolution_1.4/src/syncevo/configs/remoterules/server/08_3230.xml000066400000000000000000000006141230021373600255600ustar00rootroot00000000000000 NOKIA 3230 yes yes Nokia 3230 syncevolution_1.4/src/syncevo/configs/remoterules/server/09_3600.xml000066400000000000000000000006141230021373600255620ustar00rootroot00000000000000 NOKIA 3600 yes yes Nokia 3600 syncevolution_1.4/src/syncevo/configs/remoterules/server/10_3620.xml000066400000000000000000000006141230021373600255540ustar00rootroot00000000000000 NOKIA 3620 yes yes Nokia 3620 syncevolution_1.4/src/syncevo/configs/remoterules/server/11_3650.xml000066400000000000000000000006141230021373600255600ustar00rootroot00000000000000 NOKIA 3650 yes yes Nokia 3650 syncevolution_1.4/src/syncevo/configs/remoterules/server/12_3660.xml000066400000000000000000000006141230021373600255620ustar00rootroot00000000000000 NOKIA 3660 yes yes Nokia 3660 syncevolution_1.4/src/syncevo/configs/remoterules/server/13_6260.xml000066400000000000000000000006141230021373600255620ustar00rootroot00000000000000 NOKIA 6260 yes yes Nokia 6260 syncevolution_1.4/src/syncevo/configs/remoterules/server/14_6600.xml000066400000000000000000000006141230021373600255610ustar00rootroot00000000000000 NOKIA 6600 yes yes Nokia 6600 syncevolution_1.4/src/syncevo/configs/remoterules/server/15_6620.xml000066400000000000000000000006141230021373600255640ustar00rootroot00000000000000 NOKIA 6620 yes yes Nokia 6620 syncevolution_1.4/src/syncevo/configs/remoterules/server/16_6630.xml000066400000000000000000000006141230021373600255660ustar00rootroot00000000000000 NOKIA 6630 yes yes Nokia 6630 syncevolution_1.4/src/syncevo/configs/remoterules/server/17_6670.xml000066400000000000000000000006141230021373600255730ustar00rootroot00000000000000 NOKIA 6670 yes yes Nokia 6670 syncevolution_1.4/src/syncevo/configs/remoterules/server/18_7250.xml000066400000000000000000000006141230021373600255670ustar00rootroot00000000000000 NOKIA 7250 yes yes Nokia 7250 syncevolution_1.4/src/syncevo/configs/remoterules/server/19_7250i.xml000066400000000000000000000006171230021373600257440ustar00rootroot00000000000000 NOKIA 7250i yes yes Nokia 7250i syncevolution_1.4/src/syncevo/configs/remoterules/server/20_7260.xml000066400000000000000000000006141230021373600255610ustar00rootroot00000000000000 NOKIA 7260 yes yes Nokia 7260 syncevolution_1.4/src/syncevo/configs/remoterules/server/21_7610.xml000066400000000000000000000006141230021373600255610ustar00rootroot00000000000000 NOKIA 7610 yes yes Nokia 7610 syncevolution_1.4/src/syncevo/configs/remoterules/server/22_7650.xml000066400000000000000000000006141230021373600255660ustar00rootroot00000000000000 NOKIA 7650 yes yes Nokia 7650 syncevolution_1.4/src/syncevo/configs/remoterules/server/23_N-Gage.xml000066400000000000000000000006221230021373600262230ustar00rootroot00000000000000 NOKIA N-Gage yes yes Nokia N-Gage syncevolution_1.4/src/syncevo/configs/remoterules/server/24_N-Gage_QD.xml000066400000000000000000000006331230021373600266120ustar00rootroot00000000000000 NOKIA N-Gage QD yes yes Nokia N-Gage QD syncevolution_1.4/src/syncevo/configs/remoterules/server/25_9300.xml000066400000000000000000000006511230021373600255640ustar00rootroot00000000000000 NOKIA 9300 yes yes Nokia 9300 syncevolution_1.4/src/syncevo/configs/remoterules/server/26_9500.xml000066400000000000000000000006511230021373600255670ustar00rootroot00000000000000 NOKIA 9500 yes yes Nokia 9500 syncevolution_1.4/src/syncevo/configs/remoterules/server/27_E90.xml000066400000000000000000000005361230021373600255320ustar00rootroot00000000000000 NOKIA E90 yes Nokia E90 syncevolution_1.4/src/syncevo/configs/remoterules/server/28_X.xml000066400000000000000000000006031230021373600254000ustar00rootroot00000000000000 Sendo X yes yes Sendo X syncevolution_1.4/src/syncevo/configs/remoterules/server/29_SX1.xml000066400000000000000000000010011230021373600255760ustar00rootroot00000000000000 SIEMENS SX1 yes yes yes Siemens SX1 syncevolution_1.4/src/syncevo/configs/remoterules/server/30_M55.xml000066400000000000000000000004121230021373600255260ustar00rootroot00000000000000 SIEMENS M55 yes yes Siemens M55 syncevolution_1.4/src/syncevo/configs/remoterules/server/31_SL55.xml000066400000000000000000000004151230021373600256540ustar00rootroot00000000000000 SIEMENS SL55 yes yes Siemens SL55 syncevolution_1.4/src/syncevo/configs/remoterules/server/32_S55.xml000066400000000000000000000004121230021373600255360ustar00rootroot00000000000000 SIEMENS S55 yes yes Siemens S55 syncevolution_1.4/src/syncevo/configs/remoterules/server/33_S65.xml000066400000000000000000000004121230021373600255400ustar00rootroot00000000000000 SIEMENS S65 yes yes Siemens S65 syncevolution_1.4/src/syncevo/configs/remoterules/server/34_SL65.xml000066400000000000000000000004151230021373600256600ustar00rootroot00000000000000 SIEMENS SL65 yes yes Siemens SL65 syncevolution_1.4/src/syncevo/configs/remoterules/server/35_K700.xml000066400000000000000000000007271230021373600256170ustar00rootroot00000000000000 SonyEricsson SEMC Phone R3B yes SonyEricsson K700 syncevolution_1.4/src/syncevo/configs/remoterules/server/36_T610_T630.xml000066400000000000000000000021071230021373600263370ustar00rootroot00000000000000 SonyEricsson R2B yes SonyEricsson T610/T630 syncevolution_1.4/src/syncevo/configs/remoterules/server/37_M600i.xml000066400000000000000000000025531230021373600257720ustar00rootroot00000000000000 Sony Ericsson M600i no yes SonyEricsson M600i syncevolution_1.4/src/syncevo/configs/remoterules/server/38_P800.xml000066400000000000000000000025501230021373600256240ustar00rootroot00000000000000 Sony Ericsson P800 no yes SonyEricsson P800 syncevolution_1.4/src/syncevo/configs/remoterules/server/39_P900.xml000066400000000000000000000025501230021373600256260ustar00rootroot00000000000000 Sony Ericsson P900 no yes SonyEricsson P900 syncevolution_1.4/src/syncevo/configs/remoterules/server/40_P910.xml000066400000000000000000000025551230021373600256240ustar00rootroot00000000000000 Sony Ericsson P910 no yes SonyEricsson P910 syncevolution_1.4/src/syncevo/configs/remoterules/server/41_P910i.xml000066400000000000000000000025601230021373600257720ustar00rootroot00000000000000 Sony Ericsson P910i no yes SonyEricsson P910i syncevolution_1.4/src/syncevo/configs/remoterules/server/42_P990i.xml000066400000000000000000000011321230021373600257750ustar00rootroot00000000000000 Sony Ericsson P990i no yes SonyEricsson P990i syncevolution_1.4/src/syncevo/configs/remoterules/server/43_t68i.xml000066400000000000000000000005121230021373600257570ustar00rootroot00000000000000 SonyEricsson R2A yes ANSI SonyEricsson T68i syncevolution_1.4/src/syncevo/configs/remoterules/server/44_Funambol_Outlook.xml000066400000000000000000000005141230021373600304470ustar00rootroot00000000000000 Funambol Outlook Sync Client yes Funambol Outlook Sync Client syncevolution_1.4/src/syncevo/configs/remoterules/server/45_N7210c.xml000066400000000000000000000004321230021373600260420ustar00rootroot00000000000000 NOKIA Nokia 7210c Nokia 7210c yes yes syncevolution_1.4/src/syncevo/configs/remoterules/server/45_SyncJe_Outlook.xml000066400000000000000000000005111230021373600300750ustar00rootroot00000000000000 SyncJe Outlook Edition yes NextHaus SyncJe Outlook Client syncevolution_1.4/src/syncevo/configs/scripting/000077500000000000000000000000001230021373600222655ustar00rootroot00000000000000syncevolution_1.4/src/syncevo/configs/scripting/04vcard-photo-inlining.xml000066400000000000000000000007031230021373600272060ustar00rootroot00000000000000 syncevolution_1.4/src/syncevo/configs/scripting/04vcard-photo-value.xml000066400000000000000000000020721230021373600265140ustar00rootroot00000000000000 syncevolution_1.4/src/syncevo/configs/scripting/05vcard-evolution.xml000066400000000000000000000076761230021373600263150ustar00rootroot00000000000000 = startmiddle) { N_MIDDLE = words[startmiddle]; startmiddle = startmiddle + 1; while (endmiddle >= startmiddle) { N_MIDDLE = N_MIDDLE + " " + words[startmiddle]; startmiddle = startmiddle + 1; } } } } // Ensure that FILE-AS is set. Some EDS versions will set it, // so we need to do the same to ensure that incoming items // match DB items during a slow sync. if (FILE_AS == EMPTY) { FILE_AS = N_LAST; if (N_FIRST != EMPTY) { if (FILE_AS != EMPTY) { FILE_AS = FILE_AS + ", "; } FILE_AS = FILE_AS + N_FIRST; } } $VCARD_OUTGOING_PHOTO_VALUE_SCRIPT; ]]> syncevolution_1.4/src/syncevo/configs/scripting/05vcard-merge.xml000066400000000000000000000040241230021373600253500ustar00rootroot00000000000000 syncevolution_1.4/src/syncevo/configs/scripting/06todo-priorities.xml000066400000000000000000000020411230021373600263060ustar00rootroot00000000000000 0) { PRIORITY=1; }else if(PRIORITY==5){ PRIORITY=2; }else if(PRIORITY>5){ PRIORITY=3; } // 0 is undefined and remains unchanged ]]> syncevolution_1.4/src/syncevo/configs/scripting/06vcard-fullname.xml000066400000000000000000000007401230021373600260560ustar00rootroot00000000000000 syncevolution_1.4/src/syncevo/configs/scripting/07vcard-addrev.xml000066400000000000000000000002141230021373600255150ustar00rootroot00000000000000 syncevolution_1.4/src/syncevo/configs/scripting/08vcard-email-type.xml000066400000000000000000000006121230021373600263210ustar00rootroot00000000000000 syncevolution_1.4/src/syncevo/configs/scripting/10newuid.xml000066400000000000000000000003021230021373600244360ustar00rootroot00000000000000 syncevolution_1.4/src/syncevo/configs/scripting/11calendar.xml000066400000000000000000000254501230021373600247300ustar00rootroot00000000000000 0) { // DTSTART and DTEND represent allday event, make them date-only values // - convert start to user zone (or floating) so it represents midnight DTSTART = CONVERTTOUSERZONE(DTSTART); MAKEALLDAY(DTSTART,DTEND,i); if (RR_END != EMPTY) { RR_END = DATEONLY(CONVERTTOUSERZONE(RR_END)); } } else { // iCalendar 2.0 - only if DTSTART is a date-only value this really is an allday if (ISDATEONLY(DTSTART)) { // reshape to make sure we don't have invalid zero-duration alldays (old OCS 9 servers) MAKEALLDAY(DTSTART,DTEND,i); } } // Make sure that all EXDATE times are in the same timezone as the start // time. Some servers send them as UTC, which is all fine and well, but // only if the timezone definition doesn't change. Also, libical does not // handle such UTC EXDATEs, so let's convert it while the UTC and // time zone definition (hopefully) are in sync. if (TIMEZONE(DTSTART) != "UTC" && !ISFLOATING(DTSTART)) { i = 0; timestamp exdate; while (i0) { DUE = CONVERTTOUSERZONE(DUE); DUE = DATEONLY(DUE); } if (ITEMDATATYPE()=="vCalendar10") { $VCALENDAR_10TO20_PRIORITY_CONVERSION; } } // a workaround for funambol: adding 'action' for 'alarm' // if item data type is vCalendar1.0, also add 'action' for 'alarm'. // This is a workaround for Mobical.net, which uses vCalendar1.0. if (ALARM_TIME!=EMPTY && ALARM_ACTION==EMPTY) { ALARM_ACTION = "DISPLAY"; } ]]> lensummary && SUBSTR(DESCRIPTION, 0, lensummary) == SUMMARY && SUBSTR(DESCRIPTION, lensummary, 1) == "\n") { DESCRIPTION = SUBSTR(DESCRIPTION, lensummary + 1, lendescr - lensummary - 1); } ]]> syncevolution_1.4/src/syncevo/configs/scripting/client/000077500000000000000000000000001230021373600235435ustar00rootroot00000000000000syncevolution_1.4/src/syncevo/configs/scripting/client/00timeout.xml000066400000000000000000000000421230021373600261070ustar00rootroot00000000000000 5 syncevolution_1.4/src/syncevo/configs/scripting/server/000077500000000000000000000000001230021373600235735ustar00rootroot00000000000000syncevolution_1.4/src/syncevo/configs/scripting/server/12email.xml000066400000000000000000000141461230021373600255550ustar00rootroot00000000000000 SIZELIMIT())) { // force conflict only if this is a reload FORCECONFLICT(); } // make sure we never overwrite a body in the inbox BODY = UNASSIGNED; // delete always wins over replace in inbox (to avoid adds to inbox) DELETEWINS(); } } } else if (UPPERCASE(FOLDER)=="OUTBOX") { // never try to change something in outbox IGNOREUPDATE(); if (SYNCOP()!="delete") { // - date of mail is NOW, set it such that a correct date is written to the DB MAILDATE = DBNOW(); // MAILDATE = (INTEGER)DBNOW() - TIMEUNITS(120); // %%% backdate it 2 mins to make sure it does not get retransmitted // - echo item as replace (to force-move it to the sent folder) ECHOITEM("replace"); } CONFLICTSTRATEGY("client-wins"); } else if (UPPERCASE(FOLDER)=="SENT") { // never try to change something in sent folder IGNOREUPDATE(); // Server has precedence in case of conflicts CONFLICTSTRATEGY("server-wins"); // Implement reload capability for sent items as well if (SLOWSYNC()) { // do not add new sent items to the server in slowsync PREVENTADD(); // causes extra sent items on the client to be deleted } else { // make sure that existing server item will conflict with this item if (SYNCOP()=="replace") { if (LIMIT!=EMPTY && (LIMIT<0 || LIMIT>SIZELIMIT())) { // force conflict only if this is a reload FORCECONFLICT(); REJECTITEM(200); // but do not process the item further } else { // silently ignore other types of changes REJECTITEM(200); } // make sure we never overwrite a body in the sent folder BODY = UNASSIGNED; } } } else { // Other folder // - silently discard incoming item for other folder than the above // except if it is a delete if (SYNCOP()!="delete") REJECTITEM(0); } ]]> =V1.0.8.21 IF (LIMIT==EMPTY) LIMIT = SIZELIMIT(); // if none set already, use default for this item (=default of datastore, if not SETSIZELIMIT() called before for this item generation) } // set limit for item generator if (LIMIT!=EMPTY) SETSIZELIMIT(LIMIT); ]]> =STARTDATE(); RETURN PASSES; ]]> syncevolution_1.4/src/syncevo/configs/syncevolution.xml000066400000000000000000000015171230021373600237320ustar00rootroot00000000000000 SyncEvolution client config true syncevolution_1.4/src/syncevo/configs/update-samples.pl000077500000000000000000000044221230021373600235510ustar00rootroot00000000000000#! /usr/bin/env perl sub basename { $_ = shift; s;.*/;;; return $_; } # Concatenate all files ending in .xml in the given directory # plus those in a specific subdirectory for client or server. # Order lexicographic ascending of the base filename. sub readfragments { my $dir = shift; my $subdir = shift; my @res = (); my @files = (); if (opendir(my $dh, $dir)) { foreach (grep (/.*\.xml$/, readdir($dh))) { push @files, "$dir/$_"; } closedir($dh); if (opendir(my $dh, "$dir/$subdir")) { foreach (grep (/.*\.xml$/, readdir($dh))) { push @files, "$dir/$subdir/$_"; } closedir($dh); } } @files = sort { basename($a) <=> basename($b) } @files; foreach (@files) { open(IN, "<$_") || die "cannot read $_: $!"; push @res, ; close(IN); } return join("", @res); } # replace content of , , and all s # with the corresponding shared and/or client/server .xml fragments sub update { my $file = shift; my $subdir = shift; my $write = shift; open(IN, "<$file") || die "cannot read $file: $!"; $_ = join("", ); close(IN) || die "closing $file: $!"; s;(\n).*(\n *);$1 . readfragments("debug", $subdir) . $2;se; s;(\n).*(\n *);$1 . readfragments("scripting", $subdir) . $2;se || s;(\n *);$1 . "\n" . readfragments("scripting", $subdir) . $1 . "";se; s;(\n).*(\n *);$1 . readfragments("datatypes", $subdir) . $2;se || s;(\n *);$1 . "\n" . readfragments("datatypes", $subdir) . $1 . "";se; s;(\n *).*;$1 . readfragments("remoterules", $subdir);se || s;(\n *);readfragments("remoterules", $subdir);se; if ($write) { open(OUT, ">$file") || die "cannot write $file: $!"; print OUT; close(OUT) || die "closing $file: $!"; } else { print; } } if ($#ARGV == -1) { update("syncclient_sample_config.xml", "client", 1); update("syncserv_sample_config.xml", "server", 1); } else { update($ARGV[0], $ARGV[1], 0); } syncevolution_1.4/src/syncevo/declarations.h000066400000000000000000000033461230021373600214620ustar00rootroot00000000000000/* * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ /** * Header file that is included by all SyncEvolution files. * Sets up the "SyncEvo" namespace. */ #ifndef INCL_DECLARATIONS #define INCL_DECLARATIONS #define SE_BEGIN_CXX namespace SyncEvo { #define SE_END_CXX } #ifdef __GNUC__ # define SE_NORETURN __attribute__((noreturn)) #else # define SE_NORETURN #endif SE_BEGIN_CXX /* * SyncEvolution should never use standard IO directly. Either use the * logging facilities or use variables that point towards the real * output channels. In particular the command line code then can be * run as pointing towards real std::cout, a string stream, or redirected * via D-Bus. * * These dummy declarations trip up code inside SyncEvo namespace or using it * which use plain "cout << something" after a "using namespace std". * They don't help catching code which references std::cout. */ struct DontUseStandardIO; extern DontUseStandardIO *cout; extern DontUseStandardIO *cerr; SE_END_CXX #endif /** INCL_DECLARATIONS */ syncevolution_1.4/src/syncevo/eds_abi_wrapper.cpp000066400000000000000000000617311230021373600224750ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #define EDS_ABI_WRAPPER_NO_REDEFINE 1 #include #include #include #include #include #include #include using namespace std; namespace { std::string lookupDebug, lookupInfo; } #ifdef EVOLUTION_COMPATIBILITY struct EDSAbiWrapper EDSAbiWrapperSingleton; namespace { enum { FIND_SYMBOLS_NEED_ALL = 1<<0, /**< all symbols must be present for findSymbols() to succeed */ FIND_SYMBOLS_LENIENT_MAX_VERSION = 1<<1 /**< also allow libs with higher version, with extra warning */ }; /** * Opens a . shared object with coming from a * range of known compatible versions, falling back to even more * recent ones only after warning about it. Then searches for * function pointers. * * Either all or none of the function pointers are set. * * End user information and debug information are added to * lookupDebug and lookupInfo. * * @param libname full name including .so suffix; . gets appended * @param minver first known compatible version * @param maxver last known compatible version * @param flags controls specific features * @retval realver found version * @return dlhandle which must be kept or freed by caller */ void *findSymbols(const char *libname, int minver, int maxver, int flags, int *realver, ... /* function pointer address, name, ..., (void *)0 */) { void *dlhandle = NULL; std::ostringstream debug, info; int ver; if (!dlhandle) { for (ver = maxver; ver >= minver; --ver) { std::ostringstream soname; soname << libname << "." << ver; dlhandle = dlopen(soname.str().c_str(), RTLD_GLOBAL|RTLD_LAZY); if (dlhandle) { info << "using " << soname.str() << std::endl; break; } } } if (!dlhandle && (flags & FIND_SYMBOLS_LENIENT_MAX_VERSION)) { for (ver = maxver + 1; ver < maxver + 50; ++ver) { std::ostringstream soname; soname << libname << "." << ver; dlhandle = dlopen(soname.str().c_str(), RTLD_GLOBAL|RTLD_LAZY); if (dlhandle) { info << "using " << soname.str() << " - might not be compatible!" << std::endl; break; } } } if (realver) { *realver = ver; } if (!dlhandle) { debug << libname << " not found (tried major versions " << minver << " to " << maxver + 49 << ")" << std::endl; } else { bool allfound = true; va_list ap; va_start(ap, realver); void **funcptr = va_arg(ap, void **); const char *symname = NULL; while (funcptr) { symname = va_arg(ap, const char *); *funcptr = dlsym(dlhandle, symname); if (!*funcptr) { debug << symname << " not found" << std::endl; allfound = false; } funcptr = va_arg(ap, void **); } va_end(ap); if (!allfound && (flags & FIND_SYMBOLS_NEED_ALL)) { /* unusable, clear symbols and free handle */ va_start(ap, realver); funcptr = va_arg(ap, void **); while (funcptr) { va_arg(ap, const char *); *funcptr = NULL; funcptr = va_arg(ap, void **); } va_end(ap); info << libname << " unusable, required function no longer available" << std::endl; dlclose(dlhandle); dlhandle = NULL; } } lookupInfo += info.str(); lookupDebug += info.str(); lookupDebug += debug.str(); return dlhandle; } # ifdef HAVE_EDS void *edshandle; # endif # ifdef ENABLE_EBOOK void *ebookhandle; # endif # ifdef ENABLE_ECAL void *ecalhandle; # endif # ifdef ENABLE_BLUETOOTH void *libbluetoothhandle; sdp_record_t *wrap_sdp_extract_pdu(const uint8_t *pdata, int bufsize, int *scanned) { return EDSAbiWrapperSingleton.sdp_extract_pdu(pdata, scanned); } int wrap_sdp_extract_seqtype(const uint8_t *buf, int bufsize, uint8_t *dtdp, int *size) { return EDSAbiWrapperSingleton.sdp_extract_seqtype(buf, dtdp, size); } # endif } int EDSAbiHaveEbook, EDSAbiHaveEcal, EDSAbiHaveEdataserver; int EDSAbiHaveIcal; int EDSAbiHaveIcal1; int SyncEvoHaveLibbluetooth; extern "C" void EDSAbiWrapperInit() { static bool initialized; if (initialized) { return; } else { initialized = true; } # ifdef HAVE_EDS edshandle = findSymbols("libedataserver-1.2.so", 7, 16, FIND_SYMBOLS_NEED_ALL, NULL, &EDSAbiWrapperSingleton.e_source_get_type, "e_source_get_type", &EDSAbiWrapperSingleton.e_source_get_uri, "e_source_get_uri", &EDSAbiWrapperSingleton.e_source_group_get_type, "e_source_group_get_type", &EDSAbiWrapperSingleton.e_source_group_peek_sources, "e_source_group_peek_sources", &EDSAbiWrapperSingleton.e_source_list_peek_groups, "e_source_list_peek_groups", &EDSAbiWrapperSingleton.e_source_peek_name, "e_source_peek_name", (void *)0); EDSAbiHaveEdataserver = EDSAbiWrapperSingleton.e_source_group_peek_sources != 0; # endif // HAVE_EDS # ifdef ENABLE_EBOOK static const int libebookMinVersion = 5, libebookMaxVersion = 13; // EDS 3.4 ebookhandle = findSymbols("libebook-1.2.so", libebookMinVersion, libebookMaxVersion, FIND_SYMBOLS_NEED_ALL, NULL, &EDSAbiWrapperSingleton.e_book_add_contact, "e_book_add_contact", &EDSAbiWrapperSingleton.e_book_authenticate_user, "e_book_authenticate_user", &EDSAbiWrapperSingleton.e_book_commit_contact, "e_book_commit_contact", &EDSAbiWrapperSingleton.e_contact_duplicate, "e_contact_duplicate", &EDSAbiWrapperSingleton.e_contact_get_const, "e_contact_get_const", &EDSAbiWrapperSingleton.e_contact_get, "e_contact_get", &EDSAbiWrapperSingleton.e_contact_name_free, "e_contact_name_free", &EDSAbiWrapperSingleton.e_contact_get_type, "e_contact_get_type", &EDSAbiWrapperSingleton.e_contact_new_from_vcard, "e_contact_new_from_vcard", &EDSAbiWrapperSingleton.e_contact_set, "e_contact_set", &EDSAbiWrapperSingleton.e_book_error_quark, "e_book_error_quark", &EDSAbiWrapperSingleton.e_book_get_addressbooks, "e_book_get_addressbooks", &EDSAbiWrapperSingleton.e_book_get_changes, "e_book_get_changes", &EDSAbiWrapperSingleton.e_book_get_contact, "e_book_get_contact", &EDSAbiWrapperSingleton.e_book_get_contacts, "e_book_get_contacts", &EDSAbiWrapperSingleton.e_book_get_supported_auth_methods, "e_book_get_supported_auth_methods", &EDSAbiWrapperSingleton.e_book_get_uri, "e_book_get_uri", &EDSAbiWrapperSingleton.e_book_new, "e_book_new", &EDSAbiWrapperSingleton.e_book_new_default_addressbook, "e_book_new_default_addressbook", &EDSAbiWrapperSingleton.e_book_new_from_uri, "e_book_new_from_uri", &EDSAbiWrapperSingleton.e_book_new_system_addressbook, "e_book_new_system_addressbook", &EDSAbiWrapperSingleton.e_book_open, "e_book_open", &EDSAbiWrapperSingleton.e_book_query_any_field_contains, "e_book_query_any_field_contains", &EDSAbiWrapperSingleton.e_book_query_unref, "e_book_query_unref", &EDSAbiWrapperSingleton.e_book_remove_contact, "e_book_remove_contact", &EDSAbiWrapperSingleton.e_vcard_to_string, "e_vcard_to_string", (void *)0); EDSAbiHaveEbook = EDSAbiWrapperSingleton.e_book_new != 0; findSymbols("libebook-1.2.so", libebookMinVersion, libebookMaxVersion, 0, NULL, &EDSAbiWrapperSingleton.e_contact_inline_local_photos, "e_contact_inline_local_photos", (void *)0); # endif // ENABLE_EBOOK #define EDS_ABI_WRAPPER_ICAL_BASE \ &EDSAbiWrapperSingleton.icalcomponent_add_component, "icalcomponent_add_component", \ &EDSAbiWrapperSingleton.icalcomponent_add_property, "icalcomponent_add_property", \ &EDSAbiWrapperSingleton.icalcomponent_as_ical_string, "icalcomponent_as_ical_string", \ &EDSAbiWrapperSingleton.icalcomponent_free, "icalcomponent_free", \ &EDSAbiWrapperSingleton.icalcomponent_get_first_component, "icalcomponent_get_first_component", \ &EDSAbiWrapperSingleton.icalcomponent_get_first_property, "icalcomponent_get_first_property", \ &EDSAbiWrapperSingleton.icalcomponent_get_next_component, "icalcomponent_get_next_component", \ &EDSAbiWrapperSingleton.icalcomponent_get_next_property, "icalcomponent_get_next_property", \ &EDSAbiWrapperSingleton.icalcomponent_get_recurrenceid, "icalcomponent_get_recurrenceid", \ &EDSAbiWrapperSingleton.icalcomponent_get_timezone, "icalcomponent_get_timezone", \ &EDSAbiWrapperSingleton.icalcomponent_get_location, "icalcomponent_get_location", \ &EDSAbiWrapperSingleton.icalcomponent_get_summary, "icalcomponent_get_summary", \ &EDSAbiWrapperSingleton.icalcomponent_get_uid, "icalcomponent_get_uid", \ &EDSAbiWrapperSingleton.icalcomponent_get_dtstart, "icalcomponent_get_dtstart", \ &EDSAbiWrapperSingleton.icalcomponent_isa, "icalcomponent_isa", \ &EDSAbiWrapperSingleton.icalcomponent_new_clone, "icalcomponent_new_clone", \ &EDSAbiWrapperSingleton.icalcomponent_new_from_string, "icalcomponent_new_from_string", \ &EDSAbiWrapperSingleton.icalcomponent_new, "icalcomponent_new", \ &EDSAbiWrapperSingleton.icalcomponent_merge_component, "icalcomponent_merge_component", \ &EDSAbiWrapperSingleton.icalcomponent_remove_component, "icalcomponent_remove_component", \ &EDSAbiWrapperSingleton.icalcomponent_remove_property, "icalcomponent_remove_property", \ &EDSAbiWrapperSingleton.icalcomponent_set_uid, "icalcomponent_set_uid", \ &EDSAbiWrapperSingleton.icalcomponent_set_recurrenceid, "icalcomponent_set_recurrenceid", \ &EDSAbiWrapperSingleton.icalcomponent_vanew, "icalcomponent_vanew", \ &EDSAbiWrapperSingleton.icalparameter_get_tzid, "icalparameter_get_tzid", \ &EDSAbiWrapperSingleton.icalparameter_set_tzid, "icalparameter_set_tzid", \ &EDSAbiWrapperSingleton.icalparameter_new_from_value_string, "icalparameter_new_from_value_string", \ &EDSAbiWrapperSingleton.icalparameter_new_clone, "icalparameter_new_clone", \ &EDSAbiWrapperSingleton.icalproperty_new_clone, "icalproperty_new_clone", \ &EDSAbiWrapperSingleton.icalproperty_free, "icalproperty_free", \ &EDSAbiWrapperSingleton.icalproperty_get_description, "icalproperty_get_description", \ &EDSAbiWrapperSingleton.icalproperty_get_uid, "icalproperty_get_uid", \ &EDSAbiWrapperSingleton.icalproperty_get_recurrenceid, "icalproperty_get_recurrenceid", \ &EDSAbiWrapperSingleton.icalproperty_set_recurrenceid, "icalproperty_set_recurrenceid", \ &EDSAbiWrapperSingleton.icalproperty_get_sequence, "icalproperty_get_sequence", \ &EDSAbiWrapperSingleton.icalproperty_get_property_name, "icalproperty_get_property_name", \ &EDSAbiWrapperSingleton.icalproperty_get_first_parameter, "icalproperty_get_first_parameter", \ &EDSAbiWrapperSingleton.icalproperty_get_lastmodified, "icalproperty_get_lastmodified", \ &EDSAbiWrapperSingleton.icalproperty_get_next_parameter, "icalproperty_get_next_parameter", \ &EDSAbiWrapperSingleton.icalproperty_set_parameter, "icalproperty_set_parameter", \ &EDSAbiWrapperSingleton.icalproperty_get_summary, "icalproperty_get_summary", \ &EDSAbiWrapperSingleton.icalproperty_new_description, "icalproperty_new_description", \ &EDSAbiWrapperSingleton.icalproperty_new_summary, "icalproperty_new_summary", \ &EDSAbiWrapperSingleton.icalproperty_new_uid, "icalproperty_new_uid", \ &EDSAbiWrapperSingleton.icalproperty_new_sequence, "icalproperty_new_sequence", \ &EDSAbiWrapperSingleton.icalproperty_new_recurrenceid, "icalproperty_new_recurrenceid", \ &EDSAbiWrapperSingleton.icalproperty_set_value_from_string, "icalproperty_set_value_from_string", \ &EDSAbiWrapperSingleton.icalproperty_set_dtstamp, "icalproperty_set_dtstamp", \ &EDSAbiWrapperSingleton.icalproperty_set_lastmodified, "icalproperty_set_lastmodified", \ &EDSAbiWrapperSingleton.icalproperty_set_sequence, "icalproperty_set_sequence", \ &EDSAbiWrapperSingleton.icalproperty_set_uid, "icalproperty_set_uid", \ &EDSAbiWrapperSingleton.icalproperty_remove_parameter_by_kind, "icalproperty_remove_parameter_by_kind", \ &EDSAbiWrapperSingleton.icalproperty_add_parameter, "icalproperty_add_parameter", \ &EDSAbiWrapperSingleton.icalproperty_get_value_as_string, "icalproperty_get_value_as_string", \ &EDSAbiWrapperSingleton.icalproperty_get_x_name, "icalproperty_get_x_name", \ &EDSAbiWrapperSingleton.icalproperty_new_from_string, "icalproperty_new_from_string", \ &EDSAbiWrapperSingleton.icaltime_is_null_time, "icaltime_is_null_time", \ &EDSAbiWrapperSingleton.icaltime_is_utc, "icaltime_is_utc", \ &EDSAbiWrapperSingleton.icaltime_as_ical_string, "icaltime_as_ical_string", \ &EDSAbiWrapperSingleton.icaltime_from_string, "icaltime_from_string", \ &EDSAbiWrapperSingleton.icaltime_from_timet, "icaltime_from_timet", \ &EDSAbiWrapperSingleton.icaltime_null_time, "icaltime_null_time", \ &EDSAbiWrapperSingleton.icaltime_as_timet, "icaltime_as_timet", \ &EDSAbiWrapperSingleton.icaltime_set_timezone, "icaltime_set_timezone", \ &EDSAbiWrapperSingleton.icaltime_convert_to_zone, "icaltime_convert_to_zone", \ &EDSAbiWrapperSingleton.icaltime_get_timezone, "icaltime_get_timezone", \ &EDSAbiWrapperSingleton.icaltimezone_free, "icaltimezone_free", \ &EDSAbiWrapperSingleton.icaltimezone_get_builtin_timezone, "icaltimezone_get_builtin_timezone", \ &EDSAbiWrapperSingleton.icaltimezone_get_builtin_timezone_from_tzid, "icaltimezone_get_builtin_timezone_from_tzid", \ &EDSAbiWrapperSingleton.icaltimezone_get_component, "icaltimezone_get_component", \ &EDSAbiWrapperSingleton.icaltimezone_get_tzid, "icaltimezone_get_tzid", \ &EDSAbiWrapperSingleton.icaltimezone_new, "icaltimezone_new", \ &EDSAbiWrapperSingleton.icaltimezone_set_component, "icaltimezone_set_component", // icalparameter_new_scheduleagent was added in libical.so.1. We // use it only to detect the libical 1.0 ABI. This works because // all methods in EDS_ABI_WRAPPER_ICAL_R are considered optional. #define EDS_ABI_WRAPPER_ICAL_R \ &EDSAbiWrapperSingleton.icalparameter_new_scheduleagent, "icalparameter_new_scheduleagent", \ &EDSAbiWrapperSingleton.icalcomponent_as_ical_string_r, "icalcomponent_as_ical_string_r", \ &EDSAbiWrapperSingleton.icaltime_as_ical_string_r, "icaltime_as_ical_string_r", \ &EDSAbiWrapperSingleton.icalproperty_get_value_as_string_r, "icalproperty_get_value_as_string_r", # ifdef ENABLE_ECAL static const int libecalMinVersion = 3, libecalMaxVersion = 11; // EDS 3.4 ecalhandle = findSymbols("libecal-1.2.so", libecalMinVersion, libecalMaxVersion, FIND_SYMBOLS_NEED_ALL, NULL, &EDSAbiWrapperSingleton.e_cal_add_timezone, "e_cal_add_timezone", &EDSAbiWrapperSingleton.e_cal_component_get_icalcomponent, "e_cal_component_get_icalcomponent", &EDSAbiWrapperSingleton.e_cal_component_get_last_modified, "e_cal_component_get_last_modified", &EDSAbiWrapperSingleton.e_cal_component_get_type, "e_cal_component_get_type", &EDSAbiWrapperSingleton.e_cal_create_object, "e_cal_create_object", &EDSAbiWrapperSingleton.e_calendar_error_quark, "e_calendar_error_quark", &EDSAbiWrapperSingleton.e_cal_get_component_as_string, "e_cal_get_component_as_string", &EDSAbiWrapperSingleton.e_cal_get_object, "e_cal_get_object", &EDSAbiWrapperSingleton.e_cal_get_object_list_as_comp, "e_cal_get_object_list_as_comp", &EDSAbiWrapperSingleton.e_cal_get_sources, "e_cal_get_sources", &EDSAbiWrapperSingleton.e_cal_get_timezone, "e_cal_get_timezone", &EDSAbiWrapperSingleton.e_cal_modify_object, "e_cal_modify_object", &EDSAbiWrapperSingleton.e_cal_new, "e_cal_new", &EDSAbiWrapperSingleton.e_cal_new_from_uri, "e_cal_new_from_uri", &EDSAbiWrapperSingleton.e_cal_new_system_calendar, "e_cal_new_system_calendar", &EDSAbiWrapperSingleton.e_cal_new_system_tasks, "e_cal_new_system_tasks", &EDSAbiWrapperSingleton.e_cal_get_uri, "e_cal_get_uri", &EDSAbiWrapperSingleton.e_cal_open, "e_cal_open", &EDSAbiWrapperSingleton.e_cal_remove_object, "e_cal_remove_object", &EDSAbiWrapperSingleton.e_cal_remove_object_with_mod, "e_cal_remove_object_with_mod", &EDSAbiWrapperSingleton.e_cal_set_auth_func, "e_cal_set_auth_func", EDS_ABI_WRAPPER_ICAL_BASE (void *)0); EDSAbiHaveEcal = EDSAbiWrapperSingleton.e_cal_new != 0; ecalhandle = findSymbols("libecal-1.2.so", libecalMinVersion, libecalMaxVersion, 0, NULL, EDS_ABI_WRAPPER_ICAL_R (void *)0); # endif // ENABLE_ECAL # ifdef ENABLE_ICAL if (!EDSAbiWrapperSingleton.icalcomponent_add_component) { // libecal not found above (or not enabled), but libical // might still be available, so check for it separately ecalhandle = findSymbols("libical.so", 0, 1, FIND_SYMBOLS_NEED_ALL, NULL, EDS_ABI_WRAPPER_ICAL_BASE (void *)0); ecalhandle = findSymbols("libical.so", 0, 1, 0, NULL, EDS_ABI_WRAPPER_ICAL_R (void *)0); } EDSAbiHaveIcal = EDSAbiWrapperSingleton.icalcomponent_add_component != 0; EDSAbiHaveIcal1 = EDSAbiWrapperSingleton.icalparameter_new_scheduleagent != 0; # endif // ENABLE_ICAL # ifdef ENABLE_BLUETOOTH int bluetooth_version; libbluetoothhandle = findSymbols("libbluetooth.so", 2, 3, 0, &bluetooth_version, &EDSAbiWrapperSingleton.sdp_close, "sdp_close", &EDSAbiWrapperSingleton.sdp_connect, "sdp_connect", &EDSAbiWrapperSingleton.sdp_extract_pdu, "sdp_extract_pdu", &EDSAbiWrapperSingleton.sdp_extract_pdu_safe, "sdp_extract_pdu_safe", &EDSAbiWrapperSingleton.sdp_extract_seqtype, "sdp_extract_seqtype", &EDSAbiWrapperSingleton.sdp_extract_seqtype_safe, "sdp_extract_seqtype_safe", &EDSAbiWrapperSingleton.sdp_get_access_protos, "sdp_get_access_protos", &EDSAbiWrapperSingleton.sdp_get_proto_port, "sdp_get_proto_port", &EDSAbiWrapperSingleton.sdp_get_socket, "sdp_get_socket", &EDSAbiWrapperSingleton.sdp_list_append, "sdp_list_append", &EDSAbiWrapperSingleton.sdp_list_free, "sdp_list_free", &EDSAbiWrapperSingleton.sdp_process, "sdp_process", &EDSAbiWrapperSingleton.sdp_record_free, "sdp_record_free", &EDSAbiWrapperSingleton.sdp_service_search_attr_async, "sdp_service_search_attr_async", &EDSAbiWrapperSingleton.sdp_set_notify, "sdp_set_notify", &EDSAbiWrapperSingleton.sdp_uuid128_create, "sdp_uuid128_create", &EDSAbiWrapperSingleton.str2ba, "str2ba", (void *)0); if (bluetooth_version == 2) { // libbluetooth.so.2's sdp_extract_pdu() and // sdp_extract_seqtype() do not match our prototype (have no // bufsize). Some versions have a _safe variant. If not, use // wrappers which accept the bufsize parameter and drop it. if (!EDSAbiWrapperSingleton.sdp_extract_pdu_safe) { EDSAbiWrapperSingleton.sdp_extract_pdu_safe = wrap_sdp_extract_pdu; } if (!EDSAbiWrapperSingleton.sdp_extract_seqtype_safe) { EDSAbiWrapperSingleton.sdp_extract_seqtype_safe = wrap_sdp_extract_seqtype; } } else { // _safe variants were removed in favor of an API/ABI change. Redirect // into normal versions. We know they have the right prototype because // of that change. EDSAbiWrapperSingleton.sdp_extract_pdu_safe = reinterpret_cast(EDSAbiWrapperSingleton.sdp_extract_pdu); EDSAbiWrapperSingleton.sdp_extract_seqtype_safe = reinterpret_cast(EDSAbiWrapperSingleton.sdp_extract_seqtype); } SyncEvoHaveLibbluetooth = EDSAbiWrapperSingleton.sdp_connect != 0; # endif } #elif defined(EVOLUTION_ICAL_COMPATIBILITY) // Simpler version of the ABI wrapper which only checks for // libical.so.1. To be used with normal linking against libical.so.0 // and then patching the resulting files to be used with libical.so.1 // instead. int EDSAbiHaveIcal1; void EDSAbiWrapperInit() { static bool initialized; if (initialized) { return; } else { initialized = true; } static const char *soname = "libical.so.1"; void *dlhandle = dlopen(soname, RTLD_GLOBAL|RTLD_LAZY); if (dlhandle) { lookupInfo += "using "; lookupInfo += soname; lookupInfo += "\n"; EDSAbiHaveIcal1 = 1; dlclose(dlhandle); } } #endif // EVOLUTION_COMPATIBILITY extern "C" const char *EDSAbiWrapperInfo() { EDSAbiWrapperInit(); return lookupInfo.c_str(); } extern "C" const char *EDSAbiWrapperDebug() { EDSAbiWrapperInit(); return lookupDebug.c_str(); } #ifdef ENABLE_DBUS_TIMEOUT_HACK /** * There are valid use cases where the (previously hard-coded) default * timeout was too short. For example, libecal and libebook >= 2.30 * implement their synchronous API with synchronous D-Bus method calls, * which inevitably suffers from timeouts on slow hardware with large * amount of data (MBC #4026). * * This function replaces _DBUS_DEFAULT_TIMEOUT_VALUE and - if set - * interprets the content of SYNCEVOLUTION_DBUS_TIMEOUT as number of * milliseconds. 0 disables timeouts, which is also the default if the * env variable is not set. */ static int _dbus_connection_default_timeout(void) { const char *def = getenv("SYNCEVOLUTION_DBUS_TIMEOUT"); int timeout = 0; if (def) { timeout = atoi(def); } if (timeout == 0) { timeout = INT_MAX - 1; // not infinite, but very long; // INT_MAX led to a valgrind report in poll()/libdbus, // avoid it } return timeout; } extern "C" int dbus_connection_send_with_reply (void *connection, void *message, void **pending_return, int timeout_milliseconds) { static typeof(dbus_connection_send_with_reply) *real_func; if (!real_func) { real_func = (typeof(dbus_connection_send_with_reply) *)dlsym(RTLD_NEXT, "dbus_connection_send_with_reply"); } return real_func ? real_func(connection, message, pending_return, timeout_milliseconds == -1 ? _dbus_connection_default_timeout() : timeout_milliseconds) : 0; } #endif // ENABLE_DBUS_TIMEOUT_HACK syncevolution_1.4/src/syncevo/eds_abi_wrapper.h000066400000000000000000001271421230021373600221410ustar00rootroot00000000000000/* * Copyright (C) 2005-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ /** * The main purpose of this file is to separate SyncEvolution from * Evolution Dataserver ABI changes by never depending directly * on any symbol in its libraries. Instead all functions are * called via function pointers found via dlopen/dlsym. * * This is more flexible than linking against a specific revision of * the libs, but circumvents the usual lib versioning and therefore * may fail when the functions needed by SyncEvolution change. * * History shows that this has not happened for a long time whereas * the versions of EDS libs had to be bumped quite a few times due to other * changes. * * A similar problem came up with an API and ABI change in * libbluetooth.so.3: the sdp_extract_seqtype() and sdp_extract_pdu() * gained one more parameter (a buffer size for the buffer being * written into). When compatibility mode is enabled, this header * file provides the extended version and maps it back to the older * version if necessary. * * To use the wrappers, include this header file. It overrides * the normal C API functions with the function pointers via * defines. */ #ifndef INCL_EDS_ABI_WRAPPER #define INCL_EDS_ABI_WRAPPER #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_EDS #include #if defined(USE_EDS_CLIENT) #include #else #ifdef HAVE_LIBEDATASERVER_EDS_VERSION_H #include #endif #include #include #endif #ifdef ENABLE_EBOOK #ifdef USE_EDS_CLIENT #include #else #include #include #include #endif #endif #ifdef ENABLE_ECAL # define HANDLE_LIBICAL_MEMORY 1 #ifdef USE_EDS_CLIENT #include #else #include #endif #endif #endif #ifdef ENABLE_BLUETOOTH #include #include #endif #if !defined(ENABLE_ECAL) && defined(ENABLE_ICAL) # define HANDLE_LIBICAL_MEMORY 1 # include #endif #ifdef __cplusplus extern "C" { #endif #if defined(EVOLUTION_COMPATIBILITY) || defined(EVOLUTION_ICAL_COMPATIBILITY) extern int EDSAbiHaveIcal1; // 1 for libical.so.1, 0 for libical.so.0; numeric value matters, see defines below. /** initialize pointers to EDS functions, if necessary; can be called multiple times */ void EDSAbiWrapperInit(); #else # define EDSAbiWrapperInit() #endif #ifdef EVOLUTION_COMPATIBILITY /** libebook, libecal, libedataserver available (currently checks for e_book_new/e_cal_new/e_source_group_peek_sources) */ extern int EDSAbiHaveEbook, EDSAbiHaveEcal, EDSAbiHaveEdataserver; extern int EDSAbiHaveIcal; /** libbluetooth available (checks sdp_connect()) */ extern int SyncEvoHaveLibbluetooth; /** * This is a struct instead of a namespace because that allows * printing it in a debugger. This code also has to be usable * in plain C. */ struct EDSAbiWrapper { # ifdef HAVE_EDS GType (*e_source_get_type) (void); char *(*e_source_get_uri) (ESource *source); GType (*e_source_group_get_type) (void); GSList *(*e_source_group_peek_sources) (ESourceGroup *group); GSList *(*e_source_list_peek_groups) (ESourceList *list); const char *(*e_source_peek_name) (ESource *source); # endif /* HAVE_EDS */ # ifdef ENABLE_EBOOK gboolean (*e_book_add_contact) (EBook *book, EContact *contact, GError **error); gboolean (*e_book_authenticate_user) (EBook *book, const char *user, const char *passwd, const char *auth_method, GError **error); gboolean (*e_book_commit_contact) (EBook *book, EContact *contact, GError **error); EContact* (*e_contact_duplicate) (EContact *contact); gconstpointer (*e_contact_get_const) (EContact *contact, EContactField field_id); gpointer (*e_contact_get) (EContact *contact, EContactField field_id); void (*e_contact_name_free)(EContactName *name); GType (*e_contact_get_type) (void); EContact* (*e_contact_new_from_vcard) (const char *vcard); void (*e_contact_set) (EContact *contact, EContactField field_id, const gpointer value); gboolean (*e_book_get_addressbooks) (ESourceList** addressbook_sources, GError **error); gboolean (*e_book_get_changes) (EBook *book, char *changeid, GList **changes, GError **error); gboolean (*e_book_get_contact) (EBook *book, const char *id, EContact **contact, GError **error); gboolean (*e_book_get_contacts) (EBook *book, EBookQuery *query, GList **contacts, GError **error); gboolean (*e_book_get_supported_auth_methods) (EBook *book, GList **auth_methods, GError **error); const char *(*e_book_get_uri) (EBook *book); EBook *(*e_book_new) (ESource *source, GError **error); EBook *(*e_book_new_default_addressbook) (GError **error); EBook *(*e_book_new_from_uri) (const char *uri, GError **error); EBook *(*e_book_new_system_addressbook) (GError **error); gboolean (*e_book_open) (EBook *book, gboolean only_if_exists, GError **error); EBookQuery* (*e_book_query_any_field_contains) (const char *value); void (*e_book_query_unref) (EBookQuery *q); GQuark (*e_book_error_quark) (void); gboolean (*e_book_remove_contact) (EBook *book, const char *id, GError **error); char* (*e_vcard_to_string) (EVCard *evc, EVCardFormat format); gboolean (*e_contact_inline_local_photos) (EContact *contact, GError **error); # endif /* ENABLE_EBOOK */ # ifdef ENABLE_ECAL gboolean (*e_cal_add_timezone) (ECal *ecal, icaltimezone *izone, GError **error); icalcomponent *(*e_cal_component_get_icalcomponent) (ECalComponent *comp); void (*e_cal_component_get_last_modified) (ECalComponent *comp, struct icaltimetype **t); GType (*e_cal_component_get_type) (void); gboolean (*e_cal_create_object) (ECal *ecal, icalcomponent *icalcomp, char **uid, GError **error); GQuark (*e_calendar_error_quark) (void) G_GNUC_CONST; char* (*e_cal_get_component_as_string) (ECal *ecal, icalcomponent *icalcomp); gboolean (*e_cal_get_object) (ECal *ecal, const char *uid, const char *rid, icalcomponent **icalcomp, GError **error); gboolean (*e_cal_get_object_list_as_comp) (ECal *ecal, const char *query, GList **objects, GError **error); gboolean (*e_cal_get_sources) (ESourceList **sources, ECalSourceType type, GError **error); gboolean (*e_cal_get_timezone) (ECal *ecal, const char *tzid, icaltimezone **zone, GError **error); gboolean (*e_cal_modify_object) (ECal *ecal, icalcomponent *icalcomp, CalObjModType mod, GError **error); ECal *(*e_cal_new) (ESource *source, ECalSourceType type); ECal *(*e_cal_new_from_uri) (const gchar *uri, ECalSourceType type); ECal *(*e_cal_new_system_calendar) (void); ECal *(*e_cal_new_system_tasks) (void); const gchar *(*e_cal_get_uri)(ECal *); gboolean (*e_cal_open) (ECal *ecal, gboolean only_if_exists, GError **error); gboolean (*e_cal_remove_object) (ECal *ecal, const char *uid, GError **error); gboolean (*e_cal_remove_object_with_mod) (ECal *ecal, const char *uid, const char *rid, CalObjModType mod, GError **error); void (*e_cal_set_auth_func) (ECal *ecal, ECalAuthFunc func, gpointer data); #endif /* ENABLE_ECAL */ #ifdef ENABLE_ICAL void (*icalcomponent_add_component) (icalcomponent* parent, icalcomponent* child); void (*icalcomponent_add_property) (icalcomponent* comp, icalproperty* prop); char* (*icalcomponent_as_ical_string) (icalcomponent* component); void (*icalcomponent_free) (icalcomponent* component); icalcomponent* (*icalcomponent_get_first_component) (icalcomponent* component, icalcomponent_kind kind); icalproperty* (*icalcomponent_get_first_property) (icalcomponent* component, icalproperty_kind kind); icalcomponent* (*icalcomponent_get_next_component) (icalcomponent* component, icalcomponent_kind kind); icalproperty* (*icalcomponent_get_next_property) (icalcomponent* component, icalproperty_kind kind); struct icaltimetype (*icalcomponent_get_recurrenceid) (icalcomponent* comp); icaltimezone* (*icalcomponent_get_timezone) (icalcomponent* comp, const char *tzid); const char* (*icalcomponent_get_location) (icalcomponent* comp); const char* (*icalcomponent_get_summary) (icalcomponent* comp); const char* (*icalcomponent_get_uid) (icalcomponent* comp); struct icaltimetype (*icalcomponent_get_dtstart)(icalcomponent* comp); icalcomponent_kind (*icalcomponent_isa) (const icalcomponent* component); icalcomponent* (*icalcomponent_new_clone) (icalcomponent* component); icalcomponent* (*icalcomponent_new_from_string) (char* str); icalcomponent* (*icalcomponent_new) (icalcomponent_kind kind); void (*icalcomponent_merge_component) (icalcomponent* component, icalcomponent *comp_to_merge); void (*icalcomponent_remove_component) (icalcomponent* component, icalcomponent *comp_to_remove); void (*icalcomponent_remove_property) (icalcomponent* component, icalproperty* property); void (*icalcomponent_set_uid) (icalcomponent* comp, const char* v); void (*icalcomponent_set_recurrenceid)(icalcomponent* comp, struct icaltimetype v); icalcomponent* (*icalcomponent_vanew) (icalcomponent_kind kind, ...); const char* (*icalparameter_get_tzid) (const icalparameter* value); void (*icalparameter_set_tzid) (icalparameter* value, const char* v); icalparameter *(*icalparameter_new_from_value_string)(icalparameter_kind kind, const char *value); icalparameter *(*icalparameter_new_clone)(icalparameter *param); icalproperty *(*icalproperty_new_clone)(icalproperty *prop); void (*icalproperty_free)(icalproperty *prop); const char* (*icalproperty_get_description) (const icalproperty* prop); const char* (*icalproperty_get_property_name) (const icalproperty* prop); const char* (*icalproperty_get_uid) (const icalproperty* prop); struct icaltimetype (*icalproperty_get_recurrenceid) (const icalproperty* prop); void (*icalproperty_set_recurrenceid) (const icalproperty* prop, icaltimetype rid); int (*icalproperty_get_sequence) (const icalproperty* prop); icalparameter* (*icalproperty_get_first_parameter) (icalproperty* prop, icalparameter_kind kind); struct icaltimetype (*icalproperty_get_lastmodified) (const icalproperty* prop); icalparameter* (*icalproperty_get_next_parameter) (icalproperty* prop, icalparameter_kind kind); void (*icalproperty_set_parameter)(icalproperty *prop, icalparameter *param); const char* (*icalproperty_get_summary) (const icalproperty* prop); icalproperty* (*icalproperty_new_description) (const char* v); icalproperty* (*icalproperty_new_summary) (const char* v); icalproperty* (*icalproperty_new_sequence) (int v); icalproperty* (*icalproperty_new_uid) (const char* v); icalproperty* (*icalproperty_new_recurrenceid) (icaltimetype v); void (*icalproperty_set_value_from_string) (icalproperty* prop,const char* value, const char* kind); void (*icalproperty_set_dtstamp) (icalproperty* prop, struct icaltimetype v); void (*icalproperty_set_lastmodified) (icalproperty* prop, struct icaltimetype v); void (*icalproperty_set_sequence) (icalproperty* prop, int v); void (*icalproperty_set_uid) (icalproperty* prop, const char *v); void (*icalproperty_remove_parameter_by_kind)(icalproperty* prop, icalparameter_kind kind); void (*icalproperty_add_parameter)(icalproperty* prop,icalparameter* parameter); const char* (*icalproperty_get_value_as_string)(const icalproperty* prop); const char* (*icalproperty_get_x_name)(icalproperty* prop); icalproperty* (*icalproperty_new_from_string)(const char* str); int (*icaltime_is_null_time)(const struct icaltimetype t); int (*icaltime_is_utc)(const struct icaltimetype t); const char* (*icaltime_as_ical_string) (const struct icaltimetype tt); icaltimetype (*icaltime_from_string)(const char* str); icaltimetype (*icaltime_from_timet)(const time_t v, const int is_date); icaltimetype (*icaltime_null_time)(); time_t (*icaltime_as_timet)(const struct icaltimetype); void (*icaltime_set_timezone)(icaltimetype *tt, const icaltimezone *zone); struct icaltimetype (*icaltime_convert_to_zone)(const struct icaltimetype tt, icaltimezone *zone); const icaltimezone *(*icaltime_get_timezone)(const struct icaltimetype t); void (*icaltimezone_free) (icaltimezone *zone, int free_struct); icaltimezone* (*icaltimezone_get_builtin_timezone) (const char *location); icaltimezone* (*icaltimezone_get_builtin_timezone_from_tzid) (const char *tzid); icalcomponent* (*icaltimezone_get_component) (icaltimezone *zone); char* (*icaltimezone_get_tzid) (icaltimezone *zone); icaltimezone *(*icaltimezone_new) (void); int (*icaltimezone_set_component) (icaltimezone *zone, icalcomponent *comp); // Optional, added in libical.so.1. Can't be called be like this, // we merely check for the method to detect the ABI. void (*icalparameter_new_scheduleagent)(void); // optional variants which allocate the returned string for us const char* (*icaltime_as_ical_string_r) (const struct icaltimetype tt); char* (*icalcomponent_as_ical_string_r) (icalcomponent* component); char* (*icalproperty_get_value_as_string_r) (const icalproperty* prop); # endif /* ENABLE_ICAL */ # ifdef ENABLE_BLUETOOTH int (*sdp_close)(sdp_session_t *session); sdp_session_t *(*sdp_connect)(const bdaddr_t *src, const bdaddr_t *dst, uint32_t flags); /** libbluetooth.so.2 version of sdp_extract_pdu() */ sdp_record_t *(*sdp_extract_pdu)(const uint8_t *pdata, int *scanned); /** alternate version of sdp_extract_pdu() only found in some releases of libbluetooth.so.2 */ sdp_record_t *(*sdp_extract_pdu_safe)(const uint8_t *pdata, int bufsize, int *scanned); /** libbluetooth.so.2 version of sdp_extract_seqtype() */ int (*sdp_extract_seqtype)(const uint8_t *buf, uint8_t *dtdp, int *size); /** alternate version of sdp_extract_seqtype() only found in some releases of libbluetooth.so.2 */ int (*sdp_extract_seqtype_safe)(const uint8_t *buf, int bufsize, uint8_t *dtdp, int *size); int (*sdp_get_access_protos)(const sdp_record_t *rec, sdp_list_t **protos); int (*sdp_get_proto_port)(const sdp_list_t *list, int proto); int (*sdp_get_socket)(const sdp_session_t *session); sdp_list_t *(*sdp_list_append)(sdp_list_t *list, void *d); void (*sdp_list_free)(sdp_list_t *list, sdp_free_func_t f); int (*sdp_process)(sdp_session_t *session); void (*sdp_record_free)(sdp_record_t *rec); int (*sdp_service_search_attr_async)(sdp_session_t *session, const sdp_list_t *search, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list); int (*sdp_set_notify)(sdp_session_t *session, sdp_callback_t *func, void *udata); uuid_t *(*sdp_uuid128_create)(uuid_t *uuid, const void *data); int (*str2ba)(const char *str, bdaddr_t *ba); # endif /* ENABLE_BLUETOOTH */ int initialized; }; extern struct EDSAbiWrapper EDSAbiWrapperSingleton; # ifndef EDS_ABI_WRAPPER_NO_REDEFINE # ifdef HAVE_EDS # define e_source_get_type EDSAbiWrapperSingleton.e_source_get_type # define e_source_get_uri EDSAbiWrapperSingleton.e_source_get_uri # define e_source_group_get_type EDSAbiWrapperSingleton.e_source_group_get_type # define e_source_group_peek_sources EDSAbiWrapperSingleton.e_source_group_peek_sources # define e_source_list_peek_groups EDSAbiWrapperSingleton.e_source_list_peek_groups # define e_source_peek_name EDSAbiWrapperSingleton.e_source_peek_name # endif /* HAVE_EDS */ # ifdef ENABLE_EBOOK # define e_book_add_contact EDSAbiWrapperSingleton.e_book_add_contact # define e_book_authenticate_user EDSAbiWrapperSingleton.e_book_authenticate_user # define e_book_commit_contact EDSAbiWrapperSingleton.e_book_commit_contact # define e_contact_duplicate EDSAbiWrapperSingleton.e_contact_duplicate # define e_contact_get_const EDSAbiWrapperSingleton.e_contact_get_const # define e_contact_get EDSAbiWrapperSingleton.e_contact_get # define e_contact_name_free EDSAbiWrapperSingleton.e_contact_name_free # define e_contact_get_type EDSAbiWrapperSingleton.e_contact_get_type # define e_contact_new_from_vcard EDSAbiWrapperSingleton.e_contact_new_from_vcard # define e_contact_set EDSAbiWrapperSingleton.e_contact_set # define e_book_error_quark EDSAbiWrapperSingleton.e_book_error_quark # define e_book_get_addressbooks EDSAbiWrapperSingleton.e_book_get_addressbooks # define e_book_get_changes EDSAbiWrapperSingleton.e_book_get_changes # define e_book_get_contact EDSAbiWrapperSingleton.e_book_get_contact # define e_book_get_contacts EDSAbiWrapperSingleton.e_book_get_contacts # define e_book_get_supported_auth_methods EDSAbiWrapperSingleton.e_book_get_supported_auth_methods # define e_book_get_uri EDSAbiWrapperSingleton.e_book_get_uri # define e_book_new EDSAbiWrapperSingleton.e_book_new # define e_book_new_default_addressbook EDSAbiWrapperSingleton.e_book_new_default_addressbook # define e_book_new_from_uri EDSAbiWrapperSingleton.e_book_new_from_uri # define e_book_new_system_addressbook EDSAbiWrapperSingleton.e_book_new_system_addressbook # define e_book_open EDSAbiWrapperSingleton.e_book_open # define e_book_query_any_field_contains EDSAbiWrapperSingleton.e_book_query_any_field_contains # define e_book_query_unref EDSAbiWrapperSingleton.e_book_query_unref # define e_book_remove_contact EDSAbiWrapperSingleton.e_book_remove_contact # define e_vcard_to_string EDSAbiWrapperSingleton.e_vcard_to_string # define e_contact_inline_local_photos EDSAbiWrapperSingleton.e_contact_inline_local_photos # endif /* ENABLE_EBOOK */ # ifdef ENABLE_ECAL # define e_cal_add_timezone EDSAbiWrapperSingleton.e_cal_add_timezone # define e_cal_component_get_icalcomponent EDSAbiWrapperSingleton.e_cal_component_get_icalcomponent # define e_cal_component_get_last_modified EDSAbiWrapperSingleton.e_cal_component_get_last_modified # define e_cal_component_get_type EDSAbiWrapperSingleton.e_cal_component_get_type # define e_cal_create_object EDSAbiWrapperSingleton.e_cal_create_object # define e_calendar_error_quark EDSAbiWrapperSingleton.e_calendar_error_quark # define e_cal_get_component_as_string EDSAbiWrapperSingleton.e_cal_get_component_as_string # define e_cal_get_object EDSAbiWrapperSingleton.e_cal_get_object # define e_cal_get_object_list_as_comp EDSAbiWrapperSingleton.e_cal_get_object_list_as_comp # define e_cal_get_sources EDSAbiWrapperSingleton.e_cal_get_sources # define e_cal_get_timezone EDSAbiWrapperSingleton.e_cal_get_timezone # define e_cal_modify_object EDSAbiWrapperSingleton.e_cal_modify_object # define e_cal_new EDSAbiWrapperSingleton.e_cal_new # define e_cal_new_from_uri EDSAbiWrapperSingleton.e_cal_new_from_uri # define e_cal_new_system_calendar EDSAbiWrapperSingleton.e_cal_new_system_calendar # define e_cal_new_system_tasks EDSAbiWrapperSingleton.e_cal_new_system_tasks # define e_cal_get_uri EDSAbiWrapperSingleton.e_cal_get_uri # define e_cal_open EDSAbiWrapperSingleton.e_cal_open # define e_cal_remove_object EDSAbiWrapperSingleton.e_cal_remove_object # define e_cal_remove_object_with_mod EDSAbiWrapperSingleton.e_cal_remove_object_with_mod # define e_cal_set_auth_func EDSAbiWrapperSingleton.e_cal_set_auth_func # endif /* ENABLE_ECAL */ # ifdef ENABLE_ICAL # define icalcomponent_add_component EDSAbiWrapperSingleton.icalcomponent_add_component # define icalcomponent_add_property EDSAbiWrapperSingleton.icalcomponent_add_property # define icalcomponent_as_ical_string (EDSAbiWrapperSingleton.icalcomponent_as_ical_string_r ? EDSAbiWrapperSingleton.icalcomponent_as_ical_string_r : EDSAbiWrapperSingleton.icalcomponent_as_ical_string) # define icalcomponent_free EDSAbiWrapperSingleton.icalcomponent_free # define icalcomponent_get_first_component EDSAbiWrapperSingleton.icalcomponent_get_first_component # define icalcomponent_get_first_property EDSAbiWrapperSingleton.icalcomponent_get_first_property # define icalcomponent_get_next_component EDSAbiWrapperSingleton.icalcomponent_get_next_component # define icalcomponent_get_next_property EDSAbiWrapperSingleton.icalcomponent_get_next_property # define icalcomponent_get_recurrenceid EDSAbiWrapperSingleton.icalcomponent_get_recurrenceid # define icalcomponent_get_timezone EDSAbiWrapperSingleton.icalcomponent_get_timezone # define icalcomponent_get_location EDSAbiWrapperSingleton.icalcomponent_get_location # define icalcomponent_get_summary EDSAbiWrapperSingleton.icalcomponent_get_summary # define icalcomponent_get_uid EDSAbiWrapperSingleton.icalcomponent_get_uid # define icalcomponent_get_dtstart EDSAbiWrapperSingleton.icalcomponent_get_dtstart # define icalcomponent_isa EDSAbiWrapperSingleton.icalcomponent_isa # define icalcomponent_new_clone EDSAbiWrapperSingleton.icalcomponent_new_clone # define icalcomponent_new_from_string EDSAbiWrapperSingleton.icalcomponent_new_from_string # define icalcomponent_new EDSAbiWrapperSingleton.icalcomponent_new # define icalcomponent_merge_component EDSAbiWrapperSingleton.icalcomponent_merge_component # define icalcomponent_remove_component EDSAbiWrapperSingleton.icalcomponent_remove_component # define icalcomponent_remove_property EDSAbiWrapperSingleton.icalcomponent_remove_property # define icalcomponent_set_uid EDSAbiWrapperSingleton.icalcomponent_set_uid # define icalcomponent_set_recurrenceid EDSAbiWrapperSingleton.icalcomponent_set_recurrenceid # define icalcomponent_vanew EDSAbiWrapperSingleton.icalcomponent_vanew # define icalparameter_get_tzid EDSAbiWrapperSingleton.icalparameter_get_tzid # define icalparameter_set_tzid EDSAbiWrapperSingleton.icalparameter_set_tzid # define icalparameter_new_from_value_string EDSAbiWrapperSingleton.icalparameter_new_from_value_string # define icalparameter_new_clone EDSAbiWrapperSingleton.icalparameter_new_clone # define icalproperty_new_clone EDSAbiWrapperSingleton.icalproperty_new_clone # define icalproperty_free EDSAbiWrapperSingleton.icalproperty_free # define icalproperty_get_description EDSAbiWrapperSingleton.icalproperty_get_description # define icalproperty_get_uid EDSAbiWrapperSingleton.icalproperty_get_uid # define icalproperty_get_recurrenceid EDSAbiWrapperSingleton.icalproperty_get_recurrenceid # define icalproperty_set_recurrenceid EDSAbiWrapperSingleton.icalproperty_set_recurrenceid # define icalproperty_get_sequence EDSAbiWrapperSingleton.icalproperty_get_sequence # define icalproperty_get_property_name EDSAbiWrapperSingleton.icalproperty_get_property_name # define icalproperty_get_first_parameter EDSAbiWrapperSingleton.icalproperty_get_first_parameter # define icalproperty_get_lastmodified EDSAbiWrapperSingleton.icalproperty_get_lastmodified # define icalproperty_get_next_parameter EDSAbiWrapperSingleton.icalproperty_get_next_parameter # define icalproperty_set_parameter EDSAbiWrapperSingleton.icalproperty_set_parameter # define icalproperty_get_summary EDSAbiWrapperSingleton.icalproperty_get_summary # define icalproperty_new_description EDSAbiWrapperSingleton.icalproperty_new_description # define icalproperty_new_summary EDSAbiWrapperSingleton.icalproperty_new_summary # define icalproperty_new_uid EDSAbiWrapperSingleton.icalproperty_new_uid # define icalproperty_new_sequence EDSAbiWrapperSingleton.icalproperty_new_sequence # define icalproperty_new_recurrenceid EDSAbiWrapperSingleton.icalproperty_new_recurrenceid # define icalproperty_set_value_from_string EDSAbiWrapperSingleton.icalproperty_set_value_from_string # define icalproperty_set_dtstamp EDSAbiWrapperSingleton.icalproperty_set_dtstamp # define icalproperty_set_lastmodified EDSAbiWrapperSingleton.icalproperty_set_lastmodified # define icalproperty_set_sequence EDSAbiWrapperSingleton.icalproperty_set_sequence # define icalproperty_set_uid EDSAbiWrapperSingleton.icalproperty_set_uid # define icalproperty_remove_parameter_by_kind EDSAbiWrapperSingleton.icalproperty_remove_parameter_by_kind # define icalproperty_add_parameter EDSAbiWrapperSingleton.icalproperty_add_parameter # define icalproperty_get_value_as_string (EDSAbiWrapperSingleton.icalproperty_get_value_as_string_r ? EDSAbiWrapperSingleton.icalproperty_get_value_as_string_r : (char *(*)(const icalproperty*))EDSAbiWrapperSingleton.icalproperty_get_value_as_string) # define icalproperty_get_x_name EDSAbiWrapperSingleton.icalproperty_get_x_name # define icalproperty_new_from_string EDSAbiWrapperSingleton.icalproperty_new_from_string # define icaltime_is_null_time EDSAbiWrapperSingleton.icaltime_is_null_time # define icaltime_is_utc EDSAbiWrapperSingleton.icaltime_is_utc # define icaltime_as_ical_string (EDSAbiWrapperSingleton.icaltime_as_ical_string_r ? EDSAbiWrapperSingleton.icaltime_as_ical_string_r : EDSAbiWrapperSingleton.icaltime_as_ical_string) # define icaltime_from_string EDSAbiWrapperSingleton.icaltime_from_string # define icaltime_from_timet EDSAbiWrapperSingleton.icaltime_from_timet # define icaltime_null_time EDSAbiWrapperSingleton.icaltime_null_time # define icaltime_as_timet EDSAbiWrapperSingleton.icaltime_as_timet # define icaltime_set_timezone EDSAbiWrapperSingleton.icaltime_set_timezone # define icaltime_convert_to_zone EDSAbiWrapperSingleton.icaltime_convert_to_zone # define icaltime_get_timezone EDSAbiWrapperSingleton.icaltime_get_timezone # define icaltimezone_free EDSAbiWrapperSingleton.icaltimezone_free # define icaltimezone_get_builtin_timezone EDSAbiWrapperSingleton.icaltimezone_get_builtin_timezone # define icaltimezone_get_builtin_timezone_from_tzid EDSAbiWrapperSingleton.icaltimezone_get_builtin_timezone_from_tzid # define icaltimezone_get_component EDSAbiWrapperSingleton.icaltimezone_get_component # define icaltimezone_get_tzid EDSAbiWrapperSingleton.icaltimezone_get_tzid # define icaltimezone_new EDSAbiWrapperSingleton.icaltimezone_new # define icaltimezone_set_component EDSAbiWrapperSingleton.icaltimezone_set_component # endif /* ENABLE_ICAL */ # ifdef ENABLE_BLUETOOTH # define sdp_close EDSAbiWrapperSingleton.sdp_close # define sdp_connect EDSAbiWrapperSingleton.sdp_connect # define sdp_extract_pdu do_not_use_sdp_extract_pdu # define sdp_extract_pdu_safe EDSAbiWrapperSingleton.sdp_extract_pdu_safe # define sdp_extract_seqtype do_not_use_sdp_extract_seqtype # define sdp_extract_seqtype_safe EDSAbiWrapperSingleton.sdp_extract_seqtype_safe # define sdp_get_access_protos EDSAbiWrapperSingleton.sdp_get_access_protos # define sdp_get_proto_port EDSAbiWrapperSingleton.sdp_get_proto_port # define sdp_get_socket EDSAbiWrapperSingleton.sdp_get_socket # define sdp_list_append EDSAbiWrapperSingleton.sdp_list_append # define sdp_list_free EDSAbiWrapperSingleton.sdp_list_free # define sdp_process EDSAbiWrapperSingleton.sdp_process # define sdp_record_free EDSAbiWrapperSingleton.sdp_record_free # define sdp_service_search_attr_async EDSAbiWrapperSingleton.sdp_service_search_attr_async # define sdp_set_notify EDSAbiWrapperSingleton.sdp_set_notify # define sdp_uuid128_create EDSAbiWrapperSingleton.sdp_uuid128_create # define str2ba EDSAbiWrapperSingleton.str2ba # endif /* ENABLE_BLUETOOTH */ # endif /* EDS_ABI_WRAPPER_NO_REDEFINE */ #else /* EVOLUTION_COMPATIBILITY */ // This is necessary because in C++, 1 && 1 triggers // a warning with some gcc versions. #ifdef __cplusplus # define EDS_ABI_HACK_TRUE true #else # define EDS_ABI_HACK_TRUE 1 #endif # define EDSAbiHaveEbook EDS_ABI_HACK_TRUE # define EDSAbiHaveEcal EDS_ABI_HACK_TRUE # define EDSAbiHaveEdataserver EDS_ABI_HACK_TRUE # define EDSAbiHaveIcal EDS_ABI_HACK_TRUE # define SyncEvoHaveLibbluetooth EDS_ABI_HACK_TRUE # if !defined(EDS_ABI_WRAPPER_NO_REDEFINE) && defined(HAVE_LIBICAL_R) # ifdef ENABLE_ICAL # ifndef LIBICAL_MEMFIXES /* This changes the semantic of the normal functions the same way as libecal did. */ # define LIBICAL_MEMFIXES 1 # define icaltime_as_ical_string icaltime_as_ical_string_r # define icalcomponent_as_ical_string icalcomponent_as_ical_string_r # define icalproperty_get_value_as_string icalproperty_get_value_as_string_r # endif /* LIBICAL_MEMFIXES */ # endif /* ENABLE_ICAL */ # endif /* EDS_ABI_WRAPPER_NO_REDEFINE */ #endif /* EVOLUTION_COMPATIBILITY */ #if defined(EVOLUTION_ICAL_COMPATIBILITY) && defined(ENABLE_ICAL) /* * libical.so.1 inserted a new ICAL_ACKNOWLEDGED_PROPERTY at the start of the enum, * causing a renumbering of the constants. We need to map the names to the right constants * at runtime. * * Opening libical.so.1 instead of libical.so.0 is orthogonal to this: * it can be done either via dlopen() with EVOLUTION_COMPATIBILITY, or * by patching a shared library which was originally linked against * libical.so.0. * * unchanged: ICAL_ANY_PROPERTY = 0, * new, cannot depend on it: ICAL_ACKNOWLEDGED_PROPERTY, * * These are the original enum values from libical.so.0: */ enum { EDSABI_ICAL_ACTION_PROPERTY = 1, EDSABI_ICAL_ALLOWCONFLICT_PROPERTY, EDSABI_ICAL_ATTACH_PROPERTY, EDSABI_ICAL_ATTENDEE_PROPERTY, EDSABI_ICAL_CALID_PROPERTY, EDSABI_ICAL_CALMASTER_PROPERTY, EDSABI_ICAL_CALSCALE_PROPERTY, EDSABI_ICAL_CAPVERSION_PROPERTY, EDSABI_ICAL_CARLEVEL_PROPERTY, EDSABI_ICAL_CARID_PROPERTY, EDSABI_ICAL_CATEGORIES_PROPERTY, EDSABI_ICAL_CLASS_PROPERTY, EDSABI_ICAL_CMD_PROPERTY, EDSABI_ICAL_COMMENT_PROPERTY, EDSABI_ICAL_COMPLETED_PROPERTY, EDSABI_ICAL_COMPONENTS_PROPERTY, EDSABI_ICAL_CONTACT_PROPERTY, EDSABI_ICAL_CREATED_PROPERTY, EDSABI_ICAL_CSID_PROPERTY, EDSABI_ICAL_DATEMAX_PROPERTY, EDSABI_ICAL_DATEMIN_PROPERTY, EDSABI_ICAL_DECREED_PROPERTY, EDSABI_ICAL_DEFAULTCHARSET_PROPERTY, EDSABI_ICAL_DEFAULTLOCALE_PROPERTY, EDSABI_ICAL_DEFAULTTZID_PROPERTY, EDSABI_ICAL_DEFAULTVCARS_PROPERTY, EDSABI_ICAL_DENY_PROPERTY, EDSABI_ICAL_DESCRIPTION_PROPERTY, EDSABI_ICAL_DTEND_PROPERTY, EDSABI_ICAL_DTSTAMP_PROPERTY, EDSABI_ICAL_DTSTART_PROPERTY, EDSABI_ICAL_DUE_PROPERTY, EDSABI_ICAL_DURATION_PROPERTY, EDSABI_ICAL_EXDATE_PROPERTY, EDSABI_ICAL_EXPAND_PROPERTY, EDSABI_ICAL_EXRULE_PROPERTY, EDSABI_ICAL_FREEBUSY_PROPERTY, EDSABI_ICAL_GEO_PROPERTY, EDSABI_ICAL_GRANT_PROPERTY, EDSABI_ICAL_ITIPVERSION_PROPERTY, EDSABI_ICAL_LASTMODIFIED_PROPERTY, EDSABI_ICAL_LOCATION_PROPERTY, EDSABI_ICAL_MAXCOMPONENTSIZE_PROPERTY, EDSABI_ICAL_MAXDATE_PROPERTY, EDSABI_ICAL_MAXRESULTS_PROPERTY, EDSABI_ICAL_MAXRESULTSSIZE_PROPERTY, EDSABI_ICAL_METHOD_PROPERTY, EDSABI_ICAL_MINDATE_PROPERTY, EDSABI_ICAL_MULTIPART_PROPERTY, EDSABI_ICAL_NAME_PROPERTY, EDSABI_ICAL_ORGANIZER_PROPERTY, EDSABI_ICAL_OWNER_PROPERTY, EDSABI_ICAL_PERCENTCOMPLETE_PROPERTY, EDSABI_ICAL_PERMISSION_PROPERTY, EDSABI_ICAL_PRIORITY_PROPERTY, EDSABI_ICAL_PRODID_PROPERTY, EDSABI_ICAL_QUERY_PROPERTY, EDSABI_ICAL_QUERYLEVEL_PROPERTY, EDSABI_ICAL_QUERYID_PROPERTY, EDSABI_ICAL_QUERYNAME_PROPERTY, EDSABI_ICAL_RDATE_PROPERTY, EDSABI_ICAL_RECURACCEPTED_PROPERTY, EDSABI_ICAL_RECUREXPAND_PROPERTY, EDSABI_ICAL_RECURLIMIT_PROPERTY, EDSABI_ICAL_RECURRENCEID_PROPERTY, EDSABI_ICAL_RELATEDTO_PROPERTY, EDSABI_ICAL_RELCALID_PROPERTY, EDSABI_ICAL_REPEAT_PROPERTY, EDSABI_ICAL_REQUESTSTATUS_PROPERTY, EDSABI_ICAL_RESOURCES_PROPERTY, EDSABI_ICAL_RESTRICTION_PROPERTY, EDSABI_ICAL_RRULE_PROPERTY, EDSABI_ICAL_SCOPE_PROPERTY, EDSABI_ICAL_SEQUENCE_PROPERTY, EDSABI_ICAL_STATUS_PROPERTY, EDSABI_ICAL_STORESEXPANDED_PROPERTY, EDSABI_ICAL_SUMMARY_PROPERTY, EDSABI_ICAL_TARGET_PROPERTY, EDSABI_ICAL_TRANSP_PROPERTY, EDSABI_ICAL_TRIGGER_PROPERTY, EDSABI_ICAL_TZID_PROPERTY, EDSABI_ICAL_TZNAME_PROPERTY, EDSABI_ICAL_TZOFFSETFROM_PROPERTY, EDSABI_ICAL_TZOFFSETTO_PROPERTY, EDSABI_ICAL_TZURL_PROPERTY, EDSABI_ICAL_UID_PROPERTY, EDSABI_ICAL_URL_PROPERTY, EDSABI_ICAL_VERSION_PROPERTY, EDSABI_ICAL_X_PROPERTY, EDSABI_ICAL_XLICCLASS_PROPERTY, EDSABI_ICAL_XLICCLUSTERCOUNT_PROPERTY, EDSABI_ICAL_XLICERROR_PROPERTY, EDSABI_ICAL_XLICMIMECHARSET_PROPERTY, EDSABI_ICAL_XLICMIMECID_PROPERTY, EDSABI_ICAL_XLICMIMECONTENTTYPE_PROPERTY, EDSABI_ICAL_XLICMIMEENCODING_PROPERTY, EDSABI_ICAL_XLICMIMEFILENAME_PROPERTY, EDSABI_ICAL_XLICMIMEOPTINFO_PROPERTY, EDSABI_ICAL_NO_PROPERTY }; /* * The defines rely on EDSAbiHaveIcal1 == 0 for libical.so.0 (= original enum value used * unchanged) and EDSAbiHaveIcal1 == 1 for libical.so.1 (original value has to be incremented * by one). */ # define ICAL_ACTION_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_ACTION_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_ALLOWCONFLICT_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_ALLOWCONFLICT_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_ATTACH_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_ATTACH_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_ATTENDEE_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_ATTENDEE_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_CALID_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_CALID_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_CALMASTER_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_CALMASTER_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_CALSCALE_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_CALSCALE_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_CAPVERSION_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_CAPVERSION_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_CARLEVEL_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_CARLEVEL_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_CARID_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_CARID_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_CATEGORIES_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_CATEGORIES_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_CLASS_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_CLASS_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_CMD_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_CMD_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_COMMENT_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_COMMENT_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_COMPLETED_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_COMPLETED_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_COMPONENTS_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_COMPONENTS_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_CONTACT_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_CONTACT_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_CREATED_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_CREATED_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_CSID_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_CSID_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_DATEMAX_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_DATEMAX_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_DATEMIN_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_DATEMIN_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_DECREED_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_DECREED_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_DEFAULTCHARSET_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_DEFAULTCHARSET_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_DEFAULTLOCALE_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_DEFAULTLOCALE_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_DEFAULTTZID_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_DEFAULTTZID_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_DEFAULTVCARS_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_DEFAULTVCARS_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_DENY_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_DENY_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_DESCRIPTION_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_DESCRIPTION_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_DTEND_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_DTEND_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_DTSTAMP_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_DTSTAMP_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_DTSTART_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_DTSTART_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_DUE_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_DUE_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_DURATION_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_DURATION_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_EXDATE_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_EXDATE_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_EXPAND_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_EXPAND_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_EXRULE_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_EXRULE_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_FREEBUSY_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_FREEBUSY_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_GEO_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_GEO_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_GRANT_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_GRANT_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_ITIPVERSION_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_ITIPVERSION_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_LASTMODIFIED_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_LASTMODIFIED_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_LOCATION_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_LOCATION_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_MAXCOMPONENTSIZE_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_MAXCOMPONENTSIZE_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_MAXDATE_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_MAXDATE_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_MAXRESULTS_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_MAXRESULTS_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_MAXRESULTSSIZE_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_MAXRESULTSSIZE_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_METHOD_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_METHOD_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_MINDATE_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_MINDATE_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_MULTIPART_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_MULTIPART_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_NAME_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_NAME_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_ORGANIZER_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_ORGANIZER_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_OWNER_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_OWNER_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_PERCENTCOMPLETE_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_PERCENTCOMPLETE_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_PERMISSION_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_PERMISSION_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_PRIORITY_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_PRIORITY_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_PRODID_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_PRODID_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_QUERY_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_QUERY_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_QUERYLEVEL_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_QUERYLEVEL_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_QUERYID_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_QUERYID_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_QUERYNAME_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_QUERYNAME_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_RDATE_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_RDATE_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_RECURACCEPTED_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_RECURACCEPTED_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_RECUREXPAND_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_RECUREXPAND_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_RECURLIMIT_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_RECURLIMIT_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_RECURRENCEID_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_RECURRENCEID_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_RELATEDTO_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_RELATEDTO_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_RELCALID_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_RELCALID_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_REPEAT_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_REPEAT_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_REQUESTSTATUS_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_REQUESTSTATUS_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_RESOURCES_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_RESOURCES_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_RESTRICTION_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_RESTRICTION_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_RRULE_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_RRULE_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_SCOPE_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_SCOPE_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_SEQUENCE_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_SEQUENCE_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_STATUS_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_STATUS_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_STORESEXPANDED_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_STORESEXPANDED_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_SUMMARY_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_SUMMARY_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_TARGET_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_TARGET_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_TRANSP_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_TRANSP_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_TRIGGER_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_TRIGGER_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_TZID_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_TZID_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_TZNAME_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_TZNAME_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_TZOFFSETFROM_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_TZOFFSETFROM_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_TZOFFSETTO_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_TZOFFSETTO_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_TZURL_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_TZURL_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_UID_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_UID_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_URL_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_URL_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_VERSION_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_VERSION_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_X_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_X_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_XLICCLASS_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_XLICCLASS_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_XLICCLUSTERCOUNT_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_XLICCLUSTERCOUNT_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_XLICERROR_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_XLICERROR_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_XLICMIMECHARSET_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_XLICMIMECHARSET_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_XLICMIMECID_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_XLICMIMECID_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_XLICMIMECONTENTTYPE_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_XLICMIMECONTENTTYPE_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_XLICMIMEENCODING_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_XLICMIMEENCODING_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_XLICMIMEFILENAME_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_XLICMIMEFILENAME_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_XLICMIMEOPTINFO_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_XLICMIMEOPTINFO_PROPERTY + EDSAbiHaveIcal1)) # define ICAL_NO_PROPERTY ((icalproperty_kind)(EDSABI_ICAL_NO_PROPERTY + EDSAbiHaveIcal1)) #endif /* EVOLUTION_ICAL_COMPATIBILITY && ENABLE_ICAL */ const char *EDSAbiWrapperInfo(); const char *EDSAbiWrapperDebug(); #ifdef __cplusplus } #endif #endif /* INCL_EDS_ABI_WRAPPER */ syncevolution_1.4/src/syncevo/icalstrdup.c000066400000000000000000000045511230021373600211560ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "icalstrdup.h" #include #if !defined(LIBICAL_MEMFIXES) || defined(EVOLUTION_COMPATIBILITY) #if defined(HAVE_CONFIG_H) # include #endif #if defined(_GNU_SOURCE) && defined(HAVE_DLFCN_H) # include # define LIBICAL_RUNTIME_CHECK #endif #include char *ical_strdup(const char *x) { #ifdef LIBICAL_RUNTIME_CHECK // One situation when we must not dup strings is when // running with a libecal with the modified string // handling semantic. Check that here. static enum { PATCH_UNCHECKED, PATCH_FOUND, PATCH_NOT_FOUND } patch_status; if (patch_status == PATCH_UNCHECKED) { patch_status = dlsym(RTLD_NEXT, "ical_memfixes") != NULL ? PATCH_FOUND : PATCH_NOT_FOUND; } if (patch_status == PATCH_FOUND) { /* patch applied, no need to copy */ return (char *)x; } #endif #ifdef EVOLUTION_COMPATIBILITY // Another situation is when we have a libical with the // _r variants of the relevant calls. We can call that directly, // which has the advantage that we get the saner implementation. // There have been crashes on Debian testing with libical 0.43-2 // inside icalmemory_add_tmp_buffer/icaltime_as_ical_string. // // We assume here that if one _r variant was found, all of // them are found. See also the wrappers in eds_abi_wrapper.h. if (EDSAbiWrapperSingleton.icalcomponent_as_ical_string_r) { return (char *)x; } #endif return x ? strdup(x) : NULL; } #endif /* !LIBICAL_MEMFIXES */ syncevolution_1.4/src/syncevo/icalstrdup.h000066400000000000000000000072161230021373600211640ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef ICALSTRDUP_H #define ICALSTRDUP_H #ifdef HAVE_CONFIG_H # include #endif #ifdef ENABLE_ICAL #ifndef HANDLE_LIBICAL_MEMORY # define HANDLE_LIBICAL_MEMORY 1 #endif #include #include #ifdef __cplusplus extern "C" { #pragma } #endif /* __cplusplus */ #if !defined(LIBICAL_MEMFIXES) || defined(EVOLUTION_COMPATIBILITY) /** * The patch in http://bugzilla.gnome.org/show_bug.cgi?id=516408 * changes the ownership of strings returned by some libical and libecal * functions: previously, the memory was owned by the library. * After the patch the caller owns the copied string and must free it. * * The upstream SourceForge libical has incorporated the patch, but * without changing the semantic of the existing calls. Instead they * added _r variants which return memory that the caller must free. * As soon as Evolution switches to upstream libical (planned for 2.25), * it probably will have to bump the libecal version because the API * is reverted so that binaries which free strings will crash. * When EVOLUTION_COMPATIBILITY is defined, SyncEvolution deals with * this by always checking at runtime what the memory handling is. * * This utility function ensures that the caller *always* owns the * returned string. When compiled against a current Evolution * Dataserver, the function becomes a NOP macro, unless compatibility * mode is on (in which case the current binary might later run with * an older Evolution release!). If not a NOP macro, then the function * duplicates the string; it handles NULL by passing it through. * * When compiled against an old Evolution Dataserver, then a runtime * check can be enabled to to determine whether the string needs to be * duplicated. If uncertain, it always duplicates the string. If the * check fails, then memory is leaked, which also happens when running * programs which do not know about the patch. * * To enable the runtime check, compile the .c file with -D_GNU_SOURCE * and -DHAVE_DLFCN_H. If HAVE_CONFIG_H is set, then config.h is included * and can be set to set some of these defines. If enabled, then link with * -ldl. * * ical_strdup() must be wrapped around the following functions: * - icalreqstattype_as_string * - icalproperty_as_ical_string * - icalproperty_get_parameter_as_string * - icalproperty_get_value_as_string * - icallangbind_property_eval_string * - icalperiodtype_as_ical_string * - icaltime_as_ical_string * - icalvalue_as_ical_string * - icalcomponent_as_ical_string * - e_cal_component_get_recurid_as_string * * @param x result of one of the functions above * @return string which has to be freed by caller, may be NULL if x was NULL */ extern char *ical_strdup(const char *x); #else # define ical_strdup(_x) (_x) #endif #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* ENABLE_ICAL */ #endif /* ICALSTRDUP_H */ syncevolution_1.4/src/syncevo/installcheck-local.sh000077500000000000000000000025061230021373600227310ustar00rootroot00000000000000#!/bin/sh # # usage: PKG_CONFIG_PATH=... installcheck-local.sh set -ex DIR=`mktemp -d` TMPFILE_CXX=$DIR/installcheck-local.cpp TMPFILE_O=$DIR/installcheck-local.o TMPFILE=$DIR/installcheck-local rmtmp () { rm -f $TMPFILE $TMPFILE_CXX $TMPFILE_O rmdir $DIR } trap rmtmp EXIT # check that c++ works, whatever it is cat >$TMPFILE_CXX < int main(int argc, char **argv) { std::cout << "hello world\n"; return 0; } EOF for CXX in "c++ -Wall -Werror" "g++ -Wall -Werror" "c++" "g++" ""; do if [ ! "$CXX" ]; then echo "no usable compiler, skipping tests" exit 0 fi if $CXX $TMPFILE_CXX -o $TMPFILE; then break fi done for header in `cd $1 && echo *`; do cat >$TMPFILE_CXX < int main(int argc, char **argv) { return 0; } EOF # header must be usable stand-alone $CXX "-I$2" $TMPFILE_CXX -c -o $TMPFILE_O done # link once to check that the libs are found; # must take DESTDIR into account by adding -L (skipped when equal to /usr/lib) # and modifying any additional paths including that pkg-config --libs syncevolution env LD_LIBRARY_PATH=$3:$3/syncevolution:$LD_LIBRARY_PATH $CXX -v $TMPFILE_O -o $TMPFILE "-L$3" `pkg-config --libs syncevolution | sed -e "s;/usr/lib;$3;g"` syncevolution_1.4/src/syncevo/lcs.cpp000066400000000000000000000114341230021373600201230ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include SE_BEGIN_CXX #ifdef ENABLE_UNIT_TESTS template void readlines(std::istream &in, IT out) { std::string line; while (std::getline(in, line)) { *out++ = line; } } /** * implements the assignment of EnumerateChunks */ template class EnumerateChunksProxy { public: EnumerateChunksProxy(const std::string &keyword, const IT &out, const C count) : m_keyword(keyword), m_out(out), m_count(count) {} EnumerateChunksProxy &operator=(const std::string &rhs) { if (!rhs.compare(0, m_keyword.size(), m_keyword)) { m_count++; } m_out = std::make_pair(rhs, m_count); return *this; } protected: std::string m_keyword; IT m_out; C m_count; }; /** * Output iterator which identifies related chunks * by incrementing a running count each time a certain * keyword is found at the beginning of the line. * Writes the line,count pair to another output * iterator. */ template class EnumerateChunks : private EnumerateChunksProxy { public: EnumerateChunks(const std::string &keyword, const IT &out, const C count) : EnumerateChunksProxy(keyword, out, count) {} EnumerateChunksProxy &operator*() { return *this; } EnumerateChunks &operator++() { this->m_out++; return *this; } EnumerateChunks &operator++(int) { ++this->m_out; return *this; } }; template class EnumerateChunks make_enumerate_chunks(const std::string &keyword, IT out, C count) { return EnumerateChunks(keyword, out, count); } class LCSTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(LCSTest); CPPUNIT_TEST(lcs); CPPUNIT_TEST_SUITE_END(); public: void lcs() { std::ifstream file1("testcases/lcs/file1.txt"); std::ifstream file2("testcases/lcs/file2.txt"); typedef std::vector< std::pair > content; content content1, content2; readlines(file1, make_enumerate_chunks("begin", std::back_inserter(content1), 0)); readlines(file2, make_enumerate_chunks("begin", std::back_inserter(content2), 0)); std::vector< LCS::Entry > result; LCS::lcs(content1, content2, std::back_inserter(result), LCS::accessor()); std::ostringstream out; std::copy(result.begin(), result.end(), std::ostream_iterator< LCS::Entry >(out)); CPPUNIT_ASSERT_EQUAL(std::string("1, 4: begin\n" "2, 5: item1\n" "3, 6: end\n"), out.str()); CPPUNIT_ASSERT_EQUAL((size_t)3, result.size()); } }; SYNCEVOLUTION_TEST_SUITE_REGISTRATION(LCSTest); #ifdef MAIN int main(int argc, char **argv) { if (argc != 3) { std::cerr << "Usage: lcs file1 file2" << std::endl; return 1; } std::ifstream file1(argv[1]); std::ifstream file2(argv[2]); typedef std::vector< std::pair > content; content content1, content2; readlines(file1, make_enumerate_chunks("begin", std::back_inserter(content1), 0)); readlines(file2, make_enumerate_chunks("begin", std::back_inserter(content2), 0)); std::vector< LCS::Entry > result; LCS::lcs(content1, content2, std::back_inserter(result), LCS::accessor()); std::copy(result.begin(), result.end(), std::ostream_iterator< LCS::Entry >(std::cout)); std::cout << "Length: " << result.size() << std::endl; return 0; } #endif // MAIN #endif // ENABLE_UNIT_TESTS SE_END_CXX syncevolution_1.4/src/syncevo/lcs.h000066400000000000000000000212071230021373600175670ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * Copyright (C) 2010 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_SYNCEVOLUTION_LCS # define INCL_SYNCEVOLUTION_LCS #include #include #include // for size_t and ssize_t #include #include SE_BEGIN_CXX namespace LCS { enum Choice { /** default value for empty subsequences */ NONE, /** i,j pair matches in both subsequences */ MATCH, /** entry j is skipped in second sequence */ LEFT, /** entry i is skipped in first sequence */ UP }; /** * utility struct for the lcs algorithm: describes the optimal * solution for a subset of the full problem */ template struct Sub { /** how the algorithm decided for the last entries of each subsequence */ Choice choice; /** number of matched entries in subsequences */ size_t length; /** total cost for gaps */ C cost; }; template struct Entry { Entry(size_t i, size_t j, T e) : index_a(i), index_b(j), element(e) {} size_t index_a; size_t index_b; T element; }; template std::ostream &operator<<(std::ostream &out, const Entry &entry) { out << entry.index_a << ", " << entry.index_b << ": " << entry.element << std::endl; return out; } /** * accessor which reads from std::vector< std:pair< entry, cost > > */ template class accessor { public: typedef typename T::value_type::first_type F; typedef typename T::value_type::second_type C; /** * @param a container holding sequence of items as passed to lcs() * @param start index of first item in the gap, may be -1 * @param end index of the last item in the gap, may be one beyond end of sequence, always >= start * @return cost 0 for start == end, > 0 for start < end */ static C cost(const T &a, ssize_t start, size_t end) { return a.empty() ? 0 : ((end >= a.size() ? a[a.size() - 1].second : a[end].second) - (start < 0 ? a[0].second : a[start].second)); } /** * @param index valid index (>= 0, < a.size()) * @return entry at index */ static const F &entry_at(const T &a, size_t index) { return a[index].first; } }; /** * accessor which reads from an arbitrary random-access sequence, * using a zero cost function (to be used for original LCS) */ template class accessor_sequence { public: typedef typename T::value_type F; typedef unsigned char C; static C cost(const T &a, ssize_t start, size_t end) { return 0; } static const F &entry_at(const T &a, size_t index) { return a[index]; } }; /** * Calculates the longest common subsequence (LCS) of two * sequences stored in vectors. The result specifies the common * elements of that type and their positions in the two input * sequences and is stored in a output sequence. * * In contrast to the generic LCS algorithm from "Introduction * to Algorithms" (Cormen, Leiserson, Rivest), this extended * algorithm tries to pick "better" LCSes when more than one * exists. * * When the two sequences contain chunks of related entries, then * a "better" LCS is one where gaps go across less chunks. For * example, when "begin b end" is inserted in front of "begin a * end", then this LCS: * * begin | begin * > b * > end * > begin * a | a * end | end * * is worse than: * * > begin * > b * > end * begin | begin * a | a * end | end * * A monotonically increasing cost number has to be assigned to each * entry by the caller. The "cost" of a gap is calculated by * "substracting" the cost number at the beginning of the gap from the * cost number at the end. Both cost number and substraction are * template parameters. */ template void lcs(const T &a, const T &b, ITO out, A access) { // reserve two-dimensonal array for sub-problem solutions, // adding rows as we go typedef typename A::C C; std::vector< std::vector< Sub > > sub; sub.resize(a.size() + 1); for (size_t i = 0; i <= a.size(); i++) { sub[i].resize(b.size() + 1); for (size_t j = 0; j <= b.size(); j++) { if (i == 0 || j == 0) { sub[i][j].choice = NONE; sub[i][j].length = 0; sub[i][j].cost = 0; } else if (access.entry_at(a, i - 1) == access.entry_at(b, j - 1)) { Choice choice = MATCH; size_t length = sub[i-1][j-1].length + 1; C cost = sub[i-1][j-1].cost; C cost_left = sub[i][j-1].cost += access.cost(b, j-1, j); C cost_up = sub[i-1][j].cost += access.cost(a, i-1, i); /* * We may decide to not match at i,j if the * alternatives have the same length but lower * cost. Matching is the default. */ if (sub[i][j-1].length > sub[i-1][j].length && length == sub[i][j-1].length && cost > cost_left) { /* skipping j is cheaper */ choice = LEFT; cost = cost_left; } else if (sub[i][j-1].length < sub[i-1][j].length && length == sub[i-1][j].length && cost > cost_up) { /* skipping i is cheaper */ choice = UP; cost = cost_up; } else if (sub[i][j-1].length == sub[i-1][j].length && length == sub[i-1][j].length) { if (cost_left < cost_up) { choice = LEFT; cost = cost_left; } else { choice = UP; cost = cost_up; } } sub[i][j].choice = choice; sub[i][j].length = length; sub[i][j].cost = cost; } else if (sub[i][j-1].length > sub[i-1][j].length) { sub[i][j].choice = LEFT; sub[i][j].length = sub[i][j-1].length; sub[i][j].cost = sub[i][j-1].cost + access.cost(b, j-1, j); } else if (sub[i][j-1].length < sub[i-1][j].length) { sub[i][j].choice = UP; sub[i][j].length = sub[i-1][j].length; sub[i][j].cost = sub[i-1][j].cost + access.cost(a, i-1, i); } else { // tie: decide based on cost C cost_left = sub[i][j-1].cost += access.cost(b, j-1, j); C cost_up = sub[i-1][j].cost += access.cost(a, i-1, i); if (cost_left < cost_up) { sub[i][j].choice = LEFT; sub[i][j].length = sub[i][j-1].length; sub[i][j].cost = cost_left; } else { sub[i][j].choice = UP; sub[i][j].length = sub[i-1][j].length; sub[i][j].cost = cost_up; } } } } // copy result (using intermediate list instead of recursive function call) typedef std::list< std::pair > indexlist; std::list< std::pair > indices; size_t i = a.size(), j = b.size(); while (i > 0 && j > 0) { switch (sub[i][j].choice) { case MATCH: indices.push_front(std::make_pair(i, j)); i--; j--; break; case LEFT: j--; break; case UP: i--; break; case NONE: // not reached break; } } for (indexlist::iterator it = indices.begin(); it != indices.end(); it++) { *out++ = Entry(it->first, it->second, access.entry_at(a, it->first - 1)); } } } // namespace lcs SE_END_CXX #endif // INCL_SYNCEVOLUTION_LCS syncevolution_1.4/src/syncevo/readme2c.pl000066400000000000000000000015451230021373600206570ustar00rootroot00000000000000#!/usr/bin/perl # # Turn reStructuredText README.rst into C file with # static const char *synopsis, *options; $_ = join("", <>); # trailing :: is only needed for reST s/ :://g; # simplify colon s/::/:/g; # avoid special \-- which was necessary for some options s/^\\--/--/gm; # remove escape sequence s/\\\*/*/g; # extract parts /SYNOPSIS\n===+\n\n(.*?)\nDESCRIPTION/s || die "no synopsis"; my $synopsis = $1; /OPTIONS\n===+\n\n.*?(^--.*?)\nEXAMPLES/ms || die "no options"; my $options = $1; # condense synopsis $synopsis =~ s/\n\n/\n/g; sub text2c { my $text = shift; foreach $_ (split ('\n', $text)) { s/\\/\\\\/g; s/"/\\"/g; print " \"", $_, "\\n\"\n"; } } # now print as C code print "static const char synopsis[] =\n"; text2c($synopsis); print ";\nstatic const char options[] =\n"; text2c($options); print ";\n"; syncevolution_1.4/src/syncevo/syncevo.am000066400000000000000000000222661230021373600206500ustar00rootroot00000000000000include $(top_srcdir)/src/syncevo/configs/configs.am # applies to sources in SyncEvolution repository, but not # the Funambol C++ client library src_syncevo_cxxflags = @SYNCEVOLUTION_CXXFLAGS@ src_syncevo_cppflags = @BACKEND_CPPFLAGS@ @GLIB_CFLAGS@ -I$(top_srcdir)/test -I$(gdbus_dir) $(DBUS_CFLAGS) -I$(top_builddir)/src/syncevo -I$(top_srcdir)/src -DSYNCEVO_LIBEXEC=\"$(libexecdir)\" -DSYNCEVO_BACKEND=\"$(BACKENDS_SEARCH_DIRECTORY)\" src_syncevo_ldadd = @SYNCEVOLUTION_LDADD@ # needed in all cases src_syncevo_ldadd += $(gdbus_build_dir)/libgdbussyncevo.la if ENABLE_UNIT_TESTS src_syncevo_cxxflags += $(CPPUNIT_CXXFLAGS) src_syncevo_ldadd += $(CPPUNIT_LDFLAGS) endif lib_LTLIBRARIES += src/syncevo/libsyncevolution.la src_syncevo_sources = \ src/syncevo/GeeSupport.h \ src/syncevo/GValueSupport.h \ src/syncevo/EDSClient.h \ src/syncevo/EDSClient.cpp \ \ src/syncevo/IdentityProvider.h \ src/syncevo/IdentityProvider.cpp \ \ src/syncevo/ConfigTree.h \ src/syncevo/ConfigFilter.h \ src/syncevo/ConfigFilter.cpp \ src/syncevo/ConfigNode.h \ src/syncevo/ConfigNode.cpp \ src/syncevo/DBusTraits.h \ src/syncevo/HashConfigNode.h \ src/syncevo/VolatileConfigNode.h \ src/syncevo/VolatileConfigTree.h \ src/syncevo/SmartPtr.h \ src/syncevo/eds_abi_wrapper.h \ src/syncevo/eds_abi_wrapper.cpp \ src/syncevo/GLibSupport.h \ src/syncevo/GLibSupport.cpp \ src/syncevo/ThreadSupport.h \ \ src/syncevo/SyncML.h \ src/syncevo/SyncML.cpp \ \ src/syncevo/SynthesisEngine.h \ src/syncevo/SynthesisEngine.cpp \ \ src/syncevo/Logging.h \ src/syncevo/Logging.cpp \ src/syncevo/LogDLT.h \ src/syncevo/LogDLT.cpp \ src/syncevo/LogStdout.h \ src/syncevo/LogStdout.cpp \ src/syncevo/LogRedirect.h \ src/syncevo/LogRedirect.cpp \ src/syncevo/LogSyslog.h \ src/syncevo/LogSyslog.cpp \ \ src/syncevo/TransportAgent.h \ src/syncevo/TransportAgent.cpp \ src/syncevo/CurlTransportAgent.h \ src/syncevo/CurlTransportAgent.cpp \ \ src/syncevo/SoupTransportAgent.h \ src/syncevo/SoupTransportAgent.cpp \ \ src/syncevo/LocalTransportAgent.h \ src/syncevo/LocalTransportAgent.cpp \ \ src/syncevo/util.cpp \ src/syncevo/util.h \ src/syncevo/BoostHelper.h \ \ src/syncevo/TmpFile.cpp \ src/syncevo/TmpFile.h \ \ src/syncevo/Timespec.h \ \ src/syncevo/lcs.h \ src/syncevo/lcs.cpp \ \ src/syncevo/ForkExec.cpp \ src/syncevo/ForkExec.h \ \ src/syncevo/Cmdline.cpp \ src/syncevo/Cmdline.h \ src/syncevo/CmdlineSyncClient.h \ src/syncevo/CmdlineSyncClient.cpp \ \ src/syncevo/SyncSource.h \ src/syncevo/SyncSource.cpp \ \ src/syncevo/SynthesisDBPlugin.cpp \ \ src/syncevo/SuspendFlags.h \ src/syncevo/SuspendFlags.cpp \ \ src/syncevo/SyncContext.h \ src/syncevo/SyncContext.cpp \ \ src/syncevo/UserInterface.h \ src/syncevo/UserInterface.cpp \ \ src/syncevo/SyncConfig.h \ src/syncevo/SyncConfig.cpp \ \ src/syncevo/DevNullConfigNode.h \ src/syncevo/MultiplexConfigNode.h \ src/syncevo/MultiplexConfigNode.cpp \ \ src/syncevo/FilterConfigNode.h \ src/syncevo/FilterConfigNode.cpp \ \ src/syncevo/SafeConfigNode.h \ src/syncevo/SafeConfigNode.cpp \ \ src/syncevo/PrefixConfigNode.h \ src/syncevo/PrefixConfigNode.cpp \ \ src/syncevo/IniConfigNode.h \ src/syncevo/IniConfigNode.cpp \ src/syncevo/SingleFileConfigTree.h \ src/syncevo/SingleFileConfigTree.cpp \ \ src/syncevo/DataBlob.h \ src/syncevo/FileDataBlob.h \ src/syncevo/FileDataBlob.cpp \ src/syncevo/StringDataBlob.h \ src/syncevo/StringDataBlob.cpp \ \ src/syncevo/SafeOstream.h \ src/syncevo/SafeOstream.cpp \ \ src/syncevo/FileConfigTree.h \ src/syncevo/FileConfigTree.cpp \ \ src/syncevo/MapSyncSource.h \ src/syncevo/MapSyncSource.cpp \ \ src/syncevo/TrackingSyncSource.h \ src/syncevo/TrackingSyncSource.cpp if ENABLE_ICAL src_syncevo_sources += \ src/syncevo/icalstrdup.c \ src/syncevo/icalstrdup.h endif src_syncevo_libsyncevolution_includedir= $(includedir)/syncevo src_syncevo_libsyncevolution_include_HEADERS = \ src/syncevo/declarations.h \ src/syncevo/Cmdline.h \ src/syncevo/ConfigFilter.h \ src/syncevo/GLibSupport.h \ src/syncevo/ThreadSupport.h \ src/syncevo/TrackingSyncSource.h \ src/syncevo/MapSyncSource.h \ src/syncevo/LogRedirect.h \ src/syncevo/LogStdout.h \ src/syncevo/LogSyslog.h \ \ src/syncevo/FilterConfigNode.h \ src/syncevo/PrefixConfigNode.h \ src/syncevo/SafeConfigNode.h \ src/syncevo/SyncConfig.h \ src/syncevo/SyncSource.h \ src/syncevo/IdentityProvider.h \ src/syncevo/util.h \ src/syncevo/BoostHelper.h \ src/syncevo/SuspendFlags.h \ src/syncevo/SyncContext.h \ src/syncevo/Timespec.h \ src/syncevo/UserInterface.h \ src/syncevo/SynthesisEngine.h \ src/syncevo/Logging.h \ src/syncevo/SyncML.h \ src/syncevo/eds_abi_wrapper.h \ src/syncevo/icalstrdup.h \ src/syncevo/SmartPtr.h \ src/syncevo/ConfigNode.h if ENABLE_OBEX src_syncevo_sources += \ src/syncevo/ObexTransportAgent.h \ src/syncevo/ObexTransportAgent.cpp src_syncevo_ldadd += $(LIBOPENOBEX_LIBS) src_syncevo_cxxflags += $(LIBOPENOBEX_CFLAGS) if ENABLE_BLUETOOTH src_syncevo_ldadd += $(BLUEZ_LIBS) src_syncevo_cxxflags += $(BLUEZ_CFLAGS) endif endif #pkgconfigdir is defined in $(top_srcdir)/setup-variables.am pkgconfig_DATA += src/syncevo/syncevolution.pc DISTCLEANFILES += src/syncevo/syncevolution.pc dist_noinst_DATA += src/syncevo/syncevolution.pc.in src_syncevo_libsyncevolution_la_SOURCES = $(src_syncevo_sources) nodist_src_syncevo_libsyncevolution_la_SOURCES = src/syncevo/SyncEvolutionXML.c CLEANFILES += src/syncevo/SyncEvolutionXML.c src_syncevo_libsyncevolution_la_LIBADD = \ @GIO_LIBS@ \ @GTHREAD_LIBS@ \ @GLIB_LIBS@ \ $(SYNTHESIS_LIBS) \ $(PCRECPP_LIBS) \ $(TRANSPORT_LIBS) \ @LIBS@ \ $(src_syncevo_ldadd) \ $(DLT_LIBS) \ $(DBUS_LIBS) \ $(NSS_LIBS) if ENABLE_MODULES src_syncevo_libsyncevolution_la_LIBADD += -ldl endif src_syncevo_libsyncevolution_la_CXXFLAGS = \ $(PCRECPP_CFLAGS) \ $(TRANSPORT_CFLAGS) \ $(src_syncevo_cxxflags) \ $(SYNTHESIS_CFLAGS) \ $(NSS_CFLAGS) \ $(SYNCEVO_WFLAGS) src_syncevo_libsyncevolution_la_CFLAGS = \ $(SYNCEVO_WFLAGS) src_syncevo_libsyncevolution_la_CPPFLAGS = \ $(src_syncevo_cppflags) \ $(DLT_CFLAGS) \ $(DBUS_CFLAGS) \ -DDATA_DIR=\""$(pkgdatadir)"\" \ -DXML_CONFIG_DIR=\""$(datadir)/syncevolution/xml"\" \ -DTEMPLATE_DIR=\""$(datadir)/syncevolution/templates"\" \ -DLIBDIR=\""$(libdir)"\" src_syncevo_libsyncevolution_la_DEPENDENCIES = $(SYNTHESIS_DEP) $(filter %.la, $(src_syncevo_ldadd)) # rule which is only relevant when compiling Synthesis in subdirectory src/build-synthesis/libsynthesissdk.la: $(SYNTHESIS_SUBDIR)/all $(SYNTHESIS_SUBDIR)/% : [ ! "$(SYNTHESIS_SUBDIR)" ] || ( cd ${dir $@} && $(MAKE) ${notdir $@} ) if ENABLE_MODULES src_syncevo_libsyncevolution_la_LDFLAGS = else src_syncevo_libsyncevolution_la_LDFLAGS = -static endif # command which embeds its input lines into a C-style string that runs across multiple lines TO_C_STRING = sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/\(.*\)/"\1\\n"/' # Don't depend on specific XML files. Instead recreate # SyncEvolutionXML.c each time make is invoked (allows including new # fragments in the binary without rerunning configure). src/syncevo/SyncEvolutionXML.c: src/syncevo/GenSyncEvolutionXML $(AM_V_GEN) @true all_phonies += src/syncevo/GenSyncEvolutionXML src/syncevo/GenSyncEvolutionXML: $(AM_V_at)echo "const char *SyncEvolutionXMLClient =" >src/syncevo/SyncEvolutionXML.c.new \ && (cd $(top_srcdir)/src/syncevo/configs && perl update-samples.pl syncevolution.xml client ) | \ perl -p -e 's;; \n \n \n ;' | \ $(TO_C_STRING) >>src/syncevo/SyncEvolutionXML.c.new \ && echo ";" >>src/syncevo/SyncEvolutionXML.c.new \ && echo "const char *SyncEvolutionXMLClientRules =" >>src/syncevo/SyncEvolutionXML.c.new \ && (cd $(top_srcdir)/src/syncevo/configs && cat remoterules/*.xml remoterules/client/*.xml) | $(TO_C_STRING) >>src/syncevo/SyncEvolutionXML.c.new \ && echo ";" >>src/syncevo/SyncEvolutionXML.c.new \ && if cmp -s src/syncevo/SyncEvolutionXML.c src/syncevo/SyncEvolutionXML.c.new; \ then \ rm src/syncevo/SyncEvolutionXML.c.new; \ else \ mv src/syncevo/SyncEvolutionXML.c.new src/syncevo/SyncEvolutionXML.c; \ fi; # turn README.rst into a file with plain text strings for # "Synopsis" and "Usage" CLEANFILES += src/syncevo/CmdlineHelp.c BUILT_SOURCES += src/syncevo/CmdlineHelp.c dist_noinst_SCRIPTS += src/syncevo/readme2c.pl src/syncevo/CmdlineHelp.c: src/syncevo/readme2c.pl $(top_srcdir)/README.rst $(AM_V_GEN)perl $+ >$@ # include boost in distribution #dist-hook: # cp -r $(srcdir)/boost $(distdir) # find $(distdir) -name .libs -o -name "*~" -o -name ".*" -o -name "*.o" -o -name "*.lo" -o -name CVS -o -name autom4te.cache | xargs rm -rf # make sure that the installed development files are usable src_syncevo_install_check_local: $(top_srcdir)/src/syncevo/installcheck-local.sh env PKG_CONFIG_PATH=$(DESTDIR)/$(pkgconfigdir):$$PKG_CONFIG_PATH $< "$(DESTDIR)/$(src_syncevo_libsyncevolution_includedir)" "$(DESTDIR)/$(includedir)" "$(DESTDIR)/$(libdir)" all_local_installchecks += src_syncevo_install_check_local dist_noinst_SCRIPTS += src/syncevo/installcheck-local.sh syncevolution_1.4/src/syncevo/syncevolution.pc.in000066400000000000000000000006351230021373600225110ustar00rootroot00000000000000prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ backenddir=@libdir@/syncevolution/backends datarootdir=@datarootdir@ templatedir=@datadir@/syncevolution/templates Name: libsyncevolution Description: SyncEvolution Library Version: @VERSION@ Cflags: -I${includedir} Requires: synthesis Libs: -L${libdir} -lsyncevolution -lrt Libs.private: -L${libdir}/syncevolution -lgdbussyncevo syncevolution_1.4/src/syncevo/util.cpp000066400000000000000000000753231230021373600203260ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gdbus-cxx-bridge.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_GLIB # include #endif #if USE_SHA256 == 1 # ifndef HAVE_GLIB # error need glib.h # endif #elif USE_SHA256 == 2 # include # include # include #endif #ifdef ENABLE_UNIT_TESTS #include "test.h" CPPUNIT_REGISTRY_ADD_TO_DEFAULT("SyncEvolution"); #endif #include SE_BEGIN_CXX string normalizePath(const string &path) { string res; res.reserve(path.size()); size_t index = 0; while (index < path.size()) { char curr = path[index]; res += curr; index++; if (curr == '/') { while (index < path.size() && (path[index] == '/' || (path[index] == '.' && index + 1 < path.size() && path[index + 1] == '/'))) { index++; } } } if (!res.empty() && res[res.size() - 1] == '/') { res.resize(res.size() - 1); } return res; } string getBasename(const string &path) { string dir; string file; splitPath(path, dir, file); return file; } string getDirname(const string &path) { string dir; string file; splitPath(path, dir, file); return dir; } void splitPath(const string &path, string &dir, string &file) { string normal = normalizePath(path); size_t offset = normal.rfind('/'); if (offset != normal.npos) { dir = normal.substr(0, offset); file = normal.substr(offset + 1); } else { dir = ""; file = normal; } } bool relToAbs(string &path) { char *buffer; if ((buffer = realpath(path.c_str(), NULL)) != NULL) { path = buffer; free(buffer); return true; } else { return false; } } void mkdir_p(const string &path) { boost::scoped_array dirs(new char[path.size() + 1]); char *curr = dirs.get(); strcpy(curr, path.c_str()); do { char *nextdir = strchr(curr, '/'); if (nextdir) { *nextdir = 0; nextdir++; } if (*curr) { if (access(dirs.get(), nextdir ? (R_OK|X_OK) : (R_OK|X_OK|W_OK)) && (errno != ENOENT || mkdir(dirs.get(), 0700))) { SyncContext::throwError(string(dirs.get()), errno); } } if (nextdir) { nextdir[-1] = '/'; } curr = nextdir; } while (curr); } void rm_r(const string &path, boost::function filter) { struct stat buffer; if (lstat(path.c_str(), &buffer)) { if (errno == ENOENT) { return; } else { SyncContext::throwError(path, errno); } } if (!S_ISDIR(buffer.st_mode)) { if (!filter(path, false) || !unlink(path.c_str())) { return; } else { SyncContext::throwError(path, errno); } } ReadDir dir(path); BOOST_FOREACH(const string &entry, dir) { rm_r(path + "/" + entry, filter); } if (filter(path, true) && rmdir(path.c_str())) { SyncContext::throwError(path, errno); } } void cp_r(const string &from, const string &to) { if (isDir(from)) { mkdir_p(to); ReadDir dir(from); BOOST_FOREACH(const string &entry, dir) { cp_r(from + "/" + entry, to + "/" + entry); } } else { ofstream out; ifstream in; out.open(to.c_str()); in.open(from.c_str()); char buf[8192]; do { in.read(buf, sizeof(buf)); out.write(buf, in.gcount()); } while(in); in.close(); out.close(); if (out.bad() || in.bad()) { SE_THROW(string("failed copying ") + from + " to " + to); } } } bool isDir(const string &path) { DIR *dir = opendir(path.c_str()); if (dir) { closedir(dir); return true; } else if (errno != ENOTDIR && errno != ENOENT) { SyncContext::throwError(path, errno); } return false; } int Execute(const std::string &cmd, ExecuteFlags flags) throw() { int ret = -1; try { // use simpler system() calls whenever we don't want to capture // output, because it means that output is sent to the user // directly if (((flags & EXECUTE_NO_STDERR) || !LogRedirect::redirectingStderr()) && ((flags & EXECUTE_NO_STDOUT) || !LogRedirect::redirectingStdout())) { string fullcmd = cmd; if (flags & EXECUTE_NO_STDERR) { fullcmd += " 2>/dev/null"; } if (flags & EXECUTE_NO_STDOUT) { fullcmd += " >/dev/null"; } SE_LOG_DEBUG(NULL, "running command via system(): %s", cmd.c_str()); ret = system(fullcmd.c_str()); } else { // Need to catch at least one of stdout or stderr. A // low-tech solution would be to use temporary files which // are read after system() returns. But we want true // streaming of the output, so use fork()/exec() plus // reliable output redirection. SE_LOG_DEBUG(NULL, "running command via fork/exec with output redirection: %s", cmd.c_str()); LogRedirect io(flags); pid_t child = fork(); switch (child) { case 0: { // child process: // - close unused end of the pipes if (io.getStdout().m_read >= 0) { close(io.getStdout().m_read); } if (io.getStderr().m_read >= 0) { close(io.getStderr().m_read); } // - replace file descriptors 1 and 2 with the ones // prepared for us or /dev/null int fd; int fd_null = open("/dev/null", O_WRONLY); fd = io.getStdout().m_write; if (fd <= 0) { fd = fd_null; } dup2(fd, STDOUT_FILENO); fd = io.getStderr().m_write; if (fd <= 0) { fd = fd_null; } dup2(fd, STDERR_FILENO); // - run command execl("/bin/sh", "sh", "-c", cmd.c_str(), NULL); // - error handling if execl() failed (= returned) std::cerr << cmd << ": execl() failed: " << strerror(errno); exit(1); break; } case -1: // error handling in parent when fork() fails SE_LOG_ERROR(NULL, "%s: fork() failed: %s", cmd.c_str(), strerror(errno)); break; default: // parent: // - close write side so that we can detect "end of data" if (io.getStdout().m_write >= 0) { close(io.getStdout().m_write); } if (io.getStderr().m_write >= 0) { close(io.getStderr().m_write); } // - read until no more data or error triggers exception io.process(); // - wait for child, without caring about errors waitpid(child, &ret, 0); break; } } } catch (...) { Exception::handle(); } return ret; } UUID::UUID() { #ifdef USE_SRAND static class InitSRand { public: InitSRand() { ifstream seedsource("/dev/urandom"); unsigned int seed; if (!seedsource.get((char *)&seed, sizeof(seed))) { seed = time(NULL); } srand(seed); } int operator () () { return rand(); } } myrand; #else static class InitSRand { unsigned int m_seed; public: InitSRand() { ifstream seedsource("/dev/urandom"); if (!seedsource.get((char *)&m_seed, sizeof(m_seed))) { m_seed = time(NULL); } } int operator () () { return rand_r(&m_seed); } } myrand; #endif char buffer[16 * 4 + 5]; sprintf(buffer, "%08x-%04x-%04x-%02x%02x-%08x%04x", myrand() & 0xFFFFFFFF, myrand() & 0xFFFF, (myrand() & 0x0FFF) | 0x4000 /* RFC 4122 time_hi_and_version */, (myrand() & 0xBF) | 0x80 /* clock_seq_hi_and_reserved */, myrand() & 0xFF, myrand() & 0xFFFFFFFF, myrand() & 0xFFFF ); this->assign(buffer); } ReadDir::ReadDir(const string &path, bool throwError) : m_path(path) { DIR *dir = NULL; try { dir = opendir(path.c_str()); if (!dir) { SyncContext::throwError(path, errno); } errno = 0; struct dirent *entry = readdir(dir); while (entry) { if (strcmp(entry->d_name, ".") && strcmp(entry->d_name, "..")) { m_entries.push_back(entry->d_name); } entry = readdir(dir); } if (errno) { SyncContext::throwError(path, errno); } std::sort(m_entries.begin(), m_entries.end()); } catch(...) { if (dir) { closedir(dir); } if (throwError) { throw; } else { return; } } closedir(dir); } std::string ReadDir::find(const string &entry, bool caseSensitive) { BOOST_FOREACH(const string &e, *this) { if (caseSensitive ? e == entry : boost::iequals(e, entry)) { return m_path + "/" + e; } } return ""; } bool ReadFile(const string &filename, string &content) { ifstream in; in.open(filename.c_str()); return ReadFile(in, content); } bool ReadFile(istream &in, string &content) { ostringstream out; char buf[8192]; do { in.read(buf, sizeof(buf)); out.write(buf, in.gcount()); } while(in); content = out.str(); return in.eof(); } unsigned long Hash(const char *str) { unsigned long hashval = 5381; int c; while ((c = *str++) != 0) { hashval = ((hashval << 5) + hashval) + c; } return hashval; } unsigned long Hash(const std::string &str) { unsigned long hashval = 5381; BOOST_FOREACH(int c, str) { hashval = ((hashval << 5) + hashval) + c; } return hashval; } std::string SHA_256(const std::string &data) { #if USE_SHA256 == 1 GStringPtr hash(g_compute_checksum_for_data(G_CHECKSUM_SHA256, (guchar *)data.c_str(), data.size()), "g_compute_checksum_for_data() failed"); return std::string(hash.get()); #elif USE_SHA256 == 2 std::string res; unsigned char hash[SHA256_LENGTH]; static bool initialized; if (!initialized) { // https://wiki.mozilla.org/NSS_Shared_DB_And_LINUX has // some comments which indicate that calling init multiple // times works, but http://www.mozilla.org/projects/security/pki/nss/ref/ssl/sslfnc.html#1234224 // says it must only be called once. How that is supposed // to work when multiple, independent libraries have to // use NSS is beyond me. Bad design. At least let's do the // best we can here. NSS_NoDB_Init(NULL); initialized = true; } if (HASH_HashBuf(HASH_AlgSHA256, hash, (unsigned char *)data.c_str(), data.size()) != SECSuccess) { SE_THROW("NSS HASH_HashBuf() failed"); } res.reserve(SHA256_LENGTH * 2); BOOST_FOREACH(unsigned char value, hash) { res += StringPrintf("%02x", value); } return res; #else SE_THROW("Hash256() not implemented"); return ""; #endif } StringEscape::StringEscape(char escapeChar, const char *forbidden) : m_escapeChar(escapeChar) { while (*forbidden) { m_forbidden.insert(*forbidden); ++forbidden; } } string StringEscape::escape(const string &str) const { if (m_mode != SET) { return escape(str, m_escapeChar, m_mode); } string res; char buffer[4]; res.reserve(str.size() * 3); BOOST_FOREACH(char c, str) { if(c != m_escapeChar && m_forbidden.find(c) == m_forbidden.end()) { res += c; } else { sprintf(buffer, "%c%02x", m_escapeChar, (unsigned int)(unsigned char)c); res += buffer; } } return res; } string StringEscape::escape(const string &str, char escapeChar, Mode mode) { string res; char buffer[4]; bool isLeadingSpace = true; res.reserve(str.size() * 3); BOOST_FOREACH(char c, str) { if(c != escapeChar && (mode == STRICT ? (isalnum(c) || c == '-' || c == '_') : !(((isLeadingSpace || mode == INI_WORD) && isspace(c)) || c == '=' || c == '\r' || c == '\n'))) { res += c; if (!isspace(c)) { isLeadingSpace = false; } } else { sprintf(buffer, "%c%02x", escapeChar, (unsigned int)(unsigned char)c); res += buffer; } } // also encode trailing space? if (mode == INI_VALUE) { size_t numspaces = 0; ssize_t off = res.size() - 1; while (off >= 0 && isspace(res[off])) { off--; numspaces++; } res.resize(res.size() - numspaces); BOOST_FOREACH(char c, str.substr(str.size() - numspaces)) { sprintf(buffer, "%c%02x", escapeChar, (unsigned int)(unsigned char)c); res += buffer; } } return res; } string StringEscape::unescape(const string &str, char escapeChar) { string res; size_t curr; res.reserve(str.size()); curr = 0; while (curr < str.size()) { if (str[curr] == escapeChar) { string hex = str.substr(curr + 1, 2); res += (char)strtol(hex.c_str(), NULL, 16); curr += 3; } else { res += str[curr]; curr++; } } return res; } #ifdef ENABLE_UNIT_TESTS class StringEscapeTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(StringEscapeTest); CPPUNIT_TEST(escape); CPPUNIT_TEST(unescape); CPPUNIT_TEST_SUITE_END(); void escape() { const string test = " _-%\rfoo bar?! \n "; StringEscape def; CPPUNIT_ASSERT_EQUAL(string("%20_-%25%0dfoo%20bar%3f%21%20%0a%20"), def.escape(test)); CPPUNIT_ASSERT_EQUAL(string("%20_-%25%0dfoo%20bar%3f%21%20%0a%20"), StringEscape::escape(test, '%', StringEscape::STRICT)); StringEscape word('%', StringEscape::INI_WORD); CPPUNIT_ASSERT_EQUAL(string("%20_-%25%0dfoo%20bar?!%20%0a%20"), word.escape(test)); CPPUNIT_ASSERT_EQUAL(string("%20_-%25%0dfoo%20bar?!%20%0a%20"), StringEscape::escape(test, '%', StringEscape::INI_WORD)); StringEscape ini('%', StringEscape::INI_VALUE); CPPUNIT_ASSERT_EQUAL(string("%20_-%25%0dfoo bar?! %0a%20"), ini.escape(test)); CPPUNIT_ASSERT_EQUAL(string("%20_-%25%0dfoo bar?! %0a%20"), StringEscape::escape(test, '%', StringEscape::INI_VALUE)); StringEscape alt('!', StringEscape::INI_VALUE); CPPUNIT_ASSERT_EQUAL(string("!20_-%!0dfoo bar?!21 !0a!20"), alt.escape(test)); CPPUNIT_ASSERT_EQUAL(string("!20_-%!0dfoo bar?!21 !0a!20"), StringEscape::escape(test, '!', StringEscape::INI_VALUE)); } void unescape() { const string escaped = "%20_-%25foo%20bar%3F%21%20%0A"; const string plain = " _-%foo bar?! \n"; StringEscape def; CPPUNIT_ASSERT_EQUAL(plain, def.unescape(escaped)); CPPUNIT_ASSERT_EQUAL(plain, StringEscape::unescape(escaped, '%')); CPPUNIT_ASSERT_EQUAL(string("%41B"), StringEscape::unescape("%41!42", '!')); CPPUNIT_ASSERT_EQUAL(string("A!42"), StringEscape::unescape("%41!42", '%')); } }; SYNCEVOLUTION_TEST_SUITE_REGISTRATION(StringEscapeTest); #endif // ENABLE_UNIT_TESTS Timespec Timespec::operator + (const Timespec &other) const { Timespec res(tv_sec + other.tv_sec, tv_nsec + other.tv_nsec); if (res.tv_nsec > 1000000000) { res.tv_sec++; res.tv_nsec -= 1000000000; } return res; } Timespec Timespec::operator - (const Timespec &other) const { Timespec res(tv_sec - other.tv_sec, 0); if (other.tv_nsec > tv_nsec) { res.tv_sec--; res.tv_nsec = tv_nsec + 1000000000 - other.tv_nsec; } else { res.tv_nsec = tv_nsec - other.tv_nsec; } return res; } #ifdef ENABLE_UNIT_TESTS class TimespecTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TimespecTest); CPPUNIT_TEST(add); CPPUNIT_TEST(substract); CPPUNIT_TEST_SUITE_END(); void add() { CPPUNIT_ASSERT_EQUAL(Timespec(1, 0), Timespec(0, 0) + 1); CPPUNIT_ASSERT_EQUAL(Timespec(1, 0), Timespec(0, 0) + Timespec(1, 0)); CPPUNIT_ASSERT_EQUAL(Timespec(1, 0), Timespec(0, 500000000) + Timespec(0, 500000000)); CPPUNIT_ASSERT_EQUAL(Timespec(1, 999999998), Timespec(0, 999999999) + Timespec(0, 999999999)); } void substract() { CPPUNIT_ASSERT_EQUAL(Timespec(1, 0), Timespec(2, 0) - 1); CPPUNIT_ASSERT_EQUAL(Timespec(1, 0), Timespec(2, 0) - Timespec(1, 0)); CPPUNIT_ASSERT_EQUAL(Timespec(1, 0), Timespec(1, 500000000) - Timespec(0, 500000000)); CPPUNIT_ASSERT_EQUAL(Timespec(0, 999999999), Timespec(1, 999999998) - Timespec(0, 999999999)); } }; SYNCEVOLUTION_TEST_SUITE_REGISTRATION(TimespecTest); #endif // ENABLE_UNIT_TESTS std::string StringPrintf(const char *format, ...) { va_list ap; va_start(ap, format); std::string res = StringPrintfV(format, ap); va_end(ap); return res; } std::string StringPrintfV(const char *format, va_list ap) { va_list aq; char *buffer = NULL, *nbuffer = NULL; ssize_t size = 0; ssize_t realsize = 255; do { // vsnprintf() destroys ap, so make a copy first va_copy(aq, ap); if (size < realsize) { nbuffer = (char *)realloc(buffer, realsize + 1); if (!nbuffer) { if (buffer) { free(buffer); } return ""; } size = realsize; buffer = nbuffer; } realsize = vsnprintf(buffer, size + 1, format, aq); if (realsize == -1) { // old-style vnsprintf: exact len unknown, try again with doubled size realsize = size * 2; } va_end(aq); } while(realsize > size); std::string res = buffer; free(buffer); return res; } char *Strncpy(char *dest, const char *src, size_t n) { strncpy(dest, src, n); if (n) { dest[n - 1] = 0; } return dest; } static gboolean SleepTimeout(gpointer triggered) { *static_cast(triggered) = true; return false; } double Sleep(double seconds) { Timespec start = Timespec::monotonic(); SuspendFlags &s = SuspendFlags::getSuspendFlags(); if (s.getState() == SuspendFlags::NORMAL) { #ifdef HAVE_GLIB // Only use glib if we are the owner of the main context. // Otherwise we would interfere (?) with that owner or // depend on it to drive the context (?). The glib docs // don't say anything about this; in practice, it was // observed that with some versions of glib, a second // thread just blocked here when the main thread was not // processing glib events. if (g_main_context_is_owner(g_main_context_default())) { bool triggered = false; GLibEvent timeout(g_timeout_add(seconds * 1000, SleepTimeout, &triggered), "glib timeout"); GRunWhile(! boost::lambda::var(triggered) && boost::lambda::bind(&SuspendFlags::getState, boost::ref(s)) == SuspendFlags::NORMAL); // SleepTimeout already removed the source if it was triggered // and returned false. No need to auto-destruct it again. if (triggered) { timeout.release(); } // done return 0; } #endif // Fallback when glib is not available or unusable (= outside the main thread). // Busy loop to detect abort requests. Timespec deadline = start + Timespec(floor(seconds), (seconds - floor(seconds)) * 1e9); while (deadline > Timespec::monotonic()) { timeval delay; delay.tv_sec = 0; delay.tv_usec = 1e5; select(0, NULL, NULL, NULL, &delay); if (s.getState() != SuspendFlags::NORMAL) { break; } } } // not done normally, calculate remaining time Timespec end = Timespec::monotonic(); double left = (end - start).duration() - seconds; return std::max((double)0, left); } InitStateTri::Value InitStateTri::getValue() const { const std::string &val = get(); if (val == "1" || boost::iequals(val, "true") || boost::iequals(val, "yes")) { return VALUE_TRUE; } else if (val == "0" || boost::iequals(val, "false") || boost::iequals(val, "no")) { return VALUE_FALSE; } else { return VALUE_STRING; } } const char * const TRANSPORT_PROBLEM = "transport problem: "; const char * const SYNTHESIS_PROBLEM = "error code from Synthesis engine "; const char * const SYNCEVOLUTION_PROBLEM = "error code from SyncEvolution "; SyncMLStatus Exception::handle(SyncMLStatus *status, const std::string *logPrefix, std::string *explanation, Logger::Level level, HandleExceptionFlags flags) { // any problem here is a fatal local problem, unless set otherwise // by the specific exception SyncMLStatus new_status = SyncMLStatus(STATUS_FATAL + sysync::LOCAL_STATUS_CODE); std::string error; try { throw; } catch (const TransportException &ex) { SE_LOG_DEBUG(logPrefix, "TransportException thrown at %s:%d", ex.m_file.c_str(), ex.m_line); error = std::string(TRANSPORT_PROBLEM) + ex.what(); new_status = SyncMLStatus(sysync::LOCERR_TRANSPFAIL); } catch (const BadSynthesisResult &ex) { new_status = SyncMLStatus(ex.result()); error = StringPrintf("%s%s", SYNTHESIS_PROBLEM, Status2String(new_status).c_str()); } catch (const StatusException &ex) { new_status = ex.syncMLStatus(); SE_LOG_DEBUG(logPrefix, "exception thrown at %s:%d", ex.m_file.c_str(), ex.m_line); error = StringPrintf("%s%s: %s", SYNCEVOLUTION_PROBLEM, Status2String(new_status).c_str(), ex.what()); if (new_status == STATUS_NOT_FOUND && (flags & HANDLE_EXCEPTION_404_IS_OKAY)) { level = Logger::DEBUG; } } catch (const Exception &ex) { SE_LOG_DEBUG(logPrefix, "exception thrown at %s:%d", ex.m_file.c_str(), ex.m_line); error = ex.what(); } catch (const std::exception &ex) { error = ex.what(); } catch (...) { error = "unknown error"; } if (flags & HANDLE_EXCEPTION_FATAL) { level = Logger::ERROR; } if (flags & HANDLE_EXCEPTION_NO_ERROR) { level = Logger::DEBUG; } SE_LOG(logPrefix, level, "%s", error.c_str()); if (flags & HANDLE_EXCEPTION_FATAL) { // Something unexpected went wrong, can only shut down. ::abort(); } if (explanation) { *explanation = error; } if (status && *status == STATUS_OK) { *status = new_status; } return status ? *status : new_status; } void Exception::tryRethrow(const std::string &explanation, bool mustThrow) { static const std::string statusre = ".* \\((?:local|remote), status (\\d+)\\)"; int status; if (boost::starts_with(explanation, TRANSPORT_PROBLEM)) { SE_THROW_EXCEPTION(TransportException, explanation.substr(strlen(TRANSPORT_PROBLEM))); } else if (boost::starts_with(explanation, SYNTHESIS_PROBLEM)) { static const pcrecpp::RE re(statusre); if (re.FullMatch(explanation.substr(strlen(SYNTHESIS_PROBLEM)), &status)) { SE_THROW_EXCEPTION_1(BadSynthesisResult, "Synthesis engine failure", (sysync::TSyErrorEnum)status); } } else if (boost::starts_with(explanation, SYNCEVOLUTION_PROBLEM)) { static const pcrecpp::RE re(statusre + ": (.*)", pcrecpp::RE_Options().set_dotall(true)); std::string details; if (re.FullMatch(explanation.substr(strlen(SYNCEVOLUTION_PROBLEM)), &status, &details)) { SE_THROW_EXCEPTION_STATUS(StatusException, details, (SyncMLStatus)status); } } if (mustThrow) { throw std::runtime_error(explanation); } } void Exception::tryRethrowDBus(const std::string &error) { static const pcrecpp::RE re("(org\\.syncevolution(?:\\.\\w+)+): (.*)", pcrecpp::RE_Options().set_dotall(true)); std::string exName, explanation; if (re.FullMatch(error, &exName, &explanation)) { // found SyncEvolution exception explanation, parse it tryRethrow(explanation); // explanation not parsed, fall back to D-Bus exception throw GDBusCXX::dbus_error(exName, explanation); } } std::string SubstEnvironment(const std::string &str) { std::stringstream res; size_t envstart = std::string::npos; size_t off; for(off = 0; off < str.size(); off++) { if (envstart != std::string::npos) { if (str[off] == '}') { std::string envname = str.substr(envstart, off - envstart); envstart = std::string::npos; const char *val = getenv(envname.c_str()); if (val) { res << val; } else if (envname == "XDG_CONFIG_HOME") { res << getHome() << "/.config"; } else if (envname == "XDG_DATA_HOME") { res << getHome() << "/.local/share"; } else if (envname == "XDG_CACHE_HOME") { res << getHome() << "/.cache"; } } } else { if (str[off] == '$' && off + 1 < str.size() && str[off + 1] == '{') { envstart = off + 2; off++; } else { res << str[off]; } } } return res.str(); } std::vector unescapeJoinedString (const std::string& src, char sep) { std::vector splitStrings; size_t pos1 = 0, pos2 = 0, pos3 = 0; std::string s1, s2; while (pos3 != src.npos) { pos2 = src.find (sep, pos3); s1 = src.substr (pos1, (pos2 == std::string::npos) ? std::string::npos : pos2-pos1); size_t pos = s1.find_last_not_of ("\\"); pos3 = (pos2 == std::string::npos) ?pos2 : pos2+1; // A matching delimiter is a comma with even trailing '\' // characters if (!((s1.length() - ((pos == s1.npos) ? 0: pos-1)) &1 )) { s2=""; boost::trim (s1); for (std::string::iterator i = s1.begin(); i != s1.end(); ++i) { //unescape characters if (*i == '\\') { if(++i == s1.end()) { break; } } s2+=*i; } splitStrings.push_back (s2); pos1 = pos3; } } return splitStrings; } std::string Flags2String(int flags, const Flag *descr, const std::string &sep) { std::list tmp; while (descr->m_flag) { if (flags & descr->m_flag) { tmp.push_back(descr->m_description); } ++descr; } return boost::join(tmp, ", "); } std::string SyncEvolutionDataDir() { std::string dataDir(DATA_DIR); const char *envvar = getenv("SYNCEVOLUTION_DATA_DIR"); if (envvar) { dataDir = envvar; } return dataDir; } ScopedEnvChange::ScopedEnvChange(const string &var, const string &value) : m_var(var) { const char *oldval = getenv(var.c_str()); if (oldval) { m_oldvalset = true; m_oldval = oldval; } else { m_oldvalset = false; } setenv(var.c_str(), value.c_str(), 1); } ScopedEnvChange::~ScopedEnvChange() { if (m_oldvalset) { setenv(m_var.c_str(), m_oldval.c_str(), 1); } else { unsetenv(m_var.c_str()); } } std::string getCurrentTime() { time_t seconds = time (NULL); tm tmbuffer; tm *data = localtime_r(&seconds, &tmbuffer); if (!data) { return "???"; } arrayptr buffer (new char [13]); strftime (buffer.get(), 13, "%y%m%d%H%M%S", data); return buffer.get(); } SE_END_CXX syncevolution_1.4/src/syncevo/util.h000066400000000000000000000540761230021373600177750ustar00rootroot00000000000000/* * Copyright (C) 2008-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_SYNCEVOLUTION_UTIL # define INCL_SYNCEVOLUTION_UTIL #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // definitions used to be included in util.h, // include it to avoid changing code using the time things #include SE_BEGIN_CXX /** case-insensitive less than for assoziative containers */ template class Nocase : public std::binary_function { public: bool operator()(const T &x, const T &y) const { return boost::ilexicographical_compare(x, y); } }; /** case-insensitive equals */ template class Iequals : public std::binary_function { public: bool operator()(const T &x, const T &y) const { return boost::iequals(x, y); } }; /** shorthand, primarily useful for BOOST_FOREACH macro */ typedef std::pair StringPair; typedef std::map StringMap; /** * remove multiple slashes in a row and dots directly after a slash if not followed by filename, * remove trailing / */ std::string normalizePath(const std::string &path); /** * Returns last component of path. Trailing slash is ignored. * Empty if path is empty. */ std::string getBasename(const std::string &path); /** * Returns path without the last component. Empty if nothing left. */ std::string getDirname(const std::string &path); /** * Splits path into directory and file part. Trailing slashes * are stripped first. */ void splitPath(const std::string &path, std::string &dir, std::string &file); /** * convert relative path to canonicalized absolute path * @param path will be turned into absolute path if possible, otherwise left unchanged * @return true if conversion is successful, false otherwise(errno will be set) */ bool relToAbs(std::string &path); /** ensure that m_path is writable, otherwise throw error */ void mkdir_p(const std::string &path); inline bool rm_r_all(const std::string &path, bool isDir) { return true; } /** * remove a complete directory hierarchy; invoking on non-existant directory is okay * @param path relative or absolute path to be removed * @param filter an optional callback which determines whether an entry really is * to be deleted (return true in that case); called with full path * to entry and true if known to be a directory */ void rm_r(const std::string &path, boost::function filter = rm_r_all); /** * copy complete directory hierarchy * * If the source is a directory, then the target * also has to be a directory name. It will be * created if necessary. * * Alternatively, both names may refer to files. * In that case the directory which is going to * contain the target file must exist. * * @param from source directory or file * @param to target directory or file (must have same type as from) */ void cp_r(const std::string &from, const std::string &to); /** true if the path refers to a directory */ bool isDir(const std::string &path); /** * try to read a file into the given string, throw exception if fails * * @param filename absolute or relative file name * @retval content filled with file content * @return true if file could be read */ bool ReadFile(const std::string &filename, std::string &content); bool ReadFile(std::istream &in, std::string &content); enum ExecuteFlags { EXECUTE_NO_STDERR = 1<<0, /**< suppress stderr of command */ EXECUTE_NO_STDOUT = 1<<1 /**< suppress stdout of command */ }; /** * system() replacement * * If called without output redirection active (see LogRedirect), * then it will simply call system(). If output redirection is * active, the command is executed in a forked process without * blocking the parent process and the parent reads the output, * passing it through LogRedirect for processing. * * This is necessary to capture all output reliably: LogRedirect * ensures that we don't deadlock, but to achieve that, it drops * data when the child prints too much of it. * * @param cmd command including parameters, without output redirection * @param flags see ExecuteFlags * @return same as in system(): use WEXITSTATUS() et.al. to decode it */ int Execute(const std::string &cmd, ExecuteFlags flags) throw(); /** * Simple string hash function, derived from Dan Bernstein's algorithm. */ unsigned long Hash(const char *str); unsigned long Hash(const std::string &str); /** * SHA-256 implementation, returning hash as lowercase hex string (like sha256sum). * Might not be available, in which case it raises an exception. */ std::string SHA_256(const std::string &in); /** * escape/unescape code * * Escaping is done URL-like, with a configurable escape * character. The exact set of characters to replace (besides the * special escape character) is configurable, too. * * The code used to be in SafeConfigNode, but is of general value. */ class StringEscape { public: enum Mode { SET, /**< explicit list of characters to be escaped */ INI_VALUE, /**< right hand side of .ini assignment: escape all spaces at start and end (but not in the middle) and the equal sign */ INI_WORD, /**< same as before, but keep it one word: escape all spaces and the equal sign = */ STRICT /**< general purpose: escape all characters besides alphanumeric and -_ */ }; private: char m_escapeChar; Mode m_mode; std::set m_forbidden; public: /** * default constructor, using % as escape character, escaping all spaces (including * leading and trailing ones), and all characters besides alphanumeric and -_ */ StringEscape(char escapeChar = '%', Mode mode = STRICT) : m_escapeChar(escapeChar), m_mode(mode) {} /** * @param escapeChar character used to introduce escape sequence * @param forbidden explicit list of characters which are to be escaped */ StringEscape(char escapeChar, const char *forbidden); /** special character which introduces two-char hex encoded original character */ char getEscapeChar() const { return m_escapeChar; } void setEscapeChar(char escapeChar) { m_escapeChar = escapeChar; } Mode getMode() const { return m_mode; } void setMode(Mode mode) { m_mode = mode; } /** * escape string according to current settings */ std::string escape(const std::string &str) const; /** escape string with the given settings */ static std::string escape(const std::string &str, char escapeChar, Mode mode); /** * unescape string, with escape character as currently set */ std::string unescape(const std::string &str) const { return unescape(str, m_escapeChar); } /** * unescape string, with escape character as given */ static std::string unescape(const std::string &str, char escapeChar); }; /** * This is a simplified implementation of a class representing and calculating * UUIDs v4 inspired from RFC 4122. We do not use cryptographic pseudo-random * numbers, instead we rely on rand/srand. * * We initialize the random generation with the system time given by time(), but * only once. * * Instantiating this class will generate a new unique UUID, available afterwards * in the base string class. */ class UUID : public std::string { public: UUID(); }; /** * Safety check for string pointer. * Returns pointer if valid, otherwise the default string. */ inline const char *NullPtrCheck(const char *ptr, const char *def = "(null)") { return ptr ? ptr : def; } /** * A C++ wrapper around readir() which provides the names of all * directory entries, excluding . and .. * * In contrast to the underlying readdir(), this class sorts * the result by name before granting access to it. */ class ReadDir { public: ReadDir(const std::string &path, bool throwError = true); typedef std::vector::const_iterator const_iterator; typedef std::vector::iterator iterator; iterator begin() { return m_entries.begin(); } iterator end() { return m_entries.end(); } const_iterator begin() const { return m_entries.begin(); } const_iterator end() const { return m_entries.end(); } /** * check whether directory contains entry, returns full path * @param caseInsensitive ignore case, pick first entry which matches randomly */ std::string find(const std::string &entry, bool caseSensitive); private: std::string m_path; std::vector m_entries; }; /** * Using this macro ensures that tests, even if defined in * object files which are not normally linked into the test * binary, are included in the test suite under the group * "SyncEvolution". * * Use it like this: * @verbatim #include "config.h" #ifdef ENABLE_UNIT_TESTS # include "test.h" class Foo : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(foo); CPPUNIT_TEST(testBar); CPPUNIT_TEST_SUITE_END(); public: void testBar(); }; # SYNCEVOLUTION_TEST_SUITE_REGISTRATION(classname) #endif @endverbatim */ #define SYNCEVOLUTION_TEST_SUITE_REGISTRATION( ATestFixtureType ) \ CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( ATestFixtureType, "SyncEvolution" ); \ extern "C" { int funambolAutoRegisterRegistry ## ATestFixtureType = 12345; } std::string StringPrintf(const char *format, ...) #ifdef __GNUC__ __attribute__((format(printf, 1, 2))) #endif ; std::string StringPrintfV(const char *format, va_list ap); /** * strncpy() which inserts adds 0 byte */ char *Strncpy(char *dest, const char *src, size_t n); /** * sleep() with sub-second resolution. Might be interrupted by signals * or SuspendFlags abort/suspend requests before the time has elapsed. * * @return seconds not elapsed yet, 0 if not interrupted */ double Sleep(double seconds); /** * Acts like the underlying type. In addition ensures that plain types * are not left uninitialized. */ template class Init { public: Init(const T &val) : m_value(val) {} Init() : m_value(boost::value_initialized()) {} Init(const Init &other) : m_value(other.m_value) {} Init & operator = (const T &val) { m_value = val; return *this; } operator const T & () const { return m_value; } operator T & () { return m_value; } private: T m_value; }; /** * Version of InitState for scalar values (can't derive from them): * acts like the underlying type. In addition ensures that plain types * are not left uninitialized and tracks whether a value was every * assigned explicitly. */ template class InitStateBase { T m_value; protected: InitStateBase() : m_value(boost::value_initialized()) {} InitStateBase(const InitStateBase &other) : m_value(other.m_value) {} template InitStateBase(const V &val) : m_value(val) {} public: operator const T & () const { return m_value; } operator T & () { return m_value; } const T & get() const { return m_value; } T & get() { return m_value; } }; /** version of InitState for classes: can call methods directly */ template class InitStateBase : public T { protected: InitStateBase() {} template InitStateBase(const V &val) : T(val) {} InitStateBase(const InitStateBase &other) : T(other) {} public: const T & get() const { return *this; } T & get() { return *this; } }; template class InitState : public InitStateBase::value> { typedef InitStateBase::value> parent_type; bool m_wasSet; public: typedef T value_type; InitState() : m_wasSet(false) {} InitState(const InitState &other) : parent_type(other.get()), m_wasSet(other.m_wasSet) {} template InitState(const V &val, bool wasSet = false) : parent_type(val), m_wasSet(wasSet) {} InitState & operator = (const InitState &val) { this->get() = val; m_wasSet = val.m_wasSet; return *this; } template InitState & operator = (const V &val) { this->get() = val; m_wasSet = true; return *this; } /** * Only tracks modifications done through this class. * Modifications of the contained value after obtaining * direct access to it (for example, via get()) are not * noticed. */ bool wasSet() const { return m_wasSet; } }; /** * Retrieve value if found in map, otherwise the * default. wasSet() returns true only in the first case. */ template InitState GetWithDef(const C &map, const typename C::key_type &key, const typename C::mapped_type &def = boost::value_initialized()) { typename C::const_iterator it = map.find(key); if (it != map.end()) { return InitState(it->second, true); } else { return InitState(def, false); } } /** * a nop destructor which doesn't do anything, for boost::shared_ptr */ struct NopDestructor { template void operator () (T *) {} }; /** * Acts like a boolean, but in addition, can also tell whether the * value was explicitly set. Defaults to false for both. */ typedef InitState Bool; /** * Acts like a string, but in addition, can also tell whether the * value was explicitly set. */ typedef InitState InitStateString; /** * Version of InitState where the value can true, false, or a string. * Recognizes 0/1/false/true/no/yes case-insensitively as special * booleans, everything else is considered a string. */ class InitStateTri : public InitStateString { public: InitStateTri(const std::string &val, bool wasSet) : InitStateString(val, wasSet) {} InitStateTri() {} InitStateTri(const char *val) : InitStateString(val, false) {} InitStateTri(const InitStateTri &other) : InitStateString(other) {} InitStateTri(const InitStateString &other) : InitStateString(other) {} enum Value { VALUE_TRUE, VALUE_FALSE, VALUE_STRING }; // quick check for true/false, use get() for string case Value getValue() const; }; enum HandleExceptionFlags { HANDLE_EXCEPTION_FLAGS_NONE = 0, /** * a 404 status error is possible and must not be logged as ERROR */ HANDLE_EXCEPTION_404_IS_OKAY = 1 << 0, HANDLE_EXCEPTION_FATAL = 1 << 1, /** * don't log exception as ERROR */ HANDLE_EXCEPTION_NO_ERROR = 1 << 2, HANDLE_EXCEPTION_MAX = 1 << 3, }; /** * an exception which records the source file and line * where it was thrown * * @TODO add function name */ class Exception : public std::runtime_error { public: Exception(const std::string &file, int line, const std::string &what) : std::runtime_error(what), m_file(file), m_line(line) {} ~Exception() throw() {} const std::string m_file; const int m_line; /** * Convenience function, to be called inside a catch(..) block. * * Rethrows the exception to determine what it is, then logs it * at the chosen level (error by default). * * Turns certain known exceptions into the corresponding * status code if status still was STATUS_OK when called. * Returns updated status code. * * @param logPrefix passed to SE_LOG* messages * @retval explanation set to explanation for problem, if non-NULL * @param level level to be used for logging */ static SyncMLStatus handle(SyncMLStatus *status = NULL, const std::string *logPrefix = NULL, std::string *explanation = NULL, Logger::Level = Logger::ERROR, HandleExceptionFlags flags = HANDLE_EXCEPTION_FLAGS_NONE); static SyncMLStatus handle(const std::string &logPrefix, HandleExceptionFlags flags = HANDLE_EXCEPTION_FLAGS_NONE) { return handle(NULL, &logPrefix, NULL, Logger::ERROR, flags); } static SyncMLStatus handle(std::string &explanation, HandleExceptionFlags flags = HANDLE_EXCEPTION_FLAGS_NONE) { return handle(NULL, NULL, &explanation, Logger::ERROR, flags); } static void handle(HandleExceptionFlags flags) { handle(NULL, NULL, NULL, Logger::ERROR, flags); } static void log() { handle(NULL, NULL, NULL, Logger::DEBUG); } /** * Tries to identify exception class based on explanation string created by * handle(). If successful, that exception is throw with the same * attributes as in the original exception. * * If not, tryRethrow() returns (mustThrow false) or throws a std::runtime_error * with the explanation as text. */ static void tryRethrow(const std::string &explanation, bool mustThrow = false); /** * Same as tryRethrow() for strings with a 'org.syncevolution.xxxx:' prefix, * as passed as D-Bus error strings. */ static void tryRethrowDBus(const std::string &error); }; /** * StatusException by wrapping a SyncML status */ class StatusException : public Exception { public: StatusException(const std::string &file, int line, const std::string &what, SyncMLStatus status) : Exception(file, line, what), m_status(status) {} SyncMLStatus syncMLStatus() const { return m_status; } protected: SyncMLStatus m_status; }; class TransportException : public Exception { public: TransportException(const std::string &file, int line, const std::string &what) : Exception(file, line, what) {} ~TransportException() throw() {} }; class TransportStatusException : public StatusException { public: TransportStatusException(const std::string &file, int line, const std::string &what, SyncMLStatus status) : StatusException(file, line, what, status) {} ~TransportStatusException() throw() {} }; /** * replace ${} with environment variables, with * XDG_DATA_HOME, XDG_CACHE_HOME and XDG_CONFIG_HOME having their normal * defaults */ std::string SubstEnvironment(const std::string &str); /** getenv() with default value */ inline const char *getEnv(const char *var, const char *def) { const char *res = getenv(var); return res ? res : def; } inline std::string getHome() { return getEnv("HOME", "."); } /** * Parse a separator splitted set of strings src, the separator itself is * escaped by a backslash. Spaces around the separator is also stripped. * */ std::vector unescapeJoinedString (const std::string &src, char separator); /** * mapping from int flag to explanation */ struct Flag { int m_flag; const char *m_description; }; /** * turn flags into comma separated list of explanations * * @param flags bit mask * @param descr array with zero m_flag as end marker * @param sep used to join m_description strings */ std::string Flags2String(int flags, const Flag *descr, const std::string &sep = ", "); /** * Returns the path to the data directory. This is generally * /usr/share/syncevolution/ but can be overridden by setting the * SYNCEVOLUTION_DATA_DIR environment variable. * * @retval dataDir the path to the data directory */ std::string SyncEvolutionDataDir(); /** * Temporarily set env variable, restore old value on destruction. * Useful for unit tests which depend on the environment. */ class ScopedEnvChange { public: ScopedEnvChange(const std::string &var, const std::string &value); ~ScopedEnvChange(); private: std::string m_var, m_oldval; bool m_oldvalset; }; std::string getCurrentTime(); /** throw a normal SyncEvolution Exception, including source information */ #define SE_THROW(_what) \ SE_THROW_EXCEPTION(Exception, _what) /** throw a class which accepts file, line, what parameters */ #define SE_THROW_EXCEPTION(_class, _what) \ throw _class(__FILE__, __LINE__, _what) /** throw a class which accepts file, line, what plus 1 additional parameter */ #define SE_THROW_EXCEPTION_1(_class, _what, _x1) \ throw _class(__FILE__, __LINE__, (_what), (_x1)) /** throw a class which accepts file, line, what plus 2 additional parameters */ #define SE_THROW_EXCEPTION_2(_class, _what, _x1, _x2) \ throw _class(__FILE__, __LINE__, (_what), (_x1), (_x2)) /** throw a class which accepts file, line, what plus 2 additional parameters */ #define SE_THROW_EXCEPTION_3(_class, _what, _x1, _x2, _x3) \ throw _class(__FILE__, __LINE__, (_what), (_x1), (_x2), (_x3)) /** throw a class which accepts file, line, what plus 2 additional parameters */ #define SE_THROW_EXCEPTION_4(_class, _what, _x1, _x2, _x3, _x4) \ throw _class(__FILE__, __LINE__, (_what), (_x1), (_x2), (_x3), (_x4)) /** throw a class which accepts file, line, what parameters and status parameters*/ #define SE_THROW_EXCEPTION_STATUS(_class, _what, _status) \ throw _class(__FILE__, __LINE__, _what, _status) SE_END_CXX #endif // INCL_SYNCEVOLUTION_UTIL syncevolution_1.4/src/syncevolution.cpp000066400000000000000000001110531230021373600205730ustar00rootroot00000000000000/* * Copyright (C) 2005-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "config.h" #include #include #include using namespace std; #include #ifdef HAVE_GLIB #include #endif #ifdef DBUS_SERVICE // must come before other header files which pull in boost/intrusive_ptr.hpp // because it defines some intrusive_ptr_release implementations #include struct SourceStatus { string m_mode; string m_status; uint32_t m_error; }; namespace GDBusCXX { template<> struct dbus_traits : public dbus_struct_traits > > > {}; } using namespace GDBusCXX; #endif #include #include #include #include #include #include #include #include #include SE_BEGIN_CXX #if defined(ENABLE_MAEMO) && defined (ENABLE_EBOOK) // really override the symbol, even if redefined by EDSAbiWrapper #undef e_contact_new_from_vcard extern "C" EContact *e_contact_new_from_vcard(const char *vcard) { static typeof(e_contact_new_from_vcard) *impl; if (!impl) { impl = (typeof(impl))dlsym(RTLD_NEXT, "e_contact_new_from_vcard"); } // Old versions of EDS-DBus parse_changes_array() call // e_contact_new_from_vcard() with a pointer which starts // with a line break; Evolution is not happy with that and // refuses to parse it. This code forwards until it finds // the first non-whitespace, presumably the BEGIN:VCARD. while (*vcard && isspace(*vcard)) { vcard++; } return impl ? impl(vcard) : NULL; } #endif #ifdef DBUS_SERVICE class RemoteSession; typedef map Config_t; /** * Act as a dbus server. All requests to dbus server * are passed through this class. */ class RemoteDBusServer : public DBusRemoteObject { public: RemoteDBusServer(); GMainLoop *getLoop() { return m_loop; } /** * Check whether the server is started and can be attached. * Printing an error message is optional, some callers might * prefer a different kind of error handling. */ bool checkStarted(bool printError = true); /** * execute arguments from command line * @param args the arguments of command line * @param config the config name parsed from arguments if has * @param runSync arguments to run a sync * @return true if successfully */ bool execute(const vector &args, const string &config, bool runSync); /** * To implement the feature of '--monitor' option, monitor a * given config if there is a session running. * If config is empty, then peak a running session to monitor. * @param config the config name parsed from arguments if has * @return true if successfully */ bool monitor(const string &config); /** * To implement the feature of '--status' without a server. * get and print all running sessions in the dbus server */ void runningSessions(); /** whether the dbus call(s) has/have completed */ bool done() { return m_replyTotal == m_replyCounter; } /** one reply returns. Increase reply counter. */ void replyInc(); /** set whether there is an error */ void setResult(bool result) { m_result = result; } /** call 'Server.InfoResponse' */ void infoResponse(const string &id, const string &state, const StringMap &resp); private: /** call 'Attach' until it returns */ void attachSync(); /** * callback of 'Server.Attach': * also set up a watch and add watch callback when the daemon is gone, * then do version check before returning */ void attachCb(const boost::shared_ptr &watch, const string &error); /** * second half of attaching: check version and print warning */ void versionCb(const StringMap &versions, const string &error); /** callback of 'Server.SessionChanged' */ void sessionChangedCb(const DBusObject_t &object, bool active); /** callback of 'Server.LogOutput' */ void logOutputCb(const DBusObject_t &object, const string &level, const string &log, const string &procname); /** callback of 'Server.InfoRequest' */ void infoReqCb(const string &, const DBusObject_t &, const string &, const string &, const string &, const StringMap &); /** callback of Server.InfoResponse */ void infoResponseCb(const string &error); /** callback of calling 'Server.StartSession' */ void startSessionCb(const DBusObject_t &session, const string &error); /** * receives org.freedesktop.DBus.NameOwnerChanged signals, * watches for changes of org.syncevolution.server */ void nameOwnerChangedCB(const std::string &name, const std::string &oldOwner, const std::string &newOwner) { if (name == "org.syncevolution") { SE_LOG_ERROR(NULL, "The SyncEvolution D-Bus service died unexpectedly. A running sync might still be able to complete normally, but the command line cannot report progress anymore and has to quit."); m_result = false; g_main_loop_quit(m_loop); } } /** update active session vector according to 'SessionChanged' signal */ void updateSessions(const string &session, bool active); /** check m_session is active */ bool isActive(); /** get all running sessions. Used internally. */ void getRunningSessions(); /** called when daemon has gone */ void daemonGone(); /** set the total number of replies we must wait */ void resetReplies(int total = 1) { m_replyTotal = total; m_replyCounter = 0; } /** signal handler for 'CTRL-C' */ static void handleSignal(int sig); // session used for signal handler, // used to call 'suspend' and 'abort' static boost::weak_ptr g_session; // the main loop GMainLoop *m_loop; // whether client can attach to the daemon. // It is also used to indicate whether daemon is ready to use. bool m_attached; // error flag bool m_result; // config name string m_configName; // active session object path boost::shared_ptr m_activeSession; // session created or monitored boost::shared_ptr m_session; // active sessions after listening to 'SessionChanged' signals vector m_activeSessions; // the number of total dbus calls unsigned int m_replyTotal; // the number of returned dbus calls unsigned int m_replyCounter; // listen to dbus server signal 'SessionChanged' SignalWatch2 m_sessionChanged; // listen to dbus server signal 'LogOutput' SignalWatch4 m_logOutput; // listen to dbus server signal 'InfoRequest' SignalWatch6 m_infoReq; /** watch daemon whether it is gone */ boost::shared_ptr m_daemonWatch; }; /** * Act as a session. All requests to a session are passed * through this class. */ class RemoteSession : public DBusRemoteObject { public: RemoteSession(RemoteDBusServer &server, const std::string &path); RemoteDBusServer &getServer() { return m_server; } /** * call 'Execute' method of 'Session' in dbus server * without waiting for return */ void executeAsync(const vector &args); /** * call 'Suspend' or 'Abort' method of 'Session' in dbus server * without waiting for return */ void interruptAsync(const char *operation); /** copy config name from server's config */ void setConfigName(const Config_t &config); /** get config name of this session */ string configName() { return m_configName; } /** status 'done' is sent by session */ bool statusDone() { return boost::iequals(m_status, "done"); } /** get current status */ string status() { return m_status; } /** set the flag to indicate the session is running sync */ void setRunSync(bool runSync) { m_runSync = runSync; } /** pass through logoutput and print them if m_output is true */ void logOutput(Logger::Level level, const string &log, const string &procname); /** set whether to print output */ void setOutput(bool output) { m_output = output; } /** process signals from daemon */ void infoReq(const string &id, const DBusObject_t &session, const string &state, const string &handler, const string &type, const StringMap ¶ms); /** remove InfoReq objects from map */ void removeInfoReq(const string &id); typedef std::map SourceStatuses_t; private: /** * InfoReq to handle info requests from daemon and * call 'Server.InfoResponse' to send its response */ class InfoReq { /** the session reference */ RemoteSession &m_session; /** the id of InfoRequest */ string m_id; /** the type of InfoRequest */ string m_type; /** the response map sent to the daemon*/ StringMap m_resp; /** InfoRequest state */ enum State { INIT, // init WORKING, //'working' RESPONSE, // 'response' DONE // 'done' }; /** the current state of InfoRequest */ State m_state; public: InfoReq(RemoteSession &session, const string &id, const string &type); /** * process the info request dispatched by session */ void process(const string &id, const DBusObject_t &session, const string &state, const string &handler, const string &type, const StringMap ¶ms); }; /** callback of calling 'Session.Execute' */ void executeCb(const string &error); /** callback of 'Session.StatusChanged' */ void statusChangedCb(const string &status, uint32_t errorCode, const SourceStatuses_t &sourceStatus); /** callback of 'Session.Suspend' */ void suspendCb(const string &); /** callback of 'Session.Abort' */ void abortCb(const string &); /** * implement requirements from info req. Called by InfoReq. */ void handleInfoReq(const string &type, const StringMap ¶ms, StringMap &resp); /** dbus server */ RemoteDBusServer &m_server; /* whether to log output */ bool m_output; /** config name of the session */ string m_configName; /** current status */ string m_status; /** session is running sync */ bool m_runSync; /** signal watch 'StatusChanged' */ SignalWatch3 m_statusChanged; /** InfoReq map. store all infoReq belongs to this session */ map > m_infoReqs; }; /** * Get current known environment variables, which might be used * in executing command line arguments. This is only necessary * when using dbus daemon. * @param vars the returned environment variables */ static void getEnvVars(map &vars); #endif extern "C" int main( int argc, char **argv ) { if (boost::ends_with(argv[0], "syncevo-local-sync")) { return LocalTransportMain(argc, argv); } // Intercept stderr and route it through our logging. // stdout is printed normally. Deconstructing it when // leaving main() does one final processing of pending // output. PushLogger redirect(new LogRedirect(LogRedirect::STDERR)); setvbuf(stderr, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); SyncContext::initMain("syncevolution"); // Expand PATH to cover the directory we were started from? // This might be needed to find normalize_vcard. char *exe = strdup(argv[0]); if (!exe) { SE_THROW("out of memory"); } if (strchr(exe, '/') ) { char *dir = dirname(exe); string path; char *oldpath = getenv("PATH"); if (oldpath) { path += oldpath; path += ":"; } path += dir; setenv("PATH", path.c_str(), 1); } free(exe); try { if (getenv("SYNCEVOLUTION_DEBUG")) { Logger::instance().setLevel(Logger::DEBUG); } SyncEvo::KeyringSyncCmdline cmdline(argc, argv); vector parsedArgs; if(!cmdline.parse(parsedArgs)) { return 1; } if (cmdline.dontRun()) { return 0; } Bool useDaemon = cmdline.useDaemon(); if(cmdline.monitor()) { #ifdef DBUS_SERVICE // monitor a session RemoteDBusServer server; if(server.checkStarted() && server.monitor(cmdline.getConfigName())) { return 0; } return 1; #else SE_LOG_ERROR(NULL, "this syncevolution binary was compiled without support for monitoring a background sync"); return 1; #endif } else if(cmdline.status() && cmdline.getConfigName().empty()) { #ifdef DBUS_SERVICE // '--status' and no server name, try to get running sessions RemoteDBusServer server; if(server.checkStarted()) { server.runningSessions(); return 0; } return 1; #else SE_LOG_SHOW(NULL, "this syncevolution binary was compiled without support for monitoring a background sync"); return 1; #endif } else if (useDaemon || !useDaemon.wasSet()) { #ifdef DBUS_SERVICE RemoteDBusServer server; // Running execute() without the server available will print errors. // Avoid that unless the user explicitly asked for the daemon. bool result = server.checkStarted(false); if (useDaemon.wasSet() || result) { return !server.execute(parsedArgs, cmdline.getConfigName(), cmdline.isSync()); } else { // User didn't select --use-daemon and thus doesn't need to know about it // not being available. // SE_LOG_SHOW(NULL, "WARNING: cannot run syncevolution as daemon. " // "Trying to run it without daemon."); } #else if (useDaemon.wasSet()) { SE_LOG_SHOW(NULL, "ERROR: this syncevolution binary was compiled without support of daemon. " "Either run syncevolution with '--use-daemon=no' or without that option."); return 1; } #endif } // if forcing not using daemon or trying to use daemon with failures, // run arguments in the process if (!useDaemon.wasSet() || !useDaemon) { EDSAbiWrapperInit(); /* * don't log errors to cerr: LogRedirect cannot distinguish * between our valid error messages and noise from other * libs, therefore it would get suppressed (logged at * level DEVELOPER, while output is at most INFO) */ if (cmdline.run()) { return 0; } else { return 1; } } } catch ( const std::exception &ex ) { SE_LOG_ERROR(NULL, "%s", ex.what()); } catch (...) { SE_LOG_ERROR(NULL, "unknown error"); } return 1; } #ifdef DBUS_SERVICE /********************** RemoteDBusServer implementation **************************/ RemoteDBusServer::RemoteDBusServer() : DBusRemoteObject(dbus_get_bus_connection("SESSION", NULL, true, NULL), "/org/syncevolution/Server", "org.syncevolution.Server", "org.syncevolution", true), m_attached(false), m_result(true), m_replyTotal(0), m_replyCounter(0), m_sessionChanged(*this,"SessionChanged"), m_logOutput(*this, "LogOutput"), m_infoReq(*this, "InfoRequest") { m_loop = g_main_loop_new (NULL, FALSE); if (getConnection()) { //check whether we can attach to the daemon //also set up the daemon watch when attaching to server attachSync(); if(m_attached) { m_sessionChanged.activate(boost::bind(&RemoteDBusServer::sessionChangedCb, this, _1, _2)); m_logOutput.activate(boost::bind(&RemoteDBusServer::logOutputCb, this, _1, _2, _3, _4)); m_infoReq.activate(boost::bind(&RemoteDBusServer::infoReqCb, this, _1, _2, _3, _4, _5, _6)); } } } bool RemoteDBusServer::checkStarted(bool printError) { if(!m_attached) { if (printError) { SE_LOG_ERROR(NULL, "SyncEvolution D-Bus server not available."); } return false; } return true; } void RemoteDBusServer::attachSync() { resetReplies(); DBusClientCall1 > attach(*this, "Attach"); attach.start(boost::bind(&RemoteDBusServer::attachCb, this, _1, _2)); while(!done()) { g_main_loop_run(m_loop); } } void RemoteDBusServer::attachCb(const boost::shared_ptr &watch, const string &error) { if(error.empty()) { //if attach is successful, watch server whether it is gone m_daemonWatch = watch; m_daemonWatch->setCallback(boost::bind(&RemoteDBusServer::daemonGone,this)); // don't print error information, leave it to caller m_attached = true; // do a version check now before calling replyInc() DBusClientCall1< StringMap > getVersions(*this, "GetVersions"); getVersions.start(boost::bind(&RemoteDBusServer::versionCb, this, _1, _2)); } else { // done with attach phase, skip version check replyInc(); } } void RemoteDBusServer::versionCb(const StringMap &versions, const string &error) { replyInc(); if (!error.empty()) { SE_LOG_DEBUG(NULL, "Server.GetVersions(): %s", error.c_str()); } else { StringMap::const_iterator it = versions.find("version"); if (it != versions.end() && it->second != VERSION) { SE_LOG_INFO(NULL, "proceeding despite version mismatch between command line client 'syncevolution' and 'syncevo-dbus-server' (%s != %s)", it->second.c_str(), VERSION); } } } void RemoteDBusServer::logOutputCb(const DBusObject_t &object, const string &level, const string &log, const string &procname) { if (m_session && (boost::equals(object, getPath()) || boost::equals(object, m_session->getPath()))) { m_session->logOutput(Logger::strToLevel(level.c_str()), log, procname); } } void RemoteDBusServer::infoReqCb(const string &id, const DBusObject_t &session, const string &state, const string &handler, const string &type, const StringMap ¶ms) { // if m_session is null, just ignore if(m_session) { m_session->infoReq(id, session, state, handler, type, params); } } void RemoteDBusServer::infoResponse(const string &id, const string &state, const StringMap &resp) { //call Server.InfoResponse DBusClientCall0 call(*this, "InfoResponse"); call.start(id, state, resp, boost::bind(&RemoteDBusServer::infoResponseCb, this, _1)); } void RemoteDBusServer::infoResponseCb(const string &error) { replyInc(); if(!error.empty()) { SE_LOG_ERROR(NULL, "information response failed."); m_result = false; } g_main_loop_quit(m_loop); } void RemoteDBusServer::sessionChangedCb(const DBusObject_t &object, bool active) { // update active sessions if needed updateSessions(object, active); g_main_loop_quit(m_loop); } void RemoteDBusServer::daemonGone() { //print error info and exit SE_LOG_ERROR(NULL, "Background sync daemon has gone."); exit(1); } static void SuspendFlagsChanged(RemoteSession *session, SuspendFlags &flags) { if (flags.getState() == SuspendFlags::SUSPEND) { session->interruptAsync("Suspend"); } else if(flags.getState() == SuspendFlags::ABORT) { session->interruptAsync("Abort"); } } bool RemoteDBusServer::execute(const vector &args, const string &peer, bool runSync) { //the basic workflow is: //1) start a session //2) waiting for the session becomes active //3) execute 'arguments' once it is active // start a new session DBusClientCall1 startSession(*this, "StartSessionWithFlags"); std::vector flags; if (!runSync) { flags.push_back("no-sync"); } startSession.start(peer, flags, boost::bind(&RemoteDBusServer::startSessionCb, this, _1, _2)); // wait until 'StartSession' returns resetReplies(); while(!done()) { g_main_loop_run(m_loop); } if(m_session) { m_session->setRunSync(true); // If the syncevo-dbus-server dies while we wait for some // output from it, then we used to hang forever. Worse, if it // happened while signal handling was active, then the command // line tool couldn't even be killed with CTRL-C. // To detect this case, we watch name owner changes for org.syncevolution.server. // If it changes from now on, we know that our m_session became // invalid. If it already changed, then the next calls for that // session will fail. #if 0 GDBusCXX::DBusRemoteObject daemon(getConnection(), "/org/freedesktop/DBus", "org.freedesktop.DBus", ""); GDBusCXX::SignalWatch3 nameOwnerChanged(daemon, "NameOwnerChanged"); nameOwnerChanged.activate(boost::bind(&RemoteDBusServer::nameOwnerChangedCB, this, _1, _2, _3)); #endif //if session is not active, just wait while(!isActive()) { g_main_loop_run(m_loop); } // Logger::Level level = Logger::instance().getLevel(); // Logger::instance().setLevel(Logger::DEBUG); resetReplies(); m_session->executeAsync(args); while(m_result && !done()) { g_main_loop_run(m_loop); } //if encoutering errors, return if(!m_result) { return m_result; } // Acticate signal handling in all cases. // We let SuspendFlags catch them and then // react in the normal event loop. SuspendFlags &flags(SuspendFlags::getSuspendFlags()); boost::shared_ptr signalGuard = flags.activate(); flags.m_stateChanged.connect(SuspendFlags::StateChanged_t::slot_type(SuspendFlagsChanged, m_session.get(), _1).track(m_session)); //wait until status is 'done' while(m_result && !m_session->statusDone()) { g_main_loop_run(m_loop); } //restore logging level // Logger::instance().setLevel(level); m_session->setRunSync(false); } return m_result; } void RemoteDBusServer::startSessionCb(const DBusObject_t &sessionPath, const string &error) { replyInc(); if(!error.empty()) { SE_LOG_ERROR(NULL, "starting D-Bus session failed: %s", error.c_str()); if (error.find("org.freedesktop.DBus.Error.UnknownMethod") != error.npos) { SE_LOG_INFO(NULL, "syncevo-dbus-server is most likely too old"); } m_result = false; g_main_loop_quit(m_loop); return; } m_session.reset(new RemoteSession(*this, sessionPath)); g_main_loop_quit(m_loop); } bool RemoteDBusServer::isActive() { /** if current session is active and then start to call 'Execute' method */ if(m_session) { BOOST_FOREACH(const string &session, m_activeSessions) { if(boost::equals(m_session->getPath(), session.c_str())) { return true; } } } return false; } void RemoteDBusServer::runningSessions() { //the basic working flow is: //1) get all sessions //2) check each session and collect running sessions //3) get config name of running sessions and print them vector sessions = DBusClientCall1< vector >(*this, "GetSessions")(); if (sessions.empty()) { SE_LOG_SHOW(NULL, "Background sync daemon is idle."); } else { SE_LOG_SHOW(NULL, "Running session(s): "); // create local objects for sessions BOOST_FOREACH(const DBusObject_t &path, sessions) { RemoteSession session(*this, path); // Get status. Slight race condition here, session might // disappear before we can ask. In that case we fail by // showing the exception string instead of showing some // more comprehensible error message. Unlikely, so don't // bother... boost::tuple status = DBusClientCall3(session, "GetStatus")(); std::string syncStatus = boost::get<0>(status); if (boost::istarts_with(syncStatus, "running")) { Config_t config = DBusClientCall1(session, "GetConfig")(false); session.setConfigName(config); if (!session.configName().empty()) { SE_LOG_SHOW(NULL, " %s (%s)", session.configName().c_str(), session.getPath()); } } } } } void RemoteDBusServer::updateSessions(const string &session, bool active) { if(active) { //add it into active list m_activeSessions.push_back(session); } else { //if inactive, remove it from active list for(vector::iterator it = m_activeSessions.begin(); it != m_activeSessions.end(); ++it) { if(boost::equals(session, *it)) { m_activeSessions.erase(it); break; } } } } void RemoteDBusServer::replyInc() { // increase counter and check whether all replies are returned m_replyCounter++; if(done()) { g_main_loop_quit(m_loop); } } bool RemoteDBusServer::monitor(const string &peer) { //the basic working flow is: //1) get all sessions //2) check each session and collect running sessions or //3) peak one session with the given peer and monitor it vector sessions = DBusClientCall1< vector >(*this, "GetSessions")(); if (sessions.empty()) { SE_LOG_SHOW(NULL, "Background sync daemon is idle, no session available to be be monitored."); } else { // cheating: client and server might normalize the peer name differently... string peerNorm = SyncConfig::normalizeConfigString(peer); // create local objects for sessions BOOST_FOREACH(const DBusObject_t &path, sessions) { boost::shared_ptr session(new RemoteSession(*this, path)); boost::tuple status = DBusClientCall3(*session, "GetStatus")(); std::string syncStatus = boost::get<0>(status); if (boost::istarts_with(syncStatus, "running")) { Config_t config = DBusClientCall1(*session, "GetConfig")(false); session->setConfigName(config); if (peer.empty() || peerNorm == session->configName()) { SE_LOG_SHOW(NULL, "Monitoring '%s' (%s)\n", session->configName().c_str(), session->getPath()); // set DBusServer::m_session so that RemoteSession::logOutput gets called // and enable printing that output m_session = session; session->setOutput(true); // now wait for session to complete while (!session->statusDone()) { g_main_loop_run(getLoop()); } SE_LOG_SHOW(NULL, "Monitoring done"); return true; } } } SE_LOG_SHOW(NULL, "'%s' is not running.", peer.c_str()); } return false; } /********************** RemoteSession implementation **************************/ RemoteSession::RemoteSession(RemoteDBusServer &server, const string &path) : DBusRemoteObject(server.getConnection(), path, "org.syncevolution.Session", "org.syncevolution"), m_server(server), m_output(false), m_runSync(false), m_statusChanged(*this, "StatusChanged") { m_statusChanged.activate(boost::bind(&RemoteSession::statusChangedCb, this, _1, _2, _3)); } void RemoteSession::executeAsync(const vector &args) { //start to print outputs m_output = true; map vars; getEnvVars(vars); DBusClientCall0 call(*this, "Execute"); call.start(args, vars, boost::bind(&RemoteSession::executeCb, this, _1)); } void RemoteSession::executeCb(const string &error) { m_server.replyInc(); if(!error.empty()) { SE_LOG_ERROR(NULL, "running the command line inside the D-Bus server failed"); m_server.setResult(false); //end to print outputs m_output = false; return; } } void RemoteSession::statusChangedCb(const string &status, uint32_t errorCode, const SourceStatuses_t &sourceStatus) { m_status = status; if (errorCode) { m_server.setResult(false); g_main_loop_quit(m_server.getLoop()); } if(status == "done") { //if session is done, quit the loop g_main_loop_quit(m_server.getLoop()); m_output = false; } } void RemoteSession::setConfigName(const Config_t &config) { Config_t::const_iterator it = config.find(""); if(it != config.end()) { StringMap global = it->second; StringMap::iterator git = global.find("configName"); if(git != global.end()) { m_configName = git->second; } } } static void interruptCb(const std::string &error) { if (!error.empty()) { SE_LOG_DEBUG(NULL, "interruptAsync() error from remote: %s", error.c_str()); } } void RemoteSession::interruptAsync(const char *operation) { // call Suspend() without checking result DBusClientCall0 suspend(*this, operation); suspend.start(interruptCb); } void RemoteSession::logOutput(Logger::Level level, const string &log, const string &procname) { if(m_output) { Logger::MessageOptions options(level); options.m_processName = &procname; SyncEvo::Logger::instance().messageWithOptions(options, "%s", log.c_str()); } } void RemoteSession::infoReq(const string &id, const DBusObject_t &session, const string &state, const string &handler, const string &type, const StringMap ¶ms) { //if command line runs a sync, then try to handle req if (m_runSync && boost::iequals(session, getPath())) { //only handle password now if (boost::iequals("password", type)) { map >::iterator it = m_infoReqs.find(id); if (it != m_infoReqs.end()) { it->second->process(id, session, state, handler, type, params); } else { boost::shared_ptr passwd(new InfoReq(*this, id, type)); m_infoReqs[id] = passwd; passwd->process(id, session, state, handler, type, params); } } } } void RemoteSession::handleInfoReq(const string &type, const StringMap ¶ms, StringMap &resp) { if (boost::iequals(type, "password")) { char buffer[256]; string descr; StringMap::const_iterator it = params.find("description"); if (it != params.end()) { descr = it->second; } printf("Enter password for %s: ", descr.c_str()); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) && strcmp(buffer, "\n")) { size_t len = strlen(buffer); if (len && buffer[len - 1] == '\n') { buffer[len - 1] = 0; } resp["password"] = string(buffer); } else { SE_LOG_ERROR(NULL, "could not read password for %s", descr.c_str()); } } } void RemoteSession::removeInfoReq(const string &id) { map >::iterator it = m_infoReqs.find(id); if (it != m_infoReqs.end()) { m_infoReqs.erase(it); } } /********************** InfoReq implementation **************************/ RemoteSession::InfoReq::InfoReq(RemoteSession &session, const string &id, const string &type) :m_session(session), m_id(id), m_type(type), m_state(INIT) { } void RemoteSession::InfoReq::process(const string &id, const DBusObject_t &session, const string &state, const string &handler, const string &type, const StringMap ¶ms) { //only handle info belongs to this InfoReq if (boost::equals(m_id, id)) { //check the state and response if necessary if (m_state == INIT && boost::iequals("request", state)) { m_session.getServer().infoResponse(m_id, "working", StringMap()); m_state = WORKING; m_session.handleInfoReq(type, params, m_resp); } else if ((m_state == WORKING) && boost::iequals("waiting", state)) { m_session.getServer().infoResponse(m_id, "response", m_resp); m_state = RESPONSE; } else if (boost::iequals("done", state)) { //if request is 'done', remove it m_session.removeInfoReq(m_id); } } } void getEnvVars(map &vars) { //environment variables used to run command line static const char *varNames[] = { "http_proxy", "HOME", "PATH", "SYNCEVOLUTION_BACKEND_DIR", "SYNCEVOLUTION_DEBUG", "SYNCEVOLUTION_GNUTLS_DEBUG", "SYNCEVOLUTION_TEMPLATE_DIR", "SYNCEVOLUTION_XML_CONFIG_DIR", "SYNC_EVOLUTION_EVO_CALENDAR_DELAY", "XDG_CACHE_HOME", "XDG_CONFIG_HOME", "XDG_DATA_HOME" }; for (unsigned int i = 0; i < sizeof(varNames) / sizeof(const char*); i++) { const char *value; //get values of environment variables if they are set if ((value = getenv(varNames[i])) != NULL) { vars.insert(make_pair(varNames[i], value)); } } } #endif SE_END_CXX syncevolution_1.4/src/synthesis-includes/000077500000000000000000000000001230021373600210025ustar00rootroot00000000000000syncevolution_1.4/src/synthesis-includes/Makefile.am000066400000000000000000000005321230021373600230360ustar00rootroot00000000000000# The only purpose of this Makefile is to build # the synthesis header files if # a) the Synthesis source is bundled with SyncEvolution and # b) the core engine (which includes Synthesis) is disabled # # The GTK GUI needs some of the headers (syerror.h). all: cd $(SYNTHESIS_SUBDIR)/src && $(MAKE) synthesis/syerror.h synthesis/generic_types.h syncevolution_1.4/src/templates/000077500000000000000000000000001230021373600171435ustar00rootroot00000000000000syncevolution_1.4/src/templates/README000066400000000000000000000005211230021373600200210ustar00rootroot00000000000000The configuration templates in this directory are *not* complete and therefore not suitable for copying them directly in a .config directory. They contain only those properties which are different for a certain peer compared to the built-in default values. They are used by the SyncEvolution binaries to create complete configurations. syncevolution_1.4/src/templates/clients/000077500000000000000000000000001230021373600206045ustar00rootroot00000000000000syncevolution_1.4/src/templates/clients/SyncEvolution.ini000066400000000000000000000010441230021373600241250ustar00rootroot00000000000000=== template.ini === fingerprint = SyncEvolution Client description = SyncEvolution server side template === config.ini === PeerIsClient = 1 ConsumerReady = 1 IconURI = image://themedimage/icons/services/syncevolution === sources/addressbook/config.ini === sync = two-way uri = addressbook backend = addressbook === sources/calendar/config.ini === sync = two-way uri = calendar backend = calendar === sources/todo/config.ini === sync = two-way uri = todo backend = todo === sources/memo/config.ini === sync = two-way uri = memo backend = memo syncevolution_1.4/src/templates/clients/phone/000077500000000000000000000000001230021373600217155ustar00rootroot00000000000000syncevolution_1.4/src/templates/clients/phone/nokia.ini000066400000000000000000000017021230021373600235170ustar00rootroot00000000000000=== template.ini === fingerprint = Nokia N900,Nokia N85,Nokia 7210c,Nokia N97 mini,Nokia 2630,Nokia 6500 Slide,Nokia 5800 XpressMusic,Nokia 5230,Nokia description = Template for all Nokia phones which support contacts, notes and combined tasks+events templateName = Nokia === config.ini === peerIsClient = 1 remoteIdentifier = PC Suite ConsumerReady = 1 IconURI = image://themedimage/icons/services/nokia-phone === sources/addressbook/config.ini === sync = two-way uri = Contacts backend = addressbook === sources/calendar/config.ini === sync = none uri = use-calendar+todo-for-sync-instead-of-calendar backend = calendar === sources/todo/config.ini === sync = none uri = use-calendar+todo-for-sync-instead-of-todo backend = todo === sources/memo/config.ini === sync = two-way uri = Notes backend = memo === sources/calendar+todo/config.ini === sync = two-way syncFormat = text/x-vcalendar evolutionsource = calendar,todo uri = Calendar backend = virtual syncevolution_1.4/src/templates/clients/phone/sony-ericsson-old.ini000066400000000000000000000012161230021373600260050ustar00rootroot00000000000000=== template.ini === fingerprint = Sony Ericsson K750i description = Template for old Sony Ericsson phones, with separate databases for contacts/events/tasks/memos and SyncML 1.1 templateName = Sony Ericsson (SyncML 1.1) === config.ini === SyncMLVersion = 1.1 peerIsClient = 1 remoteIdentifier = PC Suite ConsumerReady = 1 IconURI = image://themedimage/icons/services/sony-ericsson-phone === sources/addressbook/config.ini === uri = Contact backend = addressbook === sources/calendar/config.ini === uri = Calendar backend = calendar === sources/todo/config.ini === uri = Task backend = todo === sources/memo/config.ini === uri = Memo backend = memo syncevolution_1.4/src/templates/clients/phone/sony-ericsson.ini000066400000000000000000000012261230021373600252320ustar00rootroot00000000000000=== template.ini === fingerprint = Sony Ericsson W595,Sony Ericsson W810i,Sony Ericsson description = Template for all current Sony Ericsson phones, with separate databases for contacts/events/tasks/memos and SyncML 1.2 templateName = Sony Ericsson === config.ini === peerIsClient = 1 remoteIdentifier = PC Suite ConsumerReady = 1 IconURI = image://themedimage/icons/services/sony-ericsson-phone === sources/addressbook/config.ini === uri = Contact backend = addressbook === sources/calendar/config.ini === uri = Calendar backend = calendar === sources/todo/config.ini === uri = Task backend = todo === sources/memo/config.ini === uri = Memo backend = memo syncevolution_1.4/src/templates/contexts/000077500000000000000000000000001230021373600210125ustar00rootroot00000000000000syncevolution_1.4/src/templates/contexts/Google-Calendar.ini000066400000000000000000000006571230021373600244460ustar00rootroot00000000000000=== template.ini === fingerprint = Google Calendar description = event sync via CalDAV, use for the 'target-config@google-calendar' config === config.ini === consumerReady = 1 peerType = WebDAV syncURL = https://www.google.com/calendar/dav/%u/user/?SyncEvolution=Google IconURI = image://themedimage/icons/services/google-calendar dumpData = 0 printChanges = 0 === sources/calendar/config.ini === sync = two-way backend = CalDAV syncevolution_1.4/src/templates/contexts/WebDAV.ini000066400000000000000000000010571230021373600225660ustar00rootroot00000000000000=== template.ini === fingerprint = WebDAV description = contact and event sync using WebDAV, use for the 'target-config@' config === config.ini === consumerReady = 1 peerType = WebDAV dumpData = 0 printChanges = 0 IconURI = image://themedimage/icons/services/webdav === sources/addressbook/config.ini === sync = two-way backend = CardDAV === sources/calendar/config.ini === sync = two-way backend = CalDAV === sources/memo/config.ini === sync = two-way backend = CalDAVJournal === sources/todo/config.ini === sync = two-way backend = CalDAVTodo syncevolution_1.4/src/templates/contexts/Yahoo.ini000066400000000000000000000006311230021373600225720ustar00rootroot00000000000000=== template.ini === fingerprint = Yahoo description = contact and event sync using WebDAV, use for the 'target-config@yahoo' config === config.ini === consumerReady = 1 peerType = WebDAV dumpData = 0 printChanges = 0 IconURI = image://themedimage/icons/services/yahoo === sources/addressbook/config.ini === sync = none backend = CardDAV === sources/calendar/config.ini === sync = two-way backend = CalDAV syncevolution_1.4/src/templates/servers/000077500000000000000000000000001230021373600206345ustar00rootroot00000000000000syncevolution_1.4/src/templates/servers/Funambol.ini000066400000000000000000000013461230021373600231040ustar00rootroot00000000000000=== template.ini === fingerprint = Funambol description = http://my.funambol.com === config.ini === syncURL = http://my.funambol.com/sync WebURL = http://my.funambol.com PeerName = Funambol enableWBXML = FALSE enableRefreshSync = TRUE ConsumerReady = TRUE RetryInterval = 0 IconURI = image://themedimage/icons/services/funambol === sources/addressbook/config.ini === sync = two-way uri = card backend = addressbook === sources/calendar/config.ini === sync = two-way uri = event syncFormat = text/calendar forceSyncFormat = 1 backend = calendar === sources/todo/config.ini === sync = two-way uri = task syncFormat = text/calendar forceSyncFormat = 1 backend = todo === sources/memo/config.ini === sync = two-way uri = note backend = memo syncevolution_1.4/src/templates/servers/Google-Contacts.ini000066400000000000000000000007221230021373600243260ustar00rootroot00000000000000=== template.ini === fingerprint = Google Contacts, Google description = contact sync via SyncML, see http://www.google.com/support/mobile/bin/topic.py?topic=22181 === config.ini === syncURL = https://m.google.com/syncml WebURL = http://www.google.com ConsumerReady = TRUE clientAuthType = basic IconURI = image://themedimage/icons/services/gmail === sources/addressbook/config.ini === sync = two-way uri = contacts syncFormat = text/x-vcard backend = addressbook syncevolution_1.4/src/templates/servers/Goosync.ini000066400000000000000000000011331230021373600227540ustar00rootroot00000000000000=== template.ini === fingerprint = Goosync description = http://www.goosync.com/ === config.ini === syncURL = http://sync2.goosync.com/ WebURL = http://www.goosync.com/ PeerName = Goosync IconURI = image://themedimage/icons/services/goosync === sources/addressbook/config.ini === sync = two-way uri = contacts backend = addressbook === sources/calendar/config.ini === sync = two-way uri = calendar backend = calendar === sources/todo/config.ini === sync = two-way uri = tasks backend = todo === sources/memo/config.ini === # intentionally disabled, not working yet sync = none uri = backend = memo syncevolution_1.4/src/templates/servers/Memotoo.ini000066400000000000000000000012321230021373600227520ustar00rootroot00000000000000=== template.ini === fingerprint = Memotoo description = http://www.memotoo.com === config.ini === syncURL = http://sync.memotoo.com/syncML WebURL = http://www.memotoo.com ConsumerReady = TRUE PeerName = Memotoo IconURI = image://themedimage/icons/services/memotoo === sources/addressbook/config.ini === sync = two-way uri = con # prefer vCard 3.0, works better than vCard 2.1 (NICKNAME!) syncFormat = text/vcard backend = addressbook === sources/calendar/config.ini === sync = two-way uri = cal backend = calendar === sources/todo/config.ini === sync = two-way uri = task backend = todo === sources/memo/config.ini === sync = two-way uri = note backend = memo syncevolution_1.4/src/templates/servers/Mobical.ini000066400000000000000000000011721230021373600227040ustar00rootroot00000000000000=== template.ini === fingerprint = Mobical, Everdroid description = https://www.everdroid.com === config.ini === syncURL = http://www.everdroid.com/sync/server WebURL = https://www.everdroid.com ConsumerReady = TRUE PeerName = Mobical IconURI = image://themedimage/icons/services/everdroid === sources/addressbook/config.ini === sync = two-way uri = con backend = addressbook syncFormat = text/x-vcard === sources/calendar/config.ini === sync = two-way uri = cal backend = calendar === sources/todo/config.ini === sync = two-way uri = task backend = todo === sources/memo/config.ini === sync = two-way uri = pnote backend = memo syncevolution_1.4/src/templates/servers/Oracle.ini000066400000000000000000000012461230021373600225450ustar00rootroot00000000000000=== template.ini === fingerprint = Oracle description = http://www.oracle.com/technology/products/beehive/index.html === config.ini === syncURL = https://your.company/mobilesync/server WebURL = http://www.oracle.com/technology/products/beehive/index.html PeerName = Oracle IconURI = image://themedimage/icons/services/oracle === sources/addressbook/config.ini === sync = two-way uri = ./contacts backend = addressbook === sources/calendar/config.ini === sync = two-way uri = ./calendar/events backend = calendar === sources/todo/config.ini === sync = two-way uri = ./calendar/tasks backend = todo === sources/memo/config.ini === sync = two-way uri = ./notes backend = memo syncevolution_1.4/src/templates/servers/Ovi.ini000066400000000000000000000016411230021373600220740ustar00rootroot00000000000000=== template.ini === fingerprint = Ovi description = http://www.ovi.com === config.ini === syncURL = https://sync.ovi.com/services/syncml WebURL = http://www.ovi.com ConsumerReady = TRUE PeerName = Ovi IconURI = image://themedimage/icons/services/ovi === sources/addressbook/config.ini === sync = two-way uri = ./Contact/Unfiled syncFormat = text/vcard backend = addressbook === sources/calendar/config.ini === sync = none uri = use-calendar+todo-for-sync-instead-of-calendar syncFormat = text/x-vcalendar backend = calendar === sources/todo/config.ini === sync = none uri = use-calendar+todo-for-sync-instead-of-todo syncFormat = text/x-vcalendar backend = todo === sources/memo/config.ini === # intentionally disabled, not working yet sync = none uri = backend = memo === sources/calendar+todo/config.ini === sync = two-way uri = ./EventTask/Tasks syncFormat = text/x-vcalendar database = calendar,todo backend = virtual syncevolution_1.4/src/templates/servers/ScheduleWorld.ini000066400000000000000000000011671230021373600241060ustar00rootroot00000000000000=== template.ini === fingerprint = ScheduleWorld description = server no longer in operation === config.ini === syncURL = http://sync.scheduleworld.com/funambol/ds WebURL = http://www.scheduleworld.com PeerName = ScheduleWorld IconURI = image://themedimage/icons/services/scheduleworld === sources/addressbook/config.ini === sync = two-way uri = card3 syncFormat = text/vcard backend = addressbook === sources/calendar/config.ini === sync = two-way uri = cal2 backend = calendar === sources/todo/config.ini === sync = two-way uri = task2 backend = todo === sources/memo/config.ini === sync = two-way uri = note backend = memo syncevolution_1.4/src/templates/servers/SyncEvolution.ini000066400000000000000000000011351230021373600241560ustar00rootroot00000000000000=== template.ini === fingerprint = SyncEvolution, default description = http://www.syncevolution.org === config.ini === syncURL = http://yourserver:port WebURL = http://www.syncevolution.org PeerName = SyncEvolution IconURI = image://themedimage/icons/services/syncevolution === sources/addressbook/config.ini === sync = two-way uri = addressbook backend = addressbook === sources/calendar/config.ini === sync = two-way uri = calendar backend = calendar === sources/todo/config.ini === sync = two-way uri = todo backend = todo === sources/memo/config.ini === sync = two-way uri = memo backend = memo syncevolution_1.4/src/templates/servers/Synthesis.ini000066400000000000000000000010731230021373600233270ustar00rootroot00000000000000=== template.ini === fingerprint = Synthesis description = http://www.synthesis.ch === config.ini === syncURL = http://www.synthesis.ch/sync WebURL = http://www.synthesis.ch PeerName = Synthesis IconURI = image://themedimage/icons/services/synthesis === sources/addressbook/config.ini === sync = two-way uri = contacts backend = addressbook === sources/calendar/config.ini === sync = none uri = events backend = calendar === sources/todo/config.ini === sync = none uri = tasks backend = todo === sources/memo/config.ini === sync = two-way uri = notes backend = memo syncevolution_1.4/src/templates/servers/eGroupware.ini000066400000000000000000000015761230021373600234660ustar00rootroot00000000000000=== template.ini === fingerprint = eGroupware description = http://www.egroupware.org === config.ini === syncURL = http://set.your.domain.here/rpc.php WebURL = http://www.egroupware.org # Not much testing is happening with eGroupware # and users need to be aware of the special URL; # but Ovi is not necessarily better and is visible. # Let's show it. consumerReady = TRUE PeerName = eGroupware IconURI = image://themedimage/icons/services/egroupware === sources/addressbook/config.ini === sync = two-way uri = ./contacts backend = addressbook === sources/calendar/config.ini === sync = two-way # There was some debate whether this should be # ./calendar - "calendar" seems to work, so let's # keep it. uri = calendar backend = calendar === sources/todo/config.ini === sync = two-way uri = ./tasks backend = todo === sources/memo/config.ini === sync = two-way uri = ./notes backend = memo syncevolution_1.4/src/templates/templates.am000066400000000000000000000016471230021373600214700ustar00rootroot00000000000000# TODO: preferably generate this list instead # ./ src_templatesdir = $(datadir)/syncevolution/templates dist_src_templates_DATA = src/templates/README # ./clients src_templates_clientsdir = $(datadir)/syncevolution/templates/clients dist_src_templates_clients_DATA = $(subst $(srcdir)/,,$(wildcard $(srcdir)/src/templates/clients/*.ini)) # ./clients/phone src_templates_clients_phonedir = $(datadir)/syncevolution/templates/clients/phone dist_src_templates_clients_phone_DATA = $(subst $(srcdir)/,,$(wildcard $(srcdir)/src/templates/clients/phone/*.ini)) # ./contexts src_templates_contextsdir = $(datadir)/syncevolution/templates/contexts dist_src_templates_contexts_DATA = $(subst $(srcdir)/,,$(wildcard $(srcdir)/src/templates/contexts/*.ini)) # ./servers src_templates_serversdir = $(datadir)/syncevolution/templates/servers dist_src_templates_servers_DATA = $(subst $(srcdir)/,,$(wildcard $(srcdir)/src/templates/servers/*.ini)) syncevolution_1.4/src/testcases.am000066400000000000000000000017541230021373600174710ustar00rootroot00000000000000# this file is not standalone. it needs variable TEST_FILES_GENERATED to be # already defined. src_testcasesdir = $(testdir)/testcases dist_src_testcases_DATA = \ src/testcases/local.png \ src/testcases/google_event.ics \ src/testcases/yahoo_contact.vcf \ src/testcases/eds_contact.vcf \ src/testcases/eds_event.ics \ src/testcases/eds_event.ics.local \ src/testcases/eds_memo.ics \ src/testcases/eds_task.ics \ $(TEST_FILES_GENERATED) src_testcases_lcsdir = $(testdir)/testcases/lcs dist_src_testcases_lcs_DATA = \ src/testcases/lcs/file1.txt \ src/testcases/lcs/file2.txt src_testcases_templates_clientsdir = $(testdir)/testcases/templates/clients dist_src_testcases_templates_clients_DATA = \ src/testcases/templates/clients/SyncEvolution.ini src_testcases_templates_clients_phone_nokia_s40dir = $(testdir)/testcases/templates/clients/phone/nokia/S40 dist_src_testcases_templates_clients_phone_nokia_s40_DATA = \ src/testcases/templates/clients/phone/nokia/S40/7210c.ini syncevolution_1.4/src/valgrind.supp000066400000000000000000000034451230021373600176720ustar00rootroot00000000000000{ write in pthread_create Memcheck:Param write(buf) fun:write fun:pthread_create@@GLIBC_2.1 } { write in e_book_new Memcheck:Param write(buf) fun:write obj:*libebook* fun:e_book_new } { write in g_thread_create_full Memcheck:Param write(buf) fun:write obj:*libgthread* fun:g_thread_create_full } { name server lookup Memcheck:Leak fun:malloc fun:nss_parse_service_list fun:__nss_database_lookup # obj:* # obj:* # fun:getpwuid_r@@GLIBC_2.1.2 # obj:*libglib* # fun:g_get_user_name # fun:gconf_get_daemon_dir } { bonobo_init Memcheck:Leak fun:malloc fun:g_malloc fun:ORBit_alloc_string fun:CORBA_string_dup fun:Bonobo_ActivationEnvValue_set fun:bonobo_activation_init_activation_env fun:bonobo_activation_orb_init fun:bonobo_activation_init fun:bonobo_init_full fun:bonobo_init } { corba init Memcheck:Leak fun:calloc fun:g_malloc0 obj:* obj:* fun:g_type_init_with_debug_flags fun:g_type_init fun:link_init fun:giop_init fun:CORBA_ORB_init } { pthread leak Memcheck:Leak fun:malloc fun:__pthread_initialize_manager fun:pthread_create@@GLIBC_2.1 } { libical tzutil invalid access Memcheck:Addr4 fun:icaltzutil_fetch_timezone obj:/usr/lib/libical.so.0.43.0 fun:icaltimezone_get_component fun:_ZN6sysync25loadSystemZoneDefinitionsEPNS_6GZonesE fun:_ZN6sysync6GZones10initializeEv fun:_ZN6sysync12TSyncAppBaseC2Ev fun:_ZN6sysync22TEngineSessionDispatchC2Ev fun:_ZN6sysync23TCustomServerEngineBaseC1Ev fun:_ZN6sysync28TCustomServerEngineInterface14newSyncAppBaseEv fun:_ZN6sysync16TEngineInterface4InitEv fun:_ZN6sysync17TEngineModuleBase7ConnectESsmt fun:_ZN6sysyncL22internal_ConnectEngineEbPPNS_17SDK_InterfaceTypeEtPmmt } syncevolution_1.4/svn2cl.sh000077500000000000000000000001021230021373600161150ustar00rootroot00000000000000#! /bin/sh svn update svn2cl --reparagraph --authors=authors.xml syncevolution_1.4/test/000077500000000000000000000000001230021373600153355ustar00rootroot00000000000000syncevolution_1.4/test/Algorithm/000077500000000000000000000000001230021373600172635ustar00rootroot00000000000000syncevolution_1.4/test/Algorithm/Artistic000066400000000000000000000137371230021373600210030ustar00rootroot00000000000000 The "Artistic License" Preamble The intent of this document is to state the conditions under which a Package may be copied, such that the Copyright Holder maintains some semblance of artistic control over the development of the package, while giving the users of the package the right to use and distribute the Package in a more-or-less customary fashion, plus the right to make reasonable modifications. Definitions: "Package" refers to the collection of files distributed by the Copyright Holder, and derivatives of that collection of files created through textual modification. "Standard Version" refers to such a Package if it has not been modified, or has been modified in accordance with the wishes of the Copyright Holder as specified below. "Copyright Holder" is whoever is named in the copyright or copyrights for the package. "You" is you, if you're thinking about copying or distributing this Package. "Reasonable copying fee" is whatever you can justify on the basis of media cost, duplication charges, time of people involved, and so on. (You will not be required to justify it to the Copyright Holder, but only to the computing community at large as a market that must bear the fee.) "Freely Available" means that no fee is charged for the item itself, though there may be fees involved in handling the item. It also means that recipients of the item may redistribute it under the same conditions they received it. 1. You may make and give away verbatim copies of the source form of the Standard Version of this Package without restriction, provided that you duplicate all of the original copyright notices and associated disclaimers. 2. You may apply bug fixes, portability fixes and other modifications derived from the Public Domain or from the Copyright Holder. A Package modified in such a way shall still be considered the Standard Version. 3. You may otherwise modify your copy of this Package in any way, provided that you insert a prominent notice in each changed file stating how and when you changed that file, and provided that you do at least ONE of the following: a) place your modifications in the Public Domain or otherwise make them Freely Available, such as by posting said modifications to Usenet or an equivalent medium, or placing the modifications on a major archive site such as uunet.uu.net, or by allowing the Copyright Holder to include your modifications in the Standard Version of the Package. b) use the modified Package only within your corporation or organization. c) rename any non-standard executables so the names do not conflict with standard executables, which must also be provided, and provide a separate manual page for each non-standard executable that clearly documents how it differs from the Standard Version. d) make other distribution arrangements with the Copyright Holder. 4. You may distribute the programs of this Package in object code or executable form, provided that you do at least ONE of the following: a) distribute a Standard Version of the executables and library files, together with instructions (in the manual page or equivalent) on where to get the Standard Version. b) accompany the distribution with the machine-readable source of the Package with your modifications. c) give non-standard executables non-standard names, and clearly document the differences in manual pages (or equivalent), together with instructions on where to get the Standard Version. d) make other distribution arrangements with the Copyright Holder. 5. You may charge a reasonable copying fee for any distribution of this Package. You may charge any fee you choose for support of this Package. You may not charge a fee for this Package itself. However, you may distribute this Package in aggregate with other (possibly commercial) programs as part of a larger (possibly commercial) software distribution provided that you do not advertise this Package as a product of your own. You may embed this Package's interpreter within an executable of yours (by linking); this shall be construed as a mere form of aggregation, provided that the complete Standard Version of the interpreter is so embedded. 6. The scripts and library files supplied as input to or produced as output from the programs of this Package do not automatically fall under the copyright of this Package, but belong to whoever generated them, and may be sold commercially, and may be aggregated with this Package. If such scripts or library files are aggregated with this Package via the so-called "undump" or "unexec" methods of producing a binary executable image, then distribution of such an image shall neither be construed as a distribution of this Package nor shall it fall under the restrictions of Paragraphs 3 and 4, provided that you do not represent such an executable image as a Standard Version of this Package. 7. C subroutines (or comparably compiled subroutines in other languages) supplied by you and linked into this Package in order to emulate subroutines and variables of the language defined by this Package shall not be considered part of this Package, but are the equivalent of input as in Paragraph 6, provided these subroutines do not change the language in any way that would cause it to fail the regression tests for the language. 8. Aggregation of this Package with a commercial distribution is always permitted provided that the use of this Package is embedded; that is, when no overt attempt is made to make this Package's interfaces visible to the end user of the commercial distribution. Such use shall not be construed as a distribution of this Package. 9. The name of the Copyright Holder may not be used to endorse or promote products derived from this software without specific prior written permission. 10. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. The End syncevolution_1.4/test/Algorithm/COPYING000066400000000000000000000013131230021373600203140ustar00rootroot00000000000000This is a subset of the original Algorithm::Diff distribution, added here to avoid the external dependency. No other changes were made. The original sources should always be available from the Comprehensive Perl Archive Network (CPAN). Visit to find a CPAN site near you. The Algorithm::Diff copyright is as follows: | Parts Copyright (c) 2000-2004 Ned Konz. All rights reserved. | Parts by Tye McQueen. | | This program is free software; you can redistribute it and/or modify it | under the same terms as Perl. The content of this directory is distributed under the original license: dual-licensed under GPL-2 (../../COPYING) and Larry Wall's "Artistic License" (./Artistic). syncevolution_1.4/test/Algorithm/Diff.pm000066400000000000000000001436261230021373600205050ustar00rootroot00000000000000package Algorithm::Diff; # Skip to first "=head" line for documentation. use strict; use integer; # see below in _replaceNextLargerWith() for mod to make # if you don't use this use vars qw( $VERSION @EXPORT_OK ); $VERSION = 1.19_01; # ^ ^^ ^^-- Incremented at will # | \+----- Incremented for non-trivial changes to features # \-------- Incremented for fundamental changes require Exporter; *import = \&Exporter::import; @EXPORT_OK = qw( prepare LCS LCSidx LCS_length diff sdiff compact_diff traverse_sequences traverse_balanced ); # McIlroy-Hunt diff algorithm # Adapted from the Smalltalk code of Mario I. Wolczko, # by Ned Konz, perl@bike-nomad.com # Updates by Tye McQueen, http://perlmonks.org/?node=tye # Create a hash that maps each element of $aCollection to the set of # positions it occupies in $aCollection, restricted to the elements # within the range of indexes specified by $start and $end. # The fourth parameter is a subroutine reference that will be called to # generate a string to use as a key. # Additional parameters, if any, will be passed to this subroutine. # # my $hashRef = _withPositionsOfInInterval( \@array, $start, $end, $keyGen ); sub _withPositionsOfInInterval { my $aCollection = shift; # array ref my $start = shift; my $end = shift; my $keyGen = shift; my %d; my $index; for ( $index = $start ; $index <= $end ; $index++ ) { my $element = $aCollection->[$index]; my $key = &$keyGen( $element, @_ ); if ( exists( $d{$key} ) ) { unshift ( @{ $d{$key} }, $index ); } else { $d{$key} = [$index]; } } return wantarray ? %d : \%d; } # Find the place at which aValue would normally be inserted into the # array. If that place is already occupied by aValue, do nothing, and # return undef. If the place does not exist (i.e., it is off the end of # the array), add it to the end, otherwise replace the element at that # point with aValue. It is assumed that the array's values are numeric. # This is where the bulk (75%) of the time is spent in this module, so # try to make it fast! sub _replaceNextLargerWith { my ( $array, $aValue, $high ) = @_; $high ||= $#$array; # off the end? if ( $high == -1 || $aValue > $array->[-1] ) { push ( @$array, $aValue ); return $high + 1; } # binary search for insertion point... my $low = 0; my $index; my $found; while ( $low <= $high ) { $index = ( $high + $low ) / 2; # $index = int(( $high + $low ) / 2); # without 'use integer' $found = $array->[$index]; if ( $aValue == $found ) { return undef; } elsif ( $aValue > $found ) { $low = $index + 1; } else { $high = $index - 1; } } # now insertion point is in $low. $array->[$low] = $aValue; # overwrite next larger return $low; } # This method computes the longest common subsequence in $a and $b. # Result is array or ref, whose contents is such that # $a->[ $i ] == $b->[ $result[ $i ] ] # foreach $i in ( 0 .. $#result ) if $result[ $i ] is defined. # An additional argument may be passed; this is a hash or key generating # function that should return a string that uniquely identifies the given # element. It should be the case that if the key is the same, the elements # will compare the same. If this parameter is undef or missing, the key # will be the element as a string. # By default, comparisons will use "eq" and elements will be turned into keys # using the default stringizing operator '""'. # Additional parameters, if any, will be passed to the key generation # routine. sub _longestCommonSubsequence { my $a = shift; # array ref or hash ref my $b = shift; # array ref or hash ref my $counting = shift; # scalar my $keyGen = shift; # code ref my $compare; # code ref if ( ref($a) eq 'HASH' ) { # prepared hash must be in $b my $tmp = $b; $b = $a; $a = $tmp; } # Check for bogus (non-ref) argument values if ( !ref($a) || !ref($b) ) { my @callerInfo = caller(1); die 'error: must pass array or hash references to ' . $callerInfo[3]; } # set up code refs # Note that these are optimized. if ( !defined($keyGen) ) # optimize for strings { $keyGen = sub { $_[0] }; $compare = sub { my ( $a, $b ) = @_; $a eq $b }; } else { $compare = sub { my $a = shift; my $b = shift; &$keyGen( $a, @_ ) eq &$keyGen( $b, @_ ); }; } my ( $aStart, $aFinish, $matchVector ) = ( 0, $#$a, [] ); my ( $prunedCount, $bMatches ) = ( 0, {} ); if ( ref($b) eq 'HASH' ) # was $bMatches prepared for us? { $bMatches = $b; } else { my ( $bStart, $bFinish ) = ( 0, $#$b ); # First we prune off any common elements at the beginning while ( $aStart <= $aFinish and $bStart <= $bFinish and &$compare( $a->[$aStart], $b->[$bStart], @_ ) ) { $matchVector->[ $aStart++ ] = $bStart++; $prunedCount++; } # now the end while ( $aStart <= $aFinish and $bStart <= $bFinish and &$compare( $a->[$aFinish], $b->[$bFinish], @_ ) ) { $matchVector->[ $aFinish-- ] = $bFinish--; $prunedCount++; } # Now compute the equivalence classes of positions of elements $bMatches = _withPositionsOfInInterval( $b, $bStart, $bFinish, $keyGen, @_ ); } my $thresh = []; my $links = []; my ( $i, $ai, $j, $k ); for ( $i = $aStart ; $i <= $aFinish ; $i++ ) { $ai = &$keyGen( $a->[$i], @_ ); if ( exists( $bMatches->{$ai} ) ) { $k = 0; for $j ( @{ $bMatches->{$ai} } ) { # optimization: most of the time this will be true if ( $k and $thresh->[$k] > $j and $thresh->[ $k - 1 ] < $j ) { $thresh->[$k] = $j; } else { $k = _replaceNextLargerWith( $thresh, $j, $k ); } # oddly, it's faster to always test this (CPU cache?). if ( defined($k) ) { $links->[$k] = [ ( $k ? $links->[ $k - 1 ] : undef ), $i, $j ]; } } } } if (@$thresh) { return $prunedCount + @$thresh if $counting; for ( my $link = $links->[$#$thresh] ; $link ; $link = $link->[0] ) { $matchVector->[ $link->[1] ] = $link->[2]; } } elsif ($counting) { return $prunedCount; } return wantarray ? @$matchVector : $matchVector; } sub traverse_sequences { my $a = shift; # array ref my $b = shift; # array ref my $callbacks = shift || {}; my $keyGen = shift; my $matchCallback = $callbacks->{'MATCH'} || sub { }; my $discardACallback = $callbacks->{'DISCARD_A'} || sub { }; my $finishedACallback = $callbacks->{'A_FINISHED'}; my $discardBCallback = $callbacks->{'DISCARD_B'} || sub { }; my $finishedBCallback = $callbacks->{'B_FINISHED'}; my $matchVector = _longestCommonSubsequence( $a, $b, 0, $keyGen, @_ ); # Process all the lines in @$matchVector my $lastA = $#$a; my $lastB = $#$b; my $bi = 0; my $ai; for ( $ai = 0 ; $ai <= $#$matchVector ; $ai++ ) { my $bLine = $matchVector->[$ai]; if ( defined($bLine) ) # matched { &$discardBCallback( $ai, $bi++, @_ ) while $bi < $bLine; &$matchCallback( $ai, $bi++, @_ ); } else { &$discardACallback( $ai, $bi, @_ ); } } # The last entry (if any) processed was a match. # $ai and $bi point just past the last matching lines in their sequences. while ( $ai <= $lastA or $bi <= $lastB ) { # last A? if ( $ai == $lastA + 1 and $bi <= $lastB ) { if ( defined($finishedACallback) ) { &$finishedACallback( $lastA, @_ ); $finishedACallback = undef; } else { &$discardBCallback( $ai, $bi++, @_ ) while $bi <= $lastB; } } # last B? if ( $bi == $lastB + 1 and $ai <= $lastA ) { if ( defined($finishedBCallback) ) { &$finishedBCallback( $lastB, @_ ); $finishedBCallback = undef; } else { &$discardACallback( $ai++, $bi, @_ ) while $ai <= $lastA; } } &$discardACallback( $ai++, $bi, @_ ) if $ai <= $lastA; &$discardBCallback( $ai, $bi++, @_ ) if $bi <= $lastB; } return 1; } sub traverse_balanced { my $a = shift; # array ref my $b = shift; # array ref my $callbacks = shift || {}; my $keyGen = shift; my $matchCallback = $callbacks->{'MATCH'} || sub { }; my $discardACallback = $callbacks->{'DISCARD_A'} || sub { }; my $discardBCallback = $callbacks->{'DISCARD_B'} || sub { }; my $changeCallback = $callbacks->{'CHANGE'}; my $matchVector = _longestCommonSubsequence( $a, $b, 0, $keyGen, @_ ); # Process all the lines in match vector my $lastA = $#$a; my $lastB = $#$b; my $bi = 0; my $ai = 0; my $ma = -1; my $mb; while (1) { # Find next match indices $ma and $mb do { $ma++; } while( $ma <= $#$matchVector && !defined $matchVector->[$ma] ); last if $ma > $#$matchVector; # end of matchVector? $mb = $matchVector->[$ma]; # Proceed with discard a/b or change events until # next match while ( $ai < $ma || $bi < $mb ) { if ( $ai < $ma && $bi < $mb ) { # Change if ( defined $changeCallback ) { &$changeCallback( $ai++, $bi++, @_ ); } else { &$discardACallback( $ai++, $bi, @_ ); &$discardBCallback( $ai, $bi++, @_ ); } } elsif ( $ai < $ma ) { &$discardACallback( $ai++, $bi, @_ ); } else { # $bi < $mb &$discardBCallback( $ai, $bi++, @_ ); } } # Match &$matchCallback( $ai++, $bi++, @_ ); } while ( $ai <= $lastA || $bi <= $lastB ) { if ( $ai <= $lastA && $bi <= $lastB ) { # Change if ( defined $changeCallback ) { &$changeCallback( $ai++, $bi++, @_ ); } else { &$discardACallback( $ai++, $bi, @_ ); &$discardBCallback( $ai, $bi++, @_ ); } } elsif ( $ai <= $lastA ) { &$discardACallback( $ai++, $bi, @_ ); } else { # $bi <= $lastB &$discardBCallback( $ai, $bi++, @_ ); } } return 1; } sub prepare { my $a = shift; # array ref my $keyGen = shift; # code ref # set up code ref $keyGen = sub { $_[0] } unless defined($keyGen); return scalar _withPositionsOfInInterval( $a, 0, $#$a, $keyGen, @_ ); } sub LCS { my $a = shift; # array ref my $b = shift; # array ref or hash ref my $matchVector = _longestCommonSubsequence( $a, $b, 0, @_ ); my @retval; my $i; for ( $i = 0 ; $i <= $#$matchVector ; $i++ ) { if ( defined( $matchVector->[$i] ) ) { push ( @retval, $a->[$i] ); } } return wantarray ? @retval : \@retval; } sub LCS_length { my $a = shift; # array ref my $b = shift; # array ref or hash ref return _longestCommonSubsequence( $a, $b, 1, @_ ); } sub LCSidx { my $a= shift @_; my $b= shift @_; my $match= _longestCommonSubsequence( $a, $b, 0, @_ ); my @am= grep defined $match->[$_], 0..$#$match; my @bm= @{$match}[@am]; return \@am, \@bm; } sub compact_diff { my $a= shift @_; my $b= shift @_; my( $am, $bm )= LCSidx( $a, $b, @_ ); my @cdiff; my( $ai, $bi )= ( 0, 0 ); push @cdiff, $ai, $bi; while( 1 ) { while( @$am && $ai == $am->[0] && $bi == $bm->[0] ) { shift @$am; shift @$bm; ++$ai, ++$bi; } push @cdiff, $ai, $bi; last if ! @$am; $ai = $am->[0]; $bi = $bm->[0]; push @cdiff, $ai, $bi; } push @cdiff, 0+@$a, 0+@$b if $ai < @$a || $bi < @$b; return wantarray ? @cdiff : \@cdiff; } sub diff { my $a = shift; # array ref my $b = shift; # array ref my $retval = []; my $hunk = []; my $discard = sub { push @$hunk, [ '-', $_[0], $a->[ $_[0] ] ]; }; my $add = sub { push @$hunk, [ '+', $_[1], $b->[ $_[1] ] ]; }; my $match = sub { push @$retval, $hunk if 0 < @$hunk; $hunk = [] }; traverse_sequences( $a, $b, { MATCH => $match, DISCARD_A => $discard, DISCARD_B => $add }, @_ ); &$match(); return wantarray ? @$retval : $retval; } sub sdiff { my $a = shift; # array ref my $b = shift; # array ref my $retval = []; my $discard = sub { push ( @$retval, [ '-', $a->[ $_[0] ], "" ] ) }; my $add = sub { push ( @$retval, [ '+', "", $b->[ $_[1] ] ] ) }; my $change = sub { push ( @$retval, [ 'c', $a->[ $_[0] ], $b->[ $_[1] ] ] ); }; my $match = sub { push ( @$retval, [ 'u', $a->[ $_[0] ], $b->[ $_[1] ] ] ); }; traverse_balanced( $a, $b, { MATCH => $match, DISCARD_A => $discard, DISCARD_B => $add, CHANGE => $change, }, @_ ); return wantarray ? @$retval : $retval; } ######################################## my $Root= __PACKAGE__; package Algorithm::Diff::_impl; use strict; sub _Idx() { 0 } # $me->[_Idx]: Ref to array of hunk indices # 1 # $me->[1]: Ref to first sequence # 2 # $me->[2]: Ref to second sequence sub _End() { 3 } # $me->[_End]: Diff between forward and reverse pos sub _Same() { 4 } # $me->[_Same]: 1 if pos 1 contains unchanged items sub _Base() { 5 } # $me->[_Base]: Added to range's min and max sub _Pos() { 6 } # $me->[_Pos]: Which hunk is currently selected sub _Off() { 7 } # $me->[_Off]: Offset into _Idx for current position sub _Min() { -2 } # Added to _Off to get min instead of max+1 sub Die { require Carp; Carp::confess( @_ ); } sub _ChkPos { my( $me )= @_; return if $me->[_Pos]; my $meth= ( caller(1) )[3]; Die( "Called $meth on 'reset' object" ); } sub _ChkSeq { my( $me, $seq )= @_; return $seq + $me->[_Off] if 1 == $seq || 2 == $seq; my $meth= ( caller(1) )[3]; Die( "$meth: Invalid sequence number ($seq); must be 1 or 2" ); } sub getObjPkg { my( $us )= @_; return ref $us if ref $us; return $us . "::_obj"; } sub new { my( $us, $seq1, $seq2, $opts ) = @_; my @args; for( $opts->{keyGen} ) { push @args, $_ if $_; } for( $opts->{keyGenArgs} ) { push @args, @$_ if $_; } my $cdif= Algorithm::Diff::compact_diff( $seq1, $seq2, @args ); my $same= 1; if( 0 == $cdif->[2] && 0 == $cdif->[3] ) { $same= 0; splice @$cdif, 0, 2; } my @obj= ( $cdif, $seq1, $seq2 ); $obj[_End] = (1+@$cdif)/2; $obj[_Same] = $same; $obj[_Base] = 0; my $me = bless \@obj, $us->getObjPkg(); $me->Reset( 0 ); return $me; } sub Reset { my( $me, $pos )= @_; $pos= int( $pos || 0 ); $pos += $me->[_End] if $pos < 0; $pos= 0 if $pos < 0 || $me->[_End] <= $pos; $me->[_Pos]= $pos || !1; $me->[_Off]= 2*$pos - 1; return $me; } sub Base { my( $me, $base )= @_; my $oldBase= $me->[_Base]; $me->[_Base]= 0+$base if defined $base; return $oldBase; } sub Copy { my( $me, $pos, $base )= @_; my @obj= @$me; my $you= bless \@obj, ref($me); $you->Reset( $pos ) if defined $pos; $you->Base( $base ); return $you; } sub Next { my( $me, $steps )= @_; $steps= 1 if ! defined $steps; if( $steps ) { my $pos= $me->[_Pos]; my $new= $pos + $steps; $new= 0 if $pos && $new < 0; $me->Reset( $new ) } return $me->[_Pos]; } sub Prev { my( $me, $steps )= @_; $steps= 1 if ! defined $steps; my $pos= $me->Next(-$steps); $pos -= $me->[_End] if $pos; return $pos; } sub Diff { my( $me )= @_; $me->_ChkPos(); return 0 if $me->[_Same] == ( 1 & $me->[_Pos] ); my $ret= 0; my $off= $me->[_Off]; for my $seq ( 1, 2 ) { $ret |= $seq if $me->[_Idx][ $off + $seq + _Min ] < $me->[_Idx][ $off + $seq ]; } return $ret; } sub Min { my( $me, $seq, $base )= @_; $me->_ChkPos(); my $off= $me->_ChkSeq($seq); $base= $me->[_Base] if !defined $base; return $base + $me->[_Idx][ $off + _Min ]; } sub Max { my( $me, $seq, $base )= @_; $me->_ChkPos(); my $off= $me->_ChkSeq($seq); $base= $me->[_Base] if !defined $base; return $base + $me->[_Idx][ $off ] -1; } sub Range { my( $me, $seq, $base )= @_; $me->_ChkPos(); my $off = $me->_ChkSeq($seq); if( !wantarray ) { return $me->[_Idx][ $off ] - $me->[_Idx][ $off + _Min ]; } $base= $me->[_Base] if !defined $base; return ( $base + $me->[_Idx][ $off + _Min ] ) .. ( $base + $me->[_Idx][ $off ] - 1 ); } sub Items { my( $me, $seq )= @_; $me->_ChkPos(); my $off = $me->_ChkSeq($seq); if( !wantarray ) { return $me->[_Idx][ $off ] - $me->[_Idx][ $off + _Min ]; } return @{$me->[$seq]}[ $me->[_Idx][ $off + _Min ] .. ( $me->[_Idx][ $off ] - 1 ) ]; } sub Same { my( $me )= @_; $me->_ChkPos(); return wantarray ? () : 0 if $me->[_Same] != ( 1 & $me->[_Pos] ); return $me->Items(1); } my %getName; BEGIN { %getName= ( same => \&Same, diff => \&Diff, base => \&Base, min => \&Min, max => \&Max, range=> \&Range, items=> \&Items, # same thing ); } sub Get { my $me= shift @_; $me->_ChkPos(); my @value; for my $arg ( @_ ) { for my $word ( split ' ', $arg ) { my $meth; if( $word !~ /^(-?\d+)?([a-zA-Z]+)([12])?$/ || not $meth= $getName{ lc $2 } ) { Die( $Root, ", Get: Invalid request ($word)" ); } my( $base, $name, $seq )= ( $1, $2, $3 ); push @value, scalar( 4 == length($name) ? $meth->( $me ) : $meth->( $me, $seq, $base ) ); } } if( wantarray ) { return @value; } elsif( 1 == @value ) { return $value[0]; } Die( 0+@value, " values requested from ", $Root, "'s Get in scalar context" ); } my $Obj= getObjPkg($Root); no strict 'refs'; for my $meth ( qw( new getObjPkg ) ) { *{$Root."::".$meth} = \&{$meth}; *{$Obj ."::".$meth} = \&{$meth}; } for my $meth ( qw( Next Prev Reset Copy Base Diff Same Items Range Min Max Get _ChkPos _ChkSeq ) ) { *{$Obj."::".$meth} = \&{$meth}; } 1; __END__ =head1 NAME Algorithm::Diff - Compute `intelligent' differences between two files / lists =head1 SYNOPSIS require Algorithm::Diff; # This example produces traditional 'diff' output: my $diff = Algorithm::Diff->new( \@seq1, \@seq2 ); $diff->Base( 1 ); # Return line numbers, not indices while( $diff->Next() ) { next if $diff->Same(); my $sep = ''; if( ! $diff->Items(2) ) { sprintf "%d,%dd%d\n", $diff->Get(qw( Min1 Max1 Max2 )); } elsif( ! $diff->Items(1) ) { sprintf "%da%d,%d\n", $diff->Get(qw( Max1 Min2 Max2 )); } else { $sep = "---\n"; sprintf "%d,%dc%d,%d\n", $diff->Get(qw( Min1 Max1 Min2 Max2 )); } print "< $_" for $diff->Items(1); print $sep; print "> $_" for $diff->Items(2); } # Alternate interfaces: use Algorithm::Diff qw( LCS LCS_length LCSidx diff sdiff compact_diff traverse_sequences traverse_balanced ); @lcs = LCS( \@seq1, \@seq2 ); $lcsref = LCS( \@seq1, \@seq2 ); $count = LCS_length( \@seq1, \@seq2 ); ( $seq1idxref, $seq2idxref ) = LCSidx( \@seq1, \@seq2 ); # Complicated interfaces: @diffs = diff( \@seq1, \@seq2 ); @sdiffs = sdiff( \@seq1, \@seq2 ); @cdiffs = compact_diff( \@seq1, \@seq2 ); traverse_sequences( \@seq1, \@seq2, { MATCH => \&callback1, DISCARD_A => \&callback2, DISCARD_B => \&callback3, }, \&key_generator, @extra_args, ); traverse_balanced( \@seq1, \@seq2, { MATCH => \&callback1, DISCARD_A => \&callback2, DISCARD_B => \&callback3, CHANGE => \&callback4, }, \&key_generator, @extra_args, ); =head1 INTRODUCTION (by Mark-Jason Dominus) I once read an article written by the authors of C; they said that they worked very hard on the algorithm until they found the right one. I think what they ended up using (and I hope someone will correct me, because I am not very confident about this) was the `longest common subsequence' method. In the LCS problem, you have two sequences of items: a b c d f g h j q z a b c d e f g i j k r x y z and you want to find the longest sequence of items that is present in both original sequences in the same order. That is, you want to find a new sequence I which can be obtained from the first sequence by deleting some items, and from the secend sequence by deleting other items. You also want I to be as long as possible. In this case I is a b c d f g j z From there it's only a small step to get diff-like output: e h i k q r x y + - + + - + + + This module solves the LCS problem. It also includes a canned function to generate C-like output. It might seem from the example above that the LCS of two sequences is always pretty obvious, but that's not always the case, especially when the two sequences have many repeated elements. For example, consider a x b y c z p d q a b c a x b y c z A naive approach might start by matching up the C and C that appear at the beginning of each sequence, like this: a x b y c z p d q a b c a b y c z This finds the common subsequence C. But actually, the LCS is C: a x b y c z p d q a b c a x b y c z or a x b y c z p d q a b c a x b y c z =head1 USAGE (See also the README file and several example scripts include with this module.) This module now provides an object-oriented interface that uses less memory and is easier to use than most of the previous procedural interfaces. It also still provides several exportable functions. We'll deal with these in ascending order of difficulty: C, C, C, OO interface, C, C, C, C, and C. =head2 C Given references to two lists of items, LCS returns an array containing their longest common subsequence. In scalar context, it returns a reference to such a list. @lcs = LCS( \@seq1, \@seq2 ); $lcsref = LCS( \@seq1, \@seq2 ); C may be passed an optional third parameter; this is a CODE reference to a key generation function. See L. @lcs = LCS( \@seq1, \@seq2, \&keyGen, @args ); $lcsref = LCS( \@seq1, \@seq2, \&keyGen, @args ); Additional parameters, if any, will be passed to the key generation routine. =head2 C This is just like C except it only returns the length of the longest common subsequence. This provides a performance gain of about 9% compared to C. =head2 C Like C except it returns references to two arrays. The first array contains the indices into @seq1 where the LCS items are located. The second array contains the indices into @seq2 where the LCS items are located. Therefore, the following three lists will contain the same values: my( $idx1, $idx2 ) = LCSidx( \@seq1, \@seq2 ); my @list1 = @seq1[ @$idx1 ]; my @list2 = @seq2[ @$idx2 ]; my @list3 = LCS( \@seq1, \@seq2 ); =head2 C $diff = Algorithm::Diffs->new( \@seq1, \@seq2 ); $diff = Algorithm::Diffs->new( \@seq1, \@seq2, \%opts ); C computes the smallest set of additions and deletions necessary to turn the first sequence into the second and compactly records them in the object. You use the object to iterate over I, where each hunk represents a contiguous section of items which should be added, deleted, replaced, or left unchanged. =over 4 The following summary of all of the methods looks a lot like Perl code but some of the symbols have different meanings: [ ] Encloses optional arguments : Is followed by the default value for an optional argument | Separates alternate return results Method summary: $obj = Algorithm::Diff->new( \@seq1, \@seq2, [ \%opts ] ); $pos = $obj->Next( [ $count : 1 ] ); $revPos = $obj->Prev( [ $count : 1 ] ); $obj = $obj->Reset( [ $pos : 0 ] ); $copy = $obj->Copy( [ $pos, [ $newBase ] ] ); $oldBase = $obj->Base( [ $newBase ] ); Note that all of the following methods C if used on an object that is "reset" (not currently pointing at any hunk). $bits = $obj->Diff( ); @items|$cnt = $obj->Same( ); @items|$cnt = $obj->Items( $seqNum ); @idxs |$cnt = $obj->Range( $seqNum, [ $base ] ); $minIdx = $obj->Min( $seqNum, [ $base ] ); $maxIdx = $obj->Max( $seqNum, [ $base ] ); @values = $obj->Get( @names ); Passing in C for an optional argument is always treated the same as if no argument were passed in. =item C $pos = $diff->Next(); # Move forward 1 hunk $pos = $diff->Next( 2 ); # Move forward 2 hunks $pos = $diff->Next(-5); # Move backward 5 hunks C moves the object to point at the next hunk. The object starts out "reset", which means it isn't pointing at any hunk. If the object is reset, then C moves to the first hunk. C returns a true value iff the move didn't go past the last hunk. So C will return true iff the object is not reset. Actually, C returns the object's new position, which is a number between 1 and the number of hunks (inclusive), or returns a false value. =item C C is almost identical to C; it moves to the $Nth previous hunk. On a 'reset' object, C [and C] move to the last hunk. The position returned by C is relative to the I of the hunks; -1 for the last hunk, -2 for the second-to-last, etc. =item C $diff->Reset(); # Reset the object's position $diff->Reset($pos); # Move to the specified hunk $diff->Reset(1); # Move to the first hunk $diff->Reset(-1); # Move to the last hunk C returns the object, so, for example, you could use C<< $diff->Reset()->Next(-1) >> to get the number of hunks. =item C $copy = $diff->Copy( $newPos, $newBase ); C returns a copy of the object. The copy and the orignal object share most of their data, so making copies takes very little memory. The copy maintains its own position (separate from the original), which is the main purpose of copies. It also maintains its own base. By default, the copy's position starts out the same as the original object's position. But C takes an optional first argument to set the new position, so the following three snippets are equivalent: $copy = $diff->Copy($pos); $copy = $diff->Copy(); $copy->Reset($pos); $copy = $diff->Copy()->Reset($pos); C takes an optional second argument to set the base for the copy. If you wish to change the base of the copy but leave the position the same as in the original, here are two equivalent ways: $copy = $diff->Copy(); $copy->Base( 0 ); $copy = $diff->Copy(undef,0); Here are two equivalent way to get a "reset" copy: $copy = $diff->Copy(0); $copy = $diff->Copy()->Reset(); =item C $bits = $obj->Diff(); C returns a true value iff the current hunk contains items that are different between the two sequences. It actually returns one of the follow 4 values: =over 4 =item 3 C<3==(1|2)>. This hunk contains items from @seq1 and the items from @seq2 that should replace them. Both sequence 1 and 2 contain changed items so both the 1 and 2 bits are set. =item 2 This hunk only contains items from @seq2 that should be inserted (not items from @seq1). Only sequence 2 contains changed items so only the 2 bit is set. =item 1 This hunk only contains items from @seq1 that should be deleted (not items from @seq2). Only sequence 1 contains changed items so only the 1 bit is set. =item 0 This means that the items in this hunk are the same in both sequences. Neither sequence 1 nor 2 contain changed items so neither the 1 nor the 2 bits are set. =back =item C C returns a true value iff the current hunk contains items that are the same in both sequences. It actually returns the list of items if they are the same or an emty list if they aren't. In a scalar context, it returns the size of the list. =item C $count = $diff->Items(2); @items = $diff->Items($seqNum); C returns the (number of) items from the specified sequence that are part of the current hunk. If the current hunk contains only insertions, then C<< $diff->Items(1) >> will return an empty list (0 in a scalar conext). If the current hunk contains only deletions, then C<< $diff->Items(2) >> will return an empty list (0 in a scalar conext). If the hunk contains replacements, then both C<< $diff->Items(1) >> and C<< $diff->Items(2) >> will return different, non-empty lists. Otherwise, the hunk contains identical items and all of the following will return the same lists: @items = $diff->Items(1); @items = $diff->Items(2); @items = $diff->Same(); =item C $count = $diff->Range( $seqNum ); @indices = $diff->Range( $seqNum ); @indices = $diff->Range( $seqNum, $base ); C is like C except that it returns a list of I to the items rather than the items themselves. By default, the index of the first item (in each sequence) is 0 but this can be changed by calling the C method. So, by default, the following two snippets return the same lists: @list = $diff->Items(2); @list = @seq2[ $diff->Range(2) ]; You can also specify the base to use as the second argument. So the following two snippets I return the same lists: @list = $diff->Items(1); @list = @seq1[ $diff->Range(1,0) ]; =item C $curBase = $diff->Base(); $oldBase = $diff->Base($newBase); C sets and/or returns the current base (usually 0 or 1) that is used when you request range information. The base defaults to 0 so that range information is returned as array indices. You can set the base to 1 if you want to report traditional line numbers instead. =item C $min1 = $diff->Min(1); $min = $diff->Min( $seqNum, $base ); C returns the first value that C would return (given the same arguments) or returns C if C would return an empty list. =item C C returns the last value that C would return or C. =item C ( $n, $x, $r ) = $diff->Get(qw( min1 max1 range1 )); @values = $diff->Get(qw( 0min2 1max2 range2 same base )); C returns one or more scalar values. You pass in a list of the names of the values you want returned. Each name must match one of the following regexes: /^(-?\d+)?(min|max)[12]$/i /^(range[12]|same|diff|base)$/i The 1 or 2 after a name says which sequence you want the information for (and where allowed, it is required). The optional number before "min" or "max" is the base to use. So the following equalities hold: $diff->Get('min1') == $diff->Min(1) $diff->Get('0min2') == $diff->Min(2,0) Using C in a scalar context when you've passed in more than one name is a fatal error (C is called). =back =head2 C Given a reference to a list of items, C returns a reference to a hash which can be used when comparing this sequence to other sequences with C or C. $prep = prepare( \@seq1 ); for $i ( 0 .. 10_000 ) { @lcs = LCS( $prep, $seq[$i] ); # do something useful with @lcs } C may be passed an optional third parameter; this is a CODE reference to a key generation function. See L. $prep = prepare( \@seq1, \&keyGen ); for $i ( 0 .. 10_000 ) { @lcs = LCS( $seq[$i], $prep, \&keyGen ); # do something useful with @lcs } Using C provides a performance gain of about 50% when calling LCS many times compared with not preparing. =head2 C @diffs = diff( \@seq1, \@seq2 ); $diffs_ref = diff( \@seq1, \@seq2 ); C computes the smallest set of additions and deletions necessary to turn the first sequence into the second, and returns a description of these changes. The description is a list of I; each hunk represents a contiguous section of items which should be added, deleted, or replaced. (Hunks containing unchanged items are not included.) The return value of C is a list of hunks, or, in scalar context, a reference to such a list. If there are no differences, the list will be empty. Here is an example. Calling C for the following two sequences: a b c e h j l m n p b c d e f j k l m r s t would produce the following list: ( [ [ '-', 0, 'a' ] ], [ [ '+', 2, 'd' ] ], [ [ '-', 4, 'h' ], [ '+', 4, 'f' ] ], [ [ '+', 6, 'k' ] ], [ [ '-', 8, 'n' ], [ '-', 9, 'p' ], [ '+', 9, 'r' ], [ '+', 10, 's' ], [ '+', 11, 't' ] ], ) There are five hunks here. The first hunk says that the C at position 0 of the first sequence should be deleted (C<->). The second hunk says that the C at position 2 of the second sequence should be inserted (C<+>). The third hunk says that the C at position 4 of the first sequence should be removed and replaced with the C from position 4 of the second sequence. And so on. C may be passed an optional third parameter; this is a CODE reference to a key generation function. See L. Additional parameters, if any, will be passed to the key generation routine. =head2 C @sdiffs = sdiff( \@seq1, \@seq2 ); $sdiffs_ref = sdiff( \@seq1, \@seq2 ); C computes all necessary components to show two sequences and their minimized differences side by side, just like the Unix-utility I does: same same before | after old < - - > new It returns a list of array refs, each pointing to an array of display instructions. In scalar context it returns a reference to such a list. If there are no differences, the list will have one entry per item, each indicating that the item was unchanged. Display instructions consist of three elements: A modifier indicator (C<+>: Element added, C<->: Element removed, C: Element unmodified, C: Element changed) and the value of the old and new elements, to be displayed side-by-side. An C of the following two sequences: a b c e h j l m n p b c d e f j k l m r s t results in ( [ '-', 'a', '' ], [ 'u', 'b', 'b' ], [ 'u', 'c', 'c' ], [ '+', '', 'd' ], [ 'u', 'e', 'e' ], [ 'c', 'h', 'f' ], [ 'u', 'j', 'j' ], [ '+', '', 'k' ], [ 'u', 'l', 'l' ], [ 'u', 'm', 'm' ], [ 'c', 'n', 'r' ], [ 'c', 'p', 's' ], [ '+', '', 't' ], ) C may be passed an optional third parameter; this is a CODE reference to a key generation function. See L. Additional parameters, if any, will be passed to the key generation routine. =head2 C C is much like C except it returns a much more compact description consisting of just one flat list of indices. An example helps explain the format: my @a = qw( a b c e h j l m n p ); my @b = qw( b c d e f j k l m r s t ); @cdiff = compact_diff( \@a, \@b ); # Returns: # @a @b @a @b # start start values values ( 0, 0, # = 0, 0, # a ! 1, 0, # b c = b c 3, 2, # ! d 3, 3, # e = e 4, 4, # f ! h 5, 5, # j = j 6, 6, # ! k 6, 7, # l m = l m 8, 9, # n p ! r s t 10, 12, # ); The 0th, 2nd, 4th, etc. entries are all indices into @seq1 (@a in the above example) indicating where a hunk begins. The 1st, 3rd, 5th, etc. entries are all indices into @seq2 (@b in the above example) indicating where the same hunk begins. So each pair of indices (except the last pair) describes where a hunk begins (in each sequence). Since each hunk must end at the item just before the item that starts the next hunk, the next pair of indices can be used to determine where the hunk ends. So, the first 4 entries (0..3) describe the first hunk. Entries 0 and 1 describe where the first hunk begins (and so are always both 0). Entries 2 and 3 describe where the next hunk begins, so subtracting 1 from each tells us where the first hunk ends. That is, the first hunk contains items C<$diff[0]> through C<$diff[2] - 1> of the first sequence and contains items C<$diff[1]> through C<$diff[3] - 1> of the second sequence. In other words, the first hunk consists of the following two lists of items: # 1st pair 2nd pair # of indices of indices @list1 = @a[ $cdiff[0] .. $cdiff[2]-1 ]; @list2 = @b[ $cdiff[1] .. $cdiff[3]-1 ]; # Hunk start Hunk end Note that the hunks will always alternate between those that are part of the LCS (those that contain unchanged items) and those that contain changes. This means that all we need to be told is whether the first hunk is a 'same' or 'diff' hunk and we can determine which of the other hunks contain 'same' items or 'diff' items. By convention, we always make the first hunk contain unchanged items. So the 1st, 3rd, 5th, etc. hunks (all odd-numbered hunks if you start counting from 1) all contain unchanged items. And the 2nd, 4th, 6th, etc. hunks (all even-numbered hunks if you start counting from 1) all contain changed items. Since @a and @b don't begin with the same value, the first hunk in our example is empty (otherwise we'd violate the above convention). Note that the first 4 index values in our example are all zero. Plug these values into our previous code block and we get: @hunk1a = @a[ 0 .. 0-1 ]; @hunk1b = @b[ 0 .. 0-1 ]; And C<0..-1> returns the empty list. Move down one pair of indices (2..5) and we get the offset ranges for the second hunk, which contains changed items. Since C<@diff[2..5]> contains (0,0,1,0) in our example, the second hunk consists of these two lists of items: @hunk2a = @a[ $cdiff[2] .. $cdiff[4]-1 ]; @hunk2b = @b[ $cdiff[3] .. $cdiff[5]-1 ]; # or @hunk2a = @a[ 0 .. 1-1 ]; @hunk2b = @b[ 0 .. 0-1 ]; # or @hunk2a = @a[ 0 .. 0 ]; @hunk2b = @b[ 0 .. -1 ]; # or @hunk2a = ( 'a' ); @hunk2b = ( ); That is, we would delete item 0 ('a') from @a. Since C<@diff[4..7]> contains (1,0,3,2) in our example, the third hunk consists of these two lists of items: @hunk3a = @a[ $cdiff[4] .. $cdiff[6]-1 ]; @hunk3a = @b[ $cdiff[5] .. $cdiff[7]-1 ]; # or @hunk3a = @a[ 1 .. 3-1 ]; @hunk3a = @b[ 0 .. 2-1 ]; # or @hunk3a = @a[ 1 .. 2 ]; @hunk3a = @b[ 0 .. 1 ]; # or @hunk3a = qw( b c ); @hunk3a = qw( b c ); Note that this third hunk contains unchanged items as our convention demands. You can continue this process until you reach the last two indices, which will always be the number of items in each sequence. This is required so that subtracting one from each will give you the indices to the last items in each sequence. =head2 C C used to be the most general facility provided by this module (the new OO interface is more powerful and much easier to use). Imagine that there are two arrows. Arrow A points to an element of sequence A, and arrow B points to an element of the sequence B. Initially, the arrows point to the first elements of the respective sequences. C will advance the arrows through the sequences one element at a time, calling an appropriate user-specified callback function before each advance. It willadvance the arrows in such a way that if there are equal elements C<$A[$i]> and C<$B[$j]> which are equal and which are part of the LCS, there will be some moment during the execution of C when arrow A is pointing to C<$A[$i]> and arrow B is pointing to C<$B[$j]>. When this happens, C will call the C callback function and then it will advance both arrows. Otherwise, one of the arrows is pointing to an element of its sequence that is not part of the LCS. C will advance that arrow and will call the C or the C callback, depending on which arrow it advanced. If both arrows point to elements that are not part of the LCS, then C will advance one of them and call the appropriate callback, but it is not specified which it will call. The arguments to C are the two sequences to traverse, and a hash which specifies the callback functions, like this: traverse_sequences( \@seq1, \@seq2, { MATCH => $callback_1, DISCARD_A => $callback_2, DISCARD_B => $callback_3, } ); Callbacks for MATCH, DISCARD_A, and DISCARD_B are invoked with at least the indices of the two arrows as their arguments. They are not expected to return any values. If a callback is omitted from the table, it is not called. Callbacks for A_FINISHED and B_FINISHED are invoked with at least the corresponding index in A or B. If arrow A reaches the end of its sequence, before arrow B does, C will call the C callback when it advances arrow B, if there is such a function; if not it will call C instead. Similarly if arrow B finishes first. C returns when both arrows are at the ends of their respective sequences. It returns true on success and false on failure. At present there is no way to fail. C may be passed an optional fourth parameter; this is a CODE reference to a key generation function. See L. Additional parameters, if any, will be passed to the key generation function. If you want to pass additional parameters to your callbacks, but don't need a custom key generation function, you can get the default by passing undef: traverse_sequences( \@seq1, \@seq2, { MATCH => $callback_1, DISCARD_A => $callback_2, DISCARD_B => $callback_3, }, undef, # default key-gen $myArgument1, $myArgument2, $myArgument3, ); C does not have a useful return value; you are expected to plug in the appropriate behavior with the callback functions. =head2 C C is an alternative to C. It uses a different algorithm to iterate through the entries in the computed LCS. Instead of sticking to one side and showing element changes as insertions and deletions only, it will jump back and forth between the two sequences and report I occurring as deletions on one side followed immediatly by an insertion on the other side. In addition to the C, C, and C callbacks supported by C, C supports a C callback indicating that one element got C by another: traverse_balanced( \@seq1, \@seq2, { MATCH => $callback_1, DISCARD_A => $callback_2, DISCARD_B => $callback_3, CHANGE => $callback_4, } ); If no C callback is specified, C will map C events to C and C actions, therefore resulting in a similar behaviour as C with different order of events. C might be a bit slower than C, noticable only while processing huge amounts of data. The C function of this module is implemented as call to C. C does not have a useful return value; you are expected to plug in the appropriate behavior with the callback functions. =head1 KEY GENERATION FUNCTIONS Most of the functions accept an optional extra parameter. This is a CODE reference to a key generating (hashing) function that should return a string that uniquely identifies a given element. It should be the case that if two elements are to be considered equal, their keys should be the same (and the other way around). If no key generation function is provided, the key will be the element as a string. By default, comparisons will use "eq" and elements will be turned into keys using the default stringizing operator '""'. Where this is important is when you're comparing something other than strings. If it is the case that you have multiple different objects that should be considered to be equal, you should supply a key generation function. Otherwise, you have to make sure that your arrays contain unique references. For instance, consider this example: package Person; sub new { my $package = shift; return bless { name => '', ssn => '', @_ }, $package; } sub clone { my $old = shift; my $new = bless { %$old }, ref($old); } sub hash { return shift()->{'ssn'}; } my $person1 = Person->new( name => 'Joe', ssn => '123-45-6789' ); my $person2 = Person->new( name => 'Mary', ssn => '123-47-0000' ); my $person3 = Person->new( name => 'Pete', ssn => '999-45-2222' ); my $person4 = Person->new( name => 'Peggy', ssn => '123-45-9999' ); my $person5 = Person->new( name => 'Frank', ssn => '000-45-9999' ); If you did this: my $array1 = [ $person1, $person2, $person4 ]; my $array2 = [ $person1, $person3, $person4, $person5 ]; Algorithm::Diff::diff( $array1, $array2 ); everything would work out OK (each of the objects would be converted into a string like "Person=HASH(0x82425b0)" for comparison). But if you did this: my $array1 = [ $person1, $person2, $person4 ]; my $array2 = [ $person1, $person3, $person4->clone(), $person5 ]; Algorithm::Diff::diff( $array1, $array2 ); $person4 and $person4->clone() (which have the same name and SSN) would be seen as different objects. If you wanted them to be considered equivalent, you would have to pass in a key generation function: my $array1 = [ $person1, $person2, $person4 ]; my $array2 = [ $person1, $person3, $person4->clone(), $person5 ]; Algorithm::Diff::diff( $array1, $array2, \&Person::hash ); This would use the 'ssn' field in each Person as a comparison key, and so would consider $person4 and $person4->clone() as equal. You may also pass additional parameters to the key generation function if you wish. =head1 ERROR CHECKING If you pass these routines a non-reference and they expect a reference, they will die with a message. =head1 AUTHOR This version released by Tye McQueen (http://perlmonks.org/?node=tye). =head1 LICENSE Parts Copyright (c) 2000-2004 Ned Konz. All rights reserved. Parts by Tye McQueen. This program is free software; you can redistribute it and/or modify it under the same terms as Perl. =head1 MAILING LIST Mark-Jason still maintains a mailing list. To join a low-volume mailing list for announcements related to diff and Algorithm::Diff, send an empty mail message to mjd-perl-diff-request@plover.com. =head1 CREDITS Versions through 0.59 (and much of this documentation) were written by: Mark-Jason Dominus, mjd-perl-diff@plover.com This version borrows some documentation and routine names from Mark-Jason's, but Diff.pm's code was completely replaced. This code was adapted from the Smalltalk code of Mario Wolczko , which is available at ftp://st.cs.uiuc.edu/pub/Smalltalk/MANCHESTER/manchester/4.0/diff.st C and C were written by Mike Schilli . The algorithm is that described in I, CACM, vol.20, no.5, pp.350-353, May 1977, with a few minor improvements to improve the speed. Much work was done by Ned Konz (perl@bike-nomad.com). The OO interface and some other changes are by Tye McQueen. =cut syncevolution_1.4/test/Algorithm/README000066400000000000000000000064101230021373600201440ustar00rootroot00000000000000This is a module for computing the difference between two files, two strings, or any other two lists of things. It uses an intelligent algorithm similar to (or identical to) the one used by the Unix "diff" program. It is guaranteed to find the *smallest possible* set of differences. This package contains a few parts. Algorithm::Diff is the module that contains several interfaces for which computing the differences betwen two lists. The several "diff" programs also included in this package use Algorithm::Diff to find the differences and then they format the output. Algorithm::Diff also includes some other useful functions such as "LCS", which computes the longest common subsequence of two lists. A::D is suitable for many uses. You can use it for finding the smallest set of differences between two strings, or for computing the most efficient way to update the screen if you were replacing "curses". Algorithm::DiffOld is a previous version of the module which is included primarilly for those wanting to use a custom comparison function rather than a key generating function (and who don't mind the significant performance penalty of perhaps 20-fold). diff.pl implements a "diff" in Perl that is as simple as (was previously) possible so that you can see how it works. The output format is not compatible with regular "diff". It needs to be reimplemented using the OO interface to greatly simplify the code. diffnew.pl implements a "diff" in Perl with full bells and whistles. By Mark-Jason, with code from cdiff.pl included. cdiff.pl implements "diff" that generates real context diffs in either traditional format or GNU unified format. Original contextless "context" diff supplied by Christian Murphy. Modifications to make it into a real full-featured diff with -c and -u options supplied by Amir D. Karger. Yes, you can use this program to generate patches. OTHER RESOURCES "Longest Common Subsequences", at http://www.ics.uci.edu/~eppstein/161/960229.html This code was adapted from the Smalltalk code of Mario Wolczko , which is available at ftp://st.cs.uiuc.edu/pub/Smalltalk/MANCHESTER/manchester/4.0/diff.st THANKS SECTION Thanks to Ned Konz's for rewriting the module to greatly improve performance, for maintaining it over the years, and for readilly handing it over to me so I could plod along with my improvements. (From Ned Konz's earlier versions): Thanks to Mark-Jason Dominus for doing the original Perl version and maintaining it over the last couple of years. Mark-Jason has been a huge contributor to the Perl community and CPAN; it's because of people like him that Perl has become a success. Thanks to Mario Wolczko for writing and making publicly available his Smalltalk version of diff, which this Perl version is heavily based on. Thanks to Mike Schilli for writing sdiff and traverse_balanced and making them available for the Algorithm::Diff distribution. (From Mark-Jason Dominus' earlier versions): Huge thanks to Amir Karger for adding full context diff supprt to "cdiff.pl", and then for waiting patiently for five months while I let it sit in a closet and didn't release it. Thank you thank you thank you, Amir! Thanks to Christian Murphy for adding the first context diff format support to "cdiff.pl". syncevolution_1.4/test/COPYING000066400000000000000000000025031230021373600163700ustar00rootroot00000000000000The following files were written exclusively by Patrick Ohly: ClientTest.cpp ClientTest.h client-test.cpp client-test-main.cpp test.h synccompare.pl They were contributed to the Funambol C++ client library under the "docs/Sync4jContribution.pdf" agreement. They were maintained there by Patrick and on February 17th 2009 copied back to SyncEvolution, without any commits by other authors except for the license and copyright changes applied by Funambol. On March 25 2009 they were relicensed by Patrick Ohly, executing the rights granted by the contributor agreement. ------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA syncevolution_1.4/test/ClientTest.cpp000066400000000000000000012007411230021373600201240ustar00rootroot00000000000000/* * Copyright (C) 2008 Funambol, Inc. * Copyright (C) 2008-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ /** @cond API */ /** @addtogroup ClientTest */ /** @{ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifdef ENABLE_INTEGRATION_TESTS #include "ClientTest.h" #include "test.h" #include "ClientTestAssert.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace CppUnit { /** * behaves like an int and can be compared against one in ASSERT_EQUAL, * but includes the item list when being printed */ struct ItemCount { SyncEvo::SyncSourceChanges::Items_t m_items; ItemCount() {} ItemCount(const SyncEvo::SyncSourceChanges::Items_t &items) : m_items(items) {} int size() const { return m_items.size(); } operator int () const { return size(); } }; static std::ostream &operator << (ostream &out, const ItemCount &count) { out << count.size() << " ( "; BOOST_FOREACH(const std::string &id, count.m_items) { out << id << " "; } out << ")"; return out; } template<> struct assertion_traits { template static bool equal(const E &expected, const ItemCount &count) { return expected == count; } static std::string toString(const ItemCount &count) { std::ostringstream out; out << count; return out.str(); } }; /** comparison between arbitrary type A and B */ template void assertEquals(const A& expected, const B& actual, SourceLine sourceLine, const std::string &message) { if (!assertion_traits::equal(expected,actual)) { Asserter::failNotEqual(assertion_traits::toString(expected), assertion_traits::toString(actual), sourceLine, message); } } } SE_BEGIN_CXX static set cleanupSet; /** * true when running as server, * relevant for sources instantiated by us * and testConversion, which does not work in * server mode (Synthesis engine not in the right * state when we try to run the test) */ static bool isServerMode() { const char *serverMode = getenv("CLIENT_TEST_MODE"); return serverMode && !strcmp(serverMode, "server"); } /** * CLIENT_TEST_SERVER env variable or "" if unset */ std::string currentServer() { const char *tmp = getenv("CLIENT_TEST_SERVER"); return tmp ? tmp : ""; } /** * CLIENT_TEST_NUM_ITEMS env variable or 100 */ int defNumItems() { char *numitems = getenv("CLIENT_TEST_NUM_ITEMS"); return numitems ? atoi(numitems) : 100; } static SyncMode RefreshFromPeerMode() { return isServerMode() ? SYNC_REFRESH_FROM_CLIENT : SYNC_REFRESH_FROM_SERVER; } static SyncMode RefreshFromLocalMode() { return isServerMode() ? SYNC_REFRESH_FROM_SERVER : SYNC_REFRESH_FROM_CLIENT; } static SyncMode OneWayFromPeerMode() { return isServerMode() ? SYNC_ONE_WAY_FROM_CLIENT : SYNC_ONE_WAY_FROM_SERVER; } static SyncMode OneWayFromLocalMode() { return isServerMode() ? SYNC_ONE_WAY_FROM_SERVER : SYNC_ONE_WAY_FROM_CLIENT; } /** * remove a certain property from buffer, return removed line */ static string stripProperty(std::string &data, const std::string &prop) { std::string res; size_t start = data.find(prop); if (start != data.npos) { size_t end = data.find('\n', start); if (end != data.npos) { size_t len = end + 1 - start; res = data.substr(start, len); data.erase(start, len); } } return res; } /** * insert a property (must include line end) before the end of an item */ static void insertProperty(std::string &data, const std::string &prop, const std::string &endProp = "END:VEVENT") { size_t pos = data.find(endProp); data.insert(pos, prop); } /** * remove parameter in all properties */ static void stripParameters(std::string &data, const std::string ¶m) { while (true) { size_t start = data.find(";" + param + "="); if (start == data.npos) { break; } size_t end = data.find_first_of(";:", start + 1); if (end == data.npos) { break; } data.erase(start, end - start); } } static void stripComponent(std::string &data, const std::string &comp) { size_t start = data.find("BEGIN:" + comp); if (start != data.npos) { size_t end = data.find("END:" + comp); if (end != data.npos) { end = data.find('\n', end); if (end != data.npos) { data.erase(start, end + 1 - start); } } } } /** * Using this pointer automates the open()/beginSync()/endSync()/close() * life cycle: it automatically calls these functions when a new * pointer is assigned or deleted. * * Anchors are stored globally in a hash which uses the tracking node * name as key. This name happens to be the unique file path that * is created for each source (see TestEvolution::createSource() and * SyncConfig::getSyncSourceNodes()). */ class TestingSyncSourcePtr : public std::auto_ptr { typedef std::auto_ptr base_t; bool m_active; static StringMap m_anchors; static std::string m_testName; public: TestingSyncSourcePtr() : m_active(false) {} TestingSyncSourcePtr(const TestingSyncSourcePtr &other) : m_active(false) { CPPUNIT_ASSERT(!other.get()); } ~TestingSyncSourcePtr() { // We can skip the full cleanup if the test has already failed. // Also avoids letting an exception escape from the // destructor during exception handling (= program aborted!) // when the endSync() call invoked by reset() needs to // report a proble. CT_ASSERT_NO_THROW() itself catches that // exception, but then forwards it, and thus does not // prevent the exception from escaping. if (!std::uncaught_exception()) { CT_ASSERT_NO_THROW(reset(NULL)); } } enum Flags { SLOW, /**< erase anchor, start accessing database from scratch */ INCREMENTAL /**< allow source to do incremental data read */ }; void reset(TestingSyncSource *source = NULL, Flags flags = INCREMENTAL) { if (get() && m_active) { stopAccess(); } // avoid deleting the instance that we are setting // (shouldn't happen) if (get() != source) { base_t::reset(source); } if (source) { startAccess(flags); } } /** * done automatically as part of reset(), only to be called * after an explicit stopAccess() */ void startAccess(Flags flags = INCREMENTAL) { CT_ASSERT(get()); CT_ASSERT(!m_active); int delay = atoi(getEnv("CLIENT_TEST_SOURCE_DELAY", "0")); if (delay) { CLIENT_TEST_LOG("CLIENT_TEST_SOURCE_DELAY: sleep for %d seconds", delay); sleep(delay); } CT_ASSERT_NO_THROW(get()->open()); string node = get()->getTrackingNode()->getName(); string anchor = m_anchors[node]; if (flags == SLOW) { anchor = ""; } get()->beginSync(anchor, ""); if (isServerMode()) { CT_ASSERT_NO_THROW(get()->enableServerMode()); } // the replaced m_endSession callback was invoked here, // which shouldn't have been necessary - not calling // m_endDataWrite post-signal at the moment // CT_ASSERT_NO_THROW(get()->getOperations().m_endDataWrite.getPostSignal()()); m_active = true; } /** * finish change tracking, source must be activated again * with startAccess() */ void stopAccess() { CT_ASSERT(get()); CT_ASSERT(m_active); m_active = false; char *dummy = const_cast("testing-source"); CT_ASSERT_NO_THROW(get()->getOperations().m_endDataWrite.getPostSignal()(*get(), OPERATION_FINISHED, sysync::LOCERR_OK, true, &dummy)); string node = get()->getTrackingNode()->getName(); string anchor; CT_ASSERT_NO_THROW(anchor = get()->endSync(true)); m_anchors[node] = anchor; CT_ASSERT_NO_THROW(get()->close()); } }; StringMap TestingSyncSourcePtr::m_anchors; std::string TestingSyncSourcePtr::m_testName; bool SyncOptions::defaultWBXML() { const char *t = getenv("CLIENT_TEST_XML"); if (t && (!strcmp(t, "1") || !strcasecmp(t, "t"))) { // use XML return false; } else { return true; } } std::list listItemsOfType(TestingSyncSource *source, int state) { std::list res; BOOST_FOREACH(const string &luid, source->getItems(SyncSourceChanges::State(state))) { res.push_back(luid); } return res; } static std::list listNewItems(TestingSyncSource *source) { return listItemsOfType(source, SyncSourceChanges::NEW); } static std::list listUpdatedItems(TestingSyncSource *source) { return listItemsOfType(source, SyncSourceChanges::UPDATED); } static std::list listDeletedItems(TestingSyncSource *source) { return listItemsOfType(source, SyncSourceChanges::DELETED); } static std::list listItems(TestingSyncSource *source) { return listItemsOfType(source, SyncSourceChanges::ANY); } static CppUnit::ItemCount countItemsOfType(TestingSyncSource *source, int type) { return source->getItems(SyncSourceChanges::State(type)); } static CppUnit::ItemCount countNewItems(TestingSyncSource *source) { return countItemsOfType(source, SyncSourceChanges::NEW); } static CppUnit::ItemCount countUpdatedItems(TestingSyncSource *source) { return countItemsOfType(source, SyncSourceChanges::UPDATED); } static CppUnit::ItemCount countDeletedItems(TestingSyncSource *source) { return countItemsOfType(source, SyncSourceChanges::DELETED); } static CppUnit::ItemCount countItems(TestingSyncSource *source) { return countItemsOfType(source, SyncSourceChanges::ANY); } /** insert new item, return LUID */ static std::string importItem(TestingSyncSource *source, const ClientTestConfig &config, std::string &data) { CT_ASSERT(source); if (data.size()) { SyncSourceRaw::InsertItemResult res; SOURCE_ASSERT_NO_FAILURE(source, res = source->insertItemRaw("", config.m_mangleItem(data, false, ""))); CT_ASSERT(!res.m_luid.empty()); return res.m_luid; } else { return ""; } } /** overwrite existing item */ static void updateItem(TestingSyncSource *source, std::string &data, const std::string &luid) { CT_ASSERT(source); CT_ASSERT(!data.empty()); CT_ASSERT(!luid.empty()); SyncSourceRaw::InsertItemResult res; SOURCE_ASSERT_NO_FAILURE(source, res = source->insertItemRaw(luid, data)); CT_ASSERT_EQUAL(luid, res.m_luid); } /** remove existing item */ static void removeItem(TestingSyncSource *source, const std::string &luid) { CT_ASSERT(source); CT_ASSERT(!luid.empty()); SOURCE_ASSERT_NO_FAILURE(source, source->deleteItem(luid)); } static void restoreStorage(const ClientTest::Config &config, ClientTest &client) { } static void backupStorage(const ClientTest::Config &config, ClientTest &client) { } /** adds the supported tests to the instance itself */ void LocalTests::addTests() { if (config.m_createSourceA) { ADD_TEST(LocalTests, testOpen); ADD_TEST(LocalTests, testIterateTwice); ADD_TEST(LocalTests, testDelete404); ADD_TEST(LocalTests, testReadItem404); if (!config.m_insertItem.empty()) { ADD_TEST(LocalTests, testSimpleInsert); ADD_TEST(LocalTests, testLocalDeleteAll); ADD_TEST(LocalTests, testComplexInsert); if (config.m_insertItem.find("\nUID:") != std::string::npos) { ADD_TEST(LocalTests, testInsertTwice); } if (!config.m_updateItem.empty()) { ADD_TEST(LocalTests, testLocalUpdate); if (config.m_createSourceB) { ADD_TEST(LocalTests, testChanges); ADD_TEST(LocalTests, testChangesMultiCycles); if (!config.m_linkedSources.empty()) { ADD_TEST(LocalTests, testLinkedSources); } } } if (config.m_import && config.m_dump && config.m_compare && !config.m_testcases.empty()) { ADD_TEST(LocalTests, testImport); ADD_TEST(LocalTests, testImportDelete); if (!config.m_essentialProperties.empty()) { ADD_TEST(LocalTests, testRemoveProperties); } } if (!config.m_templateItem.empty()) { ADD_TEST(LocalTests, testManyChanges); } // create a sub-suite for each set of linked items for (int i = 0; i < (int)config.m_linkedItems.size(); i++) { const ClientTestConfig::LinkedItems_t &items = config.m_linkedItems[i]; CppUnit::TestSuite *linked = new CppUnit::TestSuite(getName() + "::LinkedItems" + items.m_name); ADD_TEST_TO_SUITE(linked, LocalTests, testLinkedItemsParent); if (config.m_linkedItemsRelaxedSemantic) { ADD_TEST_TO_SUITE(linked, LocalTests, testLinkedItemsChild); } ADD_TEST_TO_SUITE(linked, LocalTests, testLinkedItemsParentChild); if (items[1].find("RECURRENCE-ID") != items[1].npos) { ADD_TEST_TO_SUITE(linked, LocalTests, testLinkedItemsInsertBothUpdateChildNoIDs); } if (config.m_linkedItemsRelaxedSemantic) { ADD_TEST_TO_SUITE(linked, LocalTests, testLinkedItemsChildParent); } if (config.m_linkedItemsRelaxedSemantic) { ADD_TEST_TO_SUITE(linked, LocalTests, testLinkedItemsChildChangesParent); } if (config.m_linkedItemsRelaxedSemantic) { ADD_TEST_TO_SUITE(linked, LocalTests, testLinkedItemsRemoveParentFirst); } ADD_TEST_TO_SUITE(linked, LocalTests, testLinkedItemsRemoveNormal); if (config.m_sourceKnowsItemSemantic) { ADD_TEST_TO_SUITE(linked, LocalTests, testLinkedItemsInsertParentTwice); if (config.m_linkedItemsRelaxedSemantic) { ADD_TEST_TO_SUITE(linked, LocalTests, testLinkedItemsInsertChildTwice); } } ADD_TEST_TO_SUITE(linked, LocalTests, testLinkedItemsParentUpdate); if (config.m_linkedItemsRelaxedSemantic) { ADD_TEST_TO_SUITE(linked, LocalTests, testLinkedItemsUpdateChild); if (items[1].find("RECURRENCE-ID") != items[1].npos) { ADD_TEST_TO_SUITE(linked, LocalTests, testLinkedItemsUpdateChildNoIDs); } } ADD_TEST_TO_SUITE(linked, LocalTests, testLinkedItemsInsertBothUpdateChild); ADD_TEST_TO_SUITE(linked, LocalTests, testLinkedItemsInsertBothUpdateParent); // tests independent of data, only add to default item set if (i == 0) { ADD_TEST_TO_SUITE(linked, LocalTests, testLinkedItemsSingle404); ADD_TEST_TO_SUITE(linked, LocalTests, testLinkedItemsMany404); } addTest(linked); } // Create a sub-suite for each set of linked items. // items.size() can be fairly large for these tests, // so avoid testing all possible combinations. BOOST_FOREACH(const ClientTestConfig::LinkedItems_t &items, config.m_linkedItemsSubset) { CppUnit::TestSuite *linked = new CppUnit::TestSuite(getName() + "::LinkedItems" + items.m_name); int stride = (items.size() + 4) / 5; for (int start = 0; (size_t)start < items.size(); start += stride ) { for (int skip = 0; !skip || (size_t)(start + skip + 1) < items.size(); skip++) { ADD_TEST_TO_SUITE_SUFFIX(linked, LocalTests, testSubset, StringPrintf("Start%dSkip%d", start, skip)); } // add a test which uses start, start + 1 and last item // if that leads to a gap (EXDATE) if (start > 0 && items.size() - start > 3) { ADD_TEST_TO_SUITE_SUFFIX(linked, LocalTests, testSubset, StringPrintf("Start%dExdate", start)); } } addTest(linked); } } } } std::string LocalTests::insert(CreateSource createSource, const std::string &data, bool relaxed, std::string *inserted, const std::string &uniqueUIDSuffix) { restoreStorage(config, client); // create source TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSource())); // count number of already existing items int numItems = 0; CT_ASSERT_NO_THROW(numItems = countItems(source.get())); SyncSourceRaw::InsertItemResult res; std::string mangled = config.m_mangleItem(data, false, uniqueUIDSuffix); if (inserted) { *inserted = mangled; } SOURCE_ASSERT_NO_FAILURE(source.get(), res = source->insertItemRaw("", mangled)); CT_ASSERT(!res.m_luid.empty()); bool updated = false; if (res.m_state == ITEM_NEEDS_MERGE) { // conflict detected, overwrite existing item as done in the past std::string luid = res.m_luid; SOURCE_ASSERT_NO_FAILURE(source.get(), res = source->insertItemRaw(luid, mangled)); CT_ASSERT_EQUAL(luid, res.m_luid); CT_ASSERT(res.m_state == ITEM_OKAY); updated = true; } // delete source again CT_ASSERT_NO_THROW(source.reset()); if (!relaxed) { // two possible results: // - a new item was added // - the item was matched against an existing one SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSource())); CT_ASSERT_EQUAL(numItems + ((res.m_state == ITEM_REPLACED || res.m_state == ITEM_MERGED || updated) ? 0 : 1), countItems(source.get())); CT_ASSERT_EQUAL(0, countNewItems(source.get())); CT_ASSERT_EQUAL(0, countUpdatedItems(source.get())); CT_ASSERT_EQUAL(0, countDeletedItems(source.get())); } backupStorage(config, client); return res.m_luid; } /** deletes specific item locally via sync source */ static std::string updateItem(CreateSource createSource, const ClientTestConfig &config, const std::string &uid, const std::string &data, std::string *updated = NULL) { std::string newuid; CT_ASSERT(createSource.createSource); // create source TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSource())); // insert item SyncSourceRaw::InsertItemResult res; std::string mangled; CT_ASSERT_NO_THROW(mangled = config.m_mangleItem(data, true, "")); if (updated) { *updated = mangled; } SOURCE_ASSERT_NO_FAILURE(source.get(), res = source->insertItemRaw(uid, mangled.c_str())); SOURCE_ASSERT(source.get(), !res.m_luid.empty()); return res.m_luid; } /** updates specific item locally via sync source */ static void removeItem(CreateSource createSource, const std::string &luid) { CT_ASSERT(createSource.createSource); // create source TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSource())); // remove item SOURCE_ASSERT_NO_FAILURE(source.get(), source->deleteItem(luid)); } void LocalTests::update(CreateSource createSource, const std::string &data, bool check, const std::string &uniqueUIDSuffix) { CT_ASSERT(createSource.createSource); restoreStorage(config, client); // create source TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSource())); // get existing item, then update it SyncSourceChanges::Items_t::const_iterator it; SOURCE_ASSERT_NO_FAILURE(source.get(), it = source->getAllItems().begin()); CT_ASSERT(it != source->getAllItems().end()); string luid = *it; SyncSourceRaw::InsertItemResult res; std::string mangled = config.m_mangleItem(data, true, uniqueUIDSuffix); SOURCE_ASSERT_NO_FAILURE(source.get(), res = source->insertItemRaw(luid, mangled)); CT_ASSERT_NO_THROW(source.reset()); CT_ASSERT_EQUAL(luid, res.m_luid); CT_ASSERT_EQUAL(ITEM_OKAY, res.m_state); if (!check) { return; } // check that the right changes are reported when reopening the source SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSource())); CT_ASSERT_EQUAL(1, countItems(source.get())); CT_ASSERT_EQUAL(0, countNewItems(source.get())); CT_ASSERT_EQUAL(0, countUpdatedItems(source.get())); CT_ASSERT_EQUAL(0, countDeletedItems(source.get())); SOURCE_ASSERT_NO_FAILURE(source.get(), it = source->getAllItems().begin()); CT_ASSERT(it != source->getAllItems().end()); CT_ASSERT_EQUAL(luid, *it); backupStorage(config, client); } void LocalTests::update(CreateSource createSource, const std::string &data, const std::string &luid) { CT_ASSERT(createSource.createSource); restoreStorage(config, client); // create source TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSource())); // update it SOURCE_ASSERT_NO_FAILURE(source.get(), source->insertItemRaw(luid, config.m_mangleItem(data, true, ""))); backupStorage(config, client); } /** deletes all items locally via sync source */ void LocalTests::deleteAll(CreateSource createSource) { CT_ASSERT(createSource.createSource); restoreStorage(config, client); // create source TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSource())); // delete all items SOURCE_ASSERT_NO_FAILURE(source.get(), source->removeAllItems()); CT_ASSERT_NO_THROW(source.reset()); // check that all items are gone SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSource())); SOURCE_ASSERT_MESSAGE( "should be empty now", source.get(), countItems(source.get()) == 0); CT_ASSERT_EQUAL( 0, countNewItems(source.get()) ); CT_ASSERT_EQUAL( 0, countUpdatedItems(source.get()) ); CT_ASSERT_EQUAL( 0, countDeletedItems(source.get()) ); backupStorage(config, client); } /** deletes specific item locally via sync source */ static void deleteItem(CreateSource createSource, const std::string &uid) { CT_ASSERT(createSource.createSource); // create source TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSource())); // delete item SOURCE_ASSERT_NO_FAILURE(source.get(), source->deleteItem(uid)); } /** * takes two databases, exports them, * then compares them using synccompare * * @param refFile existing file with source reference items, NULL uses a dump of sync source A instead * @param copy a sync source which contains the copied items, begin/endSync will be called * @param raiseAssert raise assertion if comparison yields differences (defaults to true) */ bool LocalTests::compareDatabases(const char *refFile, TestingSyncSource ©, bool raiseAssert) { CT_ASSERT(config.m_dump); std::string sourceFile, copyFile; if (refFile) { sourceFile = refFile; } else { sourceFile = getCurrentTest() + ".A.test.dat"; simplifyFilename(sourceFile); TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA())); SOURCE_ASSERT_EQUAL(source.get(), 0, config.m_dump(client, *source.get(), sourceFile)); CT_ASSERT_NO_THROW(source.reset()); } copyFile = getCurrentTest() + ".B.test.dat"; simplifyFilename(copyFile); SOURCE_ASSERT_EQUAL(©, 0, config.m_dump(client, copy, copyFile)); bool equal = false; CT_ASSERT_NO_THROW(equal = config.m_compare(client, sourceFile, copyFile)); CT_ASSERT(!raiseAssert || equal); return equal; } /** * compare data in source with vararg list of std::string pointers, NULL terminated */ void LocalTests::compareDatabases(TestingSyncSource ©, ...) { std::string sourceFile = getCurrentTest() + ".ref.test.dat"; simplifyFilename(sourceFile); ofstream out(sourceFile.c_str()); va_list ap; va_start(ap, copy); std::string *item; while ((item = va_arg(ap, std::string *)) != NULL) { out << *item; } va_end(ap); out.close(); compareDatabases(sourceFile.c_str(), copy); } void LocalTests::compareDatabasesRef(TestingSyncSource ©, const std::list &items) { std::string sourceFile = getCurrentTest() + ".ref.test.dat"; simplifyFilename(sourceFile); ofstream out(sourceFile.c_str()); BOOST_FOREACH(const std::string &item, items) { out << item; } out.close(); compareDatabases(sourceFile.c_str(), copy); } std::string LocalTests::createItem(int item, const std::string &revision, int size) { std::string data = config.m_mangleItem(config.m_templateItem, false, ""); std::stringstream prefix; // string to be inserted at start of unique properties; // avoid adding white space (not sure whether it is valid for UID) prefix << std::setfill('0') << std::setw(3) << item << "-"; BOOST_FOREACH (std::string curProp, boost::tokenizer< boost::char_separator >(config.m_uniqueProperties, boost::char_separator(":"))) { std::string property; // property is expected to not start directly at the // beginning property = "\n"; property += curProp; property += ":"; size_t off = data.find(property); if (off != data.npos) { data.insert(off + property.size(), prefix.str()); } } boost::replace_all(data, "<>", prefix.str()); boost::replace_all(data, "<>", revision); if (size > 0 && (int)data.size() < size) { int additionalBytes = size - (int)data.size(); int added = 0; /* vCard 2.1 and vCal 1.0 need quoted-printable line breaks */ bool quoted = data.find("VERSION:1.0") != data.npos || data.find("VERSION:2.1") != data.npos; size_t toreplace = 1; CT_ASSERT(!config.m_sizeProperty.empty()); /* stuff the item so that it reaches at least that size */ size_t off = data.find(config.m_sizeProperty); CT_ASSERT(off != data.npos); std::stringstream stuffing; if (quoted) { stuffing << ";ENCODING=QUOTED-PRINTABLE:"; } else { stuffing << ":"; } // insert after the first line, it often acts as the summary if (data.find("BEGIN:VJOURNAL") != data.npos) { size_t start = data.find(":", off); CT_ASSERT( start != data.npos ); size_t eol = data.find("\\n", off); CT_ASSERT( eol != data.npos ); stuffing << data.substr(start + 1, eol - start + 1); toreplace += eol - start + 1; } while(added < additionalBytes) { int linelen = 0; while(added + 4 < additionalBytes && linelen < 60) { stuffing << 'x'; added++; linelen++; } // insert line breaks to allow folding if (quoted) { stuffing << "x=0D=0Ax"; added += 8; } else { stuffing << "x\\nx"; added += 4; } } off = data.find(":", off); data.replace(off, toreplace, stuffing.str()); } return data; } /** * insert artificial items, number of them 100 * unless passed explicitly * * @param createSource a factory for the sync source that is to be used * @param startIndex IDs are generated starting with this value * @param numItems number of items to be inserted if non-null, otherwise config.numItems is used * @param size minimum size for new items * @return LUIDs of all inserted items */ std::list LocalTests::insertManyItems(CreateSource createSource, int startIndex, int numItems, int size) { std::list luids; CT_ASSERT(!config.m_templateItem.empty()); restoreStorage(config, client); TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA())); CT_ASSERT(startIndex > 1 || !countItems(source.get())); int firstIndex = startIndex; if (firstIndex < 0) { firstIndex = 1; } int lastIndex = firstIndex + (numItems >= 1 ? numItems : defNumItems()) - 1; for (int item = firstIndex; item <= lastIndex; item++) { std::string data = createItem(item, "", size); luids.push_back(importItem(source.get(), config, data)); } backupStorage(config, client); return luids; } std::list LocalTests::insertManyItems(TestingSyncSource *source, int startIndex, int numItems, int size) { std::list luids; CT_ASSERT(!config.m_templateItem.empty()); CT_ASSERT(startIndex > 1 || !countItems(source)); int firstIndex = startIndex; if (firstIndex < 0) { firstIndex = 1; } int lastIndex = firstIndex + (numItems >= 1 ? numItems : defNumItems()) - 1; for (int item = firstIndex; item <= lastIndex; item++) { std::string data = createItem(item, "", size); luids.push_back(importItem(source, config, data)); } return luids; } void LocalTests::updateManyItems(CreateSource createSource, int startIndex, int numItems, int size, int revision, std::list &luids, int offset) { CT_ASSERT(!config.m_templateItem.empty()); restoreStorage(config, client); TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA())); int firstIndex = startIndex; if (firstIndex < 0) { firstIndex = 1; } int lastIndex = firstIndex + (numItems >= 1 ? numItems : defNumItems()) - 1; std::string revstring = StringPrintf("REVISION #%d", revision); std::list::const_iterator it = luids.begin(); for (int i = 0; i < offset && it != luids.end(); i++, ++it) {} for (int item = firstIndex; item <= lastIndex && it != luids.end(); item++, ++it) { std::string data = createItem(item, revstring, size); updateItem(source.get(), data, *it); } backupStorage(config, client); } void LocalTests::removeManyItems(CreateSource createSource, int numItems, std::list &luids, int offset) { restoreStorage(config, client); TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA())); std::list::const_iterator it = luids.begin(); for (int i = 0; i < offset && it != luids.end(); i++, ++it) {} for (int item = 0; item < numItems && it != luids.end(); item++, ++it) { removeItem(source.get(), *it); } backupStorage(config, client); } // update every single item in the database void LocalTests::updateData(CreateSource createSource) { // check additional requirements CT_ASSERT(config.m_update); TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSource())); BOOST_FOREACH(const string &luid, source->getAllItems()) { string item; CT_ASSERT_NO_THROW(source->readItemRaw(luid, item)); CT_ASSERT_NO_THROW(config.m_update(item)); CT_ASSERT_NO_THROW(source->insertItemRaw(luid, item)); } CT_ASSERT_NO_THROW(source.reset()); } // creating sync source void LocalTests::testOpen() { // check requirements CT_ASSERT(config.m_createSourceA); // Intentionally use the plain auto_ptr here and // call open directly. That way it is a bit more clear // what happens and where it fails, if it fails. std::auto_ptr source; CT_ASSERT_NO_THROW(source.reset(createSourceA())); // got a sync source? CT_ASSERT(source.get() != 0); // can it be opened? SOURCE_ASSERT_NO_FAILURE(source.get(), source->open()); // delete it CT_ASSERT_NO_THROW(source.reset()); } // restart scanning of items void LocalTests::testIterateTwice() { // check requirements CT_ASSERT(config.m_createSourceA); // open source TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA())); SOURCE_ASSERT_MESSAGE( "iterating twice should produce identical results", source.get(), countItems(source.get()) == countItems(source.get())); } // deleteItem() must raise 404 for unknown item void LocalTests::testDelete404() { // check requirements CT_ASSERT(config.m_createSourceA); // open source TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA())); SyncMLStatus status = STATUS_OK; try { source->deleteItem("no-such-item"); } catch (const StatusException &ex) { status = ex.syncMLStatus(); } CT_ASSERT_EQUAL(STATUS_NOT_FOUND, status); } // deleteItem() must raise 404 for unknown item void LocalTests::testReadItem404() { // check requirements CT_ASSERT(config.m_createSourceA); // open source TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA())); SyncMLStatus status = STATUS_OK; try { std::string data; source->readItem("no-such-item", data); } catch (const StatusException &ex) { status = ex.syncMLStatus(); } CT_ASSERT_EQUAL(STATUS_NOT_FOUND, status); } void LocalTests::doInsert(bool withUID) { // check requirements CT_ASSERT(!config.m_insertItem.empty()); CT_ASSERT(!config.m_createSourceA.empty()); std::string item = config.m_insertItem; if (!withUID) { CT_ASSERT_NO_THROW(stripProperty(item, "UID")); } CT_ASSERT_NO_THROW(insert(createSourceA, item)); } // insert one contact without clearing the source first void LocalTests::testSimpleInsert() { // Insert the item without the UID. There is no guarantee that the // item wasn't already inserted before (database not cleaned by // test) and some backends (CalDAV) will catch an attempt to // add the same item twice. doInsert(false); } // delete all items void LocalTests::testLocalDeleteAll() { // check requirements CT_ASSERT(!config.m_insertItem.empty()); CT_ASSERT(config.m_createSourceA); // make sure there is something to delete, then delete again std::string item = config.m_insertItem; CT_ASSERT_NO_THROW(stripProperty(item, "UID")); CT_ASSERT_NO_THROW(insert(createSourceA, item)); CT_ASSERT_NO_THROW(deleteAll(createSourceA)); } // clean database, then insert void LocalTests::testComplexInsert() { CT_ASSERT(config.m_createSourceA); CT_ASSERT_NO_THROW(deleteAll(createSourceA)); CT_ASSERT_NO_THROW(doInsert()); CT_ASSERT_NO_THROW(testIterateTwice()); } // insert the same item (identified by UID) twice => either // ITEM_NEEDS_MERGE, ITEM_REPLACED or ITEM_MERGED are acceptable void LocalTests::testInsertTwice() { CT_ASSERT(config.m_createSourceA); CT_ASSERT(!config.m_insertItem.empty()); CT_ASSERT(config.m_insertItem.find("\nUID:") != std::string::npos); CT_ASSERT_NO_THROW(deleteAll(createSourceA)); // create source TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA())); // mangle data once std::string data = config.m_mangleItem(config.m_insertItem, false, ""); // insert new item SyncSourceRaw::InsertItemResult first; SOURCE_ASSERT_NO_FAILURE(source.get(), first = source->insertItemRaw("", data)); CT_ASSERT_EQUAL(ITEM_OKAY, first.m_state); // and again SyncSourceRaw::InsertItemResult second; SOURCE_ASSERT_NO_FAILURE(source.get(), second = source->insertItemRaw("", data)); CLIENT_TEST_LOG("item %s", second.m_state == ITEM_NEEDS_MERGE ? "needs to be merged" : second.m_state == ITEM_REPLACED ? "was replaced" : second.m_state == ITEM_MERGED ? "was merged" : second.m_state == ITEM_OKAY ? "was added, which is broken!" : "unknown result ?!"); if (config.m_sourceKnowsItemSemantic) { CT_ASSERT(second.m_state == ITEM_NEEDS_MERGE || second.m_state == ITEM_REPLACED || second.m_state == ITEM_MERGED); CT_ASSERT_EQUAL(first.m_luid, second.m_luid); } else { CT_ASSERT(second.m_state == ITEM_OKAY); CT_ASSERT(first.m_luid != second.m_luid); } if (second.m_state == ITEM_REPLACED || second.m_state == ITEM_MERGED) { CT_ASSERT(first.m_revision != second.m_revision); } } // clean database, insert item, update it void LocalTests::testLocalUpdate() { // check additional requirements CT_ASSERT(!config.m_updateItem.empty()); CT_ASSERT(config.m_createSourceA); CT_ASSERT_NO_THROW(deleteAll(createSourceA)); CT_ASSERT_NO_THROW(doInsert()); CT_ASSERT_NO_THROW(update(createSourceA, config.m_updateItem)); } // Complex sequence of changes, with one restarted instance of source // B to observe the changes or multiple instances of it. // Changes are made both via source A and via source B itself. void LocalTests::doChanges(bool restart) { SyncSourceChanges::Items_t::const_iterator it, it2; // check additional requirements CT_ASSERT(config.m_createSourceB); CT_ASSERT(config.m_createSourceA); CLIENT_TEST_LOG("clean via source A"); CT_ASSERT_NO_THROW(deleteAll(createSourceA)); CLIENT_TEST_LOG("insert item via source A"); CT_ASSERT_NO_THROW(doInsert()); CLIENT_TEST_LOG("clean changes in sync source B by creating and closing it"); TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceB())); CT_ASSERT_NO_THROW(restart ? source.stopAccess() : source.reset()); CLIENT_TEST_LOG("no new changes now in source B"); SOURCE_ASSERT_NO_FAILURE(source.get(), restart ? source.startAccess() : source.reset(createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get())); string item; string luid; SOURCE_ASSERT_NO_FAILURE(source.get(), it = source->getAllItems().begin()); CT_ASSERT(it != source->getAllItems().end()); luid = *it; // It is not required for incremental syncing that sources must be // able to return unchanged items. For example, ActiveSyncSource doesn't support // it because it gets only IDs and data of added or updated items. // Don't test it. // SOURCE_ASSERT_NO_FAILURE(source.get(), source->readItem(*it, item)); CT_ASSERT_NO_THROW(restart ? source.stopAccess() : source.reset()); CLIENT_TEST_LOG("delete item again via sync source A"); CT_ASSERT_NO_THROW(deleteAll(createSourceA)); CLIENT_TEST_LOG("check for deleted item via source B"); SOURCE_ASSERT_NO_FAILURE(source.get(), restart ? source.startAccess() : source.reset(createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 1, countDeletedItems(source.get())); SOURCE_ASSERT_NO_FAILURE(source.get(), it = source->getDeletedItems().begin()); CT_ASSERT(it != source->getDeletedItems().end()); CT_ASSERT(!it->empty()); CT_ASSERT_EQUAL(luid, *it); CT_ASSERT_NO_THROW(restart ? source.stopAccess() : source.reset()); // Now make changes via source B directly: these changes are not to be // reported back. Google CalDAV is very strict about UID/SEQUENCE, // work around that by not reusing a UID inside this test. SOURCE_ASSERT_NO_FAILURE(source.get(), restart ? source.startAccess() : source.reset(createSourceB())); // add std::string mangled = config.m_mangleItem(config.m_insertItem, false, "-B"); SyncSourceRaw::InsertItemResult res; SOURCE_ASSERT_NO_FAILURE(source.get(), res = source->insertItemRaw("", mangled)); CT_ASSERT(!res.m_luid.empty()); CT_ASSERT_NO_THROW(restart ? source.stopAccess() : source.reset()); // update SOURCE_ASSERT_NO_FAILURE(source.get(), restart ? source.startAccess() : source.reset(createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get())); mangled = config.m_mangleItem(config.m_updateItem, false, "-B"); SOURCE_ASSERT_NO_FAILURE(source.get(), res = source->insertItemRaw(res.m_luid, mangled)); CT_ASSERT_NO_THROW(restart ? source.stopAccess() : source.reset()); // delete SOURCE_ASSERT_NO_FAILURE(source.get(), restart ? source.startAccess() : source.reset(createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get())); SOURCE_ASSERT_NO_FAILURE(source.get(), source->deleteItem(res.m_luid)); CT_ASSERT_NO_THROW(restart ? source.stopAccess() : source.reset()); SOURCE_ASSERT_NO_FAILURE(source.get(), restart ? source.startAccess() : source.reset(createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get())); CT_ASSERT_NO_THROW(restart ? source.stopAccess() : source.reset()); CLIENT_TEST_LOG("insert another item via source A"); CT_ASSERT_NO_THROW(insert(createSourceA, config.m_insertItem, false, NULL, "-C")); CLIENT_TEST_LOG("check for new item via source B"); SOURCE_ASSERT_NO_FAILURE(source.get(), restart ? source.startAccess() : source.reset(createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get())); SOURCE_ASSERT_NO_FAILURE(source.get(), it = source->getAllItems().begin()); CT_ASSERT(it != source->getAllItems().end()); luid = *it; SOURCE_ASSERT_NO_FAILURE(source.get(), source->readItem(*it, item)); string newItem; SOURCE_ASSERT_NO_FAILURE(source.get(), it = source->getNewItems().begin()); CT_ASSERT(it != source->getNewItems().end()); SOURCE_ASSERT_NO_FAILURE(source.get(), source->readItem(*it, item)); CT_ASSERT_EQUAL(luid, *it); CT_ASSERT_NO_THROW(restart ? source.stopAccess() : source.reset()); CLIENT_TEST_LOG("update item via source A"); CT_ASSERT_NO_THROW(update(createSourceA, config.m_updateItem, "-C")); CLIENT_TEST_LOG("check for updated item via source B"); SOURCE_ASSERT_NO_FAILURE(source.get(), restart ? source.startAccess() : source.reset(createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 1, countUpdatedItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get())); string updatedItem; SOURCE_ASSERT_NO_FAILURE(source.get(), it = source->getUpdatedItems().begin()); CT_ASSERT(it != source->getUpdatedItems().end()); SOURCE_ASSERT_NO_FAILURE(source.get(), source->readItem(*it, updatedItem)); CT_ASSERT_EQUAL(luid, *it); CT_ASSERT_NO_THROW(restart ? source.stopAccess() : source.reset()); CLIENT_TEST_LOG("one item, no changes in source B"); SOURCE_ASSERT_NO_FAILURE(source.get(), restart ? source.startAccess() : source.reset(createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get())); CT_ASSERT_NO_THROW(restart ? source.stopAccess() : source.reset()); CLIENT_TEST_LOG("start anew in both sources"); CT_ASSERT_NO_THROW(deleteAll(createSourceA)); SOURCE_ASSERT_NO_FAILURE(source.get(), restart ? source.startAccess() : source.reset(createSourceB())); CT_ASSERT_NO_THROW(restart ? source.stopAccess() : source.reset()); CLIENT_TEST_LOG("create and update an item in source A"); // Must use a UID different than the one used before despite that data being gone, // to keep Google CalDAV server happy. CT_ASSERT_NO_THROW(insert(createSourceA, config.m_insertItem, false, NULL, "-D")); CT_ASSERT_NO_THROW(update(createSourceA, config.m_updateItem, "-D")); CLIENT_TEST_LOG("should only be listed as new or updated in source B, but not both"); SOURCE_ASSERT_NO_FAILURE(source.get(), restart ? source.startAccess() : source.reset(createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get()) + countUpdatedItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get())); CT_ASSERT_NO_THROW(restart ? source.stopAccess() : source.reset()); CLIENT_TEST_LOG("start anew once more in both sources"); CT_ASSERT_NO_THROW(deleteAll(createSourceA)); SOURCE_ASSERT_NO_FAILURE(source.get(), restart ? source.startAccess() : source.reset(createSourceB())); CT_ASSERT_NO_THROW(restart ? source.stopAccess() : source.reset()); CLIENT_TEST_LOG("create, delete and recreate an item in source A"); CT_ASSERT_NO_THROW(insert(createSourceA, config.m_insertItem, false, NULL, "-E")); CT_ASSERT_NO_THROW(deleteAll(createSourceA)); CT_ASSERT_NO_THROW(insert(createSourceA, config.m_insertItem, false, NULL, "-F")); CLIENT_TEST_LOG("should only be listed as new or updated in source B, even if\n " "(as for calendar with UID) the same LUID gets reused"); SOURCE_ASSERT_NO_FAILURE(source.get(), restart ? source.startAccess() : source.reset(createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get()) + countUpdatedItems(source.get())); if (countDeletedItems(source.get()) == 1) { // It's not nice, but acceptable to send the LUID of a deleted item to a // server which has never seen that LUID. The LUID must not be the same as // the one we list as new or updated, though. SOURCE_ASSERT_NO_FAILURE(source.get(), it = source->getDeletedItems().begin()); CT_ASSERT(it != source->getDeletedItems().end()); SOURCE_ASSERT_NO_FAILURE(source.get(), it2 = source->getNewItems().begin()); if (it2 == source->getNewItems().end()) { SOURCE_ASSERT_NO_FAILURE(source.get(), it2 = source->getUpdatedItems().begin()); CT_ASSERT(it2 != source->getUpdatedItems().end()); } CT_ASSERT(*it != *it2); } else { SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get())); } CT_ASSERT_NO_THROW(source.reset()); } // complex sequence of changes, with source B instantiated anew // after each change void LocalTests::testChanges() { doChanges(false); } // complex sequence of changes, with source B only instantiated once // and restarted multiple times void LocalTests::testChangesMultiCycles() { doChanges(true); } // Make changes in one source and verify that other linked // sources do not see and report any changes in their view // of the shared database. Source A of the other sources // is created once and is restarted, source B is created // from scratch after each change. void LocalTests::testLinkedSources() { // make changes in each of the sources (doesn't have t be // the current one) BOOST_FOREACH (LocalTests *main, m_linkedSources) { CLIENT_TEST_LOG("making changes in %s", main->getSourceName().c_str()); // first delete via *all* sources BOOST_FOREACH (LocalTests *test, m_linkedSources) { CLIENT_TEST_LOG("clean via source A of %s", test->getSourceName().c_str()); CT_ASSERT_NO_THROW(test->deleteAll(test->createSourceA)); // reset change tracking in source B TestingSyncSourcePtr sourceB; SOURCE_ASSERT_NO_FAILURE(sourceB.get(), sourceB.reset(test->createSourceB())); } std::map sourcesA; BOOST_FOREACH (LocalTests *test, m_linkedSources) { if (test == main) { continue; } TestingSyncSourcePtr &source = sourcesA[test->getSourceName()]; CLIENT_TEST_LOG("creating source A of %s", test->getSourceName().c_str()); SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(test->createSourceA())); CT_ASSERT_NO_THROW(source.stopAccess()); } // insert one item CLIENT_TEST_LOG("inserting into %s", main->getSourceName().c_str()); CT_ASSERT_NO_THROW(main->doInsert()); BOOST_FOREACH (LocalTests *test, m_linkedSources) { if (test == main) { continue; } TestingSyncSourcePtr &source = sourcesA[test->getSourceName()]; CLIENT_TEST_LOG("checking %s after insertion into %s", test->getSourceName().c_str(), main->getSourceName().c_str()); SOURCE_ASSERT_NO_FAILURE(source.get(), source.startAccess()); SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get())); SOURCE_ASSERT_NO_FAILURE(source.get(), source.stopAccess()); TestingSyncSourcePtr sourceB; SOURCE_ASSERT_NO_FAILURE(sourceB.get(), sourceB.reset(test->createSourceB())); SOURCE_ASSERT_EQUAL(sourceB.get(), 0, countItems(sourceB.get())); SOURCE_ASSERT_EQUAL(sourceB.get(), 0, countNewItems(sourceB.get())); SOURCE_ASSERT_EQUAL(sourceB.get(), 0, countUpdatedItems(sourceB.get())); SOURCE_ASSERT_EQUAL(sourceB.get(), 0, countDeletedItems(sourceB.get())); } // update one item CLIENT_TEST_LOG("updating in %s", main->getSourceName().c_str()); CT_ASSERT_NO_THROW(main->update(main->createSourceA, main->config.m_updateItem)); BOOST_FOREACH (LocalTests *test, m_linkedSources) { if (test == main) { continue; } TestingSyncSourcePtr &source = sourcesA[test->getSourceName()]; CLIENT_TEST_LOG("checking %s after update into %s", test->getSourceName().c_str(), main->getSourceName().c_str()); SOURCE_ASSERT_NO_FAILURE(source.get(), source.startAccess()); SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get())); SOURCE_ASSERT_NO_FAILURE(source.get(), source.stopAccess()); TestingSyncSourcePtr sourceB; SOURCE_ASSERT_NO_FAILURE(sourceB.get(), sourceB.reset(test->createSourceB())); SOURCE_ASSERT_EQUAL(sourceB.get(), 0, countItems(sourceB.get())); SOURCE_ASSERT_EQUAL(sourceB.get(), 0, countNewItems(sourceB.get())); SOURCE_ASSERT_EQUAL(sourceB.get(), 0, countUpdatedItems(sourceB.get())); SOURCE_ASSERT_EQUAL(sourceB.get(), 0, countDeletedItems(sourceB.get())); } // delete one item CLIENT_TEST_LOG("deleting in %s", main->getSourceName().c_str()); CT_ASSERT_NO_THROW(main->deleteAll(main->createSourceA)); BOOST_FOREACH (LocalTests *test, m_linkedSources) { if (test == main) { continue; } TestingSyncSourcePtr &source = sourcesA[test->getSourceName()]; CLIENT_TEST_LOG("checking %s after delete in %s", test->getSourceName().c_str(), main->getSourceName().c_str()); SOURCE_ASSERT_NO_FAILURE(source.get(), source.startAccess()); SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get())); SOURCE_ASSERT_NO_FAILURE(source.get(), source.stopAccess()); TestingSyncSourcePtr sourceB; SOURCE_ASSERT_NO_FAILURE(sourceB.get(), sourceB.reset(test->createSourceB())); SOURCE_ASSERT_EQUAL(sourceB.get(), 0, countItems(sourceB.get())); SOURCE_ASSERT_EQUAL(sourceB.get(), 0, countNewItems(sourceB.get())); SOURCE_ASSERT_EQUAL(sourceB.get(), 0, countUpdatedItems(sourceB.get())); SOURCE_ASSERT_EQUAL(sourceB.get(), 0, countDeletedItems(sourceB.get())); } } } // clean database, import file, then export again and compare void LocalTests::testImport() { // check additional requirements CT_ASSERT(config.m_import); CT_ASSERT(config.m_dump); CT_ASSERT(config.m_compare); CT_ASSERT(!config.m_testcases.empty()); CT_ASSERT(config.m_createSourceA); CT_ASSERT_NO_THROW(deleteAll(createSourceA)); // import via sync source A TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA())); restoreStorage(config, client); std::string testcases; std::string importFailures = config.m_import(client, *source.get(), config, config.m_testcases, testcases, NULL); backupStorage(config, client); CT_ASSERT_NO_THROW(source.reset()); // export again and compare against original file, // without relying on change tracking (because // Google ActiveSync has problems with Fetch, // which would be needed for a data dump when // using the incremental approach) TestingSyncSourcePtr copy; SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceA(), TestingSyncSourcePtr::SLOW)); bool equal = compareDatabases(testcases.c_str(), *copy.get(), false); CT_ASSERT_NO_THROW(source.reset()); if (importFailures.empty()) { CT_ASSERT_MESSAGE("imported and exported data equal", equal); } else { CT_ASSERT_EQUAL(std::string(""), importFailures); } } // same as testImport() with immediate delete void LocalTests::testImportDelete() { CT_ASSERT_NO_THROW(testImport()); // delete again, because it was observed that this did not // work right with calendars in SyncEvolution CT_ASSERT_NO_THROW(deleteAll(createSourceA)); } // clean database, import file, update with minimized test data (= all // non-essential properties removed), compare: verifies that updates // can remove data void LocalTests::testRemoveProperties() { // check additional requirements CT_ASSERT(config.m_import); CT_ASSERT(config.m_dump); CT_ASSERT(config.m_compare); CT_ASSERT(!config.m_testcases.empty()); CT_ASSERT(config.m_createSourceA); CT_ASSERT_NO_THROW(deleteAll(createSourceA)); // import via sync source A TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA())); restoreStorage(config, client); std::string testcases; std::list luids; std::string importFailures = config.m_import(client, *source.get(), config, config.m_testcases, testcases, &luids); backupStorage(config, client); CT_ASSERT_NO_THROW(source.reset()); // don't check for correct importing - that is done in testImport // reduce data std::list items; std::string dummy; CT_ASSERT_NO_THROW(ClientTest::getItems(testcases, items, dummy)); static const pcrecpp::RE bodyre("^BEGIN:(VCARD|VEVENT|VTODO|VJOURNAL)\\r?\\n(.*)^(END:\\g1)", pcrecpp::RE_Options().set_multiline(true).set_dotall(true)); std::string updated = getCurrentTest(); updated += ".updated."; updated += config.m_sourceName; updated += ".dat"; simplifyFilename(updated); ofstream out(updated.c_str()); BOOST_FOREACH (std::string &item, items) { std::string kind; pcrecpp::StringPiece body; CT_ASSERT(bodyre.PartialMatch(item, &kind, &body)); static const pcrecpp::RE propre("^((\\S[^;:]*).*\\n(?:\\s.*\\n)*)", pcrecpp::RE_Options().set_multiline(true)); pcrecpp::StringPiece input(body); pcrecpp::StringPiece prop; std::string propname; std::list result; while (propre.Consume(&input, &prop, &propname)) { if (config.m_essentialProperties.find(propname) != config.m_essentialProperties.end()) { result.push_back(prop.as_string()); } } item.replace(body.data() - item.c_str(), body.size(), boost::join(result, "")); out << item << "\n"; } out.close(); // update SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA())); std::string updateFailures = config.m_import(client, *source.get(), config, updated, dummy, &luids); CT_ASSERT_NO_THROW(source.reset()); // compare TestingSyncSourcePtr copy; SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceA(), TestingSyncSourcePtr::SLOW)); std::auto_ptr envProps; if (currentServer() == "googlecontacts") { // Google CardDAV server does not remove X- properties when // they are not sent at all. TODO (?): send them as empty // properties. envProps.reset(new ScopedEnvChange("CLIENT_TEST_STRIP_PROPERTIES", "(X-.*)")); } bool equal = compareDatabases(updated.c_str(), *copy.get(), false); CT_ASSERT_NO_THROW(source.reset()); if (importFailures.empty() && updateFailures.empty()) { CT_ASSERT_MESSAGE("imported and exported data equal", equal); } else { CT_ASSERT_EQUAL(std::string(""), importFailures + updateFailures); } } // test change tracking with large number of items void LocalTests::testManyChanges() { // check additional requirements CT_ASSERT(!config.m_templateItem.empty()); CT_ASSERT_NO_THROW(deleteAll(createSourceA)); // check that everything is empty, also resets change counter of sync source B TestingSyncSourcePtr copy; SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); CT_ASSERT_NO_THROW(copy.reset()); // now insert plenty of items int numItems; CT_ASSERT_NO_THROW(numItems = insertManyItems(createSourceA).size()); // check that exactly this number of items is listed as new SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), numItems, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), numItems, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get())); CT_ASSERT_NO_THROW(copy.reset()); // delete all items CT_ASSERT_NO_THROW(deleteAll(createSourceA)); // verify again SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), numItems, countDeletedItems(copy.get())); CT_ASSERT_NO_THROW(copy.reset()); } template int countEqual(const T &container, const V &value) { return count(container.begin(), container.end(), value); } // test inserting, removing and updating of parent + child item in // various order plus change tracking void LocalTests::testLinkedItemsParent() { ClientTestConfig::LinkedItems_t items = getParentChildData(); CT_ASSERT_NO_THROW(deleteAll(createSourceA)); std::string parent, child; std::string parentData; TestingSyncSourcePtr copy; // check that everything is empty, also resets change counter of sync source B SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); CT_ASSERT_NO_THROW(copy.reset()); // now insert main item CT_ASSERT_NO_THROW(parent = insert(createSourceA, items[0], false, &parentData)); // check that exactly the parent is listed as new SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); CT_ASSERT_NO_THROW(compareDatabases(*copy, &parentData, NULL)); SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 1, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent)); } CT_ASSERT_NO_THROW(copy.reset()); if (getenv("CLIENT_TEST_LINKED_ITEMS_NO_DELETE")) { return; } // delete all items CT_ASSERT_NO_THROW(deleteAll(createSourceA)); // verify again SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 1, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent)); } } // test inserting, removing and updating of parent + child item in // various order plus change tracking void LocalTests::testLinkedItemsChild() { ClientTestConfig::LinkedItems_t items = getParentChildData(); CT_ASSERT_NO_THROW(deleteAll(createSourceA)); std::string parent, child; std::string childData; TestingSyncSourcePtr copy; // check that everything is empty, also resets change counter of sync source B SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); CT_ASSERT_NO_THROW(copy.reset()); // same as above for child item CT_ASSERT_NO_THROW(child = insert(createSourceA, items[1], false, &childData)); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); CT_ASSERT_NO_THROW(compareDatabases(*copy, &childData, NULL)); SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 1, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child)); } CT_ASSERT_NO_THROW(copy.reset()); if (getenv("CLIENT_TEST_LINKED_ITEMS_NO_DELETE")) { return; } CT_ASSERT_NO_THROW(deleteAll(createSourceA)); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 1, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child)); } } // test inserting, removing and updating of parent + child item in // various order plus change tracking void LocalTests::testLinkedItemsParentChild() { ClientTestConfig::LinkedItems_t items = getParentChildData(); CT_ASSERT_NO_THROW(deleteAll(createSourceA)); std::string parent, child; std::string parentData, childData; TestingSyncSourcePtr copy; // check that everything is empty, also resets change counter of sync source B SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); CT_ASSERT_NO_THROW(copy.reset()); // insert parent first, then child CT_ASSERT_NO_THROW(parent = insert(createSourceA, items[0], false, &parentData)); CT_ASSERT_NO_THROW(child = insert(createSourceA, items[1], false, &childData)); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); CT_ASSERT_NO_THROW(compareDatabases(*copy, &parentData, &childData, NULL)); SOURCE_ASSERT_EQUAL(copy.get(), 2, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 2, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child)); SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent)); } CT_ASSERT_NO_THROW(copy.reset()); if (config.m_supportsReccurenceEXDates) { TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA())); CLIENT_TEST_LOG("retrieve parent as reported to the Synthesis engine, check for X-SYNCEVOLUTION-EXDATE-DETACHED"); std::string parentDataEngine; CT_ASSERT_NO_THROW(source->readItem(parent, parentDataEngine)); size_t pos = childData.find("RECURRENCE-ID"); CT_ASSERT(pos != childData.npos); size_t end = childData.find_first_of("\r\n", pos); CT_ASSERT(end != childData.npos); std::string exdate = childData.substr(pos, end - pos); boost::replace_first(exdate, "RECURRENCE-ID", "X-SYNCEVOLUTION-EXDATE-DETACHED"); // not generated because not needed by Synthesis engine boost::replace_first(exdate, ";VALUE=DATE", ""); pos = parentDataEngine.find(exdate); CT_ASSERT_MESSAGE(exdate + " not found in:\n" + parentDataEngine, pos != parentDataEngine.npos); } if (config.m_supportsReccurenceEXDates) { TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA())); CLIENT_TEST_LOG("retrieve parent as reported to the Synthesis engine, check for X-SYNCEVOLUTION-EXDATE-DETACHED"); std::string parentDataEngine; CT_ASSERT_NO_THROW(source->readItem(parent, parentDataEngine)); size_t pos = childData.find("RECURRENCE-ID"); CT_ASSERT(pos != childData.npos); size_t end = childData.find_first_of("\r\n", pos); CT_ASSERT(end != childData.npos); std::string exdate = childData.substr(pos, end - pos); boost::replace_first(exdate, "RECURRENCE-ID", "X-SYNCEVOLUTION-EXDATE-DETACHED"); // not generated because not needed by Synthesis engine boost::replace_first(exdate, ";VALUE=DATE", ""); pos = parentDataEngine.find(exdate); CT_ASSERT_MESSAGE(exdate + " not found in:\n" + parentDataEngine, pos != parentDataEngine.npos); } if (getenv("CLIENT_TEST_LINKED_ITEMS_NO_DELETE")) { return; } CT_ASSERT_NO_THROW(deleteAll(createSourceA)); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 2, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child)); SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent)); } } // test inserting, removing and updating of parent + child item in // various order plus change tracking void LocalTests::testLinkedItemsChildParent() { ClientTestConfig::LinkedItems_t items = getParentChildData(); CT_ASSERT_NO_THROW(deleteAll(createSourceA)); std::string parent, child; std::string parentData, childData; TestingSyncSourcePtr copy; // check that everything is empty, also resets change counter of sync source B SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); CT_ASSERT_NO_THROW(copy.reset()); // insert child first, then parent CT_ASSERT_NO_THROW(child = insert(createSourceA, items[1], false, &parentData)); CT_ASSERT_NO_THROW(parent = insert(createSourceA, items[0], true, &childData)); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); CT_ASSERT_NO_THROW(compareDatabases(*copy, &parentData, &childData, NULL)); SOURCE_ASSERT_EQUAL(copy.get(), 2, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 2, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child)); SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent)); } CT_ASSERT_NO_THROW(copy.reset()); if (getenv("CLIENT_TEST_LINKED_ITEMS_NO_DELETE")) { return; } CT_ASSERT_NO_THROW(deleteAll(createSourceA)); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 2, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child)); SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent)); } } // test inserting, removing and updating of parent + child item in // various order plus change tracking void LocalTests::testLinkedItemsChildChangesParent() { ClientTestConfig::LinkedItems_t items = getParentChildData(); CT_ASSERT_NO_THROW(deleteAll(createSourceA)); std::string parent, child; std::string parentData, childData; TestingSyncSourcePtr copy; // check that everything is empty, also resets change counter of sync source B SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); CT_ASSERT_NO_THROW(copy.reset()); // insert child first, check changes, then insert the parent CT_ASSERT_NO_THROW(child = insert(createSourceA, items[1], false, &childData)); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); CT_ASSERT_NO_THROW(compareDatabases(*copy, &childData, NULL)); SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 1, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child)); } CT_ASSERT_NO_THROW(copy.reset()); CT_ASSERT_NO_THROW(parent = insert(createSourceA, items[0], true, &parentData)); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); CT_ASSERT_NO_THROW(compareDatabases(*copy, &parentData, &childData, NULL)); SOURCE_ASSERT_EQUAL(copy.get(), 2, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 1, countNewItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listNewItems(copy.get()), parent)); } // relaxed semantic: the child item might be considered updated now if // it had to be modified when inserting the parent SOURCE_ASSERT(copy.get(), 1 >= countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child)); SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent)); } CT_ASSERT_NO_THROW(copy.reset()); if (getenv("CLIENT_TEST_LINKED_ITEMS_NO_DELETE")) { return; } CT_ASSERT_NO_THROW(deleteAll(createSourceA)); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 2, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child)); SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent)); } } // test inserting, removing and updating of parent + child item in // various order plus change tracking void LocalTests::testLinkedItemsRemoveParentFirst() { ClientTestConfig::LinkedItems_t items = getParentChildData(); CT_ASSERT_NO_THROW(deleteAll(createSourceA)); std::string parent, child; std::string parentData, childData; TestingSyncSourcePtr copy; // check that everything is empty, also resets change counter of sync source B SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); CT_ASSERT_NO_THROW(copy.reset()); // insert both items, remove parent, then child CT_ASSERT_NO_THROW(parent = insert(createSourceA, items[0], false, &parentData)); CT_ASSERT_NO_THROW(child = insert(createSourceA, items[1], false, &childData)); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); CT_ASSERT_NO_THROW(compareDatabases(*copy, &parentData, &childData, NULL)); SOURCE_ASSERT_EQUAL(copy.get(), 2, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 2, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child)); SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent)); } CT_ASSERT_NO_THROW(copy.reset()); CT_ASSERT_NO_THROW(deleteItem(createSourceA, parent)); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); CT_ASSERT_NO_THROW(compareDatabases(*copy, &childData, NULL)); SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get())); // deleting the parent may or may not modify the child SOURCE_ASSERT(copy.get(), 1 >= countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 1, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent)); } CT_ASSERT_NO_THROW(copy.reset()); if (getenv("CLIENT_TEST_LINKED_ITEMS_NO_DELETE")) { return; } CT_ASSERT_NO_THROW(deleteItem(createSourceA, child)); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 1, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child)); } CT_ASSERT_NO_THROW(copy.reset()); } // test inserting, removing and updating of parent + child item in // various order plus change tracking void LocalTests::testLinkedItemsRemoveNormal() { ClientTestConfig::LinkedItems_t items = getParentChildData(); CT_ASSERT_NO_THROW(deleteAll(createSourceA)); std::string parent, child; std::string parentData, childData; TestingSyncSourcePtr source, copy; // check that everything is empty, also resets change counter of sync source B SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); CT_ASSERT_NO_THROW(copy.reset()); // insert both items, remove child, then parent CT_ASSERT_NO_THROW(parent = insert(createSourceA, items[0], false, &parentData)); CT_ASSERT_NO_THROW(child = insert(createSourceA, items[1], false, &childData)); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); CT_ASSERT_NO_THROW(compareDatabases(*copy, &parentData, &childData, NULL)); SOURCE_ASSERT_EQUAL(copy.get(), 2, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 2, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child)); SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent)); } CT_ASSERT_NO_THROW(copy.reset()); CT_ASSERT_NO_THROW(deleteItem(createSourceA, child)); // The removal of the child fails with Exchange (BMC #22849). // Skip the testing, proceed to full removal. if (currentServer() != "exchange") { SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA())); if (getCurrentTest().find("::eds_event::") != std::string::npos) { bool usingEDSLT36; #ifdef EVOLUTION_COMPATIBILITY // Need to check at runtime. // We are using EDS < 3.6 if (and only if) we found the right libs. usingEDSLT36 = EDSAbiHaveEcal; #elif defined(USE_EDS_CLIENT) // Detected at runtime: new EDS API. usingEDSLT36 = false; #else // Detected at runtime: old EDS API. usingEDSLT36 = true; #endif if (usingEDSLT36) { // hack: ignore EDS < 3.6 side effect of adding EXDATE to parent, see http://bugs.meego.com/show_bug.cgi?id=10906 size_t pos = parentData.rfind("DTSTART"); parentData.insert(pos, getCurrentTest().find("LinkedItemsAllDay") == std::string::npos ? "EXDATE:20080413T090000\n" : "EXDATE:20080413\n"); } } CT_ASSERT_NO_THROW(compareDatabases(*source, &parentData, NULL)); SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get())); CT_ASSERT_NO_THROW(source.reset()); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get())); // parent might have been updated int updated = false; CT_ASSERT_NO_THROW(updated = countUpdatedItems(copy.get())); SOURCE_ASSERT(copy.get(), 0 <= updated && updated <= 1); SOURCE_ASSERT_EQUAL(copy.get(), 1, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child)); } CT_ASSERT_NO_THROW(copy.reset()); } CT_ASSERT_NO_THROW(deleteItem(createSourceA, parent)); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), // Exchange did not actually remove child above, done now. currentServer() != "exchange" ? 1 : 2, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent)); } CT_ASSERT_NO_THROW(copy.reset()); } // test inserting, removing and updating of parent + child item in // various order plus change tracking void LocalTests::testLinkedItemsInsertParentTwice() { ClientTestConfig::LinkedItems_t items = getParentChildData(); CT_ASSERT_NO_THROW(deleteAll(createSourceA)); std::string parent, child; std::string parentData, childData; TestingSyncSourcePtr copy; // check that everything is empty, also resets change counter of sync source B SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); CT_ASSERT_NO_THROW(copy.reset()); // add parent twice (should be turned into update) CT_ASSERT_NO_THROW(parent = insert(createSourceA, items[0], false, &parentData)); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); CT_ASSERT_NO_THROW(compareDatabases(*copy, &parentData, NULL)); SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 1, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent)); } CT_ASSERT_NO_THROW(copy.reset()); CT_ASSERT_NO_THROW(parent = insert(createSourceA, items[0], false, &parentData)); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); CT_ASSERT_NO_THROW(compareDatabases(*copy, &parentData, NULL)); SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 1, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listUpdatedItems(copy.get()), parent)); } CT_ASSERT_NO_THROW(copy.reset()); if (getenv("CLIENT_TEST_LINKED_ITEMS_NO_DELETE")) { return; } CT_ASSERT_NO_THROW(deleteItem(createSourceA, parent)); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 1, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent)); } } // test inserting, removing and updating of parent + child item in // various order plus change tracking void LocalTests::testLinkedItemsInsertChildTwice() { ClientTestConfig::LinkedItems_t items = getParentChildData(); CT_ASSERT_NO_THROW(deleteAll(createSourceA)); std::string parent, child; std::string parentData, childData; TestingSyncSourcePtr copy; // check that everything is empty, also resets change counter of sync source B SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); CT_ASSERT_NO_THROW(copy.reset()); // add child twice (should be turned into update) CT_ASSERT_NO_THROW(child = insert(createSourceA, items[1], false, &childData)); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); CT_ASSERT_NO_THROW(compareDatabases(*copy, &childData, NULL)); SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 1, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child)); } CT_ASSERT_NO_THROW(copy.reset()); CT_ASSERT_NO_THROW(child = insert(createSourceA, items[1])); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); CT_ASSERT_NO_THROW(compareDatabases(*copy, &childData, NULL)); SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 1, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listUpdatedItems(copy.get()), child)); } CT_ASSERT_NO_THROW(copy.reset()); if (getenv("CLIENT_TEST_LINKED_ITEMS_NO_DELETE")) { return; } CT_ASSERT_NO_THROW(deleteItem(createSourceA, child)); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 1, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child)); } } // test inserting, removing and updating of parent + child item in // various order plus change tracking void LocalTests::testLinkedItemsParentUpdate() { ClientTestConfig::LinkedItems_t items = getParentChildData(); CT_ASSERT_NO_THROW(deleteAll(createSourceA)); std::string parent, child; std::string parentData, childData; TestingSyncSourcePtr copy; // check that everything is empty, also resets change counter of sync source B SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); CT_ASSERT_NO_THROW(copy.reset()); // add parent, then update it CT_ASSERT_NO_THROW(parent = insert(createSourceA, items[0], false, &parentData)); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); CT_ASSERT_NO_THROW(compareDatabases(*copy, &parentData, NULL)); SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 1, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent)); } CT_ASSERT_NO_THROW(copy.reset()); CT_ASSERT_NO_THROW(parent = updateItem(createSourceA, config, parent, items[0], &parentData)); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); CT_ASSERT_NO_THROW(compareDatabases(*copy, &parentData, NULL)); SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 1, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listUpdatedItems(copy.get()), parent)); } CT_ASSERT_NO_THROW(copy.reset()); if (getenv("CLIENT_TEST_LINKED_ITEMS_NO_DELETE")) { return; } CT_ASSERT_NO_THROW(deleteItem(createSourceA, parent)); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 1, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent)); } CT_ASSERT_NO_THROW(copy.reset()); } // test inserting, removing and updating of parent + child item in // various order plus change tracking void LocalTests::testLinkedItemsUpdateChild() { ClientTestConfig::LinkedItems_t items = getParentChildData(); CT_ASSERT_NO_THROW(deleteAll(createSourceA)); std::string parent, child; std::string parentData, childData; TestingSyncSourcePtr copy; // check that everything is empty, also resets change counter of sync source B SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); CT_ASSERT_NO_THROW(copy.reset()); // add child, then update it CT_ASSERT_NO_THROW(child = insert(createSourceA, items[1], false, &childData)); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); CT_ASSERT_NO_THROW(compareDatabases(*copy, &childData, NULL)); SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 1, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child)); } CT_ASSERT_NO_THROW(copy.reset()); CT_ASSERT_NO_THROW(child = updateItem(createSourceA, config, child, items[1], &childData)); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); CT_ASSERT_NO_THROW(compareDatabases(*copy, &childData, NULL)); SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 1, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listUpdatedItems(copy.get()), child)); } CT_ASSERT_NO_THROW(copy.reset()); if (getenv("CLIENT_TEST_LINKED_ITEMS_NO_DELETE")) { return; } CT_ASSERT_NO_THROW(deleteItem(createSourceA, child)); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 1, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child)); } } // test inserting, removing and updating of parent + child item in // various order plus change tracking void LocalTests::testLinkedItemsInsertBothUpdateChild() { ClientTestConfig::LinkedItems_t items = getParentChildData(); CT_ASSERT_NO_THROW(deleteAll(createSourceA)); std::string parent, child; std::string parentData, childData; TestingSyncSourcePtr copy; // check that everything is empty, also resets change counter of sync source B SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); CT_ASSERT_NO_THROW(copy.reset()); // add parent and child, then update child CT_ASSERT_NO_THROW(parent = insert(createSourceA, items[0], false, &parentData)); CT_ASSERT_NO_THROW(child = insert(createSourceA, items[1], false, &childData)); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); CT_ASSERT_NO_THROW(compareDatabases(*copy, &parentData, &childData, NULL)); SOURCE_ASSERT_EQUAL(copy.get(), 2, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 2, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child)); SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent)); } CT_ASSERT_NO_THROW(copy.reset()); CT_ASSERT_NO_THROW(child = updateItem(createSourceA, config, child, items[1], &childData)); // child has to be listed as modified, parent may be SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); CT_ASSERT_NO_THROW(compareDatabases(*copy, &parentData, &childData, NULL)); SOURCE_ASSERT_EQUAL(copy.get(), 2, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get())); SOURCE_ASSERT(copy.get(), 1 <= countUpdatedItems(copy.get())); SOURCE_ASSERT(copy.get(), 2 >= countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listUpdatedItems(copy.get()), child)); } CT_ASSERT_NO_THROW(copy.reset()); if (getenv("CLIENT_TEST_LINKED_ITEMS_NO_DELETE")) { return; } CT_ASSERT_NO_THROW(deleteItem(createSourceA, parent)); CT_ASSERT_NO_THROW(deleteItem(createSourceA, child)); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 2, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent)); SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child)); } CT_ASSERT_NO_THROW(copy.reset()); } // test inserting, removing and updating of parent + child item in // various order plus change tracking void LocalTests::testLinkedItemsInsertBothUpdateParent() { ClientTestConfig::LinkedItems_t items = getParentChildData(); CT_ASSERT_NO_THROW(deleteAll(createSourceA)); std::string parent, child; std::string parentData, childData; TestingSyncSourcePtr copy; // check that everything is empty, also resets change counter of sync source B SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); CT_ASSERT_NO_THROW(copy.reset()); // add parent and child, then update parent CT_ASSERT_NO_THROW(parent = insert(createSourceA, items[0], false, &parentData)); CT_ASSERT_NO_THROW(child = insert(createSourceA, items[1], false, &childData)); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); CT_ASSERT_NO_THROW(compareDatabases(*copy, &parentData, &childData, NULL)); SOURCE_ASSERT_EQUAL(copy.get(), 2, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 2, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child)); SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent)); } CT_ASSERT_NO_THROW(copy.reset()); CT_ASSERT_NO_THROW(parent = updateItem(createSourceA, config, parent, items[0], &parentData)); // parent has to be listed as modified, child may be SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); CT_ASSERT_NO_THROW(compareDatabases(*copy, &parentData, &childData, NULL)); SOURCE_ASSERT_EQUAL(copy.get(), 2, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get())); SOURCE_ASSERT(copy.get(), 1 <= countUpdatedItems(copy.get())); SOURCE_ASSERT(copy.get(), 2 >= countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listUpdatedItems(copy.get()), parent)); } CT_ASSERT_NO_THROW(copy.reset()); if (getenv("CLIENT_TEST_LINKED_ITEMS_NO_DELETE")) { return; } CT_ASSERT_NO_THROW(deleteItem(createSourceA, parent)); CT_ASSERT_NO_THROW(deleteItem(createSourceA, child)); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get())); SOURCE_ASSERT_EQUAL(copy.get(), 2, countDeletedItems(copy.get())); if (!config.m_sourceLUIDsAreVolatile) { SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent)); SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child)); } } // - insert parent and child // - update child *without* UID and RECURRENCE-ID: source expected to re-insert them void LocalTests::testLinkedItemsInsertBothUpdateChildNoIDs() { ClientTestConfig::LinkedItems_t items = getParentChildData(); CT_ASSERT_NO_THROW(deleteAll(createSourceA)); std::string parent, child; std::string parentData, childData; TestingSyncSourcePtr copy; // add parent and child, then update child CT_ASSERT_NO_THROW(parent = insert(createSourceA, items[0], false, &parentData)); CT_ASSERT_NO_THROW(child = insert(createSourceA, items[1], false, &childData)); // remove UID and RECURRENCE-ID before updating std::string reducedChildData = items[1]; std::string uid; CT_ASSERT_NO_THROW(uid = stripProperty(reducedChildData, "UID")); std::string rid; CT_ASSERT_NO_THROW(rid = stripProperty(reducedChildData, "RECURRENCE-ID")); CT_ASSERT_NO_THROW(child = updateItem(createSourceA, config, child, reducedChildData, &childData)); // compare SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceA())); CT_ASSERT_NO_THROW(insertProperty(childData, uid, "END:VEVENT")); CT_ASSERT_NO_THROW(insertProperty(childData, rid, "END:VEVENT")); CT_ASSERT_NO_THROW(compareDatabases(*copy, &parentData, &childData, NULL)); } // - insert child // - update child *without* UID and RECURRENCE-ID: source expected to re-insert them void LocalTests::testLinkedItemsUpdateChildNoIDs() { ClientTestConfig::LinkedItems_t items = getParentChildData(); CT_ASSERT_NO_THROW(deleteAll(createSourceA)); std::string child; std::string childData; TestingSyncSourcePtr copy; // add child, then update child CT_ASSERT_NO_THROW(child = insert(createSourceA, items[1], false, &childData)); // remove UID and RECURRENCE-ID before updating std::string reducedChildData = items[1]; std::string uid; CT_ASSERT_NO_THROW(uid = stripProperty(reducedChildData, "UID")); std::string rid; CT_ASSERT_NO_THROW(rid = stripProperty(reducedChildData, "RECURRENCE-ID")); CT_ASSERT_NO_THROW(child = updateItem(createSourceA, config, child, reducedChildData, &childData)); // compare SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceA())); CT_ASSERT_NO_THROW(insertProperty(childData, uid, "END:VEVENT")); CT_ASSERT_NO_THROW(insertProperty(childData, rid, "END:VEVENT")); CT_ASSERT_NO_THROW(compareDatabases(*copy, &childData, NULL)); } // insert parent, try to delete or retrieve non-existent child: // must report 404 void LocalTests::testLinkedItemsSingle404() { ClientTestConfig::LinkedItems_t items = getParentChildData(); CT_ASSERT_NO_THROW(deleteAll(createSourceA)); std::string parent, child; // now insert main item CT_ASSERT_NO_THROW(parent = insert(createSourceA, items[0], false)); // fake subid: works for CalDAV and EDS child = parent + "no-such-subitem"; // read TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset((createSourceA()))); SyncMLStatus status = STATUS_OK; CT_ASSERT_NO_THROW(try { std::string data; source->readItem(child, data); } catch (const StatusException &ex) { status = ex.syncMLStatus(); } ); CT_ASSERT_EQUAL(STATUS_NOT_FOUND, status); // delete status = STATUS_OK; CT_ASSERT_NO_THROW(try { source->deleteItem(child); } catch (const StatusException &ex) { status = ex.syncMLStatus(); } ); CT_ASSERT_EQUAL(STATUS_NOT_FOUND, status); } // insert parent and child, try to delete or retrieve non-existent child: // must report 404 void LocalTests::testLinkedItemsMany404() { ClientTestConfig::LinkedItems_t items = getParentChildData(); CT_ASSERT_NO_THROW(deleteAll(createSourceA)); std::string parent, child; // now insert two items CT_ASSERT_NO_THROW(parent = insert(createSourceA, items[0], false)); CT_ASSERT_NO_THROW(child = insert(createSourceA, items[1], false)); // fake subid: works for CalDAV and EDS child = parent + "no-such-subitem"; // read TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA())); SyncMLStatus status = STATUS_OK; CT_ASSERT_NO_THROW(try { std::string data; source->readItem(child, data); } catch (const StatusException &ex) { status = ex.syncMLStatus(); } ); CT_ASSERT_EQUAL(STATUS_NOT_FOUND, status); // delete status = STATUS_OK; CT_ASSERT_NO_THROW(try { source->deleteItem(child); } catch (const StatusException &ex) { status = ex.syncMLStatus(); } ); CT_ASSERT_EQUAL(STATUS_NOT_FOUND, status); } // Is run as Client::Source::LinkedItems::testSubsetStartSkip // where start = first detached recurrence to send and skip = detached recurrences // to skip before adding the next one (=> 0 = send all). // // "Exdate" instead of Skip is special: it picks the , + 1 and last // item, which typically leads to an irregular pattern and requires adding EXDATEs // in the activesyncd. void LocalTests::testSubset() { ClientTestConfig::LinkedItems_t items = getParentChildData(); int start, skip; std::string test = getCurrentTest(); pcrecpp::RE re("testSubsetStart(\\d+)(?:Skip(\\d+)|(Exdate))"); std::string exdate, optSkip; CT_ASSERT(re.PartialMatch(test, &start, &optSkip, &exdate)); if (exdate.empty()) { // skip case CT_ASSERT(!optSkip.empty()); skip = atoi(optSkip.c_str()); } else { // EXDATE case CT_ASSERT_EQUAL(std::string("Exdate"), exdate); skip = -1; } CT_ASSERT(items.size() > (size_t)start); CT_ASSERT(skip >= -1); // check that everything is empty, also resets change counter of sync source B CT_ASSERT_NO_THROW(deleteAll(createSourceA)); TestingSyncSourcePtr copy; SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); CT_ASSERT_NO_THROW(copy.reset()); // insert parent first, then child std::list sent; int i = start; while ((size_t)i < items.size() && ((start == 0 && skip == 0) || /* _0_0 really uses all items (stress test) */ skip == -1 || /* _x_e already is limited to 3 items */ i - start < 5)) { /* avoid huge number of items per test */ std::string data; std::string message = StringPrintf("start %d, skip %d, at %d of %d", start, skip, i, (int)items.size()); CT_ASSERT_NO_THROW_MESSAGE(message, insert(createSourceA, items[i], false, &data)); sent.push_back(data); SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB())); std::list actual(sent); if (items.m_testLinkedItemsSubsetAdditional) { std::string event = items.m_testLinkedItemsSubsetAdditional(start, skip, i, items.size()); if (!event.empty()) { actual.push_back(event); } } CT_ASSERT_NO_THROW_MESSAGE(message, compareDatabasesRef(*copy, actual)); if (skip >= 0) { // skip intermediate items i += skip + 1; } else if (i == start) { // go to second item i++; } else if (i == start + 1) { // go to last item CT_ASSERT((size_t)i != items.size() - 1); i = items.size() - 1; } else { // done with first, second and last item break; } } } ClientTestConfig::LinkedItems_t LocalTests::getParentChildData() { // extract suffix and use it as index for our config std::string test = getCurrentTest(); const std::string testname = "LinkedItems"; size_t off = test.find(testname); CT_ASSERT(off != test.npos); off += testname.size(); size_t end = test.find(':', off); CT_ASSERT(end != test.npos); std::string name = test.substr(off, end - off); BOOST_FOREACH(const ClientTestConfig::LinkedItems_t &items, config.m_linkedItems) { if (items.m_name == name) { return items; } } BOOST_FOREACH(const ClientTestConfig::LinkedItems_t &items, config.m_linkedItemsSubset) { if (items.m_name == name) { return items; } } CT_ASSERT_MESSAGE("linked items test data not found", false); return ClientTestConfig::LinkedItems_t(); } SyncTests::SyncTests(const std::string &name, ClientTest &cl, std::vector sourceIndices, bool isClientA) : CppUnit::TestSuite(name), client(cl) { sourceArray = new int[sourceIndices.size() + 1]; int offset = 0; for (std::vector::iterator it = sourceIndices.begin(); it != sourceIndices.end(); ++it) { ClientTest::Config config; client.getSyncSourceConfig(*it, config); if (!config.m_sourceName.empty()) { sourceArray[sources.size()+offset] = *it; if (!config.m_subConfigs.empty()) { vector subs; boost::split (subs, config.m_subConfigs, boost::is_any_of(",")); offset++; ClientTest::Config subConfig; BOOST_FOREACH (string sub, subs) { client.getSourceConfig (sub, subConfig); sources.push_back(std::pair(*it, cl.createLocalTests(sub, client.getLocalSourcePosition(sub), subConfig))); offset--; } } else { sources.push_back(std::pair(*it, cl.createLocalTests(config.m_sourceName, client.getLocalSourcePosition(config.m_sourceName), config))); } } } sourceArray[sources.size()+ offset] = -1; // check whether we have a second client ClientTest *clientB = cl.getClientB(); if (clientB) { accessClientB = clientB->createSyncTests(name, sourceIndices, false); } else { accessClientB = 0; } } SyncTests::~SyncTests() { for (source_it it = sources.begin(); it != sources.end(); ++it) { delete it->second; } delete [] sourceArray; if (accessClientB) { delete accessClientB; } } /** adds the supported tests to the instance itself */ void SyncTests::addTests(bool isFirstSource) { if (sources.size()) { const ClientTest::Config &config(sources[0].second->config); // run this test first, even if it is more complex: // if it works, all the following tests will run with // the server in a deterministic state if (config.m_createSourceA) { if (!config.m_insertItem.empty()) { ADD_TEST(SyncTests, testDeleteAllRefresh); } } ADD_TEST(SyncTests, testTwoWaySync); ADD_TEST(SyncTests, testSlowSync); ADD_TEST(SyncTests, testRefreshFromServerSync); ADD_TEST(SyncTests, testRefreshFromClientSync); ADD_TEST(SyncTests, testRefreshFromRemoteSync); ADD_TEST(SyncTests, testRefreshFromLocalSync); // testTimeout is independent of the actual peer; all it needs // is a SyncML client config. Can't test for that explicitly // here, so only rule out the test if we run in server mode. if (isFirstSource && (!getenv("CLIENT_TEST_MODE") || strcmp(getenv("CLIENT_TEST_MODE"), "server"))) { ADD_TEST(SyncTests, testTimeout); } if (config.m_compare && !config.m_testcases.empty() && !isServerMode()) { ADD_TEST(SyncTests, testConversion); } if (config.m_createSourceA) { if (!config.m_insertItem.empty()) { ADD_TEST(SyncTests, testRefreshFromServerSemantic); ADD_TEST(SyncTests, testRefreshFromClientSemantic); ADD_TEST(SyncTests, testRefreshStatus); // This test works regardless whether the peer can // restart: if restarts are not possible, it checks // that they don't occur. The rest of the tests then // only make sense when restarting works. ADD_TEST(SyncTests, testTwoWayRestart); if (getenv("CLIENT_TEST_PEER_CAN_RESTART")) { ADD_TEST(SyncTests, testSlowRestart); ADD_TEST(SyncTests, testRefreshFromLocalRestart); ADD_TEST(SyncTests, testOneWayFromLocalRestart); ADD_TEST(SyncTests, testRefreshFromRemoteRestart); ADD_TEST(SyncTests, testOneWayFromRemoteRestart); ADD_TEST(SyncTests, testManyRestarts); } if (accessClientB && config.m_dump && config.m_compare) { ADD_TEST(SyncTests, testCopy); ADD_TEST(SyncTests, testDelete); ADD_TEST(SyncTests, testAddUpdate); ADD_TEST(SyncTests, testManyItems); ADD_TEST(SyncTests, testManyDeletes); ADD_TEST(SyncTests, testSlowSyncSemantic); ADD_TEST(SyncTests, testComplexRefreshFromServerSemantic); ADD_TEST(SyncTests, testDeleteBothSides); if (config.m_updateItem.find("UID:") != std::string::npos && config.m_updateItem.find("LAST-MODIFIED:") != std::string::npos && sources.size() == 1) { ADD_TEST(SyncTests, testAddBothSides); ADD_TEST(SyncTests, testAddBothSidesRefresh); } // only add when testing individual source, // test data not guaranteed to be available for all sources if (sources.size() == 1 && !config.m_linkedItems.empty()) { ADD_TEST(SyncTests, testLinkedItemsParentChild); if (config.m_linkedItemsRelaxedSemantic) { ADD_TEST(SyncTests, testLinkedItemsChild); ADD_TEST(SyncTests, testLinkedItemsChildParent); } } if (!config.m_updateItem.empty()) { ADD_TEST(SyncTests, testUpdate); } if (!config.m_complexUpdateItem.empty()) { ADD_TEST(SyncTests, testComplexUpdate); } if (!config.m_mergeItem1.empty() && !config.m_mergeItem2.empty()) { ADD_TEST(SyncTests, testMerge); } if (config.m_import) { ADD_TEST(SyncTests, testTwinning); ADD_TEST(SyncTests, testItems); ADD_TEST(SyncTests, testItemsXML); if (config.m_update) { ADD_TEST(SyncTests, testExtensions); } } if (!config.m_templateItem.empty()) { ADD_TEST(SyncTests, testMaxMsg); ADD_TEST(SyncTests, testLargeObject); ADD_TEST(SyncTests, testOneWayFromServer); ADD_TEST(SyncTests, testOneWayFromClient); ADD_TEST(SyncTests, testOneWayFromRemote); ADD_TEST(SyncTests, testOneWayFromLocal); } } } } if (config.m_retrySync && !config.m_insertItem.empty() && !config.m_updateItem.empty() && accessClientB && config.m_dump && config.m_compare) { CppUnit::TestSuite *retryTests = new CppUnit::TestSuite(getName() + "::Retry"); ADD_TEST_TO_SUITE(retryTests, SyncTests, testInterruptResumeClientAdd); ADD_TEST_TO_SUITE(retryTests, SyncTests, testInterruptResumeClientRemove); ADD_TEST_TO_SUITE(retryTests, SyncTests, testInterruptResumeClientUpdate); ADD_TEST_TO_SUITE(retryTests, SyncTests, testInterruptResumeServerAdd); ADD_TEST_TO_SUITE(retryTests, SyncTests, testInterruptResumeServerRemove); ADD_TEST_TO_SUITE(retryTests, SyncTests, testInterruptResumeServerUpdate); ADD_TEST_TO_SUITE(retryTests, SyncTests, testInterruptResumeClientAddBig); ADD_TEST_TO_SUITE(retryTests, SyncTests, testInterruptResumeClientUpdateBig); ADD_TEST_TO_SUITE(retryTests, SyncTests, testInterruptResumeServerAddBig); ADD_TEST_TO_SUITE(retryTests, SyncTests, testInterruptResumeServerUpdateBig); ADD_TEST_TO_SUITE(retryTests, SyncTests, testInterruptResumeFull); addTest(FilterTest(retryTests)); } if (config.m_suspendSync && !config.m_insertItem.empty() && !config.m_updateItem.empty() && accessClientB && config.m_dump && config.m_compare) { CppUnit::TestSuite *suspendTests = new CppUnit::TestSuite(getName() + "::Suspend"); ADD_TEST_TO_SUITE(suspendTests, SyncTests, testUserSuspendClientAdd); ADD_TEST_TO_SUITE(suspendTests, SyncTests, testUserSuspendClientRemove); ADD_TEST_TO_SUITE(suspendTests, SyncTests, testUserSuspendClientUpdate); ADD_TEST_TO_SUITE(suspendTests, SyncTests, testUserSuspendServerAdd); ADD_TEST_TO_SUITE(suspendTests, SyncTests, testUserSuspendServerRemove); ADD_TEST_TO_SUITE(suspendTests, SyncTests, testUserSuspendServerUpdate); ADD_TEST_TO_SUITE(suspendTests, SyncTests, testUserSuspendClientAddBig); ADD_TEST_TO_SUITE(suspendTests, SyncTests, testUserSuspendClientUpdateBig); ADD_TEST_TO_SUITE(suspendTests, SyncTests, testUserSuspendServerAddBig); ADD_TEST_TO_SUITE(suspendTests, SyncTests, testUserSuspendServerUpdateBig); ADD_TEST_TO_SUITE(suspendTests, SyncTests, testUserSuspendFull); addTest(FilterTest(suspendTests)); } if (config.m_resendSync && !config.m_insertItem.empty() && !config.m_updateItem.empty() && accessClientB && config.m_dump && config.m_compare) { CppUnit::TestSuite *resendTests = new CppUnit::TestSuite(getName() + "::Resend"); ADD_TEST_TO_SUITE(resendTests, SyncTests, testResendClientAdd); ADD_TEST_TO_SUITE(resendTests, SyncTests, testResendClientRemove); ADD_TEST_TO_SUITE(resendTests, SyncTests, testResendClientUpdate); ADD_TEST_TO_SUITE(resendTests, SyncTests, testResendServerAdd); ADD_TEST_TO_SUITE(resendTests, SyncTests, testResendServerRemove); ADD_TEST_TO_SUITE(resendTests, SyncTests, testResendServerUpdate); ADD_TEST_TO_SUITE(resendTests, SyncTests, testResendFull); addTest(FilterTest(resendTests)); } if (getenv("CLIENT_TEST_RESEND_PROXY") && !config.m_insertItem.empty() && !config.m_updateItem.empty() && accessClientB && config.m_dump && config.m_compare) { CppUnit::TestSuite *resendTests = new CppUnit::TestSuite(getName() + "::ResendProxy"); ADD_TEST_TO_SUITE(resendTests, SyncTests, testResendProxyClientAdd); ADD_TEST_TO_SUITE(resendTests, SyncTests, testResendProxyClientRemove); ADD_TEST_TO_SUITE(resendTests, SyncTests, testResendProxyClientUpdate); ADD_TEST_TO_SUITE(resendTests, SyncTests, testResendProxyServerAdd); ADD_TEST_TO_SUITE(resendTests, SyncTests, testResendProxyServerRemove); ADD_TEST_TO_SUITE(resendTests, SyncTests, testResendProxyServerUpdate); ADD_TEST_TO_SUITE(resendTests, SyncTests, testResendProxyFull); addTest(FilterTest(resendTests)); } } } bool SyncTests::compareDatabases(const char *refFileBase, bool raiseAssert) { source_it it1; source_it it2; bool equal = true; CT_ASSERT(accessClientB); for (it1 = sources.begin(), it2 = accessClientB->sources.begin(); it1 != sources.end() && it2 != accessClientB->sources.end(); ++it1, ++it2) { TestingSyncSourcePtr copy; SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(it2->second->createSourceB())); if (refFileBase) { std::string refFile = refFileBase; refFile += it1->second->config.m_sourceName; refFile += ".dat"; simplifyFilename(refFile); if (!it1->second->compareDatabases(refFile.c_str(), *copy.get(), raiseAssert)) { equal = false; } } else { if (!it1->second->compareDatabases(NULL, *copy.get(), raiseAssert)) { equal = false; } } CT_ASSERT_NO_THROW(copy.reset()); } CT_ASSERT(it1 == sources.end()); CT_ASSERT(it2 == accessClientB->sources.end()); CT_ASSERT(!raiseAssert || equal); return equal; } /** deletes all items locally and on server */ void SyncTests::deleteAll(DeleteAllMode mode) { SyncPrefix prefix("deleteall", *this); const char *value = getenv ("CLIENT_TEST_DELETE_REFRESH"); if (value) { mode = DELETE_ALL_REFRESH; } switch(mode) { case DELETE_ALL_SYNC: // a refresh from server would slightly reduce the amount of data exchanged, but not all servers support it CT_ASSERT_NO_THROW(allSourcesDeleteAll()); doSync(__FILE__, __LINE__, "init", SyncOptions(SYNC_SLOW)); // now that client and server are in sync, delete locally and sync again CT_ASSERT_NO_THROW(allSourcesDeleteAll()); doSync(__FILE__, __LINE__, "twoway", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, 0,0,-1, true, SYNC_TWO_WAY))); break; case DELETE_ALL_REFRESH: // delete locally and then tell the server to "copy" the empty databases CT_ASSERT_NO_THROW(allSourcesDeleteAll()); doSync(__FILE__, __LINE__, "refreshserver", SyncOptions(RefreshFromLocalMode(), CheckSyncReport(0,0,0, 0,0,-1, true, SYNC_REFRESH_FROM_LOCAL))); break; } } /** get both clients in sync with empty server, then copy one item from client A to B */ void SyncTests::doCopy() { SyncPrefix copy("copy", *this); // check requirements CT_ASSERT(accessClientB); CT_ASSERT_NO_THROW(deleteAll()); accessClientB->deleteAll(); // insert into first database, copy to server CT_ASSERT_NO_THROW(allSourcesInsert()); doSync(__FILE__, __LINE__, "send", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, 1,0,0, true, SYNC_TWO_WAY))); // copy into second database accessClientB->doSync(__FILE__, __LINE__, "recv", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(1,0,0, 0,0,0, true, SYNC_TWO_WAY))); CT_ASSERT_NO_THROW(compareDatabases()); } /** * Replicate server database locally. Works with all servers * (including those which do not support refresh sync) because the * Synthesis engine will do a local delete + slow sync (by default) * and a real refresh sync only when asked to explicitly (Funambol). */ void SyncTests::refreshClient(SyncOptions options) { doSync(__FILE__, __LINE__, "refresh", options .setSyncMode(SYNC_REFRESH_FROM_REMOTE) .setCheckReport(CheckSyncReport(-1,0,-1, 0,0,0, true, SYNC_REFRESH_FROM_REMOTE))); } // delete all items, locally and on server using refresh-from-client sync void SyncTests::testDeleteAllRefresh() { source_it it; // start with clean local data CT_ASSERT_NO_THROW(allSourcesDeleteAll()); // copy something to server first; doesn't matter whether it has the // item already or not, as long as it exists there afterwards CT_ASSERT_NO_THROW(allSourcesInsert()); doSync(__FILE__, __LINE__, "insert", SyncOptions(SYNC_SLOW)); // now ensure we can delete it deleteAll(DELETE_ALL_REFRESH); // nothing stored locally? for (it = sources.begin(); it != sources.end(); ++it) { TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceA())); SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get())); CT_ASSERT_NO_THROW(source.reset()); } // make sure server really deleted everything doSync(__FILE__, __LINE__, "check", SyncOptions(SYNC_SLOW, CheckSyncReport(0,0,0, 0,0,0, true, SYNC_SLOW))); for (it = sources.begin(); it != sources.end(); ++it) { TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceA())); SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get())); CT_ASSERT_NO_THROW(source.reset()); } } // refresh-from-server sync, regardless of peer's role void SyncTests::testRefreshFromServerSync() { doSync(__FILE__, __LINE__, SyncOptions(SYNC_REFRESH_FROM_SERVER, CheckSyncReport(-1,-1,-1, -1,-1,-1, true, isServerMode() ? SYNC_REFRESH_FROM_LOCAL : SYNC_REFRESH_FROM_REMOTE))); } // do a refresh-from-client sync, regardless of peer's role void SyncTests::testRefreshFromClientSync() { doSync(__FILE__, __LINE__, SyncOptions(SYNC_REFRESH_FROM_CLIENT, CheckSyncReport(-1,-1,-1, -1,-1,-1, true, isServerMode() ? SYNC_REFRESH_FROM_REMOTE : SYNC_REFRESH_FROM_LOCAL))); } // do a refresh-from-remote sync, regardless of peer's role void SyncTests::testRefreshFromRemoteSync() { doSync(__FILE__, __LINE__, SyncOptions(SYNC_REFRESH_FROM_REMOTE, CheckSyncReport(-1,-1,-1, -1,-1,-1, true, SYNC_REFRESH_FROM_REMOTE))); } // do a refresh-from-local sync, regardless of peer's role void SyncTests::testRefreshFromLocalSync() { doSync(__FILE__, __LINE__, SyncOptions(SYNC_REFRESH_FROM_LOCAL, CheckSyncReport(-1,-1,-1, -1,-1,-1, true, SYNC_REFRESH_FROM_LOCAL))); } // delete all items, locally and on server using two-way sync void SyncTests::testDeleteAllSync() { CT_ASSERT_NO_THROW(deleteAll(DELETE_ALL_SYNC)); } // test that a refresh sync from an empty server leads to an empty datatbase // and no changes are sent to server during next two-way sync void SyncTests::testRefreshFromServerSemantic() { source_it it; // clean client and server CT_ASSERT_NO_THROW(deleteAll()); // insert item, then refresh from empty server CT_ASSERT_NO_THROW(allSourcesInsert()); doSync(__FILE__, __LINE__, "refresh", SyncOptions(RefreshFromPeerMode(), CheckSyncReport(0,0,-1, 0,0,0, true, SYNC_REFRESH_FROM_REMOTE))); // check for (it = sources.begin(); it != sources.end(); ++it) { TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceA())); SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get())); CT_ASSERT_NO_THROW(source.reset()); } doSync(__FILE__, __LINE__, "two-way", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, 0,0,0, true, SYNC_TWO_WAY))); } // test that a refresh sync from an empty client leads to an empty datatbase // and no changes are sent to server during next two-way sync void SyncTests::testRefreshFromClientSemantic() { source_it it; // clean client and server CT_ASSERT_NO_THROW(deleteAll()); // insert item, send to server CT_ASSERT_NO_THROW(allSourcesInsert()); doSync(__FILE__, __LINE__, "send", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, 1,0,0, true, SYNC_TWO_WAY))); // delete locally CT_ASSERT_NO_THROW(allSourcesDeleteAll()); // refresh from client doSync(__FILE__, __LINE__, "refresh", SyncOptions(RefreshFromLocalMode(), CheckSyncReport(0,0,0, 0,0,0, true, SYNC_REFRESH_FROM_LOCAL))); // check doSync(__FILE__, __LINE__, "check", SyncOptions(RefreshFromPeerMode(), CheckSyncReport(0,0,0, 0,0,0, true, SYNC_REFRESH_FROM_REMOTE))); } // tests the following sequence of events: // - insert item // - delete all items // - insert one other item // - refresh from client // => no items should now be listed as new, updated or deleted for this client during another sync void SyncTests::testRefreshStatus() { source_it it; CT_ASSERT_NO_THROW(allSourcesInsert(false)); CT_ASSERT_NO_THROW(allSourcesDeleteAll()); CT_ASSERT_NO_THROW(allSourcesInsert()); doSync(__FILE__, __LINE__, "refresh-from-client", SyncOptions(RefreshFromLocalMode(), CheckSyncReport(0,0,0, -1,-1,-1, /* strictly speaking 1,0,0, but not sure exactly what the server will be told */ true, SYNC_REFRESH_FROM_LOCAL))); doSync(__FILE__, __LINE__, "two-way", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, 0,0,0, true, SYNC_TWO_WAY))); } static void log(const char *text) { CLIENT_TEST_LOG("%s", text); } static void logSyncSourceReport(SyncSource *source) { CLIENT_TEST_LOG("source %s, start of cycle #%d: local new/mod/del/conflict %d/%d/%d/%d, remote %d/%d/%d/%d, mode %s", source->getName().c_str(), source->getRestarts(), source->getItemStat(SyncSourceReport::ITEM_LOCAL, SyncSourceReport::ITEM_ADDED, SyncSource::ITEM_TOTAL), source->getItemStat(SyncSourceReport::ITEM_LOCAL, SyncSourceReport::ITEM_UPDATED, SyncSource::ITEM_TOTAL), source->getItemStat(SyncSourceReport::ITEM_LOCAL, SyncSourceReport::ITEM_REMOVED, SyncSource::ITEM_TOTAL), source->getItemStat(SyncSourceReport::ITEM_LOCAL, SyncSourceReport::ITEM_ANY, SyncSource::ITEM_REJECT), source->getItemStat(SyncSourceReport::ITEM_REMOTE, SyncSourceReport::ITEM_ADDED, SyncSource::ITEM_TOTAL), source->getItemStat(SyncSourceReport::ITEM_REMOTE, SyncSourceReport::ITEM_UPDATED, SyncSource::ITEM_TOTAL), source->getItemStat(SyncSourceReport::ITEM_REMOTE, SyncSourceReport::ITEM_REMOVED, SyncSource::ITEM_TOTAL), source->getItemStat(SyncSourceReport::ITEM_REMOTE, SyncSourceReport::ITEM_ANY, SyncSource::ITEM_REJECT), PrettyPrintSyncMode(source->getFinalSyncMode()).c_str()); } /** * Helper function, to be used inside a SyncOptions start callback * to connect all sources instantiated for a sync with the given * pre-operation signal. */ template bool connectSourceSignal(SyncContext &context, W SyncSource::Operations::*operation, M getSignal, const S &slot) { BOOST_FOREACH(const SyncSource *source, *context.getSources()) { ((source->getOperations().*operation).*getSignal)().connect(slot); } return false; } void SyncTests::doRestartSync(SyncMode mode) { CT_ASSERT_NO_THROW(deleteAll()); int startCount = 0; bool needToConnect = true; typedef std::map Reports_t; typedef std::map Cycles_t; Cycles_t results; // Triggered for every m_startDataRead. // // It records the current source statistics for later checking and // logs it. // // Also requests a restart at the very beginning, once. Must be // done before m_endDataWrite, because then it might be too late // to restart. boost::function start = (boost::lambda::if_then(boost::lambda::bind(&Cycles_t::empty, boost::ref(results)), (boost::lambda::bind(log, "requesting restart"), boost::lambda::bind(SyncContext::requestAnotherSync))), (boost::lambda::var(results)[boost::lambda::bind(&SyncSource::getRestarts, &boost::lambda::_1)] [boost::lambda::bind(&SyncSource::getName, &boost::lambda::_1)] = boost::lambda::_1), boost::lambda::bind(logSyncSourceReport, &boost::lambda::_1) ); // Triggered at the end of each m_endDataWrite. // // Adds a new item or (in later syncs) updates/deletes // it. Because the cycle is other, those changes won't // interfere with the cycle. Doing real concurrent // changes is something for another tests... boost::function end = boost::bind(boost::function( boost::lambda::if_then(++boost::lambda::var(startCount) == sources.size(), (boost::lambda::bind(log, "inserting one item"), boost::lambda::bind(&SyncTests::allSourcesInsert, this, true))) )); SyncOptions::Callback_t setup = (boost::lambda::if_then(boost::lambda::var(needToConnect), (boost::lambda::var(needToConnect) = false, boost::lambda::bind(connectSourceSignal, boost::lambda::_1, &SyncSource::Operations::m_startDataRead, &SyncSource::Operations::StartDataRead_t::getPreSignal, boost::cref(start)), boost::lambda::bind(connectSourceSignal, boost::lambda::_1, &SyncSource::Operations::m_endDataWrite, &SyncSource::Operations::EndDataWrite_t::getPostSignal, boost::cref(end)) )), boost::lambda::constant(false) ); bool canRestart = getenv("CLIENT_TEST_PEER_CAN_RESTART") != NULL && !isServerMode(); CT_ASSERT_NO_THROW(doSync(__FILE__, __LINE__, "add", SyncOptions(mode, CheckSyncReport(0, 0, // TODO (?): should the item added after the initial refresh-from-remote be deleted in the second cycle? // Right now it isn't, because the second sync is // a one-way-from-remote. mode == SYNC_REFRESH_FROM_REMOTE ? /* 1 */ 0 : 0, // nothing transferred when item only exists locally // and not transferring to peer !canRestart ? 0 : (mode == SYNC_ONE_WAY_FROM_REMOTE || mode == SYNC_REFRESH_FROM_REMOTE) ? 0 : 1, 0, 0, true, mode) .setRestarts(canRestart ? 1 : 0)) .setStartCallback(setup) )); // two cycles if restarted, one otherwise CT_ASSERT_EQUAL((size_t)(canRestart ? 2 : 1), results.size()); // nothing transfered before first or second cycle BOOST_FOREACH(const Cycles_t::value_type &cycle, results) { CT_ASSERT_EQUAL(sources.size(), cycle.second.size()); BOOST_FOREACH(const Reports_t::value_type &entry, cycle.second) { CT_ASSERT_NO_THROW(CheckSyncReport(0,0, 0, 0,0,0) .setRestarts(cycle.first) .check(entry.first, entry.second)); } } // one item exists now, in all cases // (but see remark about refresh-from-remote!) BOOST_FOREACH(source_array_t::value_type &source_pair, sources) { TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(source_pair.second->createSourceA())); CT_ASSERT_EQUAL(1, countItems(source.get())); } if (mode == SYNC_REFRESH_FROM_REMOTE || !canRestart) { // Can't continue testing for refresh-from-remote, because the // item was never sent to remote and will be gone locally // after the next refresh-from-remote (prevents updating and // deleting it locally). // Without restart support further tests don't make much sense. // We already verified above that a restart request was // correctly rejected/ignored. return; } // update item while the sync runs #ifndef __clang_analyzer__ needToConnect = true; #endif startCount = 0; results.clear(); end = boost::bind(boost::function( boost::lambda::if_then(++boost::lambda::var(startCount) == sources.size(), (boost::lambda::bind(log, "update one item"), boost::lambda::bind(&SyncTests::allSourcesUpdate, this))) )); CT_ASSERT_NO_THROW(doSync(__FILE__, __LINE__, "update", SyncOptions(mode, CheckSyncReport(0,0,0, // refresh-from-local and slow sync transfer existing item // in first cycle anew (mode == SYNC_REFRESH_FROM_LOCAL || mode == SYNC_SLOW) ? 1 : 0, // nothing transferred when item only exists locally // and not transferring to peer mode == SYNC_ONE_WAY_FROM_REMOTE ? 0 : 1, 0, true, mode) .setRestarts(1)) .setStartCallback(setup) )); // two cycles CT_ASSERT_EQUAL((size_t)2, results.size()); // nothing transfered before first or second cycle BOOST_FOREACH(const Cycles_t::value_type &cycle, results) { CLIENT_TEST_LOG("checking cycle #%d", cycle.first); CT_ASSERT_EQUAL(sources.size(), cycle.second.size()); BOOST_FOREACH(const Reports_t::value_type &entry, cycle.second) { CT_ASSERT_NO_THROW(CheckSyncReport(0,0,0, // refresh-from-local and slow sync transfer existing item // in first cycle anew (cycle.first == 1 && (mode == SYNC_REFRESH_FROM_LOCAL || mode == SYNC_SLOW)) ? 1 : 0, 0,0) .setRestarts(cycle.first) .check(entry.first, entry.second)); } } // one item exists now, in all cases // (but see remark about refresh-from-remote!) BOOST_FOREACH(source_array_t::value_type &source_pair, sources) { TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(source_pair.second->createSourceA())); CT_ASSERT_EQUAL(1, countItems(source.get())); } // delete item while the sync runs #ifndef __clang_analyzer__ needToConnect = true; #endif startCount = 0; results.clear(); end = boost::bind(boost::function( boost::lambda::if_then(++boost::lambda::var(startCount) == sources.size(), (boost::lambda::bind(log, "delete one item"), boost::lambda::bind(&SyncTests::allSourcesDeleteAll, this))) )); CT_ASSERT_NO_THROW(doSync(__FILE__, __LINE__, "delete", SyncOptions(mode, CheckSyncReport(0,0,0, // refresh-from-local and slow sync transfer existing item // in first cycle anew (mode == SYNC_REFRESH_FROM_LOCAL || mode == SYNC_SLOW) ? 1 : 0, 0, // nothing transferred when item only existed locally // and not transferring to peer mode == SYNC_ONE_WAY_FROM_REMOTE ? 0 : 1, true, mode) .setRestarts(1)) .setStartCallback(setup) )); // two cycles CT_ASSERT_EQUAL((size_t)2, results.size()); // nothing transfered before first or second cycle BOOST_FOREACH(const Cycles_t::value_type &cycle, results) { CT_ASSERT_EQUAL(sources.size(), cycle.second.size()); BOOST_FOREACH(const Reports_t::value_type &entry, cycle.second) { CT_ASSERT_NO_THROW(CheckSyncReport(0,0, 0, // refresh-from-local and slow sync transfer existing item // in first cycle anew (cycle.first == 1 && (mode == SYNC_REFRESH_FROM_LOCAL || mode == SYNC_SLOW)) ? 1 : 0, 0,0) .setRestarts(cycle.first) .check(entry.first, entry.second)); } } // no item exists now, in all cases BOOST_FOREACH(source_array_t::value_type &source_pair, sources) { TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(source_pair.second->createSourceA())); CT_ASSERT_EQUAL(0, countItems(source.get())); } } // two-way sync when both sides are empty, // insert item locally while sync runs, restart // => one item sent to peer void SyncTests::testTwoWayRestart() { CT_ASSERT_NO_THROW(doRestartSync(SYNC_TWO_WAY)); } // slow sync when both sides are empty, // insert item locally while sync runs, restart // => one item sent to peer void SyncTests::testSlowRestart() { CT_ASSERT_NO_THROW(doRestartSync(SYNC_SLOW)); } // refresh-from-local sync when both sides are empty, // insert item locally while sync runs, restart // => one item sent to peer void SyncTests::testRefreshFromLocalRestart() { CT_ASSERT_NO_THROW(doRestartSync(SYNC_REFRESH_FROM_LOCAL)); } // one-way-from-local sync when both sides are empty, // insert item locally while sync runs, restart // => one item sent to peer void SyncTests::testOneWayFromLocalRestart() { CT_ASSERT_NO_THROW(doRestartSync(SYNC_ONE_WAY_FROM_LOCAL)); } // refresh-from-remote sync when both sides are empty, // insert item locally while sync runs, restart // => *nothing* sent to peer void SyncTests::testRefreshFromRemoteRestart() { CT_ASSERT_NO_THROW(doRestartSync(SYNC_REFRESH_FROM_REMOTE)); } // one-way-from-remote sync when both sides are empty, // insert item locally while sync runs, restart // => *nothing* sent to peer void SyncTests::testOneWayFromRemoteRestart() { CT_ASSERT_NO_THROW(doRestartSync(SYNC_ONE_WAY_FROM_REMOTE)); } // Start with empty database, refresh peer. // Then add 1, 2, 4, 8 items in four cycles, // update them the same way, and finally delete them. // Results in 12 cycles with different changes and // one empty, final cycle. void SyncTests::testManyRestarts() { CT_ASSERT_NO_THROW(deleteAll()); int startCount = 0; bool needToConnect = true; typedef std::map Reports_t; typedef std::map Cycles_t; Cycles_t results; std::map > luids; // Triggered for every m_startDataRead. // // It records the current source statistics for later checking, // logs it, and does the item changes. boost::function start = (boost::lambda::if_then(boost::lambda::var(startCount) % sources.size() == 0, ( boost::lambda::switch_statement(boost::lambda::var(startCount) / sources.size(), boost::lambda::case_statement<0>( (boost::lambda::bind(log, "insert 1 item, restart"), boost::lambda::bind(&SyncTests::allSourcesInsertMany, this, 1, 1, boost::ref(luids)), boost::lambda::bind(SyncContext::requestAnotherSync) )), boost::lambda::case_statement<1>( (boost::lambda::bind(log, "insert 2 items, restart"), boost::lambda::bind(&SyncTests::allSourcesInsertMany, this, 2, 2, boost::ref(luids)), boost::lambda::bind(SyncContext::requestAnotherSync) )), boost::lambda::case_statement<2>( (boost::lambda::bind(log, "insert 4 items, restart"), boost::lambda::bind(&SyncTests::allSourcesInsertMany, this, 4, 4, boost::ref(luids)), boost::lambda::bind(SyncContext::requestAnotherSync) )), boost::lambda::case_statement<3>( (boost::lambda::bind(log, "insert 8 items, restart"), boost::lambda::bind(&SyncTests::allSourcesInsertMany, this, 8, 8, boost::ref(luids)), boost::lambda::bind(SyncContext::requestAnotherSync) )), boost::lambda::case_statement<4>( (boost::lambda::bind(log, "update 1 item, restart"), boost::lambda::bind(&SyncTests::allSourcesUpdateMany, this, 1, 1, 1, boost::ref(luids), 0), boost::lambda::bind(SyncContext::requestAnotherSync) )), boost::lambda::case_statement<5>( (boost::lambda::bind(log, "update 2 items, restart"), boost::lambda::bind(&SyncTests::allSourcesUpdateMany, this, 2, 2, 1, boost::ref(luids), 1), boost::lambda::bind(SyncContext::requestAnotherSync) )), boost::lambda::case_statement<6>( (boost::lambda::bind(log, "update 4 items, restart"), boost::lambda::bind(&SyncTests::allSourcesUpdateMany, this, 4, 4, 1, boost::ref(luids), 3), boost::lambda::bind(SyncContext::requestAnotherSync) )), boost::lambda::case_statement<7>( (boost::lambda::bind(log, "update 8 items, restart"), boost::lambda::bind(&SyncTests::allSourcesUpdateMany, this, 8, 8, 1, boost::ref(luids), 7), boost::lambda::bind(SyncContext::requestAnotherSync) )) ), // must break up switch statement, it only has a limited number of case slots boost::lambda::switch_statement(boost::lambda::var(startCount) / sources.size(), boost::lambda::case_statement<8>( (boost::lambda::bind(log, "delete 1 item, restart"), boost::lambda::bind(&SyncTests::allSourcesRemoveMany, this, 1, boost::ref(luids), 0), boost::lambda::bind(SyncContext::requestAnotherSync) )), boost::lambda::case_statement<9>( (boost::lambda::bind(log, "delete 2 items, restart"), boost::lambda::bind(&SyncTests::allSourcesRemoveMany, this, 2, boost::ref(luids), 1), boost::lambda::bind(SyncContext::requestAnotherSync) )), boost::lambda::case_statement<10>( (boost::lambda::bind(log, "delete 4 items, restart"), boost::lambda::bind(&SyncTests::allSourcesRemoveMany, this, 4, boost::ref(luids), 3), boost::lambda::bind(SyncContext::requestAnotherSync) )), boost::lambda::case_statement<11>( (boost::lambda::bind(log, "delete 8 items, restart"), boost::lambda::bind(&SyncTests::allSourcesRemoveMany, this, 8, boost::ref(luids), 7), boost::lambda::bind(SyncContext::requestAnotherSync) )) ) ) ), (boost::lambda::var(results)[boost::lambda::bind(&SyncSource::getRestarts, &boost::lambda::_1)] [boost::lambda::bind(&SyncSource::getName, &boost::lambda::_1)] = boost::lambda::_1 ), boost::lambda::bind(logSyncSourceReport, &boost::lambda::_1), ++boost::lambda::var(startCount) ); SyncOptions::Callback_t setup = (boost::lambda::if_then(boost::lambda::var(needToConnect), (boost::lambda::var(needToConnect) = false, boost::lambda::bind(connectSourceSignal, boost::lambda::_1, &SyncSource::Operations::m_startDataRead, &SyncSource::Operations::StartDataRead_t::getPreSignal, boost::cref(start)) )), boost::lambda::constant(false) ); CT_ASSERT_NO_THROW(doSync(__FILE__, __LINE__, SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0, 0, 0, 15, 15, 15, true, SYNC_TWO_WAY) .setRestarts(12)) .setStartCallback(setup) )); // 13 cycles CT_ASSERT_EQUAL((size_t)13, results.size()); static const int changes[13][3] = { { 0, 0, 0 }, // nothing before first cycle { 1, 0, 0 }, // result of first cycle { 3, 0, 0 }, // statistics are cummulative: first + second { 7, 0, 0 }, { 15, 0, 0 }, { 15, 1, 0 }, { 15, 3, 0 }, { 15, 7, 0 }, { 15, 15, 0 }, { 15, 15, 1 }, { 15, 15, 3 }, { 15, 15, 7 }, { 15, 15, 15 } }; BOOST_FOREACH(const Cycles_t::value_type &cycle, results) { CT_ASSERT_EQUAL(sources.size(), cycle.second.size()); BOOST_FOREACH(const Reports_t::value_type &entry, cycle.second) { const int *c = changes[cycle.first]; CLIENT_TEST_LOG("Checking stats before cycle #%d, source %s: expected remote %d/%d/%d", cycle.first, entry.first.c_str(), c[0], c[1], c[2]); CT_ASSERT_NO_THROW(CheckSyncReport(0,0,0, c[0], c[1], c[2]) .setRestarts(cycle.first) .check(entry.first, entry.second)); } } // no item exists now BOOST_FOREACH(source_array_t::value_type &source_pair, sources) { TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(source_pair.second->createSourceA())); CT_ASSERT_EQUAL(0, countItems(source.get())); } } // test that a two-way sync copies an item from one address book into the other void SyncTests::testCopy() { CT_ASSERT_NO_THROW(doCopy()); CT_ASSERT_NO_THROW(compareDatabases()); } // test that a two-way sync copies updates from database to the other client, // using simple data commonly supported by servers void SyncTests::testUpdate() { CT_ASSERT(sources.begin() != sources.end()); CT_ASSERT(!sources.begin()->second->config.m_updateItem.empty()); // setup client A, B and server so that they all contain the same item CT_ASSERT_NO_THROW(doCopy()); source_it it; for (it = sources.begin(); it != sources.end(); ++it) { CT_ASSERT_NO_THROW(it->second->update(it->second->createSourceA, it->second->config.m_updateItem)); } doSync(__FILE__, __LINE__, "update", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, 0,1,0, true, SYNC_TWO_WAY))); accessClientB->doSync(__FILE__, __LINE__, "update", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,1,0, 0,0,0, true, SYNC_TWO_WAY))); CT_ASSERT_NO_THROW(compareDatabases()); } // test that a two-way sync copies updates from database to the other client, // using data that some, but not all servers support, like adding a second // phone number to a contact void SyncTests::testComplexUpdate() { // setup client A, B and server so that they all contain the same item CT_ASSERT_NO_THROW(doCopy()); source_it it; for (it = sources.begin(); it != sources.end(); ++it) { it->second->update(it->second->createSourceA, /* this test might get executed with some sources which have a complex update item while others don't: use the normal update item for them or even just the same item */ !it->second->config.m_complexUpdateItem.empty() ? it->second->config.m_complexUpdateItem : !it->second->config.m_updateItem.empty() ? it->second->config.m_updateItem : it->second->config.m_insertItem ); } doSync(__FILE__, __LINE__, "update", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, 0,1,0, true, SYNC_TWO_WAY))); accessClientB->doSync(__FILE__, __LINE__, "update", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,1,0, 0,0,0, true, SYNC_TWO_WAY))); CT_ASSERT_NO_THROW(compareDatabases()); } // test that a two-way sync deletes the copy of an item in the other database void SyncTests::testDelete() { // setup client A, B and server so that they all contain the same item CT_ASSERT_NO_THROW(doCopy()); // delete it on A CT_ASSERT_NO_THROW(allSourcesDeleteAll()); // transfer change from A to server to B doSync(__FILE__, __LINE__, "delete", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, 0,0,1, true, SYNC_TWO_WAY))); accessClientB->doSync(__FILE__, __LINE__, "delete", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,1, 0,0,0, true, SYNC_TWO_WAY))); // check client B: shouldn't have any items now for (source_it it = sources.begin(); it != sources.end(); ++it) { TestingSyncSourcePtr copy; SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(it->second->createSourceA())); SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get())); CT_ASSERT_NO_THROW(copy.reset()); } } // test what the server does when it finds that different // fields of the same item have been modified void SyncTests::testMerge() { // setup client A, B and server so that they all contain the same item CT_ASSERT_NO_THROW(doCopy()); // update in client A source_it it; for (it = sources.begin(); it != sources.end(); ++it) { CT_ASSERT_NO_THROW(it->second->update(it->second->createSourceA, it->second->config.m_mergeItem1)); } // update in client B for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) { CT_ASSERT_NO_THROW(it->second->update(it->second->createSourceA, it->second->config.m_mergeItem2)); } // send change to server from client A (no conflict) doSync(__FILE__, __LINE__, "update", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, 0,1,0, true, SYNC_TWO_WAY))); // Now the changes from client B (conflict!). // There are several possible outcomes: // - client item completely replaces server item // - server item completely replaces client item (update on client) // - server merges and updates client accessClientB->doSync(__FILE__, __LINE__, "conflict", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(-1,-1,-1, -1,-1,-1, true, SYNC_TWO_WAY))); // figure out how the conflict during ".conflict" was handled for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) { TestingSyncSourcePtr copy; SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(it->second->createSourceA())); int numItems = 0; SOURCE_ASSERT_NO_FAILURE(copy.get(), numItems = countItems(copy.get())); CT_ASSERT(numItems >= 1); CT_ASSERT(numItems <= 2); std::cerr << " \"" << it->second->config.m_sourceName << ": " << (numItems == 1 ? "conflicting items were merged" : "both of the conflicting items were preserved") << "\" "; std::cerr.flush(); CT_ASSERT_NO_THROW(copy.reset()); } // now pull the same changes into client A doSync(__FILE__, __LINE__, "refresh", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(-1,-1,-1, 0,0,0, true, SYNC_TWO_WAY))); // client A and B should have identical data now CT_ASSERT_NO_THROW(compareDatabases()); // Furthermore, it should be identical with the server. // Be extra careful and pull that data anew and compare once more. doSync(__FILE__, __LINE__, "check", SyncOptions(RefreshFromPeerMode(), CheckSyncReport(-1,-1,-1, -1,-1,-1, true, SYNC_REFRESH_FROM_REMOTE))); CT_ASSERT_NO_THROW(compareDatabases()); } // test what the server does when it has to execute a slow sync // with identical data on client and server: // expected behaviour is that nothing changes void SyncTests::testTwinning() { // clean server and client A CT_ASSERT_NO_THROW(deleteAll()); // import test data source_it it; for (it = sources.begin(); it != sources.end(); ++it) { CT_ASSERT_NO_THROW(it->second->testImport()); } // send to server doSync(__FILE__, __LINE__, "send", SyncOptions(SYNC_TWO_WAY)); // ensure that client has the same data, thus ignoring data conversion // issues (those are covered by testItems()) CT_ASSERT_NO_THROW(refreshClient()); // copy to client B to have another copy CT_ASSERT_NO_THROW(accessClientB->refreshClient()); // slow sync should not change anything doSync(__FILE__, __LINE__, "twinning", SyncOptions(SYNC_SLOW)); // check CT_ASSERT_NO_THROW(compareDatabases()); } // tests one-way sync from peer: // - get both clients and server in sync with no items anywhere // - add one item on first client, copy to server // - add a different item on second client, one-way-from-server // - two-way sync with first client // => one item on first client, two on second // - delete on first client, sync that to second client // via two-way sync + one-way-from-server // => one item left on second client (the one inserted locally) void SyncTests::doOneWayFromRemote(SyncMode oneWayFromRemote) { // no items anywhere CT_ASSERT_NO_THROW(deleteAll()); CT_ASSERT_NO_THROW(accessClientB->refreshClient()); // check that everything is empty, also resets change tracking // in second sources of each client source_it it; for (it = sources.begin(); it != sources.end(); ++it) { if (it->second->config.m_createSourceB) { TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get())); CT_ASSERT_NO_THROW(source.reset()); } } for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) { if (it->second->config.m_createSourceB) { TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get())); CT_ASSERT_NO_THROW(source.reset()); } } // add one item on first client, copy to server, and check change tracking via second source for (it = sources.begin(); it != sources.end(); ++it) { CT_ASSERT_NO_THROW(it->second->insertManyItems(it->second->createSourceA, 200, 1)); } doSync(__FILE__, __LINE__, "send", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, 1,0,0, true, SYNC_TWO_WAY))); for (it = sources.begin(); it != sources.end(); ++it) { if (it->second->config.m_createSourceB) { TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get())); CT_ASSERT_NO_THROW(source.reset()); } } // add a different item on second client, one-way-from-server // => one item added locally, none sent to server for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) { CT_ASSERT_NO_THROW(it->second->insertManyItems(it->second->createSourceA, 2, 1)); if (it->second->config.m_createSourceB) { TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get())); CT_ASSERT_NO_THROW(source.reset()); } } accessClientB->doSync(__FILE__, __LINE__, "recv", SyncOptions(oneWayFromRemote, CheckSyncReport(1,0,0, 0,0,0, true, SYNC_ONE_WAY_FROM_REMOTE))); for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) { if (it->second->config.m_createSourceB) { TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 2, countItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get())); CT_ASSERT_NO_THROW(source.reset()); } } // two-way sync with first client for verification // => no changes doSync(__FILE__, __LINE__, "check", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, 0,0,0, true, SYNC_TWO_WAY))); for (it = sources.begin(); it != sources.end(); ++it) { if (it->second->config.m_createSourceB) { TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get())); CT_ASSERT_NO_THROW(source.reset()); } } // delete items on clientA, sync to server for (it = sources.begin(); it != sources.end(); ++it) { CT_ASSERT_NO_THROW(it->second->deleteAll(it->second->createSourceA)); if (it->second->config.m_createSourceB) { TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 1, countDeletedItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get())); CT_ASSERT_NO_THROW(source.reset()); } } doSync(__FILE__, __LINE__, "delete", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, 0,0,1, true, SYNC_TWO_WAY))); for (it = sources.begin(); it != sources.end(); ++it) { if (it->second->config.m_createSourceB) { TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get())); CT_ASSERT_NO_THROW(source.reset()); } } // sync the same change to second client // => one item left (the one inserted locally) accessClientB->doSync(__FILE__, __LINE__, "delete", SyncOptions(oneWayFromRemote, CheckSyncReport(0,0,1, 0,0,0, true, SYNC_ONE_WAY_FROM_REMOTE))); for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) { if (it->second->config.m_createSourceB) { TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 1, countDeletedItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get())); CT_ASSERT_NO_THROW(source.reset()); } } } // one-way-from-remote test with one-way-from-client/server, depending // on role of remote side void SyncTests::testOneWayFromServer() { CT_ASSERT_NO_THROW(doOneWayFromRemote(OneWayFromPeerMode())); } void SyncTests::testOneWayFromRemote() { CT_ASSERT_NO_THROW(doOneWayFromRemote(SYNC_ONE_WAY_FROM_REMOTE)); } // tests one-way sync from local side: // - get both clients and server in sync with no items anywhere // - add one item on first client, copy to server // - add a different item on second client, one-way-from-client // - two-way sync with first client // => two items on first client, one on second // - delete on second client, sync that to first client // via one-way-from-client, two-way // => one item left on first client (the one inserted locally) void SyncTests::doOneWayFromLocal(SyncMode oneWayFromLocal) { // no items anywhere CT_ASSERT_NO_THROW(deleteAll()); CT_ASSERT_NO_THROW(accessClientB->deleteAll()); // check that everything is empty, also resets change tracking // in second sources of each client source_it it; for (it = sources.begin(); it != sources.end(); ++it) { if (it->second->config.m_createSourceB) { TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get())); CT_ASSERT_NO_THROW(source.reset()); } } for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) { if (it->second->config.m_createSourceB) { TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get())); CT_ASSERT_NO_THROW(source.reset()); } } // add one item on first client, copy to server, and check change tracking via second source for (it = sources.begin(); it != sources.end(); ++it) { CT_ASSERT_NO_THROW(it->second->insertManyItems(it->second->createSourceA, 1, 1)); } doSync(__FILE__, __LINE__, "send", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, 1,0,0, true, SYNC_TWO_WAY))); for (it = sources.begin(); it != sources.end(); ++it) { if (it->second->config.m_createSourceB) { TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get())); CT_ASSERT_NO_THROW(source.reset()); } } // add a different item on second client, one-way-from-client // => no item added locally, one sent to server for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) { CT_ASSERT_NO_THROW(it->second->insertManyItems(it->second->createSourceA, 2, 1)); if (it->second->config.m_createSourceB) { TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get())); CT_ASSERT_NO_THROW(source.reset()); } } accessClientB->doSync(__FILE__, __LINE__, "send", SyncOptions(oneWayFromLocal, CheckSyncReport(0,0,0, 1,0,0, true, SYNC_ONE_WAY_FROM_LOCAL))); for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) { if (it->second->config.m_createSourceB) { TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get())); CT_ASSERT_NO_THROW(source.reset()); } } // two-way sync with client A for verification // => receive one item doSync(__FILE__, __LINE__, "check", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(1,0,0, 0,0,0, true, SYNC_TWO_WAY))); for (it = sources.begin(); it != sources.end(); ++it) { if (it->second->config.m_createSourceB) { TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 2, countItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get())); CT_ASSERT_NO_THROW(source.reset()); } } // delete items on client B, sync to server for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) { CT_ASSERT_NO_THROW(it->second->deleteAll(it->second->createSourceA)); if (it->second->config.m_createSourceB) { TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 1, countDeletedItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get())); CT_ASSERT_NO_THROW(source.reset()); } } accessClientB->doSync(__FILE__, __LINE__, "delete", SyncOptions(oneWayFromLocal, CheckSyncReport(0,0,0, 0,0,1, true, SYNC_ONE_WAY_FROM_LOCAL))); for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) { if (it->second->config.m_createSourceB) { TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get())); CT_ASSERT_NO_THROW(source.reset()); } } // sync the same change to client A // => one item left (the one inserted locally) doSync(__FILE__, __LINE__, "delete", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,1, 0,0,0, true, SYNC_TWO_WAY))); for (it = sources.begin(); it != sources.end(); ++it) { if (it->second->config.m_createSourceB) { TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 1, countDeletedItems(source.get())); SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get())); CT_ASSERT_NO_THROW(source.reset()); } } } // do a two-way sync without additional checks, // may or may not actually be done in two-way mode void SyncTests::testTwoWaySync() { doSync(__FILE__, __LINE__, SyncOptions(SYNC_TWO_WAY)); } void SyncTests::testSlowSync() { doSync(__FILE__, __LINE__, SyncOptions(SYNC_SLOW, CheckSyncReport(-1,-1,-1, -1,-1,-1, true, SYNC_SLOW))); } // one-way-from-local test with one-way-from-client/server, depending // on role of local side void SyncTests::testOneWayFromClient() { CT_ASSERT_NO_THROW(doOneWayFromLocal(OneWayFromLocalMode())); } // do a slow sync without additional checks void SyncTests::testOneWayFromLocal() { CT_ASSERT_NO_THROW(doOneWayFromLocal(SYNC_ONE_WAY_FROM_LOCAL)); } // get engine ready, then use it to convert our test items // to and from the internal field list void SyncTests::testConversion() { bool success = false; SyncOptions::Callback_t callback = boost::bind(&SyncTests::doConversionCallback, this, &success, _1, _2); doSync(__FILE__, __LINE__, SyncOptions(SYNC_TWO_WAY, CheckSyncReport(-1,-1,-1, -1,-1,-1, false)) .setStartCallback(callback)); CT_ASSERT(success); } bool SyncTests::doConversionCallback(bool *success, SyncContext &syncClient, SyncOptions &options) { *success = false; for (source_it it = sources.begin(); it != sources.end(); ++it) { const ClientTest::Config *config = &it->second->config; TestingSyncSource *source = static_cast(syncClient.findSource(config->m_sourceName)); CT_ASSERT(source); std::string type = source->getNativeDatatypeName(); if (type.empty()) { continue; } std::list items; std::string testcases; ClientTest::getItems(config->m_testcases, items, testcases); std::string converted = getCurrentTest(); converted += ".converted."; converted += config->m_sourceName; converted += ".dat"; simplifyFilename(converted); std::ofstream out(converted.c_str()); BOOST_FOREACH(const string &item, items) { string convertedItem = item; if(!sysync::DataConversion(syncClient.getSession().get(), type.c_str(), type.c_str(), convertedItem)) { SE_LOG_ERROR(NULL, "failed parsing as %s:\n%s", type.c_str(), item.c_str()); } else { out << convertedItem << "\n"; } } out.close(); // The test used peer-specific test cases, but the actual // result does not depend on the peer because we haven't // received the peer's DevInf at the point where we // import/export the test cases (=> don't apply peer-specific // synccompare workarounds). // // Due to the lack of DevInf, properties and parameters which // need to be enabled via DevInf get lost (= filter them out). ScopedEnvChange env("CLIENT_TEST_SERVER", ""); ScopedEnvChange envParams("CLIENT_TEST_STRIP_PARAMETERS", "X-EVOLUTION-UI-SLOT"); CT_ASSERT(config->m_compare(client, testcases, converted)); } // abort sync after completing the test successfully (no exception so far!) *success = true; return true; } // imports test data, transmits it from client A to the server to // client B and then compares which of the data has been transmitted void SyncTests::testItems() { // clean server and first test database CT_ASSERT_NO_THROW(deleteAll()); // import data source_it it; for (it = sources.begin(); it != sources.end(); ++it) { CT_ASSERT_NO_THROW(it->second->testImport()); } // transfer from client A to server to client B doSync(__FILE__, __LINE__, "send", SyncOptions(SYNC_TWO_WAY).setWBXML(true)); CT_ASSERT_NO_THROW(accessClientB->refreshClient(SyncOptions().setWBXML(true))); CT_ASSERT_NO_THROW(compareDatabases()); } // creates several items, transmits them back and forth and // then compares which of them have been preserved void SyncTests::testItemsXML() { // clean server and first test database CT_ASSERT_NO_THROW(deleteAll()); // import data source_it it; for (it = sources.begin(); it != sources.end(); ++it) { CT_ASSERT_NO_THROW(it->second->testImport()); } // transfer from client A to server to client B using the non-default XML format doSync(__FILE__, __LINE__, "send", SyncOptions(SYNC_TWO_WAY).setWBXML(false)); CT_ASSERT_NO_THROW(accessClientB->refreshClient(SyncOptions().setWBXML(false))); CT_ASSERT_NO_THROW(compareDatabases()); } // imports test data, transmits it from client A to the server to // client B, update on B and transfers back to the server, // then compares against reference data that has the same changes // applied on A void SyncTests::testExtensions() { // clean server and first test database CT_ASSERT_NO_THROW(deleteAll()); // import data and create reference data source_it it; for (it = sources.begin(); it != sources.end(); ++it) { CT_ASSERT_NO_THROW(it->second->testImport()); string refDir = getCurrentTest() + "." + it->second->config.m_sourceName + ".ref.dat"; simplifyFilename(refDir); rm_r(refDir); mkdir_p(refDir); TestingSyncSourcePtr source; int counter = 0; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB())); BOOST_FOREACH(const string &luid, source->getAllItems()) { string item; source->readItemRaw(luid, item); CT_ASSERT_NO_THROW(it->second->config.m_update(item)); ofstream out(StringPrintf("%s/%d", refDir.c_str(), counter).c_str()); out.write(item.c_str(), item.size()); counter++; } CT_ASSERT_NO_THROW(source.reset()); } // transfer from client A to server to client B doSync(__FILE__, __LINE__, "send", SyncOptions(SYNC_TWO_WAY)); CT_ASSERT_NO_THROW(accessClientB->refreshClient(SyncOptions())); // update on client B for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) { CT_ASSERT_NO_THROW(it->second->updateData(it->second->createSourceB)); } // send back accessClientB->doSync(__FILE__, __LINE__, "update", SyncOptions(SYNC_TWO_WAY)); doSync(__FILE__, __LINE__, "patch", SyncOptions(SYNC_TWO_WAY)); bool equal = true; for (it = sources.begin(); it != sources.end(); ++it) { string refDir = getCurrentTest() + "." + it->second->config.m_sourceName + ".ref.dat"; simplifyFilename(refDir); TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB())); // Compare data in source A against reference data *without* // telling synccompare to ignore known data loss for the // server. CLIENT_TEST_SERVER is relevant for finding the // right source config and thus setting it must come after the // createSourceB() call. ScopedEnvChange env("CLIENT_TEST_SERVER", ""); ScopedEnvChange envParams("CLIENT_TEST_STRIP_PARAMETERS", "X-EVOLUTION-UI-SLOT"); // X-EVOLUTION-FILE-AS is not preserved correctly with EDS due // to setting it on incoming items. It is not always set as already stored // locally, so when writing back, the new, artificially generated value overwrites // the one chosen by the user. Not ideal, but the alternative (not setting it on // incoming items) is worse. ScopedEnvChange envProps("CLIENT_TEST_STRIP_PROPERTIES", "(PHOTO|FN|X-EVOLUTION-FILE-AS)"); if (!it->second->compareDatabases(refDir.c_str(), *source, false)) { equal = false; } } CT_ASSERT(equal); } // tests the following sequence of events: // - both clients in sync with server // - client 1 adds item // - client 1 updates the same item // - client 2 gets item: the client should be asked to add the item // // However it has been observed that sometimes the item was sent as "update" // for a non-existant local item. This is a server bug, the client does not // have to handle that. See // http://forge.objectweb.org/tracker/?func=detail&atid=100096&aid=305018&group_id=96 void SyncTests::testAddUpdate() { // clean server and both test databases CT_ASSERT_NO_THROW(deleteAll()); accessClientB->refreshClient(); // add item source_it it; for (it = sources.begin(); it != sources.end(); ++it) { CT_ASSERT_NO_THROW(it->second->insert(it->second->createSourceA, it->second->config.m_insertItem, false)); } doSync(__FILE__, __LINE__, "add", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, 1,0,0, true, SYNC_TWO_WAY))); // update it for (it = sources.begin(); it != sources.end(); ++it) { CT_ASSERT_NO_THROW(it->second->update(it->second->createSourceB, it->second->config.m_updateItem)); } doSync(__FILE__, __LINE__, "update", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, 0,1,0, true, SYNC_TWO_WAY))); // now download the updated item into the second client accessClientB->doSync(__FILE__, __LINE__, "recv", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(1,0,0, 0,0,0, true, SYNC_TWO_WAY))); // compare the two databases CT_ASSERT_NO_THROW(compareDatabases()); } // test copying with maxMsg and no large object support void SyncTests::testMaxMsg() { CT_ASSERT_NO_THROW(doVarSizes(true, false)); } // test copying with maxMsg and large object support void SyncTests::testLargeObject() { CT_ASSERT_NO_THROW(doVarSizes(true, true)); } // // stress tests: execute some of the normal operations, // but with large number of artificially generated items // // two-way sync with clean client/server, // followed by slow sync and comparison // via second client void SyncTests::testManyItems() { // clean server and client A CT_ASSERT_NO_THROW(deleteAll()); // import artificial data: make them large to generate some // real traffic and test buffer handling source_it it; int num_items = defNumItems(); for (it = sources.begin(); it != sources.end(); ++it) { CT_ASSERT_NO_THROW(it->second->insertManyItems(it->second->createSourceA, 0, num_items, 2000)); } // send data to server doSync(__FILE__, __LINE__, "send", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, num_items,0,0, true, SYNC_TWO_WAY), SyncOptions::DEFAULT_MAX_MSG_SIZE, SyncOptions::DEFAULT_MAX_OBJ_SIZE, true)); // ensure that client has the same data, ignoring data conversion // issues (those are covered by testItems()) CT_ASSERT_NO_THROW(refreshClient()); // also copy to second client accessClientB->refreshClient(); // slow sync now should not change anything doSync(__FILE__, __LINE__, "twinning", SyncOptions(SYNC_SLOW, CheckSyncReport(-1,-1,-1, -1,-1,-1, true, SYNC_SLOW), SyncOptions::DEFAULT_MAX_MSG_SIZE, SyncOptions::DEFAULT_MAX_OBJ_SIZE, true)); // compare CT_ASSERT_NO_THROW(compareDatabases()); } /** * Tell server to delete plenty of items. */ void SyncTests::testManyDeletes() { // clean server and client A CT_ASSERT_NO_THROW(deleteAll()); // import artificial data: make them small, we just want // many of them source_it it; int num_items = defNumItems(); for (it = sources.begin(); it != sources.end(); ++it) { CT_ASSERT_NO_THROW(it->second->insertManyItems(it->second->createSourceA, 0, num_items, 100)); } // send data to server doSync(__FILE__, __LINE__, "send", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, num_items,0,0, true, SYNC_TWO_WAY), 64 * 1024, 64 * 1024, true)); // ensure that client has the same data, ignoring data conversion // issues (those are covered by testItems()) CT_ASSERT_NO_THROW(refreshClient()); // also copy to second client accessClientB->refreshClient(); // slow sync now should not change anything doSync(__FILE__, __LINE__, "twinning", SyncOptions(SYNC_SLOW, CheckSyncReport(-1,-1,-1, -1,-1,-1, true, SYNC_SLOW), 64 * 1024, 64 * 1024, true)); // compare CT_ASSERT_NO_THROW(compareDatabases()); // delete everything locally CT_ASSERT_NO_THROW(allSourcesDeleteAll()); doSync(__FILE__, __LINE__, "delete-server", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, 0,0,num_items, true, SYNC_TWO_WAY), 10 * 1024)); // Reporting locally deleted items depends on sync mode // recognition, see SyncContext.cpp. const char* checkSyncModeStr = getenv("CLIENT_TEST_NOCHECK_SYNCMODE"); // update second client accessClientB->doSync(__FILE__, __LINE__, "delete-client", SyncOptions(RefreshFromPeerMode(), checkSyncModeStr ? CheckSyncReport() : CheckSyncReport(0,0,num_items, 0,0,0, true, SYNC_REFRESH_FROM_REMOTE), 10 * 1024)); } /** * - get client A, server, client B in sync with one item * - force slow sync in A: must not duplicate items, but may update it locally * - refresh client B (in case that the item was updated) * - delete item in B and server via two-way sync * - refresh-from-server in B to check that item is gone * - two-way in A: must delete the item */ void SyncTests::testSlowSyncSemantic() { // set up one item everywhere CT_ASSERT_NO_THROW(doCopy()); // slow in A doSync(__FILE__, __LINE__, "slow", SyncOptions(SYNC_SLOW, CheckSyncReport(0,-1,0, -1,-1,0, true, SYNC_SLOW))); // refresh B, delete item accessClientB->doSync(__FILE__, __LINE__, "refresh", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,-1,0, 0,0,0, true, SYNC_TWO_WAY))); CT_ASSERT_NO_THROW(accessClientB->allSourcesDeleteAll()); accessClientB->doSync(__FILE__, __LINE__, "delete", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, 0,0,1, true, SYNC_TWO_WAY))); accessClientB->doSync(__FILE__, __LINE__, "check", SyncOptions(RefreshFromPeerMode(), CheckSyncReport(0,0,0, 0,0,0, true, SYNC_REFRESH_FROM_REMOTE))); // now the item should also be deleted on A doSync(__FILE__, __LINE__, "delete", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,1, 0,0,0, true, SYNC_TWO_WAY))); } /** * check that refresh-from-server works correctly: * - create the same item on A, server, B via testCopy() * - refresh B (one item deleted, one created) * - delete item on A and server * - refresh B (one item deleted) */ void SyncTests::testComplexRefreshFromServerSemantic() { CT_ASSERT_NO_THROW(testCopy()); // Reporting locally deleted items depends on sync mode // recognition, see SyncContext.cpp. const char* checkSyncModeStr = getenv("CLIENT_TEST_NOCHECK_SYNCMODE"); // check refresh with one item on server const char *value = getenv ("CLIENT_TEST_NOREFRESH"); // If refresh_from_server or refresh_from_client (depending on this is a // server or client) is not supported, we can still test via slow sync. if (value) { accessClientB->refreshClient(); } else { accessClientB->doSync(__FILE__, __LINE__, "refresh-one", SyncOptions(RefreshFromPeerMode(), checkSyncModeStr ? CheckSyncReport() : CheckSyncReport(1,0,1, 0,0,0, true, SYNC_REFRESH_FROM_REMOTE))); } // delete that item via A, check again CT_ASSERT_NO_THROW(allSourcesDeleteAll()); doSync(__FILE__, __LINE__, "delete-item", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, 0,0,1, true, SYNC_TWO_WAY))); if (value) { accessClientB->refreshClient(); } else { accessClientB->doSync(__FILE__, __LINE__, "refresh-none", SyncOptions(RefreshFromPeerMode(), checkSyncModeStr ? CheckSyncReport() : CheckSyncReport(0,0,1, 0,0,0, true, SYNC_REFRESH_FROM_REMOTE))); } } /** * - create the same item on A, server, B via testCopy() * - delete on both sides * - sync A * - sync B * * Must not fail, even though the Synthesis engine will ask the backends * for deletion of an already deleted item. */ void SyncTests::testDeleteBothSides() { CT_ASSERT_NO_THROW(testCopy()); CT_ASSERT_NO_THROW(allSourcesDeleteAll()); CT_ASSERT_NO_THROW(accessClientB->allSourcesDeleteAll()); doSync(__FILE__, __LINE__, "delete-item-A", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, 0,0,1, true, SYNC_TWO_WAY))); source_it it; for (it = sources.begin(); it != sources.end(); ++it) { if (it->second->config.m_createSourceB) { TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get())); CT_ASSERT_NO_THROW(source.reset()); } } // it is undefined whether the item is meant to be reported as deleted again here: // a SyncML client test will mark it as deleted, local sync as server won't accessClientB->doSync(__FILE__, __LINE__, "delete-item-B", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, 0,0,-1, true, SYNC_TWO_WAY))); for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) { if (it->second->config.m_createSourceB) { TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB())); SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get())); CT_ASSERT_NO_THROW(source.reset()); } } } /** * - clean A, server, B * - create an item on A * - sync A * - create a modified version of the item on B * - sync B * * Depends on UID and LAST-MODIFIED in item data, i.e., iCalendar 2.0. * Uses the normal "insertItem" test case. Only works for a single source. * * The server must not duplicate the item *and* preserve the modified * properties. * * Temporary: because conflict resolution is server-dependent, such a strict * test fails. For example, with SyncEvolution 1.2 as server, DESCRIPTION and * LOCATION end up being concatenated (merge=lines mode). The test now avoids * using different data, with the expected outcome that only one item * is present at the end and no unnecessary data transfers happen (only true * for SyncEvolution server). * * A similar situation occurs on the client side, but it is harder to * trigger: the updated item must be added to the client's database * after it has reported its changes. Because if it happens earlier, * it would send an Add to the server and the server would have to * resolve the add<->add conflict, as in this test here. */ // using updated item data makes the test harder to pass: // server must use exactly the right item, which currently // is not the case for SyncEvolution bool addBothSidesUsesUpdateItem = true; // SyncEvolution passes with addBothSidesUsesUpdateItem == true // if we avoid changes to those properties in the iCalendar test // set which currently use merge=lines. bool addBothSidesNoMergeLines=true; // if true, relax expectations for updates from server: // may or may not send one bool addBothSidesMayUpdate = false; // if true, then accept that the Synthesis server mode counts // Add commands as "added items" even if they are turned into updates bool addBothSidesAddStatsBroken = false; // if true, then the peer is a SyncML server which does not // support UID/RECURRENCE-ID and thus doesn't detect // duplicates itself; the client needs to do that bool addBothSidesServerIsDumb = getenv("CLIENT_TEST_ADD_BOTH_SIDES_SERVER_IS_DUMB") != NULL; static void testAddBothSidesFixUpdateItem(std::string &updateItem) { if (addBothSidesNoMergeLines) { // VEVENT boost::replace_all(updateItem, "LOCATION:big meeting room", "LOCATION:my office"); boost::replace_all(updateItem, "DESCRIPTION:nice to see you", "DESCRIPTION:let's talk<>"); // VJOURNAL boost::replace_all(updateItem, "DESCRIPTION:Summary\\nBody text", "DESCRIPTION:Summary Modified\\nBody text"); // VTODO boost::replace_all(updateItem, "DESCRIPTION:to be done", "DESCRIPTION:to be done<>"); } } void SyncTests::testAddBothSides() { CT_ASSERT_NO_THROW(deleteAll()); accessClientB->deleteAll(); std::string insertItem = sources[0].second->config.m_insertItem; std::string updateItem = sources[0].second->config.m_updateItem; testAddBothSidesFixUpdateItem(updateItem); CT_ASSERT_NO_THROW(sources[0].second->insert(sources[0].second->createSourceA, insertItem)); doSync(__FILE__, __LINE__, "send-old", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, 1,0,0, true, SYNC_TWO_WAY))); // insert updated item data on B std::string data; CT_ASSERT_NO_THROW(accessClientB->sources[0].second->insert(accessClientB->sources[0].second->createSourceA, addBothSidesUsesUpdateItem ? updateItem: insertItem, false, &data)); // As far as the client knows, it is adding an item; // server not expected to send back an update (our data was more recent // and completely overwrites the server's data). // When acting as server, we do the duplicate detection and thus know // more about the actual outcome. accessClientB->doSync(__FILE__, __LINE__, "send-update", SyncOptions(SYNC_TWO_WAY, isServerMode() ? CheckSyncReport(addBothSidesAddStatsBroken ? -1 : 0,0,0, 0, addBothSidesMayUpdate ? -1 : addBothSidesUsesUpdateItem ? 1 : 0, 0, true, SYNC_TWO_WAY) : addBothSidesServerIsDumb ? CheckSyncReport(addBothSidesServerIsDumb ? 1 : 0, addBothSidesMayUpdate ? -1 : 0, 0, // client got one redundant item from // server, had to receive it, match against // its own copy, then tell the server to // update one copy and delete the other; // no update necessary on server because // it already had the latest copy 1,0,1, true, SYNC_TWO_WAY).setRestarts(1) : CheckSyncReport(0, addBothSidesMayUpdate ? -1 : 0, 0, // client doesn't know that the add // was an update, in contrast to server 1,0,0, true, SYNC_TWO_WAY))); // update sent to client A doSync(__FILE__, __LINE__, "update", SyncOptions(SYNC_TWO_WAY, (!isServerMode() && addBothSidesServerIsDumb) ? // server had to be told to update old item // and delete redundant one, which is what it now // also tells us here CheckSyncReport(1,0,1, 0,0,0, true, SYNC_TWO_WAY) : CheckSyncReport(0, addBothSidesMayUpdate ? -1 : addBothSidesUsesUpdateItem ? 1 : 0, 0, 0,0,0, true, SYNC_TWO_WAY))); // nothing necessary for client B accessClientB->doSync(__FILE__, __LINE__, "nop", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, 0,0,0, true, SYNC_TWO_WAY))); // now compare client A against reference data TestingSyncSourcePtr copy; SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(sources[0].second->createSourceB())); sources[0].second->compareDatabases(*copy, &data, (void *)NULL); CT_ASSERT_NO_THROW(copy.reset()); } /** * compared to testAddBothSides the age of the items is reversed now; * a server which always copies the client's data passes testAddBothSides * but fails here */ void SyncTests::testAddBothSidesRefresh() { CT_ASSERT_NO_THROW(deleteAll()); accessClientB->deleteAll(); std::string insertItem = sources[0].second->config.m_insertItem; std::string updateItem = sources[0].second->config.m_updateItem; testAddBothSidesFixUpdateItem(updateItem); // insert initial item data on B CT_ASSERT_NO_THROW(accessClientB->sources[0].second->insert(accessClientB->sources[0].second->createSourceA, insertItem)); // sleep one second to ensure that it's mangled LAST-MODIFIED is older than // the one from the next item, inserted on A sleep(1); // more recent data sent to server first std::string data; CT_ASSERT_NO_THROW(sources[0].second->insert(sources[0].second->createSourceA, addBothSidesUsesUpdateItem ? updateItem : insertItem, false, &data)); doSync(__FILE__, __LINE__, "send-new", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, 1,0,0, true, SYNC_TWO_WAY))); // As far as the client knows, it is adding an item; // server expected to send back an update (client's data was out-dated); // When acting as server, we do the duplicate detection and thus // know more about the actual outcome. accessClientB->doSync(__FILE__, __LINE__, "send-old", SyncOptions(SYNC_TWO_WAY, isServerMode() ? CheckSyncReport(addBothSidesAddStatsBroken ? -1 : 0, addBothSidesMayUpdate ? -1 : addBothSidesUsesUpdateItem ? 1 : 0, 0, 0, addBothSidesMayUpdate ? -1 : 0, 0, true, SYNC_TWO_WAY) : // When the server is dumb, it // will just accept the added // item and send us an // with an item that has the // same UID as the one it just // received. The client then // must start a second sync and // fix the server by sending an update // (of the old version) and a delete (of the // new one) addBothSidesServerIsDumb ? CheckSyncReport(1,0,0, 1,1,1, true, SYNC_TWO_WAY).setRestarts(1) : CheckSyncReport(0, addBothSidesMayUpdate ? -1 : addBothSidesUsesUpdateItem ? 1 : 0, 0, // client doesn't know that add was // an update 1,0,0, true, SYNC_TWO_WAY))); // potentially send update to A doSync(__FILE__, __LINE__, "nopA", SyncOptions(SYNC_TWO_WAY, (!isServerMode() && addBothSidesServerIsDumb) ? // receives extra changes because dumb server had to be fixed CheckSyncReport(1,0,1, 0,0,0, true, SYNC_TWO_WAY) : CheckSyncReport(0,addBothSidesMayUpdate ? -1 : 0,0, 0,0,0, true, SYNC_TWO_WAY))); // nothing necessary for client B (already synchronized completely above in one sync) accessClientB->doSync(__FILE__, __LINE__, "nopB", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, 0,0,0, true, SYNC_TWO_WAY))); // now compare client A against reference data TestingSyncSourcePtr copy; SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(sources[0].second->createSourceB())); sources[0].second->compareDatabases(*copy, &data, (void *)NULL); CT_ASSERT_NO_THROW(copy.reset()); } /** * - adds parent on client A * - syncs A * - adds unrelated item via client B (necessary to trigger corner cases in * change tracking, see BMC #22329) * - syncs B * - adds child on client A * - syncs A and B * - compares */ void SyncTests::testLinkedItemsParentChild() { source_it it; // clean server, client A and client B CT_ASSERT_NO_THROW(deleteAll()); accessClientB->refreshClient(); // create and copy parent item for (it = sources.begin(); it != sources.end(); ++it) { CT_ASSERT(!it->second->config.m_linkedItems.empty()); CT_ASSERT(it->second->config.m_linkedItems[0].size() >= 2); TestingSyncSourcePtr source; CT_ASSERT_NO_THROW(it->second->insert(it->second->createSourceA, it->second->config.m_linkedItems[0][0], false)); } doSync(__FILE__, __LINE__, "send-parent", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, 1,0,0, true, SYNC_TWO_WAY))); // create independent item, refresh client B and server for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) { CT_ASSERT_NO_THROW(it->second->insert(it->second->createSourceA, it->second->config.m_insertItem, false)); } accessClientB->doSync(__FILE__, __LINE__, "recv-parent", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(1,0,0, 1,0,0, true, SYNC_TWO_WAY))); // add child on client A for (it = sources.begin(); it != sources.end(); ++it) { CT_ASSERT(!it->second->config.m_linkedItems.empty()); CT_ASSERT(it->second->config.m_linkedItems[0].size() >= 2); TestingSyncSourcePtr source; CT_ASSERT_NO_THROW(it->second->insert(it->second->createSourceA, it->second->config.m_linkedItems[0][1], false)); } // parent may or may not be considered updated doSync(__FILE__, __LINE__, "send-child", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(1,0,0, 1,-1,0, true, SYNC_TWO_WAY))); // parent may or may not be considered updated here accessClientB->doSync(__FILE__, __LINE__, "recv-child", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(1,-1,0, 0,0,0, true, SYNC_TWO_WAY))); // final comparison CT_ASSERT_NO_THROW(compareDatabases()); } /** * - adds child on client A * - syncs A * - syncs B * - compare */ void SyncTests::testLinkedItemsChild() { source_it it; // clean server, client A and client B CT_ASSERT_NO_THROW(deleteAll()); accessClientB->refreshClient(); // create and copy child item for (it = sources.begin(); it != sources.end(); ++it) { CT_ASSERT(!it->second->config.m_linkedItems.empty()); CT_ASSERT(it->second->config.m_linkedItems[0].size() >= 2); TestingSyncSourcePtr source; CT_ASSERT_NO_THROW(it->second->insert(it->second->createSourceA, it->second->config.m_linkedItems[0][1], false)); } doSync(__FILE__, __LINE__, "send", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, 1,0,0, true, SYNC_TWO_WAY))); accessClientB->doSync(__FILE__, __LINE__, "recv", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(1,0,0, 0,0,0, true, SYNC_TWO_WAY))); // final comparison CT_ASSERT_NO_THROW(compareDatabases()); } /** * - adds child on client A * - syncs A and B * - adds parent on client A * - syncs A and B * - compares */ void SyncTests::testLinkedItemsChildParent() { source_it it; // clean server, client A and client B CT_ASSERT_NO_THROW(deleteAll()); accessClientB->refreshClient(); // create and copy child item for (it = sources.begin(); it != sources.end(); ++it) { CT_ASSERT(!it->second->config.m_linkedItems[0].empty()); CT_ASSERT(it->second->config.m_linkedItems[0].size() >= 2); TestingSyncSourcePtr source; CT_ASSERT_NO_THROW(it->second->insert(it->second->createSourceA, it->second->config.m_linkedItems[0][1], false)); } doSync(__FILE__, __LINE__, "send-child", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, 1,0,0, true, SYNC_TWO_WAY))); accessClientB->doSync(__FILE__, __LINE__, "recv-child", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(1,0,0, 0,0,0, true, SYNC_TWO_WAY))); // add parent on client A for (it = sources.begin(); it != sources.end(); ++it) { CT_ASSERT(!it->second->config.m_linkedItems.empty()); CT_ASSERT(it->second->config.m_linkedItems[0].size() >= 2); TestingSyncSourcePtr source; // relaxed change checks because child event is also modified CT_ASSERT_NO_THROW(it->second->insert(it->second->createSourceA, it->second->config.m_linkedItems[0][0], true)); } // child may or may not be considered updated doSync(__FILE__, __LINE__, "send-parent", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, 1,-1,0, true, SYNC_TWO_WAY))); // child may or may not be considered updated here accessClientB->doSync(__FILE__, __LINE__, "recv-parent", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(1,-1,0, 0,0,0, true, SYNC_TWO_WAY))); // final comparison CT_ASSERT_NO_THROW(compareDatabases()); } /** * implements testMaxMsg(), testLargeObject(), testLargeObjectEncoded() * using a sequence of items with varying sizes */ void SyncTests::doVarSizes(bool withMaxMsgSize, bool withLargeObject) { int maxMsgSize = 8 * 1024; const char* maxItemSize = getenv("CLIENT_TEST_MAX_ITEMSIZE"); int tmpSize = maxItemSize ? atoi(maxItemSize) : 0; if(tmpSize > 0) maxMsgSize = tmpSize; // clean server and client A CT_ASSERT_NO_THROW(deleteAll()); // insert items, doubling their size, then restart with small size source_it it; for (it = sources.begin(); it != sources.end(); ++it) { int item = 1; restoreStorage(it->second->config, client); TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceA())); for (int i = 0; i < 2; i++ ) { int size = 1; while (size < 2 * maxMsgSize) { CT_ASSERT_NO_THROW(it->second->insertManyItems(source.get(), item, 1, it->second->config.m_templateItem.size() + 10 + size)); size *= 2; item++; } } backupStorage(it->second->config, client); } // transfer to server doSync(__FILE__, __LINE__, "send", SyncOptions(SYNC_TWO_WAY, CheckSyncReport(0,0,0, -1,0,0, true, SYNC_TWO_WAY), // number of items sent to server depends on source withMaxMsgSize ? SyncOptions::DEFAULT_MAX_MSG_SIZE: 0, withMaxMsgSize ? SyncOptions::DEFAULT_MAX_OBJ_SIZE : 0, withLargeObject)); // copy to second client const char *value = getenv ("CLIENT_TEST_NOREFRESH"); // If refresh_from_server or refresh_from_client (depending on this is a // server or client) is not supported, we can still test via slow sync. if (value) { accessClientB->refreshClient(); } else { accessClientB->doSync(__FILE__, __LINE__, "recv", SyncOptions(RefreshFromPeerMode(), CheckSyncReport(-1,0,-1, 0,0,0, true, SYNC_REFRESH_FROM_REMOTE), // number of items received from server depends on source withLargeObject ? maxMsgSize : withMaxMsgSize ? maxMsgSize * 100 /* large enough so that server can sent the largest item */ : 0, withMaxMsgSize ? maxMsgSize * 100 : 0, withLargeObject)); } // compare CT_ASSERT_NO_THROW(compareDatabases()); } /** * Send message to server, then pretend that we timed out at exactly * one specific message, specified via m_interruptAtMessage. The * caller is expected to resend the message, without aborting the * session. That resend and all following message will get through * again. * * Each send() is counted as one message, starting at 1 for the first * message. */ class TransportResendInjector : public TransportWrapper{ private: int timeout; public: TransportResendInjector() :TransportWrapper() { const char *s = getenv("CLIENT_TEST_RESEND_TIMEOUT"); timeout = s ? atoi(s) : 0; } ~TransportResendInjector() { } virtual int getResendFailureThreshold() { return 0; } virtual void send(const char *data, size_t len) { m_messageCount++; if (m_interruptAtMessage >= 0 && m_messageCount == m_interruptAtMessage+1) { m_wrappedAgent->send(data, len); m_status = m_wrappedAgent->wait(); //trigger client side resend sleep (timeout); m_status = TIME_OUT; } else { m_wrappedAgent->send(data, len); m_status = m_wrappedAgent->wait(); } } virtual void getReply(const char *&data, size_t &len, std::string &contentType) { if (m_status == FAILED) { data = ""; len = 0; } else { m_wrappedAgent->getReply(data, len, contentType); } } }; /** * Stop sending at m_interruptAtMessage. The caller is forced to abort * the current session and will recover by retrying in another * session. * * Each send() increments the counter by two, so that 1 aborts before * the first message and 2 after it. */ class TransportFaultInjector : public TransportWrapper{ public: TransportFaultInjector() :TransportWrapper() { } ~TransportFaultInjector() { } virtual void send(const char *data, size_t len) { if (m_interruptAtMessage == m_messageCount) { SE_LOG_DEBUG(NULL, "TransportFaultInjector: interrupt before sending message #%d", m_messageCount); } m_messageCount++; if (m_interruptAtMessage >= 0 && m_messageCount > m_interruptAtMessage) { throw string("TransportFaultInjector: interrupt before send"); } m_wrappedAgent->send(data, len); m_status = m_wrappedAgent->wait(); if (m_interruptAtMessage == m_messageCount) { SE_LOG_DEBUG(NULL, "TransportFaultInjector: interrupt after receiving reply #%d", m_messageCount); } m_messageCount++; if (m_interruptAtMessage >= 0 && m_messageCount > m_interruptAtMessage) { m_status = FAILED; } } virtual void getReply(const char *&data, size_t &len, std::string &contentType) { if (m_status == FAILED) { data = ""; len = 0; } else { m_wrappedAgent->getReply(data, len, contentType); } } }; /** * Swallow data at various points: * - between "client sent data" and "server receives data" * - after "server received data" and before "server sends reply" * - after "server has sent reply" * * The client deals with it by resending. This is similar to * TransportResendInjector and the ::Resend tests, but more thorough, * and stresses the HTTP server more (needs to deal with "reply not * delivered" error). * * Each send() increments the counter by three, so that 0 aborts * before the first message, 1 after sending it, and 2 after receiving * its reply. * * Swallowing data is implemented via the proxy.py script. This is * necessary because the wrapped agent has no API to trigger the second * error scenario. The wrapped agent is told to use a specific port * on localhost, with the base port passing message and reply through, * "base + 1" intercepting the message, etc. * * Because of the use of a proxy, this cannot be used to test servers * where a real proxy is needed. */ class TransportResendProxy : public TransportWrapper { private: int port; public: TransportResendProxy() : TransportWrapper() { const char *s = getenv("CLIENT_TEST_RESEND_PROXY"); port = s ? atoi(s) : 0; } virtual int getResendFailureThreshold() { return 2; } virtual void send(const char *data, size_t len) { HTTPTransportAgent *agent = dynamic_cast(m_wrappedAgent.get()); CT_ASSERT(agent); m_messageCount += 3; if (m_interruptAtMessage >= 0 && m_interruptAtMessage < m_messageCount && m_interruptAtMessage >= m_messageCount - 3) { int offset = m_interruptAtMessage - m_messageCount + 4; SE_LOG_DEBUG(NULL, "TransportResendProxy: interrupt %s", offset == 1 ? "before sending message" : offset == 2 ? "directly after sending message" : "after receiving reply"); agent->setProxy(StringPrintf("http://127.0.0.1:%d", offset + port)); } else { agent->setProxy(""); } agent->send(data, len); m_status = agent->wait(); } virtual void getReply(const char *&data, size_t &len, std::string &contentType) { if (m_status == FAILED) { data = ""; len = 0; } else { m_wrappedAgent->getReply(data, len, contentType); } } }; /** * Emulates a user suspend just after receving response * from server. */ class UserSuspendInjector : public TransportWrapper{ public: UserSuspendInjector() :TransportWrapper() { } ~UserSuspendInjector() { } virtual void send(const char *data, size_t len) { m_wrappedAgent->send(data, len); m_status = m_wrappedAgent->wait(); } virtual void getReply(const char *&data, size_t &len, std::string &contentType) { if (m_status == FAILED) { data = ""; len = 0; } else { if (m_interruptAtMessage == m_messageCount) { SE_LOG_DEBUG(NULL, "UserSuspendInjector: user suspend after getting reply #%d", m_messageCount); } m_messageCount++; if (m_interruptAtMessage >= 0 && m_messageCount > m_interruptAtMessage) { m_options->m_isSuspended = SuspendFlags::getSuspendFlags().suspend(); } m_wrappedAgent->getReply(data, len, contentType); } } }; /** * This function covers different error scenarios that can occur * during real synchronization. To pass, clients must either force a * slow synchronization after a failed synchronization or implement * the error handling described in the design guide (track server's * status for added/updated/deleted items and resend unacknowledged * changes). * * The items used during these tests are synthetic. They are * constructed so that normally a server should be able to handle * twinning during a slow sync correctly. * * Errors are injected into a synchronization by wrapping the normal * HTTP transport agent. The wrapper enumerates messages sent between * client and server (i.e., one message exchange increments the * counter by two), starting from zero. It "cuts" the connection before * sending out the next message to the server respectively after the * server has replied, but before returning the reply to the client. * The first case simulates a lost message from the client to the server * and the second case a lost message from the server to the client. * * The expected result is the same as in an uninterrupted sync, which * is done once at the beginning. * * Each test goes through the following steps: * - client A and B reset local data store * - client A creates 3 new items, remembers LUIDs * - refresh-from-client A sync * - refresh-from-client B sync * - client B creates 3 different items, remembers LUIDs * - client B syncs * - client A syncs => A, B, server are in sync * - client A modifies his items (depends on test) and * sends changes to server => server has changes for B * - client B modifies his items (depends on test) * - client B syncs, transport wrapper simulates lost message n * - client B syncs again, resuming synchronization if possible or * slow sync otherwise (responsibility of the client!) * - client A syncs (not tested yet: A should be sent exactly the changes made by B) * - test that A and B contain same items * - test that A contains the same items as the uninterrupted reference run * - repeat the steps above ranging starting with lost message 0 until no * message got lost * * Set the CLIENT_TEST_INTERRUPT_AT env variable to a message number * >= 0 to execute one uninterrupted run and then interrupt at that * message. Set to -1 to just do the uninterrupted run. */ void SyncTests::doInterruptResume(int changes, boost::shared_ptr wrapper) { int interruptAtMessage = -1; const char *t = getenv("CLIENT_TEST_INTERRUPT_AT"); int requestedInterruptAt = t ? atoi(t) : -2; const char *s = getenv("CLIENT_TEST_INTERRUPT_SLEEP"); int sleep_t = s ? atoi(s) : 0; size_t i; std::string refFileBase = getCurrentTest() + ".ref."; bool equal = true; bool resend = wrapper->getResendFailureThreshold() != -1; bool suspend = dynamic_cast (wrapper.get()) != NULL; bool interrupt = dynamic_cast (wrapper.get()) != NULL; // better be large enough for complete DevInf, 20000 is already a // bit small when running with many stores size_t maxMsgSize = 20000; size_t changedItemSize = (changes & BIG) ? 5 * maxMsgSize / 2 : // large enough to be split over three messages 0; // After running the uninterrupted sync, we remember the number // of sent messages. We never interrupt between sending our // own last message and receiving the servers last reply, // because the server is unable to detect that we didn't get // the reply. It will complete the session whereas the client // suspends, leading to an unexpected slow sync the next time. int maxMsgNum = 0; while (true) { char buffer[80]; sprintf(buffer, "%d", interruptAtMessage); const char *prefix = interruptAtMessage == -1 ? "complete" : buffer; SyncPrefix prefixA(prefix, *this); SyncPrefix prefixB(prefix, *accessClientB); std::vector< std::list > clientAluids; std::vector< std::list > clientBluids; // create new items in client A and sync to server clientAluids.resize(sources.size()); for (i = 0; i < sources.size(); i++) { sources[i].second->deleteAll(sources[i].second->createSourceA); clientAluids[i] = sources[i].second->insertManyItems(sources[i].second->createSourceA, 1, 3, 0); } doSync(__FILE__, __LINE__, "fromA", SyncOptions(RefreshFromLocalMode())); // init client B and add its items to server and client A accessClientB->doSync(__FILE__, __LINE__, "initB", SyncOptions(RefreshFromPeerMode())); clientBluids.resize(sources.size()); for (i = 0; i < sources.size(); i++) { clientBluids[i] = accessClientB->sources[i].second->insertManyItems(accessClientB->sources[i].second->createSourceA, 11, 3, 0); } accessClientB->doSync(__FILE__, __LINE__, "fromB", SyncOptions(SYNC_TWO_WAY)); doSync(__FILE__, __LINE__, "updateA", SyncOptions(SYNC_TWO_WAY)); // => client A, B and server in sync with a total of six items // make changes as requested on client A and sync to server for (i = 0; i < sources.size(); i++) { if (changes & SERVER_ADD) { sources[i].second->insertManyItems(sources[i].second->createSourceA, 4, 1, changedItemSize); } if (changes & SERVER_REMOVE) { // remove second item removeItem(sources[i].second->createSourceA, *(++clientAluids[i].begin())); } if (changes & SERVER_UPDATE) { // update third item updateItem(sources[i].second->createSourceA, sources[i].second->config, *(++ ++clientAluids[i].begin()), sources[i].second->createItem(3, "updated", changedItemSize).c_str()); } } // send using the same mode as in the interrupted sync with client B if (changes & (SERVER_ADD|SERVER_REMOVE|SERVER_UPDATE)) { doSync(__FILE__, __LINE__, "changesFromA", SyncOptions(SYNC_TWO_WAY).setMaxMsgSize(maxMsgSize)); } // make changes as requested on client B for (i = 0; i < sources.size(); i++) { if (changes & CLIENT_ADD) { accessClientB->sources[i].second->insertManyItems(accessClientB->sources[i].second->createSourceA, 14, 1, changedItemSize); } if (changes & CLIENT_REMOVE) { // remove second item removeItem(accessClientB->sources[i].second->createSourceA, *(++clientBluids[i].begin())); } if (changes & CLIENT_UPDATE) { // update third item updateItem(accessClientB->sources[i].second->createSourceA, accessClientB->sources[i].second->config, *(++ ++clientBluids[i].begin()), accessClientB->sources[i].second->createItem(13, "updated", changedItemSize).c_str()); } } // Now do an interrupted sync between B and server. // The explicit delete of the TransportAgent is suppressed // by overloading the delete operator. int wasInterrupted; { CheckSyncReport check(-1, -1, -1, -1, -1, -1, false); if (resend && interruptAtMessage > wrapper->getResendFailureThreshold()) { // resend tests must succeed, except for the first // message in the session, which is not resent check.mustSucceed = true; } SyncOptions options(SYNC_TWO_WAY, check); options.setTransportAgent(wrapper); options.setMaxMsgSize(maxMsgSize); // disable resending completely or shorten the resend // interval to speed up testing options.setRetryInterval(resend ? 10 : 0); wrapper->setInterruptAtMessage(interruptAtMessage); accessClientB->doSync(__FILE__, __LINE__, "changesFromB", options); wasInterrupted = interruptAtMessage != -1 && wrapper->getMessageCount() <= interruptAtMessage; if (!maxMsgNum) { maxMsgNum = wrapper->getMessageCount(); } wrapper->rewind(); } if (interruptAtMessage != -1) { if (wasInterrupted) { // uninterrupted sync, done break; } // continue, wait until server timeout if(sleep_t) sleep (sleep_t); // no need for resend tests, unless they were interrupted at the first message if (!resend || interruptAtMessage <= wrapper->getResendFailureThreshold()) { SyncReport report; accessClientB->doSync(__FILE__, __LINE__, "retryB", SyncOptions(SYNC_TWO_WAY, CheckSyncReport().setMode(SYNC_TWO_WAY).setReport(&report))); // Suspending at first and last message doesn't need a // resume, everything else does. When multiple sources // are involved, some may suspend, some may not, so we // cannot check. if (suspend && interruptAtMessage != 0 && interruptAtMessage + 1 != maxMsgNum && report.size() == 1) { BOOST_FOREACH(const SyncReport::SourceReport_t &sourceReport, report) { CT_ASSERT(sourceReport.second.isResumeSync()); } } } } // copy changes to client A doSync(__FILE__, __LINE__, "toA", SyncOptions(SYNC_TWO_WAY)); // compare client A and B if (interruptAtMessage != -1 && !compareDatabases(refFileBase.c_str(), false)) { equal = false; std::cerr << "====> comparison of client B against reference file(s) failed after interrupting at message #" << interruptAtMessage << std::endl; std::cerr.flush(); } if (!compareDatabases(NULL, false)) { equal = false; std::cerr << "====> comparison of client A and B failed after interrupting at message #" << interruptAtMessage << std::endl; std::cerr.flush(); } // save reference files from uninterrupted run? if (interruptAtMessage == -1) { for (source_it it = sources.begin(); it != sources.end(); ++it) { std::string refFile = refFileBase; refFile += it->second->config.m_sourceName; refFile += ".dat"; simplifyFilename(refFile); TestingSyncSourcePtr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceA())); SOURCE_ASSERT_EQUAL(source.get(), 0, it->second->config.m_dump(client, *source.get(), refFile.c_str())); CT_ASSERT_NO_THROW(source.reset()); } } // pick next iteration if (requestedInterruptAt == -1) { // user requested to stop after first iteration break; } else if (requestedInterruptAt >= 0) { // only do one interrupted run of the test if (requestedInterruptAt == interruptAtMessage) { break; } else { interruptAtMessage = requestedInterruptAt; } } else { // interrupt one message later than before interruptAtMessage++; if (interrupt && interruptAtMessage + 1 >= maxMsgNum) { // Don't interrupt before the server's last reply, // because then the server thinks we completed the // session when we think we didn't, which leads to a // slow sync. Testing that is better done with a // specific test. break; } if (interruptAtMessage >= maxMsgNum) { // next run would not interrupt at all, stop now break; } } } CT_ASSERT(equal); } void SyncTests::testInterruptResumeClientAdd() { CT_ASSERT_NO_THROW(doInterruptResume(CLIENT_ADD, boost::shared_ptr (new TransportFaultInjector()))); } void SyncTests::testInterruptResumeClientRemove() { CT_ASSERT_NO_THROW(doInterruptResume(CLIENT_REMOVE, boost::shared_ptr (new TransportFaultInjector()))); } void SyncTests::testInterruptResumeClientUpdate() { CT_ASSERT_NO_THROW(doInterruptResume(CLIENT_UPDATE, boost::shared_ptr (new TransportFaultInjector()))); } void SyncTests::testInterruptResumeServerAdd() { CT_ASSERT_NO_THROW(doInterruptResume(SERVER_ADD, boost::shared_ptr (new TransportFaultInjector()))); } void SyncTests::testInterruptResumeServerRemove() { CT_ASSERT_NO_THROW(doInterruptResume(SERVER_REMOVE, boost::shared_ptr (new TransportFaultInjector()))); } void SyncTests::testInterruptResumeServerUpdate() { CT_ASSERT_NO_THROW(doInterruptResume(SERVER_UPDATE, boost::shared_ptr (new TransportFaultInjector()))); } void SyncTests::testInterruptResumeClientAddBig() { CT_ASSERT_NO_THROW(doInterruptResume(CLIENT_ADD|BIG, boost::shared_ptr (new TransportFaultInjector()))); } void SyncTests::testInterruptResumeClientUpdateBig() { CT_ASSERT_NO_THROW(doInterruptResume(CLIENT_UPDATE|BIG, boost::shared_ptr (new TransportFaultInjector()))); } void SyncTests::testInterruptResumeServerAddBig() { CT_ASSERT_NO_THROW(doInterruptResume(SERVER_ADD|BIG, boost::shared_ptr (new TransportFaultInjector()))); } void SyncTests::testInterruptResumeServerUpdateBig() { CT_ASSERT_NO_THROW(doInterruptResume(SERVER_UPDATE|BIG, boost::shared_ptr (new TransportFaultInjector()))); } void SyncTests::testInterruptResumeFull() { CT_ASSERT_NO_THROW(doInterruptResume(CLIENT_ADD|CLIENT_REMOVE|CLIENT_UPDATE| SERVER_ADD|SERVER_REMOVE|SERVER_UPDATE, boost::shared_ptr (new TransportFaultInjector()))); } void SyncTests::testUserSuspendClientAdd() { CT_ASSERT_NO_THROW(doInterruptResume(CLIENT_ADD, boost::shared_ptr (new UserSuspendInjector()))); } void SyncTests::testUserSuspendClientRemove() { CT_ASSERT_NO_THROW(doInterruptResume(CLIENT_REMOVE, boost::shared_ptr (new UserSuspendInjector()))); } void SyncTests::testUserSuspendClientUpdate() { CT_ASSERT_NO_THROW(doInterruptResume(CLIENT_UPDATE, boost::shared_ptr (new UserSuspendInjector()))); } void SyncTests::testUserSuspendServerAdd() { CT_ASSERT_NO_THROW(doInterruptResume(SERVER_ADD, boost::shared_ptr (new UserSuspendInjector()))); } void SyncTests::testUserSuspendServerRemove() { CT_ASSERT_NO_THROW(doInterruptResume(SERVER_REMOVE, boost::shared_ptr (new UserSuspendInjector()))); } void SyncTests::testUserSuspendServerUpdate() { CT_ASSERT_NO_THROW(doInterruptResume(SERVER_UPDATE, boost::shared_ptr (new UserSuspendInjector()))); } void SyncTests::testUserSuspendClientAddBig() { CT_ASSERT_NO_THROW(doInterruptResume(CLIENT_ADD|BIG, boost::shared_ptr (new UserSuspendInjector()))); } void SyncTests::testUserSuspendClientUpdateBig() { CT_ASSERT_NO_THROW(doInterruptResume(CLIENT_UPDATE|BIG, boost::shared_ptr (new UserSuspendInjector()))); } void SyncTests::testUserSuspendServerAddBig() { CT_ASSERT_NO_THROW(doInterruptResume(SERVER_ADD|BIG, boost::shared_ptr (new UserSuspendInjector()))); } void SyncTests::testUserSuspendServerUpdateBig() { CT_ASSERT_NO_THROW(doInterruptResume(SERVER_UPDATE|BIG, boost::shared_ptr (new UserSuspendInjector()))); } void SyncTests::testUserSuspendFull() { CT_ASSERT_NO_THROW(doInterruptResume(CLIENT_ADD|CLIENT_REMOVE|CLIENT_UPDATE| SERVER_ADD|SERVER_REMOVE|SERVER_UPDATE, boost::shared_ptr (new UserSuspendInjector()))); } void SyncTests::testResendClientAdd() { CT_ASSERT_NO_THROW(doInterruptResume(CLIENT_ADD, boost::shared_ptr (new TransportResendInjector()))); } void SyncTests::testResendClientRemove() { CT_ASSERT_NO_THROW(doInterruptResume(CLIENT_REMOVE, boost::shared_ptr (new TransportResendInjector()))); } void SyncTests::testResendClientUpdate() { CT_ASSERT_NO_THROW(doInterruptResume(CLIENT_UPDATE, boost::shared_ptr (new TransportResendInjector()))); } void SyncTests::testResendServerAdd() { CT_ASSERT_NO_THROW(doInterruptResume(SERVER_ADD, boost::shared_ptr (new TransportResendInjector()))); } void SyncTests::testResendServerRemove() { CT_ASSERT_NO_THROW(doInterruptResume(SERVER_REMOVE, boost::shared_ptr (new TransportResendInjector()))); } void SyncTests::testResendServerUpdate() { CT_ASSERT_NO_THROW(doInterruptResume(SERVER_UPDATE, boost::shared_ptr (new TransportResendInjector()))); } void SyncTests::testResendFull() { CT_ASSERT_NO_THROW(doInterruptResume(CLIENT_ADD|CLIENT_REMOVE|CLIENT_UPDATE| SERVER_ADD|SERVER_REMOVE|SERVER_UPDATE, boost::shared_ptr (new TransportResendInjector()))); } void SyncTests::testResendProxyClientAdd() { CT_ASSERT_NO_THROW(doInterruptResume(CLIENT_ADD, boost::shared_ptr (new TransportResendProxy()))); } void SyncTests::testResendProxyClientRemove() { CT_ASSERT_NO_THROW(doInterruptResume(CLIENT_REMOVE, boost::shared_ptr (new TransportResendProxy()))); } void SyncTests::testResendProxyClientUpdate() { CT_ASSERT_NO_THROW(doInterruptResume(CLIENT_UPDATE, boost::shared_ptr (new TransportResendProxy()))); } void SyncTests::testResendProxyServerAdd() { CT_ASSERT_NO_THROW(doInterruptResume(SERVER_ADD, boost::shared_ptr (new TransportResendProxy()))); } void SyncTests::testResendProxyServerRemove() { CT_ASSERT_NO_THROW(doInterruptResume(SERVER_REMOVE, boost::shared_ptr (new TransportResendProxy()))); } void SyncTests::testResendProxyServerUpdate() { CT_ASSERT_NO_THROW(doInterruptResume(SERVER_UPDATE, boost::shared_ptr (new TransportResendProxy()))); } void SyncTests::testResendProxyFull() { CT_ASSERT_NO_THROW(doInterruptResume(CLIENT_ADD|CLIENT_REMOVE|CLIENT_UPDATE| SERVER_ADD|SERVER_REMOVE|SERVER_UPDATE, boost::shared_ptr (new TransportResendProxy()))); } static bool setDeadSyncURL(SyncContext &context, SyncOptions &options, int port, bool *skipped) { vector urls = context.getSyncURL(); string url; if (urls.size() == 1) { url = urls.front(); } // use IPv4 localhost address, that's what we listen on string fakeURL = StringPrintf("http://127.0.0.1:%d/foobar", port); if (boost::starts_with(url, "http")) { context.setSyncURL(fakeURL, true); context.setSyncUsername("foo", true); context.setSyncPassword("bar", true); return false; } else if (boost::starts_with(url, "local://")) { FullProps props = context.getConfigProps(); string target = url.substr(strlen("local://")); props[target].m_syncProps["syncURL"] = fakeURL; props[target].m_syncProps["retryDuration"] = InitStateString("10", true); props[target].m_syncProps["retryInterval"] = InitStateString("10", true); context.setConfigProps(props); return false; } else { // cannot run test, tell parent *skipped = true; return true; } } void SyncTests::testTimeout() { // Create a dead listening socket, then run a sync with a sync URL // which points towards localhost at that port. Do this with no // message resending and a very short overall timeout. The // expectation is that the transmission timeout strikes. time_t start = time(NULL); int fd = socket(AF_INET, SOCK_STREAM, 0); CT_ASSERT(fd != -1); struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); int res = bind(fd, (sockaddr *)&servaddr, sizeof(servaddr)); CT_ASSERT_EQUAL(0, res); socklen_t len = sizeof(servaddr); res = getsockname(fd, (sockaddr *)&servaddr, &len); CT_ASSERT_EQUAL(0, res); res = listen(fd, 10); CT_ASSERT_EQUAL(0, res); bool skipped = false; SyncReport report; doSync(__FILE__, __LINE__, "timeout", SyncOptions(SYNC_SLOW, CheckSyncReport(-1, -1, -1, -1, -1, -1, false).setReport(&report)) .setPrepareCallback(boost::bind(setDeadSyncURL, _1, _2, ntohs(servaddr.sin_port), &skipped)) .setRetryDuration(20) .setRetryInterval(20)); time_t end = time(NULL); close(fd); if (!skipped) { CT_ASSERT_EQUAL(STATUS_TRANSPORT_FAILURE, report.getStatus()); CT_ASSERT(end - start >= 19); CT_ASSERT(end - start < 40); // needs to be sufficiently larger than 20s timeout // because under valgrind the startup time is considerable } } void SyncTests::doSync(const SyncOptions &options) { int res = 0; static int syncCounter = 0; static std::string lastTest; std::stringstream logstream; // reset counter when switching tests if (lastTest != getCurrentTest()) { syncCounter = 0; lastTest = getCurrentTest(); } std::string prefix; prefix.reserve(80); for (std::list::iterator it = logPrefixes.begin(); it != logPrefixes.end(); ++it) { prefix += "."; prefix += *it; } if (!prefix.empty()) { printf(" %s", prefix.c_str() + 1); fflush(stdout); } logstream /* << std::setw(4) << std::setfill('0') << syncCounter << "_" */ << getCurrentTest() << prefix << ".client." << (accessClientB ? "A" : "B"); std::string logname = logstream.str(); simplifyFilename(logname); syncCounter++; SE_LOG_DEBUG(NULL, "%d. starting %s with sync mode %s", syncCounter, logname.c_str(), PrettyPrintSyncMode(options.m_syncMode).c_str()); try { CT_ASSERT_NO_THROW(res = client.doSync(sourceArray, logname, options)); } catch (...) { postSync(res, logname); throw; } CT_ASSERT_NO_THROW(postSync(res, logname)); } void SyncTests::postSync(int res, const std::string &logname) { client.postSync(res, logname); } void SyncTests::allSourcesInsert(bool withUID) { BOOST_FOREACH(source_array_t::value_type &source_pair, sources) { CT_ASSERT_NO_THROW(source_pair.second->doInsert(withUID)); } } void SyncTests::allSourcesUpdate() { BOOST_FOREACH(source_array_t::value_type &source_pair, sources) { CT_ASSERT_NO_THROW(source_pair.second->update(source_pair.second->createSourceA, source_pair.second->config.m_updateItem)); } } void SyncTests::allSourcesDeleteAll() { BOOST_FOREACH(source_array_t::value_type &source_pair, sources) { CT_ASSERT_NO_THROW(source_pair.second->deleteAll(source_pair.second->createSourceA)); } } void SyncTests::allSourcesInsertMany(int startIndex, int numItems, std::map > &luids) { BOOST_FOREACH(source_array_t::value_type &source_pair, sources) { std::list l; CT_ASSERT_NO_THROW(l = source_pair.second->insertManyItems(source_pair.second->createSourceA, startIndex, numItems, 0)); CT_ASSERT_EQUAL((size_t)numItems, l.size()); // append instead of overwriting - useful when multiple // insertMany calls share the same luid buffer luids[source_pair.first].insert(luids[source_pair.first].end(), l.begin(), l.end()); } } void SyncTests::allSourcesUpdateMany(int startIndex, int numItems, int revision, std::map > &luids, int offset) { BOOST_FOREACH(source_array_t::value_type &source_pair, sources) { CT_ASSERT_NO_THROW(source_pair.second->updateManyItems(source_pair.second->createSourceA, startIndex, numItems, 0, revision, luids[source_pair.first], offset)); } } void SyncTests::allSourcesRemoveMany(int numItems, std::map > &luids, int offset) { BOOST_FOREACH(source_array_t::value_type &source_pair, sources) { CT_ASSERT_NO_THROW(source_pair.second->removeManyItems(source_pair.second->createSourceA, numItems, luids[source_pair.first], offset)); } } /** generates tests on demand based on what the client supports */ class ClientTestFactory : public CppUnit::TestFactory { public: ClientTestFactory(ClientTest &c) : client(c) {} virtual CppUnit::Test *makeTest() { int source; CppUnit::TestSuite *alltests = new CppUnit::TestSuite("Client"); CppUnit::TestSuite *tests; // create local source tests typedef std::map ConfigMap; ConfigMap configs; tests = new CppUnit::TestSuite(alltests->getName() + "::Source"); for (source=0; source < client.getNumLocalSources(); source++) { ClientTest::Config config; client.getLocalSourceConfig(source, config); if (!config.m_sourceName.empty()) { LocalTests *sourcetests = client.createLocalTests(tests->getName() + "::" + config.m_sourceName, source, config); sourcetests->addTests(); tests->addTest(FilterTest(sourcetests)); configs[config.m_sourceName] = sourcetests; } } // link configs of sources which share the same database BOOST_FOREACH (const ConfigMap::value_type &entry, configs) { LocalTests *sourcetests = entry.second; const ClientTest::Config &config = sourcetests->config; if (!config.m_linkedSources.empty()) { sourcetests->m_linkedSources.push_back(sourcetests); BOOST_FOREACH (const std::string &source, config.m_linkedSources) { sourcetests->m_linkedSources.push_back(configs[source]); } } } alltests->addTest(FilterTest(tests)); // create sync tests with just one source tests = new CppUnit::TestSuite(alltests->getName() + "::Sync"); for (source=0; source < client.getNumSyncSources(); source++) { ClientTest::Config config; client.getSyncSourceConfig(source, config); if (!config.m_sourceName.empty()) { std::vector sources; sources.push_back(source); SyncTests *synctests = client.createSyncTests(tests->getName() + "::" + config.m_sourceName, sources); synctests->addTests(source == 0); tests->addTest(FilterTest(synctests)); } } // create sync tests with all sources enabled, unless we only have one: // that would be identical to the test above std::vector sources; std::string name, name_reversed; for (source=0; source < client.getNumSyncSources(); source++) { ClientTest::Config config; client.getSyncSourceConfig(source, config); if (!config.m_sourceName.empty()) { sources.push_back(source); if (name.size() > 0) { name += "_"; name_reversed = std::string("_") + name_reversed; } name += config.m_sourceName; name_reversed = config.m_sourceName + name_reversed; } } if (sources.size() > 1) { SyncTests *synctests = client.createSyncTests(tests->getName() + "::" + name, sources); synctests->addTests(); tests->addTest(FilterTest(synctests)); synctests = 0; if (getenv("CLIENT_TEST_REVERSE_SOURCES")) { // now also in reversed order - who knows, it might make a difference; // typically it just makes the whole run slower, so not enabled // by default std::reverse(sources.begin(), sources.end()); synctests = client.createSyncTests(tests->getName() + "::" + name_reversed, sources); synctests->addTests(); tests->addTest(FilterTest(synctests)); synctests = 0; } } alltests->addTest(FilterTest(tests)); tests = 0; return alltests; } private: ClientTest &client; }; void ClientTest::registerTests() { factory = (void *)new ClientTestFactory(*this); CppUnit::TestFactoryRegistry::getRegistry().registerFactory((CppUnit::TestFactory *)factory); } ClientTest::ClientTest(int serverSleepSec, const std::string &serverLog) : serverSleepSeconds(serverSleepSec), serverLogFileName(serverLog), factory(NULL) { } ClientTest::~ClientTest() { if(factory) { CppUnit::TestFactoryRegistry::getRegistry().unregisterFactory((CppUnit::TestFactory *)factory); delete (CppUnit::TestFactory *)factory; factory = 0; } } void ClientTest::registerCleanup(Cleanup_t cleanup) { cleanupSet.insert(cleanup); } void ClientTest::shutdown() { BOOST_FOREACH(Cleanup_t cleanup, cleanupSet) { cleanup(); } } LocalTests *ClientTest::createLocalTests(const std::string &name, int sourceParam, ClientTest::Config &co) { return new LocalTests(name, *this, sourceParam, co); } SyncTests *ClientTest::createSyncTests(const std::string &name, std::vector sourceIndices, bool isClientA) { return new SyncTests(name, *this, sourceIndices, isClientA); } int ClientTest::dump(ClientTest &client, TestingSyncSource &source, const std::string &file) { BackupReport report; boost::shared_ptr node(new VolatileConfigNode); rm_r(file); mkdir_p(file); CT_ASSERT(source.getOperations().m_backupData); source.getOperations().m_backupData(SyncSource::Operations::ConstBackupInfo(), SyncSource::Operations::BackupInfo(SyncSource::Operations::BackupInfo::BACKUP_OTHER, file, node), report); return 0; } void ClientTest::getItems(const std::string &file, list &items, std::string &testcases) { items.clear(); // import the file, trying a .tem file (base file plus patch) // first std::ifstream input; string server = currentServer(); testcases = file + '.' + server +".tem"; input.open(testcases.c_str()); if (input.fail()) { // try server-specific file (like eds_event.ics.local) testcases = file + '.' + server; input.open(testcases.c_str()); } if (input.fail()) { // try base file testcases = file; input.open(testcases.c_str()); } CT_ASSERT(!input.bad()); CT_ASSERT(input.is_open()); std::string data, line; while (input) { bool wasend = false; do { getline(input, line); CT_ASSERT(!input.bad()); // empty lines directly after line which starts with END mark end of record; // check for END necessary becayse vCard 2.1 ENCODING=BASE64 may have empty lines in body of VCARD! if ((line != "\r" && line.size() > 0) || !wasend) { data += line; data += "\n"; } else { if (!data.empty()) { items.push_back(data); } data = ""; } wasend = !line.compare(0, 4, "END:"); } while(!input.eof()); } if (data != "" && data != "\r\n" && data != "\n") { items.push_back(data); } } std::string ClientTest::import(ClientTest &client, TestingSyncSource &source, const ClientTestConfig &config, const std::string &file, std::string &realfile, std::list *luids) { list items; getItems(file, items, realfile); SE_LOG_DEBUG(NULL, "importing %d test cases from file %s", (int)items.size(), realfile.c_str()); std::string failures; bool doImport = !luids || luids->empty(); std::list::const_iterator it; if (!doImport) { it = luids->begin(); } BOOST_FOREACH(string &data, items) { std::string luid; try { if (doImport) { luid = importItem(&source, config, data); CT_ASSERT(!luid.empty()); if (luids) { luids->push_back(luid); } } else { CT_ASSERT(it != luids->end()); luid = *it; ++it; // Did import already fail? If yes, then don't try to // update because it will also fail. if (!luid.empty()) { // TODO: should fail for status = 6 in eas updateItem(&source, data, luid); } } } catch (...) { std::string explanation; Exception::handle(explanation); failures += "Failed to "; if (doImport) { failures += "import:\n"; } else { failures += "update " + luid + ":\n"; } failures += data; failures += "\n"; failures += explanation; failures += "\n"; if (doImport && luids) { luids->push_back(""); } } } return failures; } bool ClientTest::compare(ClientTest &client, const std::string &fileA, const std::string &fileB) { setenv("CLIENT_TEST_HEADER", "\n\n", 1); setenv("CLIENT_TEST_LEFT_NAME", fileA.c_str(), 1); setenv("CLIENT_TEST_RIGHT_NAME", fileB.c_str(), 1); setenv("CLIENT_TEST_REMOVED", "only in left file", 1); setenv("CLIENT_TEST_ADDED", "only in right file", 1); bool success = false; const char* compareLog = getenv("CLIENT_TEST_COMPARE_LOG"); if(compareLog && strlen(compareLog)) { std::string cmdstr = std::string("synccompare ") + fileA + " " + fileB; string tmpfile = "____compare.log"; cmdstr =string("bash -c 'set -o pipefail;") + cmdstr; cmdstr += " 2>&1|tee " +tmpfile+"'"; success = system(cmdstr.c_str()) == 0; } else { // Shortcut: run synccompare directly, without shell in the middle // (reduces overhead and output when running under valgrind). pid_t child = fork(); switch (child) { case -1: perror("fork"); break; case 0: // child execl("synccompare", "synccompare", fileA.c_str(), fileB.c_str(), (char *)NULL); perror("synccompare"); exit(1); break; default: // parent int status; child = waitpid(child, &status, 0); if (child == -1) { perror("wait for synccompare"); } else if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { success = true; } break; } } if (!success) { printf("failed: env CLIENT_TEST_SERVER=%s 'CLIENT_TEST_STRIP_PARAMETERS=%s' 'CLIENT_TEST_STRIP_PROPERTIES=%s'synccompare %s %s\n", getEnv("CLIENT_TEST_SERVER", ""), getEnv("CLIENT_TEST_STRIP_PARAMETERS", ""), getEnv("CLIENT_TEST_STRIP_PROPERTIES", ""), fileA.c_str(), fileB.c_str()); } return success; } void ClientTest::update(std::string &item) { const static char *props[] = { "\nSUMMARY", "\nNOTE", NULL }; for (const char **prop = props; *prop; prop++) { size_t pos; pos = item.find(*prop); if (pos != item.npos) { // Modify existing property. Fast-forward to : (works as // long as colon is not in parameters). pos = item.find(':', pos); } if (pos != item.npos) { item.insert(pos + 1, "MOD-"); } else if (!strcmp(*prop, "\nNOTE") && (pos = item.find("END:VCARD")) != item.npos) { // add property, but only if it is allowed in the item item.insert(pos, "NOTE:MOD\n"); } } } void ClientTest::postSync(int res, const std::string &logname) { #ifdef WIN32 Sleep(serverSleepSeconds * 1000); #else sleep(serverSleepSeconds); // make a copy of the server's log (if found), then truncate it if (serverLogFileName.size()) { int fd = open(serverLogFileName.c_str(), O_RDWR); if (fd >= 0) { int out = open((logname + ".server.log").c_str(), O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP); if (out >= 0) { char buffer[4096]; bool cont = true; ssize_t len; while (cont && (len = read(fd, buffer, sizeof(buffer))) > 0) { ssize_t total = 0; while (cont && total < len) { ssize_t written = write(out, buffer, len); if (written < 0) { perror(("writing " + logname + ".server.log").c_str()); cont = false; } else { total += written; } } } if (len < 0) { perror(("reading " + serverLogFileName).c_str()); } close(out); } if (ftruncate(fd, 0)) { perror("truncating log file"); } close(fd); } else { perror(serverLogFileName.c_str()); } } #endif } static string mangleGeneric(const std::string &data, bool update, const std::string &uniqueUIDSuffix) { std::string item = data; if (update) { boost::replace_first(item, "NOTE:", "NOTE:U "); } return item; } static string mangleICalendar20(const std::string &data, bool update, const std::string &uniqueUIDSuffix) { std::string item = data; std::string type; static const pcrecpp::RE re("BEGIN:(VEVENT|VJOURNAL|VTODO)\n"); re.PartialMatch(data, &type); if (update) { if (type == "VJOURNAL") { // Need to modify first line of description and summary // consistently for a note because in plain text // representation, these lines are expected to be // identical. boost::replace_first(item, "SUMMARY:", "SUMMARY:U "); } boost::replace_first(item, "DESCRIPTION:", "DESCRIPTION:U "); } if (getenv("CLIENT_TEST_NO_UID")) { stripProperty(item, "UID"); } else if (getenv("CLIENT_TEST_SIMPLE_UID")) { boost::replace_all(item, "UID:1234567890!@#$%^&*()<>@dummy", "UID:1234567890@dummy"); } const char *uniqueUID = getenv("CLIENT_TEST_UNIQUE_UID"); if (uniqueUID) { // Making UID unique per test to avoid issues // when the source already holds older copies. // Might still be an issue in real life?! static time_t start; static std::string test; if (test != getCurrentTest()) { start = time(NULL); test = getCurrentTest(); } std::string unique = StringPrintf("UID:UNIQUE-UID-%llu-", (long long unsigned)start); boost::replace_all(item, "UID:", unique); if (atoi(uniqueUID) > 1) { // Also avoid reusing the same UID inside the same test. // Required by Google CalDAV in calendar testChanges, because // they keep even deleted items around and check the SEQUENCE // number against their old data. boost::replace_all(item, "UNIQUE-UID", "UNIQUE-UID" + uniqueUIDSuffix); } } else if (getenv("CLIENT_TEST_LONG_UID")) { boost::replace_all(item, "UID:", "UID:this-is-a-ridiculously-long-uid-"); } size_t offset = item.find("\nLAST-MODIFIED:"); static const size_t len = strlen("\nLAST-MODIFIED:20100131T235959Z"); if (offset != item.npos) { // Special semantic for iCalendar 2.0: LAST-MODIFIED should be // incremented in updated items. Emulate that by inserting the // current time. time_t now = time(NULL); struct tm tm; gmtime_r(&now, &tm); std::string mod = StringPrintf("\nLAST-MODIFIED:%04d%02d%02dT%02d%02d%02dZ", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); item.replace(offset, len, mod); } const static string sequence("\nSEQUENCE:XXX"); offset = item.find(sequence); if (offset != item.npos) { if (getenv("CLIENT_TEST_INCREASE_SEQUENCE")) { // Increment sequence number in steps of 100 to ensure that our // new item is considered more recent than any corresponding // item in the source. Some storages (Google CalDAV) check that. static int counter = 100; item.replace(offset, sequence.size(), StringPrintf("\nSEQUENCE:%d", counter)); counter += 100; } else { item.replace(offset, sequence.size(), "\nSEQUENCE:1"); } } return item; } static std::string additionalYearly(const std::string &single, const std::string &many, int start, int skip, int index, int total) { int startYear = 2012 + start - 1; std::string event; if (start == 0) { // no missing parent, nothing to add } else if (start == index) { // inserting a single detached recurrence event = StringPrintf(single.c_str(), startYear); } else { // many detached recurrences int endYear = startYear + index - start; std::string exdates; for (int year = startYear; year <= endYear; year++) { // a gap? if ((year - startYear) % (skip + 1)) { exdates += StringPrintf("EXDATE;TZID=Standard Timezone:%04d0101T120000\n", year); } } event = StringPrintf(many.c_str(), startYear, endYear, exdates.c_str()); } SE_LOG_DEBUG(NULL, "additional yearly: start %d, skip %d, index %d/%d:\n%s", start, skip, index, total, event.c_str()); return event; } static std::string additionalMonthly(const std::string &single, const std::string &many, int day, int start, int skip, int index, int total) { int startMonth = 1 + start - 1; std::string event; int endMonth = startMonth + index - start; int time = (endMonth >= 4 && endMonth <= 10) ? 10 : 11; if (start == 0) { } else if (start == index) { event = StringPrintf(single.c_str(), startMonth, day, time); } else { // Monthly recurrence uses INTERVAL instead of // EXDATEs, in contrast to yearly recurrence // (where Exchange somehow didn't grok the // INTERVAL). So EXDATEs are only necessary // for the first, second, last case. if (skip == -1 ) { std::string exdates; for (int month = startMonth; month <= endMonth; month++) { int step = month - startMonth; // a gap? if (step > 1 && step < total - start - 1) { exdates += StringPrintf("EXDATE;TZID=Standard Timezone:2012%02d01T120000\n", month); } } event = StringPrintf(many.c_str(), startMonth, day, endMonth, time, 1, exdates.c_str()); } else { event = StringPrintf(many.c_str(), startMonth, day, endMonth, time, skip + 1, ""); } } SE_LOG_DEBUG(NULL, "additional monthly: start %d, skip %d, index %d/%d:\n%s", start, skip, index, total, event.c_str()); return event; } // instead of trying to determine the dates of all Sundays in 2012 // algorithmically, hard-code them... static const struct { int m_month, m_day; } sundays[] = { { 1, 1 }, { 1, 8 }, { 1, 15 }, { 1, 22 }, { 1, 29 }, { 2, 5 }, { 2, 12 }, { 2, 19 }, { 2, 26 }, { 3, 4 }, { 3, 11 }, { 3, 18 }, // winter time ends on March 25th, week 12 (counting from zero) #define SUNDAYS_2012_WINTER_TIME_ENDS 12 { 3, 25 }, { 4, 1 }, { 4, 8 }, { 4, 15 }, { 4, 22 }, { 4, 29 }, { 5, 6 }, { 5, 13 }, { 5, 20 }, { 5, 27 }, { 6, 3 }, { 6, 10 }, { 6, 17 }, { 6, 24 }, { 7, 1 }, { 7, 8 }, { 7, 15 }, { 7, 22 }, { 7, 29 }, { 8, 5 }, { 8, 12 }, { 8, 19 }, { 8, 26 }, { 9, 2 }, { 9, 9 }, { 9, 16 }, { 9, 23 }, { 9, 30 }, { 10, 7 }, { 10, 14 }, { 10, 21 }, // winter time start on October 28th, week 43 (counting from zero) #define SUNDAYS_2012_WINTER_TIME_STARTS 43 { 10, 28 }, { 11, 4 }, { 11, 11 }, { 11, 18 }, { 11, 25 }, { 12, 2 }, { 12, 9 }, { 12, 16 }, { 12, 23 }, { 12, 30 }, { 0, 0 } }; static std::string additionalWeekly(const std::string &single, const std::string &many, int start, int skip, int index, int total) { int startWeek = start - 1; // numbered from zero in "sundays" array if (startWeek < 0) { startWeek = 0; } std::string event; int endWeek = startWeek + index - start; int time = (endWeek >= SUNDAYS_2012_WINTER_TIME_ENDS && endWeek < SUNDAYS_2012_WINTER_TIME_STARTS) ? 12 : 13; int startMonth = sundays[startWeek].m_month; int startDay = sundays[startWeek].m_day; if (start == 0) { } else if (start == index) { event = StringPrintf(single.c_str(), startMonth, startDay, time); } else { int endMonth = sundays[endWeek].m_month; int endDay = sundays[endWeek].m_day; // Weekly recurrence uses INTERVAL instead of // EXDATEs, in contrast to yearly recurrence // (where Exchange somehow didn't grok the // INTERVAL). So EXDATEs are only necessary // for the first, second, last case. std::string exdates; if (skip == -1 ) { for (int week = startWeek; week <= endWeek; week++) { int step = week - startWeek; // a gap? if (step > 1 && step < total - start - 1) { exdates += StringPrintf("EXDATE;TZID=Standard Timezone:2012%02d%02dT140000\n", sundays[week].m_month, sundays[week].m_day); } } event = StringPrintf(many.c_str(), startMonth, startDay, endMonth, endDay, time, 1, exdates.c_str()); } else { event = StringPrintf(many.c_str(), startMonth, startDay, endMonth, endDay, time, skip + 1, ""); } } SE_LOG_DEBUG(NULL, "additional weekly: start %d, skip %d, index %d/%d:\n%s", start, skip, index, total, event.c_str()); return event; } static void addMonthly(size_t &index, ClientTestConfig::MultipleLinkedItems_t &subset, const std::string &pre, const std::string &post, const char *suffix, int day, int months) { index++; subset.resize(index + 1); ClientTestConfig::LinkedItems_t *items = &subset[index]; items->m_name = std::string("Monthly") + suffix; /* month varies */ std::string parent = pre + "BEGIN:VEVENT\n" "UID:monthly\n" "DTSTAMP:20110101T120000Z\n" "DTSTART;TZID=Standard Timezone:2012" "%1$02d" "%2$02d" "T120000\n" "DTEND;TZID=Standard Timezone:2012" "%1$02d" "%2$02d" "T121000\n" "SUMMARY:monthly " + suffix + " Berlin\n" "RRULE:BYMONTHDAY=1;COUNT=12;FREQ=MONTHLY\n" "TRANSP:TRANSPARENT\n" "END:VEVENT\n" + post; std::string child = pre + "BEGIN:VEVENT\n" "UID:monthly\n" "DTSTAMP:20110101T120000Z\n" "DTSTART;TZID=Standard Timezone:2012" "%1$02d" "%2$02d" "T120000\n" "DTEND;TZID=Standard Timezone:2012" "%1$02d" "%2$02d" "T121000\n" "SUMMARY:%1$04d monthly " + suffix + " Berlin\n" "RECURRENCE-ID;TZID=Standard Timezone:2012" "%1$02d" "%2$02d" "T120000\n" "TRANSP:TRANSPARENT\n" "END:VEVENT\n" + post; items->push_back(StringPrintf(parent.c_str(), 1, day)); for (int month = 1; month <= months; month++) { items->push_back(StringPrintf(child.c_str(), month, day)); } if (currentServer() == "exchange") { /* month of event varies and UTC time of UNTIL clause (11 during winter time, 10 during summer) */ std::string single = pre + "BEGIN:VEVENT\n" "SUMMARY:[[activesyncd pseudo event - ignore me]]\n" "DTSTART;TZID=Standard Timezone:2012" "%1$02d" "%2$02d" "T120000\n" "DTEND;TZID=Standard Timezone:2012" "%1$02d" "%2$02d" "T120000\n" "RRULE:FREQ=YEARLY;UNTIL=2012" "%1$02d" "%2$02d" "T" "%3$02d" "0000Z;BYMONTHDAY=1;BYMONTH=%1$d\n" "UID:monthly\n" "TRANSP:TRANSPARENT\n" "END:VEVENT\n" + post; /* first month, last month, UTC time, INTERVAL and sometimes EXDATE varies */ std::string many = pre + "BEGIN:VEVENT\n" "SUMMARY:[[activesyncd pseudo event - ignore me]]\n" "DTSTART;TZID=Standard Timezone:2012" "%1$02d" "%2$02d" "T120000\n" "DTEND;TZID=Standard Timezone:2012" "%1$02d" "%2$02d" "T120000\n" "RRULE:BYMONTHDAY=1;FREQ=MONTHLY;INTERVAL=%5$d;UNTIL=2012" "%3$02d" "%2$02d" "T" "%4$02d" "0000Z\n" "%6$s" "UID:monthly\n" "TRANSP:TRANSPARENT\n" "END:VEVENT\n" + post; items->m_testLinkedItemsSubsetAdditional = boost::bind(additionalMonthly, single, many, day, _1, _2, _3, _4); } } void ClientTest::getTestData(const char *type, Config &config) { std::string server = currentServer(); config = Config(); char *env = getenv("CLIENT_TEST_RETRY"); config.m_retrySync = (env && !strcmp (env, "t")) ?true :false; env = getenv("CLIENT_TEST_RESEND"); config.m_resendSync = (env && !strcmp (env, "t")) ?true :false; env = getenv("CLIENT_TEST_SUSPEND"); config.m_suspendSync = (env && !strcmp (env, "t")) ?true :false; config.m_sourceKnowsItemSemantic = true; config.m_linkedItemsRelaxedSemantic = true; config.m_itemType = ""; config.m_import = import; config.m_dump = dump; config.m_compare = compare; // Sync::*::testExtensions not enabled by default. config.m_update = 0; config.m_genericUpdate = update; // redirect requests for "eds_event" towards "eds_event_noutc"? bool noutc = false; env = getenv ("CLIENT_TEST_NOUTC"); if (env && !strcmp (env, "t")) { noutc = true; } config.m_mangleItem = mangleGeneric; static std::set vCardEssential = boost::assign::list_of("FN")("N")("UID")("VERSION"), iCalEssential = boost::assign::list_of("DTSTART")("DTEND")("DTSTAMP")("SUMMARY")("UID")("RRULE")("RECURRENCE-ID")("VERSION"); // RRULE is not essential for a valid item, but removing it has implications // for other properties (EXDATE) and other items (detached recurrences) and // thus cannot be tested in testRemoveProperties (because it doesn't know about // these inter-depdendencies). if (!strcmp(type, "eds_contact")) { config.m_sourceName = "eds_contact"; config.m_sourceNameServerTemplate = "addressbook"; config.m_uri = "card3"; // ScheduleWorld config.m_type = "text/vcard"; config.m_essentialProperties = vCardEssential; config.m_insertItem = "BEGIN:VCARD\n" "VERSION:3.0\n" "TITLE:tester\n" "FN:John Doe\n" "N:Doe;John;;;\n" "TEL;TYPE=WORK;TYPE=VOICE:business 1\n" "X-EVOLUTION-FILE-AS:Doe\\, John\n" "X-MOZILLA-HTML:FALSE\n" "END:VCARD\n"; config.m_updateItem = "BEGIN:VCARD\n" "VERSION:3.0\n" "TITLE:tester\n" "FN:Joan Doe\n" "N:Doe;Joan;;;\n" "X-EVOLUTION-FILE-AS:Doe\\, Joan\n" "TEL;TYPE=WORK;TYPE=VOICE:business 2\n" "BDAY:2006-01-08\n" "X-MOZILLA-HTML:TRUE\n" "END:VCARD\n"; /* adds a second phone number: */ config.m_complexUpdateItem = "BEGIN:VCARD\n" "VERSION:3.0\n" "TITLE:tester\n" "FN:Joan Doe\n" "N:Doe;Joan;;;\n" "X-EVOLUTION-FILE-AS:Doe\\, Joan\n" "TEL;TYPE=WORK;TYPE=VOICE:business 1\n" "TEL;TYPE=HOME;TYPE=VOICE:home 2\n" "BDAY:2006-01-08\n" "X-MOZILLA-HTML:TRUE\n" "END:VCARD\n"; /* add a telephone number, email and X-AIM to initial item */ config.m_mergeItem1 = "BEGIN:VCARD\n" "VERSION:3.0\n" "TITLE:tester\n" "FN:John Doe\n" "N:Doe;John;;;\n" "X-EVOLUTION-FILE-AS:Doe\\, John\n" "X-MOZILLA-HTML:FALSE\n" "TEL;TYPE=WORK;TYPE=VOICE:business 1\n" "EMAIL:john.doe@work.com\n" "X-AIM:AIM JOHN\n" "END:VCARD\n"; config.m_mergeItem2 = "BEGIN:VCARD\n" "VERSION:3.0\n" "TITLE:developer\n" "FN:John Doe\n" "N:Doe;John;;;\n" "TEL;TYPE=WORK;TYPE=VOICE:123456\n" "X-EVOLUTION-FILE-AS:Doe\\, John\n" "X-MOZILLA-HTML:TRUE\n" "BDAY:2006-01-08\n" "END:VCARD\n"; // use NOTE and N to make the item unique config.m_templateItem = "BEGIN:VCARD\n" "VERSION:3.0\n" "TITLE:tester\n" "N:Doe;<>;<>;;\n" "FN:<> Doe\n" "TEL;TYPE=WORK;TYPE=VOICE:business 1\n" "X-EVOLUTION-FILE-AS:Doe\\, <>\n" "X-MOZILLA-HTML:FALSE\n" "NOTE:<>\n" "END:VCARD\n"; config.m_uniqueProperties = ""; config.m_sizeProperty = "NOTE"; config.m_testcases = "testcases/eds_contact.vcf"; if (server == "exchange" || server == "googleeas") { // X-MOZILLA-HTML not supported by ActiveSync. boost::replace_first(config.m_updateItem, "X-MOZILLA-HTML:TRUE", "X-MOZILLA-HTML:FALSE"); boost::replace_first(config.m_complexUpdateItem, "X-MOZILLA-HTML:TRUE", "X-MOZILLA-HTML:FALSE"); boost::replace_first(config.m_mergeItem2, "X-MOZILLA-HTML:TRUE", "X-MOZILLA-HTML:FALSE"); } } else if (!strcmp(type, "eds_event") && !noutc) { config.m_sourceName = "eds_event"; config.m_sourceNameServerTemplate = "calendar"; config.m_uri = "cal2"; // ScheduleWorld config.m_type = "text/x-vcalendar"; config.m_essentialProperties = iCalEssential; if (server == "exchange") { // currently cannot remove EXDATE properties, see BMC #24290 config.m_essentialProperties.insert("EXDATE"); } config.m_mangleItem = mangleICalendar20; config.m_insertItem = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VEVENT\n" "SUMMARY:phone meeting - old\n" "DTEND:20060406T163000Z\n" "DTSTART:20060406T160000Z\n" "UID:1234567890!@#$%^&*()<>@dummyVEVENT\n" "DTSTAMP:20060406T211449Z\n" "LAST-MODIFIED:20060409T213201Z\n" "CREATED:20060409T213201Z\n" "LOCATION:my office\n" "DESCRIPTION:let's talk<>\n" "CLASS:PUBLIC\n" "TRANSP:OPAQUE\n" "SEQUENCE:XXX\n" "END:VEVENT\n" "END:VCALENDAR\n"; config.m_updateItem = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VEVENT\n" "SUMMARY:meeting on site - updated\n" "DTEND:20060406T163000Z\n" "DTSTART:20060406T160000Z\n" "UID:1234567890!@#$%^&*()<>@dummyVEVENT\n" "DTSTAMP:20060406T211449Z\n" "LAST-MODIFIED:20060409T213201Z\n" "CREATED:20060409T213201Z\n" "SEQUENCE:XXX\n" "LOCATION:big meeting room\n" "DESCRIPTION:nice to see you\n" "CLASS:PUBLIC\n" "TRANSP:OPAQUE\n" "END:VEVENT\n" "END:VCALENDAR\n"; /* change location and description of insertItem in testMerge(), add alarm */ config.m_mergeItem1 = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VEVENT\n" "SUMMARY:phone meeting\n" "DTEND:20060406T163000Z\n" "DTSTART:20060406T160000Z\n" "UID:1234567890!@#$%^&*()<>@dummyVEVENT\n" "DTSTAMP:20060406T211449Z\n" "LAST-MODIFIED:20060409T213201Z\n" "CREATED:20060409T213201Z\n" "SEQUENCE:XXX\n" "LOCATION:calling from home\n" "DESCRIPTION:let's talk\n" "CLASS:PUBLIC\n" "TRANSP:OPAQUE\n" "BEGIN:VALARM\n" "DESCRIPTION:alarm\n" "ACTION:DISPLAY\n" "TRIGGER;VALUE=DURATION;RELATED=START:-PT15M\n" "END:VALARM\n" "END:VEVENT\n" "END:VCALENDAR\n"; /* change location to something else, add category */ config.m_mergeItem2 = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VEVENT\n" "SUMMARY:phone meeting\n" "DTEND:20060406T163000Z\n" "DTSTART:20060406T160000Z\n" "UID:1234567890!@#$%^&*()<>@dummyVEVENT\n" "DTSTAMP:20060406T211449Z\n" "LAST-MODIFIED:20060409T213201Z\n" "CREATED:20060409T213201Z\n" "SEQUENCE:XXX\n" "LOCATION:my office\n" "CATEGORIES:WORK\n" "DESCRIPTION:what the heck\\, let's even shout a bit\n" "CLASS:PUBLIC\n" "TRANSP:OPAQUE\n" "END:VEVENT\n" "END:VCALENDAR\n"; // Servers have very different understandings of how // recurrence interacts with time zones and RRULE. // Must use different test cases for some servers to // avoid having the linkedItems test cases fail // because of that. // default: time zones + UNTIL in UTC, with VALARM config.m_linkedItems.resize(1); config.m_linkedItems[0].m_name = "Default"; config.m_linkedItems[0].resize(2); config.m_linkedItems[0][0] = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VTIMEZONE\n" "TZID:Europe/Berlin\n" "X-LIC-LOCATION:Europe/Berlin\n" "BEGIN:DAYLIGHT\n" "TZOFFSETFROM:+0100\n" "TZOFFSETTO:+0200\n" "TZNAME:CEST\n" "DTSTART:19700329T020000\n" "RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3\n" "END:DAYLIGHT\n" "BEGIN:STANDARD\n" "TZOFFSETFROM:+0200\n" "TZOFFSETTO:+0100\n" "TZNAME:CET\n" "DTSTART:19701025T030000\n" "RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10\n" "END:STANDARD\n" "END:VTIMEZONE\n" "BEGIN:VEVENT\n" "UID:20080407T193125Z-19554-727-1-50@dummyVEVENT\n" "DTSTAMP:20080407T193125Z\n" "DTSTART;TZID=Europe/Berlin:20080406T090000\n" "DTEND;TZID=Europe/Berlin:20080406T093000\n" "TRANSP:OPAQUE\n" "SEQUENCE:XXX\n" "SUMMARY:Recurring\n" "DESCRIPTION:recurs each Monday\\, 10 times\n" "CLASS:PUBLIC\n" "RRULE:FREQ=WEEKLY;UNTIL=20080608T070000Z;INTERVAL=1;BYDAY=SU\n" "CREATED:20080407T193241Z\n" "LAST-MODIFIED:20080407T193241Z\n" "END:VEVENT\n" "END:VCALENDAR\n"; config.m_linkedItems[0][1] = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VTIMEZONE\n" "TZID:Europe/Berlin\n" "X-LIC-LOCATION:Europe/Berlin\n" "BEGIN:DAYLIGHT\n" "TZOFFSETFROM:+0100\n" "TZOFFSETTO:+0200\n" "TZNAME:CEST\n" "DTSTART:19700329T020000\n" "RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3\n" "END:DAYLIGHT\n" "BEGIN:STANDARD\n" "TZOFFSETFROM:+0200\n" "TZOFFSETTO:+0100\n" "TZNAME:CET\n" "DTSTART:19701025T030000\n" "RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10\n" "END:STANDARD\n" "END:VTIMEZONE\n" "BEGIN:VEVENT\n" "UID:20080407T193125Z-19554-727-1-50@dummyVEVENT\n" "DTSTAMP:20080407T193125Z\n" "DTSTART;TZID=Europe/Berlin:20080413T090000\n" "DTEND;TZID=Europe/Berlin:20080413T093000\n" "TRANSP:OPAQUE\n" "SEQUENCE:XXX\n" "SUMMARY:Recurring: Modified\n" "CLASS:PUBLIC\n" "CREATED:20080407T193241Z\n" "LAST-MODIFIED:20080407T193647Z\n" "RECURRENCE-ID;TZID=Europe/Berlin:20080413T090000\n" "DESCRIPTION:second instance modified\n" "END:VEVENT\n" "END:VCALENDAR\n"; bool recurringAllDay = false; bool recurringNoTZ = false; bool subsets = false; if (server == "funambol") { // converts UNTIL into floating time - broken?! config.m_linkedItems[0].m_name = "UntilFloatTime"; config.m_linkedItems[0][0] = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VTIMEZONE\n" "TZID:Europe/Berlin\n" "X-LIC-LOCATION:Europe/Berlin\n" "BEGIN:DAYLIGHT\n" "TZOFFSETFROM:+0100\n" "TZOFFSETTO:+0200\n" "TZNAME:CEST\n" "DTSTART:19700329T020000\n" "RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3\n" "END:DAYLIGHT\n" "BEGIN:STANDARD\n" "TZOFFSETFROM:+0200\n" "TZOFFSETTO:+0100\n" "TZNAME:CET\n" "DTSTART:19701025T030000\n" "RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10\n" "END:STANDARD\n" "END:VTIMEZONE\n" "BEGIN:VEVENT\n" "UID:20080407T193125Z-19554-727-1-50@dummyVEVENT\n" "DTSTAMP:20080407T193125Z\n" "DTSTART;TZID=Europe/Berlin:20080406T090000\n" "DTEND;TZID=Europe/Berlin:20080406T093000\n" "TRANSP:OPAQUE\n" "SEQUENCE:XXX\n" "SUMMARY:Recurring\n" "DESCRIPTION:recurs each Monday\\, 10 times\n" "CLASS:PUBLIC\n" "RRULE:FREQ=WEEKLY;UNTIL=20080608T090000;INTERVAL=1;BYDAY=SU\n" "CREATED:20080407T193241Z\n" "LAST-MODIFIED:20080407T193241Z\n" "END:VEVENT\n" "END:VCALENDAR\n"; } else if (server == "mobical") { // UTC time config.m_linkedItems[0].m_name = "UTC"; config.m_linkedItems[0][0] = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VEVENT\n" "UID:20080407T193125Z-19554-727-1-50@dummyVEVENT\n" "DTSTAMP:20080407T193125Z\n" "DTSTART:20080406T070000Z\n" "DTEND:20080406T073000Z\n" "TRANSP:OPAQUE\n" "SEQUENCE:XXX\n" "SUMMARY:Recurring\n" "DESCRIPTION:recurs each Monday\\, 10 times\n" "CLASS:PUBLIC\n" "RRULE:FREQ=WEEKLY;UNTIL=20080608T070000Z;INTERVAL=1;BYDAY=SU\n" "CREATED:20080407T193241Z\n" "LAST-MODIFIED:20080407T193241Z\n" "END:VEVENT\n" "END:VCALENDAR\n"; config.m_linkedItems[0][1] = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VEVENT\n" "UID:20080407T193125Z-19554-727-1-50@dummyVEVENT\n" "DTSTAMP:20080407T193125Z\n" "DTSTART:20080413T070000Z\n" "DTEND:20080413T073000Z\n" "TRANSP:OPAQUE\n" "SEQUENCE:XXX\n" "SUMMARY:Recurring: Modified\n" "CLASS:PUBLIC\n" "CREATED:20080407T193241Z\n" "LAST-MODIFIED:20080407T193647Z\n" "RECURRENCE-ID:20080413T070000Z\n" "DESCRIPTION:second instance modified\n" "END:VEVENT\n" "END:VCALENDAR\n"; } else if (server == "memotoo") { // local floating time, always, regardless what the original // time zone might have been (TZID, UTC, floating) config.m_linkedItems[0].m_name = "LocalTime"; config.m_linkedItems[0][0] = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VEVENT\n" "UID:20080407T193125Z-19554-727-1-50@dummyVEVENT\n" "DTSTAMP:20080407T193125Z\n" "DTSTART:20080406T070000\n" "DTEND:20080406T073000\n" "TRANSP:OPAQUE\n" "SEQUENCE:XXX\n" "SUMMARY:Recurring\n" "DESCRIPTION:recurs each Monday\\, 10 times\n" "CLASS:PUBLIC\n" "RRULE:FREQ=WEEKLY;UNTIL=20080608T070000;INTERVAL=1;BYDAY=SU\n" "CREATED:20080407T193241Z\n" "LAST-MODIFIED:20080407T193241Z\n" "END:VEVENT\n" "END:VCALENDAR\n"; config.m_linkedItems[0][1] = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VEVENT\n" "UID:20080407T193125Z-19554-727-1-50@dummyVEVENT\n" "DTSTAMP:20080407T193125Z\n" "DTSTART:20080413T070000\n" "DTEND:20080413T073000\n" "TRANSP:OPAQUE\n" "SEQUENCE:XXX\n" "SUMMARY:Recurring: Modified\n" "CLASS:PUBLIC\n" "CREATED:20080407T193241Z\n" "LAST-MODIFIED:20080407T193647Z\n" "RECURRENCE-ID:20080413T070000\n" "DESCRIPTION:second instance modified\n" "END:VEVENT\n" "END:VCALENDAR\n"; // also affects normal test items std::string *items[] = { &config.m_insertItem, &config.m_updateItem, &config.m_mergeItem1, &config.m_mergeItem2 }; BOOST_FOREACH(std::string *item, items) { static const pcrecpp::RE times("^(DTSTART|DTEND)(.*)Z$", pcrecpp::RE_Options().set_multiline(true)); times.GlobalReplace("\\1\\2", item); } } else if (server == "exchange") { config.m_linkedItems[0].m_name = "StandardTZ"; BOOST_FOREACH(std::string &item, config.m_linkedItems[0]) { // time zone name changes on server to "Standard Timezone", // with some information stripped boost::replace_all(item, "Europe/Berlin", "Standard Timezone"); // some properties are not stored/supported boost::replace_all(item, "TZNAME:CET\n", ""); boost::replace_all(item, "TZNAME:CEST\n", ""); boost::replace_all(item, "X-LIC-LOCATION:Standard Timezone\n", ""); } recurringAllDay = true; subsets = true; } else { // in particular for Google Calendar: also try with // VALARM, because testing showed that the server works // differently with and without VALARM data included config.m_linkedItems.resize(2); config.m_linkedItems[1].m_name = "WithVALARM"; config.m_linkedItems[1].resize(2); const std::string valarm = "BEGIN:VALARM\n" "ACTION:DISPLAY\n" "DESCRIPTION:This is an event reminder\n" "TRIGGER;VALUE=DURATION;RELATED=START:-PT1H\n" "X-EVOLUTION-ALARM-UID:foo@bar\n" "END:VALARM\nEND:VEVENT"; config.m_linkedItems[1][0] = config.m_linkedItems[0][0]; boost::replace_first(config.m_linkedItems[1][0], "END:VEVENT", valarm); config.m_linkedItems[1][1] = config.m_linkedItems[0][1]; boost::replace_first(config.m_linkedItems[1][1], "END:VEVENT", valarm); // also enable other linked item variants recurringAllDay = true; recurringNoTZ = true; } if (boost::starts_with(server, "google")) { // converts local time into time zone of the user, // which breaks the test recurringNoTZ = false; } // test is fairly slow, only test with some CalDAV servers if (boost::starts_with(server, "apple")) { subsets = true; } if (recurringAllDay) { // also test recurring all-day events with exceptions size_t index = config.m_linkedItems.size(); config.m_linkedItems.resize(index + 1); config.m_linkedItems[index].m_name = "AllDay"; config.m_linkedItems[index].resize(2); config.m_linkedItems[index][0] = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VEVENT\n" "UID:20110829T130000Z-19554-727-1-50@dummyVEVENT\n" "DTSTAMP:20080407T193125Z\n" "DTSTART;VALUE=DATE:20080406\n" "DTEND;VALUE=DATE:20080407\n" "TRANSP:OPAQUE\n" "SEQUENCE:XXX\n" "SUMMARY:Recurring all day event\n" "DESCRIPTION:recurs each Monday\\, 3 times\n" "CLASS:PUBLIC\n" "RRULE:FREQ=WEEKLY;UNTIL=20080420;INTERVAL=1;BYDAY=SU\n" "CREATED:20080407T193241Z\n" "LAST-MODIFIED:20080407T193241Z\n" "END:VEVENT\n" "END:VCALENDAR\n"; // workaround for http://code.google.com/p/google-caldav-issues/issues/detail?id=63 // Google CalDAV inserts a time into the UNTIL clause, do the same in the // reference data. if (boost::starts_with(server, "google")) { config.m_linkedItems[index].m_name = "AllDayGoogle"; boost::replace_first(config.m_linkedItems[index][0], "UNTIL=20080420", "UNTIL=20080420T070000Z"); } config.m_linkedItems[index][1] = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VEVENT\n" "UID:20110829T130000Z-19554-727-1-50@dummyVEVENT\n" "DTSTAMP:20080407T193125Z\n" "DTSTART;VALUE=DATE:20080413\n" "DTEND;VALUE=DATE:20080414\n" "TRANSP:OPAQUE\n" "SEQUENCE:XXX\n" "SUMMARY:Recurring: Modified second instance\n" "CLASS:PUBLIC\n" "CREATED:20080407T193241Z\n" "LAST-MODIFIED:20080407T193647Z\n" "RECURRENCE-ID;VALUE=DATE:20080413\n" "DESCRIPTION:second instance modified\n" "END:VEVENT\n" "END:VCALENDAR\n"; } if (recurringNoTZ) { // also test recurring event with no timezone size_t index = config.m_linkedItems.size(); config.m_linkedItems.resize(index + 1); config.m_linkedItems[index].m_name = "NoTZ"; config.m_linkedItems[index].resize(2); config.m_linkedItems[index][0] = config.m_linkedItems[0][0]; config.m_linkedItems[index][1] = config.m_linkedItems[0][1]; stripComponent(config.m_linkedItems[index][0], "VTIMEZONE"); stripParameters(config.m_linkedItems[index][0], "TZID"); stripComponent(config.m_linkedItems[index][1], "VTIMEZONE"); stripParameters(config.m_linkedItems[index][1], "TZID"); } if (subsets) { static const std::string pre = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VTIMEZONE\n" // Actually, this is Europe/Berlin. // Was renamed to fit the simplified activesyncd naming // and DTSTART was adapted. "TZID:Standard Timezone\n" "BEGIN:STANDARD\n" "DTSTART:19701025T030000\n" "RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10\n" "TZOFFSETFROM:+0200\n" "TZOFFSETTO:+0100\n" "END:STANDARD\n" "BEGIN:DAYLIGHT\n" "DTSTART:19700329T020000\n" "RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3\n" "TZOFFSETFROM:+0100\n" "TZOFFSETTO:+0200\n" "END:DAYLIGHT\n" "END:VTIMEZONE\n"; static const std::string post = "END:VCALENDAR\n"; size_t index = config.m_linkedItemsSubset.size(); config.m_linkedItemsSubset.resize(index + 1); ClientTestConfig::LinkedItems_t *items = &config.m_linkedItemsSubset[index]; items->m_name = "Yearly"; /* year varies */ std::string parent = pre + "BEGIN:VEVENT\n" "UID:yearly\n" "DTSTAMP:20110101T120000Z\n" "DTSTART;TZID=Standard Timezone:" "%1$04d" "0101T120000\n" "DTEND;TZID=Standard Timezone:" "%1$04d" "0101T121000\n" "SUMMARY:yearly Berlin\n" "RRULE:BYMONTH=1;BYMONTHDAY=1;UNTIL=20140101T110000Z;FREQ=YEARLY\n" "TRANSP:TRANSPARENT\n" "END:VEVENT\n" + post; std::string child = pre + "BEGIN:VEVENT\n" "UID:yearly\n" "DTSTAMP:20110101T120000Z\n" "DTSTART;TZID=Standard Timezone:" "%1$04d" "0101T120000\n" "DTEND;TZID=Standard Timezone:" "%1$04d" "0101T121000\n" "SUMMARY:" "%1$04d" "yearly Berlin\n" "RECURRENCE-ID;TZID=Standard Timezone:" "%1$04d" "0101T120000\n" "TRANSP:TRANSPARENT\n" "END:VEVENT\n" + post; boost::assign::push_back(config.m_linkedItemsSubset[index]) (StringPrintf(parent.c_str(), 2012)) (StringPrintf(child.c_str(), 2012)) (StringPrintf(child.c_str(), 2013)) (StringPrintf(child.c_str(), 2014)) ; if (server == "exchange") { /* only year of event varies */ std::string single = pre + "BEGIN:VEVENT\n" "SUMMARY:[[activesyncd pseudo event - ignore me]]\n" "DTSTART;TZID=Standard Timezone:" "%1$04d" "0101T120000\n" "DTEND;TZID=Standard Timezone:" "%1$04d" "0101T120000\n" "RRULE:FREQ=YEARLY;UNTIL=" "%1$04d" "0101T110000Z;BYMONTHDAY=1;BYMONTH=1\n" "UID:yearly\n" "TRANSP:TRANSPARENT\n" "END:VEVENT\n" + post; /* first year, last year and EXDATE varies */ std::string many = pre + "BEGIN:VEVENT\n" "SUMMARY:[[activesyncd pseudo event - ignore me]]\n" "DTSTART;TZID=Standard Timezone:" "%1$04d" "0101T120000\n" "DTEND;TZID=Standard Timezone:" "%1$04d" "0101T120000\n" "RRULE:FREQ=YEARLY;UNTIL=" "%2$04d" "0101T110000Z;BYMONTHDAY=1;BYMONTH=1\n" "%3$s" "UID:yearly\n" "TRANSP:TRANSPARENT\n" "END:VEVENT\n" + post; items->m_testLinkedItemsSubsetAdditional = boost::bind(additionalYearly, single, many, _1, _2, _3, _4); } addMonthly(index, config.m_linkedItemsSubset, pre, post, "First", 1, 12); addMonthly(index, config.m_linkedItemsSubset, pre, post, "Middle", 1, 6); index++; config.m_linkedItemsSubset.resize(index + 1); items = &config.m_linkedItemsSubset[index]; items->m_name = "Weekly"; items->push_back(pre + "BEGIN:VEVENT\n" "UID:weekly\n" "DTSTAMP:20110101T120000Z\n" "DTSTART;TZID=Standard Timezone:20120101T140000\n" "DTEND;TZID=Standard Timezone:20120101T141000\n" "SUMMARY:weekly Sunday Berlin\n" "RRULE:FREQ=WEEKLY;COUNT=54;BYDAY=SU\n" "TRANSP:TRANSPARENT\n" "END:VEVENT\n" + post); for (int i = 0; sundays[i].m_month; i++) { items->push_back(StringPrintf((pre + "BEGIN:VEVENT\n" "UID:weekly\n" "DTSTAMP:20110101T120000Z\n" "DTSTART;TZID=Standard Timezone:2012" "%1$02d%2$02d" "T140000\n" "DTEND;TZID=Standard Timezone:2012" "%1$02d%2$02d" "T141000\n" "SUMMARY:2012-%1$02d-%2$02d %3$d. weekly Sunday Berlin\n" "RECURRENCE-ID;TZID=Standard Timezone:2012" "%1$02d%2$02d" "T140000\n" "TRANSP:TRANSPARENT\n" "END:VEVENT\n" + post).c_str(), sundays[i].m_month, sundays[i].m_day, i + 1)); } if (server == "exchange") { /* date varies and UTC time of UNTIL clause (11 during winter time, 10 during summer) */ std::string single = pre + "BEGIN:VEVENT\n" "SUMMARY:[[activesyncd pseudo event - ignore me]]\n" "DTSTART;TZID=Standard Timezone:2012" "%1$02d" "%2$02d" "T140000\n" "DTEND;TZID=Standard Timezone:2012" "%1$02d" "%2$02d" "T140000\n" "RRULE:FREQ=YEARLY;UNTIL=2012" "%1$02d" "%2$02d" "T" "%3$02d" "0000Z;BYMONTHDAY=%2$d;BYMONTH=%1$d\n" "UID:weekly\n" "TRANSP:TRANSPARENT\n" "END:VEVENT\n" + post; /* first month, last month, UTC time, INTERVAL and sometimes EXDATE varies */ std::string many = pre + "BEGIN:VEVENT\n" "SUMMARY:[[activesyncd pseudo event - ignore me]]\n" "DTSTART;TZID=Standard Timezone:2012" "%1$02d" "%2$02d" "T140000\n" "DTEND;TZID=Standard Timezone:2012" "%1$02d" "%2$02d" "T140000\n" "RRULE:BYDAY=SU;FREQ=WEEKLY;INTERVAL=%6$d;UNTIL=2012" "%3$02d" "%4$02d" "T" "%5$02d" "0000Z\n" "%7$s" "UID:weekly\n" "TRANSP:TRANSPARENT\n" "END:VEVENT\n" + post; items ->m_testLinkedItemsSubsetAdditional = boost::bind(additionalWeekly, single, many, _1, _2, _3, _4); } } config.m_templateItem = config.m_insertItem; config.m_uniqueProperties = "SUMMARY:UID:LOCATION"; config.m_sizeProperty = "DESCRIPTION"; config.m_testcases = "testcases/eds_event.ics"; } else if (!strcmp(type, "eds_event_noutc") || (!strcmp(type, "eds_event") && noutc)) { config.m_sourceName = "eds_event"; config.m_sourceNameServerTemplate = "calendar"; config.m_uri = "cal2"; // ScheduleWorld config.m_type = "text/x-vcalendar"; config.m_essentialProperties = iCalEssential; config.m_mangleItem = mangleICalendar20; config.m_insertItem = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VTIMEZONE\n" "TZID:Asia/Shanghai\n" "BEGIN:STANDARD\n" "DTSTART:19670101T000000\n" "TZOFFSETFROM:+0800\n" "TZOFFSETTO:+0800\n" "END:STANDARD\n" "END:VTIMEZONE\n" "BEGIN:VTIMEZONE\n" "TZID:/freeassociation.sourceforge.net/Tzfile/Asia/Shanghai\n" "X-LIC-LOCATION:Asia/Shanghai\n" "BEGIN:STANDARD\n" "TZNAME:CST\n" "DTSTART:19700914T230000\n" "TZOFFSETFROM:+0800\n" "TZOFFSETTO:+0800\n" "END:STANDARD\n" "END:VTIMEZONE\n" "BEGIN:VEVENT\n" "SUMMARY:phone meeting\n" "DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/Asia/Shanghai:20060406T163000\n" "DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/Asia/Shanghai:20060406T160000\n" "UID:1234567890!@#$%^&*()<>@dummyVEVENT\n" "DTSTAMP:20060406T211449Z\n" "LAST-MODIFIED:20060409T213201Z\n" "CREATED:20060409T213201Z\n" "LOCATION:my office\n" "DESCRIPTION:let's talk<>\n" "CLASS:PUBLIC\n" "TRANSP:OPAQUE\n" "SEQUENCE:XXX\n" "END:VEVENT\n" "END:VCALENDAR\n"; config.m_updateItem = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VTIMEZONE\n" "TZID:Asia/Shanghai\n" "BEGIN:STANDARD\n" "DTSTART:19670101T000000\n" "TZOFFSETFROM:+0800\n" "TZOFFSETTO:+0800\n" "END:STANDARD\n" "END:VTIMEZONE\n" "BEGIN:VTIMEZONE\n" "TZID:/freeassociation.sourceforge.net/Tzfile/Asia/Shanghai\n" "X-LIC-LOCATION:Asia/Shanghai\n" "BEGIN:STANDARD\n" "TZNAME:CST\n" "DTSTART:19700914T230000\n" "TZOFFSETFROM:+0800\n" "TZOFFSETTO:+0800\n" "END:STANDARD\n" "END:VTIMEZONE\n" "BEGIN:VEVENT\n" "SUMMARY:meeting on site\n" "DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/Asia/Shanghai:20060406T163000\n" "DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/Asia/Shanghai:20060406T160000\n" "UID:1234567890!@#$%^&*()<>@dummyVEVENT\n" "DTSTAMP:20060406T211449Z\n" "LAST-MODIFIED:20060409T213201Z\n" "CREATED:20060409T213201Z\n" "LOCATION:big meeting room\n" "DESCRIPTION:nice to see you\n" "CLASS:PUBLIC\n" "TRANSP:OPAQUE\n" "SEQUENCE:XXX\n" "END:VEVENT\n" "END:VCALENDAR\n"; /* change location and description of insertItem in testMerge(), add alarm */ config.m_mergeItem1 = ""; config.m_mergeItem2 = ""; config.m_templateItem = config.m_insertItem; config.m_uniqueProperties = "SUMMARY:UID:LOCATION"; config.m_sizeProperty = "DESCRIPTION"; config.m_testcases = "testcases/eds_event.ics"; } else if(!strcmp(type, "eds_task")) { config.m_sourceName = "eds_task"; config.m_sourceNameServerTemplate = "todo"; config.m_uri = "task2"; // ScheduleWorld config.m_type = "text/x-vcalendar"; config.m_essentialProperties = iCalEssential; config.m_mangleItem = mangleICalendar20; config.m_insertItem = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VTODO\n" "UID:20060417T173712Z-4360-727-1-2730@dummyVTODO\n" "DTSTAMP:20060417T173712Z\n" "SUMMARY:do me\n" "DESCRIPTION:to be done<>\n" "PRIORITY:0\n" "STATUS:NEEDS-ACTION\n" "CREATED:20060417T173712Z\n" "LAST-MODIFIED:20060417T173712Z\n" "END:VTODO\n" "END:VCALENDAR\n"; config.m_updateItem = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VTODO\n" "UID:20060417T173712Z-4360-727-1-2730@dummyVTODO\n" "DTSTAMP:20060417T173712Z\n" "SUMMARY:do me ASAP\n" "DESCRIPTION:to be done\n" "PRIORITY:1\n" "STATUS:NEEDS-ACTION\n" "CREATED:20060417T173712Z\n" "LAST-MODIFIED:20060417T173712Z\n" "END:VTODO\n" "END:VCALENDAR\n"; /* change summary in insertItem in testMerge() */ config.m_mergeItem1 = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VTODO\n" "UID:20060417T173712Z-4360-727-1-2730@dummyVTODO\n" "DTSTAMP:20060417T173712Z\n" "SUMMARY:do me please\\, please\n" "DESCRIPTION:to be done\n" "PRIORITY:0\n" "STATUS:NEEDS-ACTION\n" "CREATED:20060417T173712Z\n" "LAST-MODIFIED:20060417T173712Z\n" "END:VTODO\n" "END:VCALENDAR\n"; config.m_mergeItem2 = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VTODO\n" "UID:20060417T173712Z-4360-727-1-2730@dummyVTODO\n" "DTSTAMP:20060417T173712Z\n" "SUMMARY:do me\n" "DESCRIPTION:to be done\n" "PRIORITY:7\n" "STATUS:NEEDS-ACTION\n" "CREATED:20060417T173712Z\n" "LAST-MODIFIED:20060417T173712Z\n" "END:VTODO\n" "END:VCALENDAR\n"; config.m_templateItem = config.m_insertItem; config.m_uniqueProperties = "SUMMARY:UID"; config.m_sizeProperty = "DESCRIPTION"; config.m_testcases = "testcases/eds_task.ics"; } else if(!strcmp(type, "eds_memo")) { // The "eds_memo" test uses iCalendar 2.0 VJOURNAL // as format because synccompare doesn't handle // plain text. A backend which wants to use this // test data must support importing/exporting // the test data in that format, see EvolutionMemoSource // for an example. config.m_uri = "note"; // ScheduleWorld config.m_sourceName = "eds_memo"; config.m_sourceNameServerTemplate = "memo"; config.m_type = "memo"; config.m_itemType = "text/calendar"; config.m_essentialProperties = iCalEssential; config.m_mangleItem = mangleICalendar20; config.m_insertItem = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VJOURNAL\n" "SUMMARY:Summary\n" "DESCRIPTION:Summary\\nBody text\n" "END:VJOURNAL\n" "END:VCALENDAR\n"; config.m_updateItem = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VJOURNAL\n" "SUMMARY:Summary Modified\n" "DESCRIPTION:Summary Modified\\nBody text\n" "END:VJOURNAL\n" "END:VCALENDAR\n"; /* change summary, as in updateItem, and the body in the other merge item */ config.m_mergeItem1 = config.m_updateItem; config.m_mergeItem2 = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VJOURNAL\n" "SUMMARY:Summary\n" "DESCRIPTION:Summary\\nBody modified\n" "END:VJOURNAL\n" "END:VCALENDAR\n"; config.m_templateItem = "BEGIN:VCALENDAR\n" "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" "VERSION:2.0\n" "BEGIN:VJOURNAL\n" "SUMMARY:Summary\n" "DESCRIPTION:Summary\\nBody text <>\n" "END:VJOURNAL\n" "END:VCALENDAR\n"; config.m_uniqueProperties = "SUMMARY:DESCRIPTION"; config.m_sizeProperty = "DESCRIPTION"; config.m_testcases = "testcases/eds_memo.ics"; }else if (!strcmp (type, "calendar+todo")) { config.m_uri=""; config.m_sourceNameServerTemplate = "calendar+todo"; } } void CheckSyncReport::check(SyncMLStatus status, SyncReport &report) const { if (m_report) { *m_report = report; } stringstream str; str << report; str << "----------|--------CLIENT---------|--------SERVER---------|\n"; str << " | NEW | MOD | DEL | NEW | MOD | DEL |\n"; str << "----------|-----------------------------------------------|\n"; str << StringPrintf("Expected | %3d | %3d | %3d | %3d | %3d | %3d |\n", clientAdded, clientUpdated, clientDeleted, serverAdded, serverUpdated, serverDeleted); str << "Expected sync mode: " << PrettyPrintSyncMode(syncMode) << "\n"; str << "Expected cycles: " << restarts + 1 << "\n"; SE_LOG_INFO(NULL, "sync report:\n%s\n", str.str().c_str()); if (mustSucceed) { // both STATUS_OK and STATUS_HTTP_OK map to the same // string, so check the formatted status first, then // the numerical one CT_ASSERT_EQUAL(string("no error (remote, status 0)"), Status2String(status)); CT_ASSERT_EQUAL(STATUS_OK, status); } BOOST_FOREACH(SyncReport::value_type &entry, report) { const std::string &name = entry.first; const SyncSourceReport &source = entry.second; SE_LOG_DEBUG(NULL, "Checking sync source %s...", name.c_str()); if (mustSucceed) { CLIENT_TEST_EQUAL(name, STATUS_OK, source.getStatus()); } check(name, source); } SE_LOG_DEBUG(NULL, "Done with checking sync report."); } void CheckSyncReport::check(const std::string &name, const SyncSourceReport &source) const { // this code is intentionally duplicated to produce nicer CPPUNIT asserts CLIENT_TEST_EQUAL(name, 0, source.getItemStat(SyncSourceReport::ITEM_LOCAL, SyncSourceReport::ITEM_ANY, SyncSourceReport::ITEM_REJECT)); CLIENT_TEST_EQUAL(name, 0, source.getItemStat(SyncSourceReport::ITEM_REMOTE, SyncSourceReport::ITEM_ANY, SyncSourceReport::ITEM_REJECT)); const char* checkSyncModeStr = getenv("CLIENT_TEST_NOCHECK_SYNCMODE"); bool checkSyncMode = true; bool checkSyncStats = getenv ("CLIENT_TEST_NOCHECK_SYNCSTATS") ? false : true; if (checkSyncModeStr && (!strcmp(checkSyncModeStr, "1") || !strcasecmp(checkSyncModeStr, "t"))) { checkSyncMode = false; } if (syncMode != SYNC_NONE && checkSyncMode) { CLIENT_TEST_EQUAL(name, syncMode, source.getFinalSyncMode()); } CLIENT_TEST_EQUAL(name, restarts + 1, source.getRestarts() + 1); if (clientAdded != -1 && checkSyncStats) { CLIENT_TEST_EQUAL(name, clientAdded, source.getItemStat(SyncSourceReport::ITEM_LOCAL, SyncSourceReport::ITEM_ADDED, SyncSourceReport::ITEM_TOTAL)); } if (clientUpdated != -1 && checkSyncStats) { CLIENT_TEST_EQUAL(name, clientUpdated, source.getItemStat(SyncSourceReport::ITEM_LOCAL, SyncSourceReport::ITEM_UPDATED, SyncSourceReport::ITEM_TOTAL)); } if (clientDeleted != -1 && checkSyncStats) { CLIENT_TEST_EQUAL(name, clientDeleted, source.getItemStat(SyncSourceReport::ITEM_LOCAL, SyncSourceReport::ITEM_REMOVED, SyncSourceReport::ITEM_TOTAL)); } if (serverAdded != -1 && checkSyncStats) { CLIENT_TEST_EQUAL(name, serverAdded, source.getItemStat(SyncSourceReport::ITEM_REMOTE, SyncSourceReport::ITEM_ADDED, SyncSourceReport::ITEM_TOTAL)); } if (serverUpdated != -1 && checkSyncStats) { CLIENT_TEST_EQUAL(name, serverUpdated, source.getItemStat(SyncSourceReport::ITEM_REMOTE, SyncSourceReport::ITEM_UPDATED, SyncSourceReport::ITEM_TOTAL)); } if (serverDeleted != -1 && checkSyncStats) { CLIENT_TEST_EQUAL(name, serverDeleted, source.getItemStat(SyncSourceReport::ITEM_REMOTE, SyncSourceReport::ITEM_REMOVED, SyncSourceReport::ITEM_TOTAL)); } } /** @} */ /** @endcond */ #endif // ENABLE_INTEGRATION_TESTS SE_END_CXX syncevolution_1.4/test/ClientTest.h000066400000000000000000001147141230021373600175740ustar00rootroot00000000000000/* * Copyright (C) 2008 Funambol, Inc. * Copyright (C) 2008-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_TESTSYNCCLIENT #define INCL_TESTSYNCCLIENT #include #include #include #include #include #include #include #include #include #include "test.h" #include "ClientTestAssert.h" #ifdef ENABLE_INTEGRATION_TESTS #include #include #include #include #include #include #include SE_BEGIN_CXX class SyncContext; class EvolutionSyncSource; class TransportWrapper; class TestingSyncSource; /** * This class encapsulates logging and checking of a SyncReport. * When constructed with default parameters, no checking will be done. * Otherwise the sync report has to contain exactly the expected result. * When multiple sync sources are active, @b all of them have to behave * alike (which is how the tests are constructed). * * No item is ever supposed to fail. */ class CheckSyncReport { public: CheckSyncReport(int clAdded = -1, int clUpdated = -1, int clDeleted = -1, int srAdded = -1, int srUpdated = -1, int srDeleted = -1, bool mstSucceed = true, SyncMode mode = SYNC_NONE) : clientAdded(clAdded), clientUpdated(clUpdated), clientDeleted(clDeleted), serverAdded(srAdded), serverUpdated(srUpdated), serverDeleted(srDeleted), restarts(0), mustSucceed(mstSucceed), syncMode(mode), m_report(NULL) {} int clientAdded, clientUpdated, clientDeleted, serverAdded, serverUpdated, serverDeleted; int restarts; bool mustSucceed; SyncMode syncMode; // if set, then the report is copied here SyncReport *m_report; CheckSyncReport &setMode(SyncMode mode) { syncMode = mode; return *this; } CheckSyncReport &setReport(SyncReport *report) { m_report = report; return *this; } CheckSyncReport &setRestarts(int r) { restarts = r; return *this; } /** * checks that the sync completed as expected and throws * CPPUnit exceptions if something is wrong * * @param res return code from SyncClient::sync() * @param report the sync report stored in the SyncClient */ void check(SyncMLStatus status, SyncReport &report) const; /** * checks that the source report matches with expectations */ void check(const std::string &name, const SyncSourceReport &report) const; }; /** * parameters for running a sync */ struct SyncOptions { /** default maximum message size */ static const long DEFAULT_MAX_MSG_SIZE = 128 * 1024; /** default maximum object size */ static const long DEFAULT_MAX_OBJ_SIZE = 1024 * 1024 * 1024; /** sync mode chosen by client */ SyncMode m_syncMode; /** * has to be called after a successful or unsuccessful sync, * will dump the report and (optionally) check the result; * beware, the later may throw exceptions inside CPPUNIT macros */ CheckSyncReport m_checkReport; /** maximum message size supported by client */ long m_maxMsgSize; /** maximum object size supported by client */ long m_maxObjSize; /** enabled large object support */ bool m_loSupport; /** enabled WBXML (default) */ bool m_isWBXML; /** overrides resend properties */ int m_retryDuration; int m_retryInterval; boost::shared_ptr m_isSuspended; boost::shared_ptr m_isAborted; /** * Callback to be invoked after setting up local sources, but * before running the engine. May throw exception to indicate * error and return true to stop sync without error. */ typedef boost::function Callback_t; Callback_t m_startCallback; /** * called while configuration is prepared for sync, see * SyncContext::prepare() */ Callback_t m_prepareCallback; boost::shared_ptr m_transport; SyncOptions(SyncMode syncMode = SYNC_NONE, const CheckSyncReport &checkReport = CheckSyncReport(), long maxMsgSize = DEFAULT_MAX_MSG_SIZE, // 128KB = large enough that normal tests should run with a minimal number of messages long maxObjSize = DEFAULT_MAX_OBJ_SIZE, // 1GB = basically unlimited... bool loSupport = false, bool isWBXML = defaultWBXML(), Callback_t startCallback = EmptyCallback, boost::shared_ptr transport = boost::shared_ptr()) : m_syncMode(syncMode), m_checkReport(checkReport), m_maxMsgSize(maxMsgSize), m_maxObjSize(maxObjSize), m_loSupport(loSupport), m_isWBXML(isWBXML), m_retryDuration(300), m_retryInterval(60), m_startCallback(startCallback), m_transport (transport) {} SyncOptions &setSyncMode(SyncMode syncMode) { m_syncMode = syncMode; return *this; } SyncOptions &setCheckReport(const CheckSyncReport &checkReport) { m_checkReport = checkReport; return *this; } SyncOptions &setMaxMsgSize(long maxMsgSize) { m_maxMsgSize = maxMsgSize; return *this; } SyncOptions &setMaxObjSize(long maxObjSize) { m_maxObjSize = maxObjSize; return *this; } SyncOptions &setLOSupport(bool loSupport) { m_loSupport = loSupport; return *this; } SyncOptions &setWBXML(bool isWBXML) { m_isWBXML = isWBXML; return *this; } SyncOptions &setRetryDuration(int retryDuration) { m_retryDuration = retryDuration; return *this; } SyncOptions &setRetryInterval(int retryInterval) { m_retryInterval = retryInterval; return *this; } SyncOptions &setStartCallback(const Callback_t &callback) { m_startCallback = callback; return *this; } SyncOptions &setPrepareCallback(const Callback_t &callback) { m_prepareCallback = callback; return *this; } SyncOptions &setTransportAgent(const boost::shared_ptr transport) {m_transport = transport; return *this;} static bool EmptyCallback(SyncContext &, SyncOptions &) { return false; } /** if CLIENT_TEST_XML=1, then XML, otherwise WBXML */ static bool defaultWBXML(); }; class LocalTests; class SyncTests; /** * This is the interface expected by the testing framework for sync * clients. It defines several methods that a derived class must * implement if it wants to use that framework. Note that this class * itself is not derived from SyncClient. This gives a user of this * framework the freedom to implement it in two different ways: * - implement a class derived from both SyncClient and ClientTest * - add testing of an existing subclass of SyncClient by implementing * a ClientTest which uses that subclass * * The client is expected to support change tracking for multiple * servers. Although the framework always always tests against the * same server, for most tests it is necessary to access the database * without affecting the next synchronization with the server. This is * done by asking the client for two different sync sources via * Config::createSourceA and Config::createSourceB which have to * create them in a suitable way - pretty much as if the client was * synchronized against different server. A third, different change * tracking is needed for real synchronizations of the data. * * Furthermore the client is expected to support multiple data sources * of the same kind, f.i. two different address books. This is used to * test full client A <-> server <-> client B synchronizations in some * tests or to check server modifications done by client A with a * synchronization against client B. In those tests client A is mapped * to the first data source and client B to the second one. * * Handling configuration and creating classes is entirely done by the * subclass of ClientTest, the frameworks makes no assumptions * about how this is done. Instead it queries the ClientTest for * properties (like available sync sources) and then creates several * tests. */ class ClientTest : private boost::noncopyable { public: ClientTest(int serverSleepSec = 0, const std::string &serverLog= ""); virtual ~ClientTest(); /** a unique string - "1" or "2" in practice */ virtual std::string getClientID() const = 0; /** set up before running a test */ virtual void setup() { } /** cleanup function to be called when shutting down testing */ typedef void (*Cleanup_t)(void); /** * Call this to register another shutdown cleanup functions. * Every unique function will be called exactly once. */ static void registerCleanup(Cleanup_t cleanup); /** * Call cleanup functions. */ static void shutdown(); /** * This function registers tests using this instance of ClientTest for * later use during a test run. * * The instance must remain valid until after the tests were * run. To run them use a separate test runner, like the one from * client-test-main.cpp. */ virtual void registerTests(); typedef ClientTestConfig Config; /** * Creates an instance of LocalTests (default implementation) or a * class derived from it. LocalTests provides tests which cover * the SyncSource interface and can be executed without a SyncML * server. It also contains utility functions for working with * SyncSources. * * A ClientTest implementation can, but doesn't have to extend * these tests by instantiating a derived class here. */ virtual LocalTests *createLocalTests(const std::string &name, int sourceParam, ClientTest::Config &co); /** * Creates an instance of SyncTests (default) or a class derived * from it. SyncTests provides tests which cover the actual * interaction with a SyncML server. * * A ClientTest implementation can, but doesn't have to extend * these tests by instantiating a derived class here. */ virtual SyncTests *createSyncTests(const std::string &name, std::vector sourceIndices, bool isClientA = true); /** * utility function for dumping items which are C strings with blank lines as separator */ static int dump(ClientTest &client, TestingSyncSource &source, const std::string &file); /** * utility function for splitting file into items with blank lines as separator * * @retval realfile If ..tem exists, then it is used instead * of the generic version. The caller gets the name of the * file that was opened here. */ static void getItems(const std::string &file, std::list &items, std::string &realfile); /** * utility function for importing items with blank lines as separator, * for ClientTestConfig::m_import */ static std::string import(ClientTest &client, TestingSyncSource &source, const ClientTestConfig &config, const std::string &file, std::string &realfile, std::list *luids); /** * utility function for comparing vCard and iCal files with the external * synccompare.pl Perl script */ static bool compare(ClientTest &client, const std::string &fileA, const std::string &fileB); /** * utility function: update a vCard or iCalendar item by inserting "MOD-" into * FN, N, resp. SUMMARY; used for ClientTestConfig::update */ static void update(std::string &item); struct ClientTestConfig config; /** * A derived class can use this call to get default test * cases, but still has to add callbacks which create sources * and execute a sync session. * * Some of the test cases are compiled into the library, other * depend on the auxiliary files from the "test" directory. * Currently supported types: * - eds_contact = vCard 3.0 contacts, with Evolution extensions * - eds_event = iCalendar 2.0 events, as used by Evolution * - eds_task = iCalendar 2.0 tasks, as used by Evolution * - eds_memo = iCalendar 2.0 journals, as used by Evolution */ static void getTestData(const char *type, Config &config); /** * Data sources are enumbered from 0 to n-1 for the purpose of * testing. This call returns n. */ virtual int getNumLocalSources() = 0; virtual int getNumSyncSources() = 0; /** * Called to fill the given test source config with information * about a sync source identified by its index. It's okay to only * fill in the available pieces of information and set everything * else to zero. * Two kinds of source config indexs are maintained, used for localSources * and SyncSources, this is because virtual datasoures should be visible as * a whole to the synccontext while should be viewed as a list of sub * datasoures for Localtests. */ virtual void getLocalSourceConfig(int source, Config &config) = 0; virtual void getSyncSourceConfig(int source, Config &config) = 0; /** * Find the correspoding test source config via config name. */ virtual void getSourceConfig(const string &configName, Config &config) =0; /* * Give me a test source config name, return the index in localSyncSources. * */ virtual int getLocalSourcePosition (const string &configName) =0; /** * The instance to use as second client. Returning NULL disables * all checks which require a second client. The returned pointer * must remain valid throughout the life time of the tests. * * The second client must be configured to access the same server * and have data sources which match the ones from the primary * client. */ virtual ClientTest *getClientB() = 0; /** * Execute a synchronization with the selected sync sources * and the selected synchronization options. The log file * in LOG has been set up already for the synchronization run * and should not be changed by the client. * * @param activeSources a -1 terminated array of sync source indices * @param logbase basename for logging: can be used for directory or as file (by adding .log suffix) * @param options sync options to be used * @return return code of SyncClient::sync() */ virtual SyncMLStatus doSync( const int *activeSources, const std::string &logbase, const SyncOptions &options) = 0; /** * This is called after successful sync() calls (res == 0) as well * as after unsuccessful ones (res != 1). The default implementation * sleeps for the number of seconds specified when constructing this * instance and copies the server log if one was named. * * @param res result of sync() * @param logname base name of the current sync log (without ".client.[AB].log" suffix) */ virtual void postSync(int res, const std::string &logname); protected: /** * time to sleep in postSync() */ int serverSleepSeconds; /** * server log file which is copied by postSync() and then * truncated (Unix only, Windows does not allow such access * to an open file) */ std::string serverLogFileName; private: /** * really a CppUnit::TestFactory, but declared as void * to avoid * dependencies on the CPPUnit header files: created by * registerTests() and remains valid until the client is deleted */ void *factory; }; /** * helper class to encapsulate ClientTest::Config::createsource_t * pointer and the corresponding parameters */ class CreateSource { public: CreateSource(const ClientTest::Config::createsource_t &createSourceParam, ClientTest &clientParam, int sourceParam, bool isSourceAParam) : createSource(createSourceParam), client(clientParam), source(sourceParam), isSourceA(isSourceAParam) {} TestingSyncSource *operator() () { CPPUNIT_ASSERT(createSource); return createSource(client, client.getClientID(), source, isSourceA); } const ClientTest::Config::createsource_t createSource; ClientTest &client; const int source; const bool isSourceA; }; /** * local test of one sync source and utility functions also used by * sync tests */ class LocalTests : public CppUnit::TestSuite, public CppUnit::TestFixture { public: /** the client we are testing */ ClientTest &client; /** number of the source we are testing in that client */ const int source; /** configuration that corresponds to source */ const ClientTest::Config config; /** shortcut for config.m_sourceName */ const std::string &getSourceName() const { return config.m_sourceName; } /** * A list of config pointers which share the same * database. Normally, sources are tested in isolation, but for * such linked sources we also need to test interdependencies, in * particular regarding change tracking and item listing. * * This includes *all* configs, not just the other ones. */ std::list m_linkedSources; /** helper funclets to create sources */ CreateSource createSourceA, createSourceB; LocalTests(const std::string &name, ClientTest &cl, int sourceParam, ClientTest::Config &co) : CppUnit::TestSuite(name), client(cl), source(sourceParam), config(co), createSourceA(co.m_createSourceA, cl, sourceParam, true), createSourceB(co.m_createSourceB, cl, sourceParam, false) {} /** set up before running a test */ virtual void setUp() { client.setup(); } /** * adds the supported tests to the instance itself; * this is the function that a derived class can override * to add additional tests */ virtual void addTests(); /** * opens source and inserts the given item; can be called * regardless whether the data source already contains items or not * * @param relaxed if true, then disable some of the additional checks after adding the item * @retval inserted actual data that was inserted, optional * @param uniqueUIDSuffix gets added to UID of the inserted item if unique UIDs are necessary * @return the LUID of the inserted item */ virtual std::string insert(CreateSource createSource, const std::string &data, bool relaxed = false, std::string *inserted = NULL, const std::string &uniqueUIDSuffix = ""); /** * assumes that exactly one element is currently inserted and updates it with the given item * * @param check if true, then reopen the source and verify that the reported items are as expected * @param uniqueUIDSuffix same UID suffix as when creating the item */ virtual void update(CreateSource createSource, const std::string &data, bool check = true, const std::string &uniqueUIDSuffix = ""); /** * updates one item identified by its LUID with the given item * * The type of the item is cleared, as in insert() above. */ virtual void update(CreateSource createSource, const std::string &data, const std::string &luid); /** deletes all items locally via sync source */ virtual void deleteAll(CreateSource createSource); /** * takes two databases, exports them, * then compares them using synccompare * * @param refFile existing file with source reference items, NULL uses a dump of sync source A instead * @param copy a sync source which contains the copied items, begin/endSync will be called * @param raiseAssert raise assertion if comparison yields differences (defaults to true) * @return true if the two databases are equal */ virtual bool compareDatabases(const char *refFile, TestingSyncSource ©, bool raiseAssert = true); /** * compare data in source with set of items */ void compareDatabasesRef(TestingSyncSource ©, const std::list &items); /** * compare data in source with vararg list of std::string pointers, NULL terminated */ void compareDatabases(TestingSyncSource ©, ...); /** * insert artificial items, number of them determined by TEST_EVOLUTION_NUM_ITEMS * unless passed explicitly * * @param createSource a factory for the sync source that is to be used * @param startIndex IDs are generated starting with this value * @param numItems number of items to be inserted if non-null, otherwise TEST_EVOLUTION_NUM_ITEMS is used * @param size minimum size for new items * @return number of items inserted */ virtual std::list insertManyItems(CreateSource createSource, int startIndex = 1, int numItems = 0, int size = -1); virtual std::list insertManyItems(TestingSyncSource *source, int startIndex = 1, int numItems = 0, int size = -1); /** * Update existing items. Must match a corresponding previous call to * insertManyItems(). * * @param revision revision number, used to distinguish different generations of each item * @param luids result from corresponding insertManyItems() call * @param offset skip that many items at the start of luids before updating the following ones */ void updateManyItems(CreateSource createSource, int startIndex, int numItems, int size, int revision, std::list &luids, int offset); /** * Delete items. Skips offset items in luids before deleting numItems. */ void removeManyItems(CreateSource createSource, int numItems, std::list &luids, int offset); /** * update every single item, using config.update */ virtual void updateData(CreateSource createSource); /** * create an artificial item for the current database * * @param item item number: items with different number should be * recognized as different by SyncML servers * @param revision differentiates items with the same item number (= updates of an older item) * @param size if > 0, then create items at least that large (in bytes) * @return created item */ std::string createItem(int item, const std::string &revision, int size); std::string createItem(int item, int revision, int size) { char buffer[32]; sprintf(buffer, "%d", revision); return createItem(item, std::string(buffer), size); } /* for more information on the different tests see their implementation */ virtual void testOpen(); virtual void testIterateTwice(); virtual void testDelete404(); virtual void testReadItem404(); void doInsert(bool withUID = true); virtual void testSimpleInsert(); virtual void testLocalDeleteAll(); virtual void testComplexInsert(); virtual void testInsertTwice(); virtual void testLocalUpdate(); void doChanges(bool restart); virtual void testChanges(); virtual void testChangesMultiCycles(); virtual void testLinkedSources(); virtual void testImport(); virtual void testImportDelete(); virtual void testRemoveProperties(); virtual void testManyChanges(); virtual void testLinkedItemsParent(); virtual void testLinkedItemsChild(); virtual void testLinkedItemsParentChild(); virtual void testLinkedItemsChildParent(); virtual void testLinkedItemsChildChangesParent(); virtual void testLinkedItemsRemoveParentFirst(); virtual void testLinkedItemsRemoveNormal(); virtual void testLinkedItemsInsertParentTwice(); virtual void testLinkedItemsInsertChildTwice(); virtual void testLinkedItemsParentUpdate(); virtual void testLinkedItemsUpdateChild(); virtual void testLinkedItemsInsertBothUpdateChild(); virtual void testLinkedItemsInsertBothUpdateParent(); virtual void testLinkedItemsInsertBothUpdateChildNoIDs(); virtual void testLinkedItemsUpdateChildNoIDs(); virtual void testLinkedItemsSingle404(); virtual void testLinkedItemsMany404(); virtual void testSubset(); /** retrieve right set of items for running test */ ClientTestConfig::LinkedItems_t getParentChildData(); }; std::list listItemsOfType(TestingSyncSource *source, int state); /** * Tests synchronization with one or more sync sources enabled. * When testing multiple sources at once only the first config * is checked to see which tests can be executed. */ class SyncTests : public CppUnit::TestSuite, public CppUnit::TestFixture { public: /** the client we are testing */ ClientTest &client; SyncTests(const std::string &name, ClientTest &cl, std::vector sourceIndices, bool isClientA = true); ~SyncTests(); /** * adds the supported tests to the instance itself * @param isFirstSource the tests are getting generated for a single source, * the one which was listed first; some tests are the * same for all sources and should only be run once */ virtual void addTests(bool isFirstSource = false); /** set up before running a test */ virtual void setUp() { client.setup(); } protected: /** list with all local test classes for manipulating the sources and their index in the client */ typedef std::vector< std::pair > source_array_t; source_array_t sources; typedef source_array_t::iterator source_it; /** * Stack of log file prefixes which are to be appended to the base name, * which already contains the current test name. Add a new prefix by * instantiating SyncPrefix. Its destructor takes care of popping * the prefix. */ std::list logPrefixes; class SyncPrefix { SyncTests &m_tests; public: SyncPrefix(const std::string &prefix, SyncTests &tests) : m_tests(tests) { tests.logPrefixes.push_back(prefix); } ~SyncPrefix() { m_tests.logPrefixes.pop_back(); } }; friend class SyncPrefix; /** the indices from sources, terminated by -1 (for sync()) */ int *sourceArray; /** utility functions for second client */ SyncTests *accessClientB; enum DeleteAllMode { DELETE_ALL_SYNC, /**< make sure client and server are in sync, delete locally, sync again */ DELETE_ALL_REFRESH /**< delete locally, refresh server */ }; /** * Compare databases second client with either reference file(s) * or first client. The reference file(s) must follow the naming * scheme .dat */ virtual bool compareDatabases(const char *refFileBase = NULL, bool raiseAssert = true); /** deletes all items locally and on server */ virtual void deleteAll(DeleteAllMode mode = DELETE_ALL_SYNC); /** get both clients in sync with empty server, then copy one item from client A to B */ virtual void doCopy(); /** * replicate server database locally: same as SYNC_REFRESH_FROM_SERVER, * but done with explicit local delete and then a SYNC_SLOW because some * servers do no support SYNC_REFRESH_FROM_SERVER */ virtual void refreshClient(SyncOptions options = SyncOptions()); /* for more information on the different tests see their implementation */ virtual void testTwoWaySync(); virtual void testSlowSync(); virtual void testRefreshFromServerSync(); virtual void testRefreshFromClientSync(); virtual void testRefreshFromRemoteSync(); virtual void testRefreshFromLocalSync(); virtual void testDeleteAllSync(); virtual void testDeleteAllRefresh(); virtual void testRefreshFromClientSemantic(); virtual void testRefreshFromServerSemantic(); virtual void testRefreshStatus(); void doRestartSync(SyncMode mode); void testTwoWayRestart(); void testSlowRestart(); void testRefreshFromLocalRestart(); void testOneWayFromLocalRestart(); void testRefreshFromRemoteRestart(); void testOneWayFromRemoteRestart(); void testManyRestarts(); void testCopy(); virtual void testUpdate(); virtual void testComplexUpdate(); virtual void testDelete(); virtual void testMerge(); virtual void testTwinning(); void doOneWayFromRemote(SyncMode oneWayFromRemote); void testOneWayFromServer(); void testOneWayFromRemote(); void doOneWayFromLocal(SyncMode oneWayFromLocal); void testOneWayFromClient(); void testOneWayFromLocal(); bool doConversionCallback(bool *success, SyncContext &client, SyncOptions &options); virtual void testConversion(); virtual void testItems(); virtual void testItemsXML(); virtual void testExtensions(); virtual void testAddUpdate(); void testMaxMsg(); void testLargeObject(); virtual void testManyItems(); virtual void testManyDeletes(); virtual void testSlowSyncSemantic(); virtual void testComplexRefreshFromServerSemantic(); virtual void testDeleteBothSides(); virtual void testAddBothSides(); virtual void testAddBothSidesRefresh(); virtual void testLinkedItemsParentChild(); virtual void testLinkedItemsChild(); virtual void testLinkedItemsChildParent(); virtual void doInterruptResume(int changes, boost::shared_ptr wrapper); /** * CLIENT_ = change made on client B before interrupting * SERVER_ = change made on client A and applied to server before interrupting * while sending to B * _ADD = new item added * _REMOVE = existing item deleted * _UPDATE = existing item replaced * BIG = when adding or updating, make the new item so large that it does * not fit into a single message */ enum { CLIENT_ADD = (1<<0), CLIENT_REMOVE = (1<<1), CLIENT_UPDATE = (1<<2), SERVER_ADD = (1<<3), SERVER_REMOVE = (1<<4), SERVER_UPDATE = (1<<5), BIG = (1<<6) }; virtual void testInterruptResumeClientAdd(); virtual void testInterruptResumeClientRemove(); virtual void testInterruptResumeClientUpdate(); virtual void testInterruptResumeServerAdd(); virtual void testInterruptResumeServerRemove(); virtual void testInterruptResumeServerUpdate(); virtual void testInterruptResumeClientAddBig(); virtual void testInterruptResumeClientUpdateBig(); virtual void testInterruptResumeServerAddBig(); virtual void testInterruptResumeServerUpdateBig(); virtual void testInterruptResumeFull(); virtual void testUserSuspendClientAdd(); virtual void testUserSuspendClientRemove(); virtual void testUserSuspendClientUpdate(); virtual void testUserSuspendServerAdd(); virtual void testUserSuspendServerRemove(); virtual void testUserSuspendServerUpdate(); virtual void testUserSuspendClientAddBig(); virtual void testUserSuspendClientUpdateBig(); virtual void testUserSuspendServerAddBig(); virtual void testUserSuspendServerUpdateBig(); virtual void testUserSuspendFull(); virtual void testResendClientAdd(); virtual void testResendClientRemove(); virtual void testResendClientUpdate(); virtual void testResendServerAdd(); virtual void testResendServerRemove(); virtual void testResendServerUpdate(); virtual void testResendFull(); virtual void testResendProxyClientAdd(); virtual void testResendProxyClientRemove(); virtual void testResendProxyClientUpdate(); virtual void testResendProxyServerAdd(); virtual void testResendProxyServerRemove(); virtual void testResendProxyServerUpdate(); virtual void testResendProxyFull(); virtual void testTimeout(); /** * implements testMaxMsg(), testLargeObject(), testLargeObjectEncoded() * using a sequence of items with varying sizes */ virtual void doVarSizes(bool withMaxMsgSize, bool withLargeObject); /** * executes a sync with the given options, * checks the result and (optionally) the sync report */ virtual void doSync(const SyncOptions &options); virtual void doSync(const char *logPrefix, const SyncOptions &options) { SyncPrefix prefix(logPrefix, *this); doSync(options); } void doSync(const char *file, int line, const char *logPrefix, const SyncOptions &options) { CT_WRAP_ASSERT(file, line, doSync(logPrefix, options), true); } void doSync(const char *file, int line, const SyncOptions &options) { CT_WRAP_ASSERT(file, line, doSync(options), true); } virtual void postSync(int res, const std::string &logname); private: void allSourcesInsert(bool withUID = true); void allSourcesUpdate(); void allSourcesDeleteAll(); void allSourcesInsertMany(int startIndex, int numItems, std::map > &luids); void allSourcesUpdateMany(int startIndex, int numItems, int revision, std::map > &luids, int offset); void allSourcesRemoveMany(int numItems, std::map > &luids, int offset); }; /* * A transport wraper wraps a real transport impl and gives user * possibility to do additional work before/after transport operation. * We use TransportFaultInjector to emulate a network failure; * We use UserSuspendInjector to emulate a user suspend after receving * a response. */ class TransportWrapper : public TransportAgent { protected: int m_interruptAtMessage, m_messageCount; boost::shared_ptr m_wrappedAgent; Status m_status; SyncOptions *m_options; public: TransportWrapper() { m_messageCount = 0; m_interruptAtMessage = -1; m_wrappedAgent = boost::shared_ptr(); m_status = INACTIVE; m_options = NULL; } ~TransportWrapper() { } /** * -1 for wrappers which are meant to be used without message resending, * otherwise the number x for which "interrupt" <= x will lead to * an aborted sync (0 for TransportResendInjector, 2 for TransportResendProxy) */ virtual int getResendFailureThreshold() { return -1; } virtual int getMessageCount() { return m_messageCount; } virtual void setURL(const std::string &url) { m_wrappedAgent->setURL(url); } virtual void setContentType(const std::string &type) { m_wrappedAgent->setContentType(type); } virtual void setAgent(boost::shared_ptr agent) {m_wrappedAgent = agent;} virtual void setSyncOptions(SyncOptions *options) {m_options = options;} virtual void setInterruptAtMessage (int interrupt) {m_interruptAtMessage = interrupt;} virtual void cancel() { m_wrappedAgent->cancel(); } virtual void shutdown() { m_wrappedAgent->shutdown(); } virtual void rewind() { m_messageCount = 0; m_interruptAtMessage = -1; m_status = INACTIVE; m_options = NULL; m_wrappedAgent.reset(); } virtual Status wait(bool noReply = false) { return m_status; } virtual void setTimeout(int seconds) { m_wrappedAgent->setTimeout(seconds); } }; /** write log message into *.log file of a test */ #define CLIENT_TEST_LOG(_format, _args...) \ SE_LOG_DEBUG(NULL, "\n%s:%d *** " _format, \ getBasename(__FILE__).c_str(), __LINE__, \ ##_args) /** assert equality, include string in message if unequal */ #define CLIENT_TEST_EQUAL( _prefix, \ _expected, \ _actual ) \ CT_ASSERT_EQUAL_MESSAGE( std::string(_prefix) + ": " + #_expected + " == " + #_actual, \ _expected, \ _actual ) /** execute _x and then check the status of the _source pointer */ #define SOURCE_ASSERT_NO_FAILURE(_source, _x) \ { \ CT_ASSERT_NO_THROW(_x); \ CT_ASSERT((_source)); \ } /** check _x for true and then the status of the _source pointer */ #define SOURCE_ASSERT(_source, _x) \ { \ CT_ASSERT(_x); \ CT_ASSERT((_source)); \ } /** check that _x evaluates to a specific value and then the status of the _source pointer */ #define SOURCE_ASSERT_EQUAL(_source, _value, _x) \ { \ CT_ASSERT_EQUAL(_value, _x); \ CT_ASSERT((_source)); \ } /** same as SOURCE_ASSERT() with a specific failure message */ #define SOURCE_ASSERT_MESSAGE(_message, _source, _x) \ { \ CT_ASSERT_MESSAGE((_message), (_x)); \ CT_ASSERT((_source)); \ } /** * convenience macro for adding a test name like a function, * to be used inside addTests() of an instance of that class * * @param _class class which contains the function * @param _function a function without parameters in that class */ #define ADD_TEST(_class, _function) \ ADD_TEST_TO_SUITE(this, _class, _function) #define ADD_TEST_TO_SUITE(_suite, _class, _function) \ _suite->addTest(FilterTest(new CppUnit::TestCaller<_class>(_suite->getName() + "::" #_function, &_class::_function, *this))) #define ADD_TEST_TO_SUITE_SUFFIX(_suite, _class, _function, _suffix) \ _suite->addTest(FilterTest(new CppUnit::TestCaller<_class>(_suite->getName() + "::" #_function + _suffix, &_class::_function, *this))) SE_END_CXX #endif // ENABLE_INTEGRATION_TESTS #endif // INCL_TESTSYNCCLIENT syncevolution_1.4/test/ClientTestAssert.h000066400000000000000000000147241230021373600207560ustar00rootroot00000000000000/* * Copyright (C) 2011 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef INCL_CLIENTTESTASSERT #define INCL_CLIENTTESTASSERT #include #include #include SE_BEGIN_CXX /** * All of the macros below include the behavior of the corresponding * CPUNIT assertion. In addition they catch exceptions and turn them * into the following, extended CPPUnit exception by preserving the * information from the original exception and adding the current * source code line. * * Source information is listed as inner first (as part of the error * preamble), followed by error at that location and all locations * that the error passed through until caught at the top level of a * test. */ class CTException : public CppUnit::Exception { public: CTException(const CppUnit::Message &message = CppUnit::Message(), const std::string ¤tMessage = "", const CppUnit::SourceLine ¤tSourceLine = CppUnit::SourceLine(), const CppUnit::SourceLine &previousSourceLine = CppUnit::SourceLine()) : CppUnit::Exception(message, previousSourceLine) { CppUnit::Message extendedMessage = message; if (!currentMessage.empty()) { extendedMessage.addDetail(currentMessage); } if (currentSourceLine.isValid()) { extendedMessage.addDetail(StringPrintf("%s:%d", getBasename(currentSourceLine.fileName()).c_str(), currentSourceLine.lineNumber())); } setMessage(extendedMessage); } }; // Avoid clang 'error: attributes are not allowed on a function-definition' by putting the SE_NORETURN // into a separate declaration. static void ClientTestExceptionHandle(const char *file, int line, const std::string &message = "") SE_NORETURN; static void ClientTestExceptionHandle(const char *file, int line, const std::string &message) { CppUnit::SourceLine here(file, line); try { throw; } catch (const CTException &ex) { throw(CTException(ex.message(), message, here, ex.sourceLine())); } catch (const CppUnit::Exception &ex) { if (ex.sourceLine() == CPPUNIT_SOURCELINE()) { /* failure in condition expression itself, already includes source info, pass through */ throw; } else { throw(CTException(ex.message(), message, here, ex.sourceLine())); } } catch (const Exception &ex) { throw(CTException(CppUnit::Message(ex.what()), message, here, CppUnit::SourceLine(ex.m_file, ex.m_line))); } catch (const std::exception &ex) { CppUnit::Message msg(CPPUNIT_EXTRACT_EXCEPTION_TYPE_(ex, "std::exception or derived")); msg.addDetail(std::string("What(): ") + ex.what()); throw(CTException(msg, message, here)); } } // The only purpose of this here is to teach clang's scan-build that CT_ASSERT // doesn't continue unless the condition is true // (http://clang-analyzer.llvm.org/annotations.html#custom_assertions). // The code never actually gets called. #define CT_ASSERT_TRUE(_expression) if (!(_expression)) { exit(1); } #define CT_WRAP_ASSERT(_file, _line, _assert, _expression) \ do { \ try { \ SE_LOG_DEBUG(NULL, "%s:%d: starting %s", getBasename(_file).c_str(), _line, #_assert); \ _assert; \ CT_ASSERT_TRUE(_expression); \ SE_LOG_DEBUG(NULL, "%s:%d: ending %s", getBasename(_file).c_str(), _line, #_assert); \ } catch (...) { \ ClientTestExceptionHandle(_file, _line); \ } \ } while (false) #define CT_WRAP_ASSERT_MESSAGE(_file, _line, _message, _assert, _expression) \ do { \ try { \ SE_LOG_DEBUG(NULL, "%s:%d: starting %s %s", \ getBasename(_file).c_str(), _line, \ std::string(_message).c_str(), \ #_assert); \ _assert; \ CT_ASSERT_TRUE(_expression); \ SE_LOG_DEBUG(NULL, "%s:%d: ending %s", getBasename(_file).c_str(), _line, #_assert); \ } catch (...) { \ ClientTestExceptionHandle(_file, _line, _message); \ } \ } while (false) #define CT_ASSERT(condition) CT_WRAP_ASSERT(__FILE__, __LINE__, CPPUNIT_ASSERT(condition), (condition)) #define CT_ASSERT_NO_THROW(expression) CT_WRAP_ASSERT(__FILE__, __LINE__, expression, true) #define CT_ASSERT_NO_THROW_MESSAGE(message, expression) CT_WRAP_ASSERT_MESSAGE(__FILE__, __LINE__, message, (expression), true) #define CT_ASSERT_MESSAGE(message,condition) CT_WRAP_ASSERT(__FILE__, __LINE__, CPPUNIT_ASSERT_MESSAGE(message, (condition)), (condition)) #define CT_FAIL(message) CPPUNIT_FAIL(message) #define CT_ASSERT_EQUAL(expected,actual) CT_WRAP_ASSERT(__FILE__, __LINE__, CPPUNIT_ASSERT_EQUAL((expected),(actual)), ((expected) == (actual))) #define CT_ASSERT_EQUAL_MESSAGE(message,expected,actual) CT_WRAP_ASSERT(__FILE__, __LINE__, CPPUNIT_ASSERT_EQUAL_MESSAGE(message,(expected),(actual)), ((expected) == (actual))) #define CT_ASSERT_DOUBLES_EQUAL(expected,actual,delta) CT_WRAP_ASSERT(__FILE__, __LINE__, CPPUNIT_ASSERT_DOUBLES_EQUAL((expected),(actual),(delta)), true) #define CT_ASSERT_DOUBLES_EQUAL_MESSAGE(message,expected,actual,delta) CT_WRAP_ASSERT(__FILE__, __LINE__, CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(__FILE__, __LINE__, message,(expected),(actual),(delta)), true) SE_END_CXX #endif // INCL_CLIENTTESTASSERT syncevolution_1.4/test/Makefile000066400000000000000000000007121230021373600167750ustar00rootroot00000000000000# # Hand-written Makefile for using installed tests. Installed together # with test binaries and data files as part of "make install" if # either --enable-unit-tests or --enable-integration-tests was used # during configure. # # Currently it only provides the "testclean" target # expected by runtest.py. # .PHONY: testclean clean testclean: rm -rf *.test.vcf *.log *.log.html *.tests *.diff *.dat *Client_Sync_*client.* *Client_Source* clean: testclean syncevolution_1.4/test/README.Ovi000066400000000000000000000121601230021373600167510ustar00rootroot00000000000000This document is used to show interoperability test information with Nokia Ovi server. http://bugzilla.moblin.org/show_bug.cgi?id=3182 * How to get the password Register your account at Ovi.com. Add your phone by Ovi.com->Login->Add Device. At the end of your adding device page, the synchronization configuration will be sent to your phone by mms but it also provides a 'Manual Setting' link which provides the synchronization password used to sync this device in case you could not receive the mms. Please use your Ovi user name and this per-device password for SyncEvolution. * The Ovi Server's device information Man=Intellisync Mod=- SwV=6.5.0.OVI.1033 HwV=- OEM=- DevID=Ovi.com DevTyp=server VerDTD=1.2 UTC= SupportLargeObjs=true SupportNumberOfChanges=true * Test Environment Variables Settings CLIENT_TEST_SERVER=Ovi \ CLIENT_TEST_DELETE_REFRESH=1 \ CLIENT_TEST_NUM_ITEMS=50 \ CLIENT_TEST_MAX_ITEM_SIZE=512\ CLIENT_TEST_EVOLUTION_PREFIX=file:///tmp/test/ \ CLIENT_TEST_SKIP=Client::Sync::vcard30::testRefreshFromServerSync, \ Client::Sync::vcard30::testOneWayFromClient, \ Client::Sync::vcard30::testOneWayFromServer, \ Client::Sync::vcard30::testSlowSyncSemantic, \ Client::Sync::vcard30::testComplexRefreshFromServerSemantic, \ Client::Sync::vcard30::testDelete, \ Client::Sync::vcard30::testManyDeletes, \ Client::Sync::vcard30::testDeleteAllSync, \ Client::Sync::vcard30::testRetry,Client::Sync::vcard30::Suspend \ Client::Sync::calendar+task::testRefreshFromServerSync, \ Client::Sync::calendar+task::testOneWayFromClient, \ Client::Sync::calendar+task::testOneWayFromServer, \ Client::Sync::calendar+task::testSlowSyncSemantic, \ Client::Sync::calendar+task::testComplexRefreshFromServerSemantic, \ Client::Sync::calendar+task::testDelete, \ Client::Sync::calendar+task::testManyDeletes, \ Client::Sync::calendar+task::testDeleteAllSync, \ Client::Sync::calendar+task::testRetry,Client::Sync::vcard30::Suspend, \ Client::Sync::calendar+task::testDeleteAllRefresh, \ Client::Sync::calendar+task::testItemsXML, \ Client::Sync::calendar+task::testMaxMsg, \ Client::Sync::calendar+task::testLargeObject * Data formats we need to test: Client::Sync::vcard30 * Know Limitations in Ovi server: The server is unstable, during testing, it returns '400' error from time to time. The authentication process need 3 retries to success. Delete in normal sync (including two-way, one-way-from-client) does not effectively delete the data in the server, we can only use (refresh-from-client). because of this limitation, many cases failed. VCard30 -- The server does not accept entries without an empty REV field. -- Below properties are lost: X-AIM X-EVOLUTION-UI-SLOT FBURL CALURI -- 'FN' value mismatch -- 'X-EVOLUTION-FILE-AS' mismatch, the server adds '\' -- 'ADR' only support maximum two fields -- Several fields is accepted by the server but do not send back to client (PHOTO, URL, EMAIL). -- Character 'Tab' in Note will confuse the server and causing value mismatch * Known test failures: Client::Sync::vcard30 -- Client::Sync::vcard30::testRefreshFromServerSync, Client::Sync::vcard30::testComplexRefreshFromServerSemantic, Client::Sync::vcard30::testOneWayFromClient, Client::Sync::vcard30::testOneWayFromServer, Client::Sync::vcard30::testSlowSyncSemantic, Client::Sync::vcard30::testManyDeletes, Client::Sync::vcard30::testDelete, Client::Sync::vcard30::testDeleteAllSync, Client::Sync::vcard30::testRetry,Client::Sync::vcard30::Suspend Client::Sync::calendar+todo (vcalendar1.0) EVENTS: --All day event not supported --Yearly recur and Monthly recur event not supported --'attendee' not supported --The maxsize of an event items seems less than 512 (tested with MAX_ITEM_SIZE=512) TODO: --Lost properties: SEQUENCE, PERCENT-COMPLETE --MISMATCH properties: DTSTART, COMPLETED; TODO only supports date not datetime testDeleteAllRefresh, testItemsXML, testMaxMsg, testLargeObject these cases were tested OK sometime with serveral retires but the server is quite unstable and usually failed with an unexpected response. Also tried with icalendar 2.0 format which also works badly (all day event recognized but with one additional day added, events with VTimeZone rejected by server, some events have the closing date wrongly calculatd (seems treating DTEND as inclusive). Client::Sync::text Reject contents, No devinfo. Items sent from client is rejected by server. Adding a note from the web interface and try testFreshFromServerSync, the server sends 1 item with empty content thus also rejected by our client. syncevolution_1.4/test/README.funambol000066400000000000000000000050061230021373600200200ustar00rootroot00000000000000This document is used to show interoperability test information with funambol server. http://bugzilla.moblin.org/show_bug.cgi?id=2422 * The Funambol Server's device information Funambol Data Synchronization Server v.7.1.1 Man=Funambol Mod=DS Server CarEd SwV=7.1.1 HwV=- FwV=- OEM=- DevID=funambol DevTyp=server VerDTD=1.2 UTC=true SupportLargeObjs=true SupportNumberOfChanges=true Ext=X-funambol-smartslow * Test Environment Variables Settings CLIENT_TEST_SERVER=funambol CLIENT_TEST_XML=1 CLIENT_TEST_MAX_ITEMSIZE=2048 \ CLIENT_TEST_EVOLUTION_PREFIX=file:///tmp/test/ * Test Profiles Settings Make sure your calendar type is 'calendar:text/calendar!' and task type is 'tasks:text/calendar!' to force to use ical20 * Data formats we need to test: Client::Sync::vcard21 Client::Sync::ical20 Client::Sync::itodo20 Client::Sync::text * Know Limitations in Funambol server: VCard21 -- Below properties are lost: CALURI, FBURL, X-MOZILLA-HTML, X-EVOLUTION-FILE-AS, X-AIM, X-EVOLUTION-BLOG-URL, X-EVOLUTION-VIDEO-URL, X-GROUPWISE, X-ICQ, X-YAHOO, X-ASSISTANT -- The parameter 'X-EVOLUTION-URI-SLOT'of properties like 'TEL' and 'EMAIL' is lost -- 'ORG' loses 'office' -- 'TEL' has no concept of 'preferred' phone number ical20 -- Below properties are lost: UID, SEQUENCE, TRANSP, LAST-MODIFIED, X-EVOLUTION-ALARM-UID, RECURRENCE-ID, ATTENDEE -- Below properties(together with default values) are added by server: REPEAT:0 -- Below properties lose parameters by server: ORGANIZER(CN) http://forge.ow2.org/tracker/index.php?func=detail&aid=313968&group_id=96&atid=100096 -- If 'BYMONTHDAY' of 'RRULE' is not presented in 'monthly', funambol will generate a 'BYMONTHDAY' part automatically. itodo20 -- Below properties are lost: UID, SEQUENCE, TRANSP, LAST-MODIFIED, X-EVOLUTION-ALARM-UID, STATUS, URL, COMPLETED -- Below properties(together with default values) are added by server: CLASS:PUBLIC, PERCENT-COMPLETE:0 Both itodo20 and ical20: -- ACTION in VALARM is never sent although required by standard http://forge.ow2.org/tracker/index.php?func=detail&aid=313969&group_id=96&atid=100096 -- TRIGGER + relative alarm time is replaced with a fixed UTC date+time (wrong, fires only once for recurring meetings, ignores timezone changes) * Known test failures: Client::Sync::vcard21 -- NONE Client::Sync::ical20 -- Client::Sync::ical20::testItems Client::Sync::itodo20 -- NONE Client::Sync::text -- NONE syncevolution_1.4/test/README.google000066400000000000000000000043751230021373600175010ustar00rootroot00000000000000This document is used to show interoperability test information with google server. http://bugzilla.moblin.org/show_bug.cgi?id=2423 * The google Server's device information Man=Google Mod=Sync SwV=0.01 HwV=- OEM=- DevID=Google DevTyp=server VerDTD=1.2 UTC= SupportLargeObjs=true SupportNumberOfChanges=true * Test Environment Variables Settings CLIENT_TEST_SERVER=google CLIENT_TEST_XML=0 CLIENT_TEST_MAX_ITEMSIZE=2048 \ CLIENT_TEST_EVOLUTION_PREFIX=file:///tmp/test/ \ CLIENT_TEST_SKIP=Client::Sync::vcard21::testRefreshFromClientSync, \ Client::Sync::vcard21::testRefreshFromClientSemantic, \ Client::Sync::vcard21::testRefreshStatus, \ Client::Sync::vcard21::testOneWayFromClient, \ Client::Sync::vcard21::testItemsXML, \ Client::Sync::vcard21::testRetry,Client::Sync::vcard21::Suspend * Data formats we need to test: Client::Sync::vcard21 * Know Limitations in google server: Only supports Contact Sync over SyncML and only supports vcard2.1 format. Only supports WBXML for transmitting. Don't support one-way-from-client and refresh-from-client sync. Return 200(OK) instead of 201(Added) for replace operation from client. Delete operation does not permanently remove items. The server drops photos if they exceed a certain size. The limit is somewhere between 40KB (okay) and 80KB (dropped). VCard21 -- Below properties are lost: X-EVOLUTION-FILE-AS X-AIM X-EVOLUTION-UI-SLOT X-ANNIVERSARY X-ASSISTANT X-EVOLUTION-BLOG-URL X-EVOLUTION-VIDEO-URL X-GROUPWISE X-ICQ X-MANAGER X-SPOUSE X-MOZILLA-HTML X-YAHOO CATEGORIES NICKNAME BDAY URL FBURL CALURI ROLE -- 'FN' value mismatch -- 'NOTE' lost ';' -- 'TEL' do not support 'CAR' sub type -- 'ORG' lost ';' as the delimiter * Known test failures: Client::Sync::vcard21 -- Client::Sync::vcard21::testRefreshFromClientSync Client::Sync::vcard21::testRefreshFromClientSemantic Client::Sync::vcard21::testRefreshStatus Client::Sync::vcard21::testOneWayFromClient Client::Sync::vcard21::testItemsXML Client::Sync::vcard21::Retry Client::Sync::vcard21::Suspend syncevolution_1.4/test/README.googlecalendar000066400000000000000000000023761230021373600211720ustar00rootroot00000000000000Done via CalDAV. Running backend tests (no syncing): CLIENT_TEST_SIMPLE_UID=1 \ CLIENT_TEST_UNIQUE_UID=1 \ CLIENT_TEST_SERVER=google \ "CLIENT_TEST_WEBDAV=google caldav testcases=testcases/google_event.ics" \ ./client-test Client::Source "simple UID" needed because special characters like % confuse the server. "unique ID" works around issues with unintentionally restoring older revisions of the test cases (?). google_event.ics is derived from eds_event.ics and was modified to avoid test breakage because of the following changes seen in testImport (all caused by the server): * EST/DST timezone mapped to America/New_York, despite not having quite the same transition rules * all recurring events must have a time zone, UTC is not supported (but correctly converted into time zone of calendar) * BYMONTHDAY=6 is added in a sitatution were it is redundant (can be inferred from day of recurring event) * UNTIL clause turned into UTC * "DESCRIPTION:This is an event reminder" added to VALARM * CN property is replaced with lower case email * email address all lower case * ATTENDEE RSVP not supported * X-NUM-GUESTS added to ATTENDEE Changes dealt with in synccompare: * calendar owner is added as ATTENDEE to meetings, regardless whether it was meant to attend syncevolution_1.4/test/README.memotoo000066400000000000000000000154111230021373600176750ustar00rootroot00000000000000This document is used to show interoperability test information with Memotoo server. http://bugzilla.moblin.org/show_bug.cgi?id=5635 * The Memotoo Server's device information Man=Memotoo.com Mod=SyncML SwV=2.14 HwV=- FwV=- OEM=- DevID=memotoo DevTyp=server VerDTD=1.2 UTC=- SupportLargeObjs=- SupportNumberOfChanges=- * Test Environment Variables Settings CLIENT_TEST_SERVER=memotoo CLIENT_TEST_EVOLUTION_PREFIX=file:///tmp/test/ \ CLIENT_TEST_NUM_ITEMS=10 \ CLIENT_TEST_NOCHECK_SYNCMODE=1 \ CLIENT_TEST_SKIP=Client::Sync::vcard21::testRefreshFromClientSync, \ Client::Sync::vcard21::testRefreshFromClientSemantic, \ Client::Sync::vcard21::testRefreshStatus, \ Client::Sync::vcard21::testOneWayFromServer, \ Client::Sync::vcard21::Retry,Client::Sync::vcard21::Suspend, \ Client::Sync::vcard21::Resend, \ Client::Sync::ical20::testRefreshFromClientSync, \ Client::Sync::ical20::testRefreshFromClientSemantic, \ Client::Sync::ical20::testRefreshStatus, \ Client::Sync::ical20::testOneWayFromServer, \ Client::Sync::ical20::Retry,Client::Sync::ical20::Suspend, \ Client::Sync::ical20::Resend, \ Client::Sync::itodo20::testRefreshFromClientSync, \ Client::Sync::itodo20::testRefreshFromClientSemantic, \ Client::Sync::itodo20::testRefreshStatus, \ Client::Sync::itodo20::testOneWayFromServer, \ Client::Sync::itodo20::Retry,Client::Sync::itodo20::Suspend, \ Client::Sync::itodo20::Resend, \ Client::Sync::text::testRefreshFromClientSync, \ Client::Sync::text::testRefreshFromClientSemantic, \ Client::Sync::text::testRefreshStatus, \ Client::Sync::text::testOneWayFromServer, \ Client::Sync::text::Retry,Client::Sync::text::Suspend, \ Client::Sync::text::Resend * Test Profiles Settings N/A * Data formats we need to test: Client::Sync::vcard21 Client::Sync::ical20 Client::Sync::itodo20 Client::Sync::text * Known Limitations or issues in memotoo server: All -- If no data in memotoo server, it always initiates a slow sync. This causes failures of testRefreshFromClientSync,testRefreshFromClientSemantic, testRefreshStatus, testOneWayFromServer. vCard21 -- Below properties are not supported: CALURI, FBURL, ROLE, X-MOZILLA-HTML, X-EVOLUTION-FILE-AS,X-EVOLUTION-BLOG-URL, X-EVOLUTION-VIDEO-URL, X-ICQ, X-YAHOO -- Some extension properties are supported: X-AIM, X-ANNIVERSARY, X-ASSISTANT, X-MANAGER, X-SPOUSE -- The parameter 'X-EVOLUTION-URI-SLOT'of properties like 'TEL' and 'EMAIL' is lost -- 'PHOTO' is compressed by server but still be correct -- 'FN' is not reserved and is replaced by the composition of 'N' -- If 'ADR' has type information, then the last field value is lost -- All properties value has parser's bugs for escaped char "\;". This string is lost -- 'N' property lost "\;" if containing it -- ‘ORG’ only reserves its first and second fields ‘org name’ and 'department' -- 'URL' will be added 'TYPE=HOME' if no type information -- 'TEL' will be lost if it has only 'TYPE=PREF' information -- 'EMAIL' will be added 'TYPE=HOME' if no any type information -- For emails without type information, memotoo only preserves one of them -- If 'X-GROUPWISE' is present, CATEGORIES will be set as the value of 'X-GROUPWISE' instead -- For the string "=3D0D=3D0A", which is encoded for "=0D=0A", it could not be treated as "\n". ical20 -- Support iCalendar2.0 -- Below properties are not supported by server: UID, SEQUENCE, TRANSP, X-EVOLUTION-ALARM-UID, RECURRENCE-ID, ORGANIZER -- Timezone related information isn't kept on the server and relative date-time is converted into UTC time correctly, such as DTSTART, DTEND -- The 'UNTIL' component of the 'RRULE' property is converted to UTC time if it is floating time -- String "\\n" is interpreted as "\n" -- String "\\r" is interpreted as "" -- 'DESCRIPTION' in the 'VALARM' component is lost itodo20 -- Support iCalendar2.0 -- Below properties are not supported by server: UID, SEQUENCE, URL -- 'PERCENT-COMPLETE' is set as '0' by default if absent -- 'CLASS' value is always changed to 'private' by server -- 'PRIORITY': Server only accepts 5 values, '1', '3', '5', '7' and '9'. Others will be converted into these values. Default value is '5' text -- '<' and '>' in the plain text are converted to "<" and ">" separately * Known test failures: Client::Sync::vcard21 Client::Sync::vcard21::testRefreshFromClientSync, Client::Sync::vcard21::testRefreshFromClientSemantic, Client::Sync::vcard21::testRefreshStatus, Client::Sync::vcard21::testOneWayFromServer, Client::Sync::vcard21::Retry,Client::Sync::vcard21::Suspend, Client::Sync::vcard21::Resend, Client::Sync::ical20 Client::Sync::ical20::testRefreshFromClientSync, Client::Sync::ical20::testRefreshFromClientSemantic, Client::Sync::ical20::testRefreshStatus, Client::Sync::ical20::testOneWayFromServer, Client::Sync::ical20::Retry,Client::Sync::ical20::Suspend, Client::Sync::ical20::Resend, Client::Sync::itodo20 Client::Sync::itodo20::testRefreshFromClientSync, Client::Sync::itodo20::testRefreshFromClientSemantic, Client::Sync::itodo20::testRefreshStatus, Client::Sync::itodo20::testOneWayFromServer, Client::Sync::itodo20::Retry,Client::Sync::itodo20::Suspend, Client::Sync::itodo20::Resend, Client::Sync::text Client::Sync::text::testRefreshFromClientSync, Client::Sync::text::testRefreshFromClientSemantic, Client::Sync::text::testRefreshStatus, Client::Sync::text::testOneWayFromServer, Client::Sync::text::Retry,Client::Sync::text::Suspend, Client::Sync::text::Resend syncevolution_1.4/test/README.mobical000066400000000000000000000200551230021373600176240ustar00rootroot00000000000000This document is used to show interoperability test information with Mobical server. http://bugzilla.moblin.org/show_bug.cgi?id=3009 * Password: beware that the Mobical SyncML password is *not* the same as the one for their web site. Log into mobical.net, the go to "my accounts >> configure new device >> manual settings" to find the SyncML credentials. * The Mobical Server's device information mobical Data Synchronization Server v.7.1.1 Man=Tactel AB Mod=Mobical Sync Server SwV=3.0 HwV=- FwV=- OEM=- DevID=http://www.mobical.net/sync/server DevTyp=server VerDTD=1.2 UTC=- SupportLargeObjs=- SupportNumberOfChanges=- * Test Environment Variables Settings CLIENT_TEST_SERVER=mobical CLIENT_TEST_EVOLUTION_PREFIX=file:///tmp/test/ \ CLIENT_TEST_MAX_ITEMSIZE=1 \ CLIENT_TEST_NOCHECK_SYNCMODE=1 \ CLIENT_TEST_SKIP=Client::Sync::vcard21::testRefreshFromClientSync, \ Client::Sync::vcard21::testSlowSyncSemantic, \ Client::Sync::vcard21::testRefreshStatus, \ Client::Sync::vcard21::testDelete, \ Client::Sync::vcard21::testItemsXML, \ Client::Sync::vcard21::testOneWayFromServer, \ Client::Sync::vcard21::testOneWayFromClient, \ Client::Sync::vcard21::Retry,Client::Sync::vcard21::Suspend, \ Client::Sync::ical20::testRefreshFromClientSync, \ Client::Sync::ical20::testSlowSyncSemantic, \ Client::Sync::ical20::testRefreshStatus, \ Client::Sync::ical20::testDelete, \ Client::Sync::ical20::testItemsXML, \ Client::Sync::ical20::testOneWayFromServer, \ Client::Sync::ical20::testOneWayFromClient, \ Client::Sync::ical20::Retry,Client::Sync::ical20::Suspend, \ Client::Sync::itodo20::testRefreshFromClientSync, \ Client::Sync::itodo20::testSlowSyncSemantic, \ Client::Sync::itodo20::testRefreshStatus, \ Client::Sync::itodo20::testDelete, \ Client::Sync::itodo20::testItemsXML, \ Client::Sync::itodo20::testOneWayFromServer, \ Client::Sync::itodo20::testOneWayFromClient, \ Client::Sync::itodo20::Retry,Client::Sync::itodo20::Suspend, \ Client::Sync::text::testRefreshFromClientSync, \ Client::Sync::text::testSlowSyncSemantic, \ Client::Sync::text::testRefreshStatus, \ Client::Sync::text::testDelete, \ Client::Sync::text::testItemsXML, \ Client::Sync::text::testOneWayFromServer, \ Client::Sync::text::testOneWayFromClient, \ Client::Sync::text::Retry,Client::Sync::text::Suspend, \ disable "prevent slow sync" * Test Profiles Settings N/A * Data formats we need to test: Client::Sync::vcard21 Client::Sync::ical20 Client::Sync::itodo20 Client::Sync::text * Know Limitations in Mobical server: All -- If no data of any type of PIM in Mobical server, it always initiates a slow-sync. This causes failures of testSlowSyncSemantic, testRefreshStatus, testDelete, testOneWayFromServer,testRefreshFromClientSync. -- It fails in xml mode due to devinfo wrapped with cdata type This causes failure of testItemsXML. -- Server initiates a slow-sync when one client syncs with server in one-way-from-client mode from the second time. vCard21 -- Below properties are not supported: CALURI, CATEGORIES, FBURL, NICKNAME, X-MOZILLA-HTML, X-EVOLUTION-FILE-AS, X-AIM, X-ANNIVERSARY, X-SPOUSE, X-EVOLUTION-BLOG-URL, X-EVOLUTION-VIDEO-URL, X-GROUPWISE, X-ICQ, X-YAHOO, X-ASSISTANT -- The parameter 'X-EVOLUTION-URI-SLOT'of properties like 'TEL' and 'EMAIL' is lost -- 'FN' is not reserved and is replaced by server's composition of 'N' -- 'NOTE' length is less than 4000 -- vcard must have one of 'tel', 'email', or 'url','addr', otherwise, this vcard will not treat as a full item and server won't send them to client when sync -- 'ADR' if it has no type information, -- 'N' has parser's bugs for escaped char "\;" -- 'BDAY' is always one-day behind the date client sends to server -- ‘ORG’ property only reserves its first field ‘org name’ ical20 -- Only support vCalendar1.0 -- Only Below properties are support by server: DTSTART, DTEND, SUMMARY, DESCRIPTION, LOCATION, CATEGORIES, LAST-MODIFIED, DCREATED, CLASS, RRULE, EXRULE, RDATE, EXDATE, DTSTART, AALARM, DALARM -- Below properties are lost by server though they are in the support list: CLASS, AALARM, DALARM -- Below properties are not accepted but in our test: TRANSP, SEQUENCE,UID,RECURRENCE-ID, ATTENDEE,ORGANIZER -- Timezone related information is lost by server: TZ, DAYLIGHT -- DTSTART, DTEND: if no timezone information and their value are not UTC time, server will convert them into UTC time according to your account timezone information -- If RRULE is 'yearly', server converts it into dayofyear mode. That is not supported by synthesis -- The 'UNTIL' component of the 'RRULE' property is converted to UTC time if it is floating time -- The value of 'EXDATE' property is converted to UTC time if it is floating time itodo20 -- Only support vCalendar1.0 -- Only below properties are supported by server: DUE, COMPLETED, LAST-COMPLETED, DCREATED, DALARM, AALARM, DESCRIPTION, SUMMARY, STATUS, CATEGORIES, PRIORITY -- 'PRIORITY': if value is '0', it is lost by server -- Below properties are not accepted but in our test: UID, DTSTART,PERCENT-COMPLETE,SEQUENCE, URL, CLASS text -- N/A -- All-day events are not supported. The server is sent such events as DTSTART/END 00:00:00/23:59:59 floating time and returns them with shifted times (DTSTART 22:00:00 UTC on a different day). This cannot be recognized as an all-day event anymore by the client. -- For the same reason, exceptions to a recurring meeting may shift during synchronization (EXDATE:20001231 -> EXDATE:20001230T220000Z). * Known test failures: Client::Sync::vcard21 -- Client::Sync::vcard21::testRefreshFromClientSync Client::Sync::vcard21::testRefreshStatus Client::Sync::vcard21::testSlowSyncSemantic Client::Sync::vcard21::testDelete Client::Sync::vcard21::testItemsXML Client::Sync::vcard21::testOneWayFromServer Client::Sync::vcard21::Retry Client::Sync::vcard21::Suspend Client::Sync::ical20 -- Client::Sync::ical20::testRefreshFromClientSync Client::Sync::ical20::testRefreshStatus Client::Sync::ical20::testSlowSyncSemantic Client::Sync::ical20::testDelete Client::Sync::ical20::testItemsXML Client::Sync::ical20::testOneWayFromServer Client::Sync::ical20::Retry Client::Sync::ical20::Suspend Client::Sync::itodo20 -- Client::Sync::itodo20::testRefreshFromClientSync Client::Sync::itodo20::testRefreshStatus Client::Sync::itodo20::testSlowSyncSemantic Client::Sync::itodo20::testDelete Client::Sync::itodo20::testOneWayFromServer Client::Sync::itodo20::testItemsXML Client::Sync::itodo20::Retry Client::Sync::itodo20::Suspend Client::Sync::text -- Client::Sync::text::testRefreshFromClientSync Client::Sync::text::testRefreshStatus Client::Sync::text::testSlowSyncSemantic Client::Sync::text::testDelete Client::Sync::text::testOneWayFromServer Client::Sync::text::testItemsXML Client::Sync::text::Retry Client::Sync::text::Suspend syncevolution_1.4/test/README.nokia_7210c000066400000000000000000000051051230021373600201320ustar00rootroot00000000000000This document is used to show interoperability test information with Nokia phone 7210c. * Nokia 7210c's (S40 5the) device information Man=Nokia Mod=Nokia 7210c SwV=V 05.60 10-12-08 RM-436 (c) N.. FwV=V 05.60 10-12-08 RM-436 (c) N.. Hwv=1001 DevID=Phone's device id DevTyp=phone VerDTD=1.2 UTC=- SupportLargeObjs=true SupportNumberOfChanges=true * Test Environment Variables Settings CLIENT_TEST_SERVER=nokia_7210c CLIENT_TEST_XML=0 CLIENT_TEST_NOCHECK_SYNCMOD=1\ CLIENT_TEST_DELAY=3 CLIENT_TEST_MAX_ITEM_SIZE=512 CLIENT_TEST_EVOLUTION_PREFIX=file:///tmp/test/ CLIENT_TEST_NOCHECK_SYNCSTATS=1 CLIENT_TEST_MODE=server CLIENT_TEST_FORCESLOW=1 CLIENT_TEST_NOREFRESH=1 CLIENT_TEST_NOUTC=1 * Data formats we need to test: vcard21 vcal1.0 * Know Limitations: Only supports WBXML for transmitting. Don't support refresh-from-client and refresh-from-server sync. Treat one-way-from-client and one-way-from-server the same as two-way sync. VCard21 'NOTE' has a limited size, about 80 characters, add/lost spaces in between. 'DESCRIPTION' add/lost spaces in between. supoprts only one 'ADR', 'type' in 'ADR' is lost, only preserves one line. supports three 'EMAIL', 'type' and 'X-EVOLUTION-UI-SLOT' lost. supports 5 'TEL' sometimes add a default 'unknown' photo. -- Below properties are lost: X-MOZILLA-HTML NICKNAME CALURL CATEGORIES FBURL ROLE X-AIM X-ANNIVERSARY X-ASSISTANT X-EVOLUTION-BLOG-URL X-EVOLUTION-VIDEO-URL X-GROUPWISE X-ICQ X-MANAGER X-SPOUSE X-YAHOO -- 'TEL' value do not preserve non-digits, add type=PREF by default. -- 'N' mismatch, sometimes lost part of the value -- 'ORG' only stores the first component. -- 'X-EVOLUTION-FILE-AS' value incorrect * Known test failures: Not supported/applicable test: testConversion testComplexRefreshFromServer testDeleteAllRefresh testSlowSync testSlowSyncSemantic testRefresh* testOneWay* testMerge testManyDeletes testManyItems testItemsXML VCal1.0 VEVENT: 'DESCRIPTION' add/lost spaces in between. UTC time is supported by the phone though it does not declare in DevInf. (It correctly parse UTC time but will not rely with UTC time). Add 'CATEGORIES' by default as 'MEETING', 'PRIORITY' by default '0' VTODO: 'PRIORITY' , 'CATEGORIES' and 'STATUS' value mismatch. Don't support 'ORGANIZER' 'ALARM' is not recognized. Lost 'DTSTART' * Known test failures: Not supported/applicable test: testConversion testComplexRefreshFromServer testDeleteAllRefresh testSlowSync testSlowSyncSemantic testRefresh* testOneWay* testMerge testManyDeletes testManyItems testItemsXML syncevolution_1.4/test/README.qtcontacts000066400000000000000000000035461230021373600204070ustar00rootroot00000000000000This document is used to show the limitation of qtcontacts and interoperability test information between Buteo and Google. QtContacts -- Below properties are not supported in Qtcontacts: X-AIM, X-GROUPWISE, X-ICQ, X-YAHOO, FBURL, CALURI, LOGO, LABEL -- Other Qtcontacts limitations X-GENDER: Male or Female, first letter in upper-case, others in lower-case TEL: type [HOME | WORK] and [VOICE | FAX | PAGER] are mandantory ADR: type POSTAL is mandantory EMAIL: type INTERNET is not supported ORG: only value of the first field in organization are supported VCard30 (interoperability) -- Google contacts limitations Below properties can be set on web page and are kept by Google but are not transferred when doing sync: Birthday, Anniversary, Home-URL, Profile-URL, Blog-URL, Work-URL, Relationship, AIM, Google Talk, Yahoo, Skype, QQ, MSN, ICQ, Jabber, Phonetic Name, File As. ADR: Once updated on Google web page, its value is empty if doing sync with client. TEL doesn't support TYPE PAGER. But 'TYPE VOICE' is added by default. BDAY, X-GENDER, X-ASSISTANT, X-ASSISTANT-TEL, URL and X-SIP are kept by Google, but they are not shown in Google web page. X-IMPP, CATEGORIES, X-NICKNAME and ROLE are not supported, thus not keep them * Known test failures: Client::Sync::vcard21 -- Client::Sync::qt_vcard30::testDeleteAllRefresh Client::Sync::qt_vcard30::testRefreshFromClientSync Client::Sync::qt_vcard30::testConversion Client::Sync::qt_vcard30::testRefreshFromClientSemantic Client::Sync::qt_vcard30::testRefreshStatus Client::Sync::qt_vcard30::testComplexUpdate Client::Sync::qt_vcard30::testItemsXML Client::Sync::qt_vcard30::testOneWayFromServer Client::Sync::qt_vcard30::testOneWayFromClient syncevolution_1.4/test/README.scheduleworld000066400000000000000000000004531230021373600210620ustar00rootroot00000000000000Very complete support for Evolution data. No known lost properties at this time. Interrupted sync sessions are not resumed. Instead the server falls back to a slow sync, which may lead to duplicates (Bugzilla #3733). Resuming from a failed sync is currently only supported by the Synthesis server. syncevolution_1.4/test/README.syncevolution-server000066400000000000000000000050221230021373600224400ustar00rootroot00000000000000Here are some quick (and probably incomplete) notes about setting up SyncEvolution as HTTP server for client-test runs using just the local account and machine. # 1. run client-test like this: CLIENT_TEST_SERVER=syncevolution --help # 2. set the URL (using IPv4 here, in case that "localhost" expands to IPv6, which # can be problematic): for i in 1 2; do syncevolution --configure --sync-property syncURL=http://127.0.0.1:9000/syncevolution syncevolution_$i done # 3. configure the server, using a separate context with the four standard URIs # implemented via files in /tmp, no credential checking, maximum message size # which forces the clients to split items (makes testInterruptResume*Big more # interesting), low timeout to keep tests going quickly, with device IDs as # set by client-test-app.cpp in step 1: for i in 1 2; do syncevolution --configure --template SyncEvolutionClient \ --sync-property username= \ --sync-property password= \ --sync-property maxMsgSize=20000 \ --sync-property RetryDuration=20 \ syncevolution_client_$i@server done syncevolution --configure \ --source-property type=file:text/vcard:3.0 \ --source-property evolutionsource=file:///tmp/server_addressbook \ @server addressbook syncevolution --configure \ --source-property type=file:text/calendar:2.0 \ --source-property evolutionsource=file:///tmp/server_calendar \ @server calendar syncevolution --configure \ --source-property type=file:text/plain:1.0 \ --source-property evolutionsource=file:///tmp/server_memo \ @server memo syncevolution --configure \ --source-property type=file:text/calendar:2.0 \ --source-property evolutionsource=file:///tmp/server_todo \ @server todo syncevolution --configure --sync-property remoteDeviceId=sc-api-nat syncevolution_client_1@server syncevolution --configure --sync-property remoteDeviceId=sc-pim-ppc syncevolution_client_2@server # 4. run the syncevo-dbus-server (necessary when not installed): syncevo-dbus-server -d 1000000 & # 5. run http server: syncevo-http-server http://localhost:9000/syncevolution & # 6. run testing, here using again file backends: CLIENT_TEST_SERVER=syncevolution \ CLIENT_TEST_RETRY=t CLIENT_TEST_RESEND=t CLIENT_TEST_SUSPEND=t \ CLIENT_TEST_SOURCES=file_vcard30,file_ical20 \ CLIENT_TEST_EVOLUTION_PREFIX=file:///tmp/test_ \ client-test syncevolution_1.4/test/README.yahoo000066400000000000000000000017411230021373600173360ustar00rootroot00000000000000CardDAV ======= Not usable at the moment. Several valid test cases cause the server to fail with a 500 error (Source::testImport) Other test cases with special characters have these characters replaced. Tests pass only because all the problematic test cases were removed. See git log for testcases/yahoo_contact.vcf. Other data changes seen in server: * first component in ADR removed * TYPE=CAR not supported in TEL, complete entry removed * TYPE=PREF stripped from TEL * TYPE=PARCEL;TYPE=POSTAL added to all ADRs * no distinction between TYPE=HOME,FAX and TYPE=WORK,FAX, both become TYPE=FAX * no TYPE at all for EMAIL * only one level in ORG It works a bit better with vCards encoded by Synthesis (Sync::testItems): there's no 500 error. Apparently line folding (enabled in Source::testImport test cases, disabled during sync) is something that the server has problems with. But photos still get lost. Results for non-ASCII characters is inconclusive; testing is currently disabled. syncevolution_1.4/test/README.zyb000066400000000000000000000017431230021373600170250ustar00rootroot00000000000000This document is used to show interoperability test information with zyb server. http://bugzilla.moblin.org/show_bug.cgi?id=2424 * The zyb Server's device information ZYB HTTP SyncML Server Version:3.9.18 Build:3.9.18b572 * Test Environment Variables Settings CLIENT_TEST_SERVER=zyb CLIENT_TEST_EVOLUTION_PREFIX=file:///tmp/test/ * Test Profiles Settings -- N/A * Data formats we need to test: -- Client::Sync::vcard21 * Know Limitations in ZYB server: VCard21 -- Only below properties are accepted: FN, N, PHOTO, BDAY, ADR, LABEL, TEL, EMAIL, TZ, GEO, TITLE, ROLE, LOGO, ORG, NOTE, REV, SOUND, URL -- The property 'N' parser error: treat "\;" as the delimiter -- The property 'ADR': if it is long, three extra chars '\r', '\n' and ' 'will be added in the property value. -- Server does escaped text processing, e.g, "<\;" is converted to "<" -- Server sends mismatch anchors between two sync even if there is no failure. * Known test failures: -- NONE syncevolution_1.4/test/__init__.py000066400000000000000000000000001230021373600174340ustar00rootroot00000000000000syncevolution_1.4/test/abort-redirect.cpp000066400000000000000000000040001230021373600207410ustar00rootroot00000000000000/* * Copyright (C) 2009 Patrick Ohly * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include SE_BEGIN_CXX int main(int argc, char **argv) { // Check that we catch stderr message generated // while the process shut down. We assume that // libc detects double frees. The expected // outcome of this program is that the message // appears in abort-redirect.log instead of // stderr. A core file should be written normally. LogRedirect redirect; LoggerStdout out(fopen("abort-redirect.log", "w")); out.pushLogger(&out); // write without explicit flushing fprintf(stdout, "a normal info message, also redirected"); // cause libc error and abort: for small chunks // glibc tends to detect double frees while large // chunks are done as mmap()/munmap() and just // segfault void *small = malloc(1); free(small); // cppcheck-suppress deallocDealloc // cppcheck-suppress doubleFree // cppcheck-suppress uninitvar free(small); void *large = malloc(1024 * 1024); free(large); // cppcheck-suppress deallocDealloc // cppcheck-suppress doubleFree // cppcheck-suppress uninitvar free(large); return 0; } SE_END_CXX syncevolution_1.4/test/bluetooth-device-id-inspector.py000066400000000000000000000106111230021373600235460ustar00rootroot00000000000000#! /usr/bin/python -u # * Copyright (C) 2011 Intel Corporation # This file 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 or 3.0 of the License. # # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import dbus import string import sys # Defines SYNCML_UUID = "00000002-0000-1000-8000-0002ee000002" PNPINFO_UUID = "00001200-0000-1000-8000-00805f9b34fb" PNPINFO_ATTRIB = "0x1200" SOURCE_ATTRIB = "0x0205" VENDOR_ATTRIB = "0x0201" PRODUCT_ATTRIB = "0x0202" def extractValueFromServiceRecord(servRec, attribId): pos = string.find(servRec, attribId) if(pos < 0): return "" pos = string.find(servRec, "value", pos + len(attribId)) pos = string.find(servRec, '"', pos) + 1 endPos = string.find(servRec,'"', pos) return servRec[pos:endPos] def getVendorAndProductId(pnpInfoServRec): '''Get the vendor and product ids from xml formatted service record.''' servRec = pnpInfoServRec.values()[0] sourceVal = extractValueFromServiceRecord(servRec, SOURCE_ATTRIB) vendorVal = extractValueFromServiceRecord(servRec, VENDOR_ATTRIB) productVal = extractValueFromServiceRecord(servRec, PRODUCT_ATTRIB) return (sourceVal, vendorVal, productVal) def writeDeviceInfoToFile(ids, vendor, product, hasSyncML): filename = "syncevo-phone-info-[%s].txt" % product FILE = open(filename,"w") FILE.write("Thanks, for helping us improve phone syncing on Linux.\n") FILE.write("Please send this file or its contents to blixtra [at] gmail.com\n\n" ) FILE.write("SyncML support: %s\n" % hasSyncML) if(len(ids) > 0): FILE.write("Source: %s\n" % (ids[0])) FILE.write("Vendor: %s=%s\n" % (ids[1], vendor)) FILE.write("product: %s=%s\n\n" % (ids[2], product)) else: FILE.write("Vendor: %s\n" % vendor) FILE.write("product: %s\n\n" % product) FILE.write("This phone doesn't support the bluetooth Device ID profile.\n" ) FILE.close() return filename # Start main program bus = dbus.SystemBus() bluezIface = dbus.Interface(bus.get_object('org.bluez', '/'), 'org.bluez.Manager') hasSyncmlSupport = False hasPnpInfoSupport = False ids = {} adapters = bluezIface.ListAdapters() for adapter in adapters: adapterIface = dbus.Interface(bus.get_object('org.bluez', adapter), 'org.bluez.Adapter') devices = adapterIface.ListDevices() for device in devices: try: deviceIface = dbus.Interface(bus.get_object('org.bluez', device), 'org.bluez.Device') props = deviceIface.GetProperties(); uuids = props["UUIDs"] print "Device name:", props.get("Name", "???") print "MAC Address:", props.get("Address", "???") for uuid in uuids: if SYNCML_UUID == uuid: hasSyncmlSupport = True print " Supports SyncML." if PNPINFO_UUID == uuid: hasPnpInfoSupport = True print " Looking up device information..." sys.stdout.flush() serviceRecord = deviceIface.DiscoverServices(PNPINFO_ATTRIB) ids = getVendorAndProductId(serviceRecord) vendor = raw_input(" What company makes this phone? (examples: Nokia, Sony Ericsson), empty to skip: ") if vendor: product = raw_input(" What is the model of this phone? (example: N900, K750i), empty to skip: ") if product: # Write the results to a file filename = writeDeviceInfoToFile(ids, vendor, product, hasSyncmlSupport) print "Thanks, please send the file %s to blixtra [at] gmail.com" % filename except dbus.exceptions.DBusException, ex: print " Failed, skipping device: %s" % ex syncevolution_1.4/test/client-test-main.cpp000066400000000000000000000244751230021373600212320ustar00rootroot00000000000000/* * Copyright (C) 2008 Funambol, Inc. * Copyright (C) 2008-2009 Patrick Ohly * Copyright (C) 2009 Intel Corporation * * 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ /** @cond API */ /** @addtogroup ClientTest */ /** @{ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "test.h" #include #ifdef HAVE_VALGRIND_VALGRIND_H # include #endif #ifdef HAVE_EXECINFO_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "ClientTest.h" #include #include #include #include #include #ifdef HAVE_SIGNAL_H # include #endif #include #include #include #include #include #include SE_BEGIN_CXX using namespace std; void simplifyFilename(string &filename) { size_t pos = 0; while (true) { pos = filename.find(":", pos); if (pos == filename.npos ) { break; } filename.replace(pos, 1, "_"); } pos = 0; while (true) { pos = filename.find("__", pos); if (pos == filename.npos) { break; } filename.erase(pos, 1); } } class ClientOutputter : public CppUnit::CompilerOutputter { public: ClientOutputter(CppUnit::TestResultCollector *result, std::ostream &stream) : CompilerOutputter(result, stream) {} void write() { CompilerOutputter::write(); } }; class ClientListener : public CppUnit::TestListener { public: ClientListener() : m_failed(false), // Not really necessary, will be initialized once the test starts. // Set it anyway, to keep cppcheck happy. m_testFailed(false) { #ifdef HAVE_SIGNAL_H // install signal handler which turns an alarm signal into a runtime exception // to abort tests which run too long const char *alarm = getenv("CLIENT_TEST_ALARM"); m_alarmSeconds = alarm ? atoi(alarm) : -1; struct sigaction action; memset(&action, 0, sizeof(action)); action.sa_handler = alarmTriggered; action.sa_flags = SA_NOMASK; sigaction(SIGALRM, &action, NULL); #endif } ~ClientListener() { m_logger.reset(); } void addAllowedFailures(string allowedFailures) { boost::split(m_allowedFailures, allowedFailures, boost::is_from_range(',', ',')); } void startTest (CppUnit::Test *test) { m_currentTest = test->getName(); std::cout << m_currentTest << std::flush; if (!getenv("SYNCEVOLUTION_DEBUG")) { string logfile = m_currentTest + ".log"; simplifyFilename(logfile); m_logger.reset(new LogRedirect(LogRedirect::STDERR_AND_STDOUT, logfile.c_str())); m_logger->setLevel(Logger::DEBUG); } SE_LOG_DEBUG(NULL, "*** starting %s ***", m_currentTest.c_str()); m_failures.reset(); m_testFailed = false; #ifdef HAVE_SIGNAL_H if (m_alarmSeconds > 0) { alarm(m_alarmSeconds); } #endif } void addFailure(const CppUnit::TestFailure &failure) { m_failures.addFailure(failure); m_testFailed = true; } void endTest (CppUnit::Test *test) { #ifdef HAVE_SIGNAL_H if (m_alarmSeconds > 0) { alarm(0); } #endif std::string result; std::string failure; if (m_testFailed) { stringstream output; CppUnit::CompilerOutputter formatter(&m_failures, output); formatter.printFailureReport(); failure = output.str(); bool failed = true; BOOST_FOREACH (const std::string &re, m_allowedFailures) { if (pcrecpp::RE(re).FullMatch(m_currentTest)) { result = "*** failure ignored ***"; failed = false; break; } } if (failed) { result = "*** failed ***"; m_failed = true; } } else { result = "okay"; } SE_LOG_DEBUG(NULL, "*** ending %s: %s ***", m_currentTest.c_str(), result.c_str()); if (!failure.empty()) { SE_LOG_ERROR(NULL, "%s", failure.c_str()); } m_logger.reset(); string logfile = m_currentTest + ".log"; simplifyFilename(logfile); const char* compareLog = getenv("CLIENT_TEST_COMPARE_LOG"); if(compareLog && strlen(compareLog)) { int fd = open("____compare.log", O_RDONLY); if (fd >= 0) { int out = open(logfile.c_str(), O_WRONLY|O_APPEND); if (out >= 0) { char buffer[4096]; bool cont = true; ssize_t len; while (cont && (len = read(fd, buffer, sizeof(buffer))) > 0) { ssize_t total = 0; while (cont && total < len) { ssize_t written = write(out, buffer, len); if (written < 0) { perror(("writing " + logfile).c_str()); cont = false; } else { total += written; } } } if (len < 0) { perror("reading ____compare.log"); } close(out); } close(fd); } } std::cout << " " << result << "\n"; if (!failure.empty()) { std::cout << failure << "\n"; } std::cout << std::flush; } bool hasFailed() { return m_failed; } const string &getCurrentTest() const { return m_currentTest; } private: set m_allowedFailures; bool m_failed, m_testFailed; string m_currentTest; #ifdef HAVE_SIGNAL_H int m_alarmSeconds; #endif PushLogger m_logger; CppUnit::TestResultCollector m_failures; static void alarmTriggered(int signal) { CPPUNIT_ASSERT_MESSAGE("test timed out", false); } } syncListener; const string &getCurrentTest() { return syncListener.getCurrentTest(); } static void printTests(CppUnit::Test *test, int indention) { if (!test) { return; } std::string name = test->getName(); printf("%*s%s\n", indention * 3, "", name.c_str()); for (int i = 0; i < test->getChildTestCount(); i++) { printTests(test->getChildTestAt(i), indention+1); } } static void handler(int sig) { void *buffer[100]; int size; fprintf(stderr, "\ncaught signal %d\n", sig); fflush(stderr); #ifdef HAVE_EXECINFO_H size = backtrace(buffer, sizeof(buffer)/sizeof(buffer[0])); backtrace_symbols_fd(buffer, size, 2); #endif #ifdef HAVE_VALGRIND_VALGRIND_H VALGRIND_PRINTF_BACKTRACE("\ncaught signal %d\n", sig); #endif /* system("objdump -l -C -d client-test >&2"); */ struct sigaction act; memset(&act, 0, sizeof(act)); act.sa_handler = SIG_DFL; sigaction(SIGABRT, &act, NULL); abort(); } extern "C" int main(int argc, char* argv[]) { SyncContext::initMain("client-test"); struct sigaction act; memset(&act, 0, sizeof(act)); act.sa_handler = handler; sigaction(SIGABRT, &act, NULL); sigaction(SIGSEGV, &act, NULL); sigaction(SIGILL, &act, NULL); // Get the top level suite from the registry CppUnit::Test *suite = CppUnit::TestFactoryRegistry::getRegistry().makeTest(); if (argc >= 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) { printf("usage: %s [test name]+\n\n" "Without arguments all available tests are run.\n" "Otherwise only the tests or group of tests listed are run.\n" "Here is the test hierarchy of this test program:\n", argv[0]); printTests(suite, 1); return 0; } // Adds the test to the list of test to run CppUnit::TextUi::TestRunner runner; runner.addTest( suite ); // Change the default outputter to a compiler error format outputter runner.setOutputter( new ClientOutputter( &runner.result(), std::cout ) ); // track current test and failure state const char *allowedFailures = getenv("CLIENT_TEST_FAILURES"); if (allowedFailures) { syncListener.addAllowedFailures(allowedFailures); } runner.eventManager().addListener(&syncListener); if (getenv("SYNCEVOLUTION_DEBUG")) { Logger::instance().setLevel(Logger::DEBUG); } try { // Run the tests. if (argc <= 1) { // all tests runner.run("", false, true, false); } else { // run selected tests individually for (int test = 1; test < argc; test++) { runner.run(argv[test], false, true, false); } } // Return error code 1 if the one of test failed. ClientTest::shutdown(); return syncListener.hasFailed() ? 1 : 0; } catch (invalid_argument e) { // Test path not resolved std::cout << std::endl << "ERROR: " << e.what() << std::endl; ClientTest::shutdown(); return 1; } } /** @} */ /** @endcond */ SE_END_CXX syncevolution_1.4/test/client.supp000066400000000000000000000124321230021373600175260ustar00rootroot00000000000000# ==7948== 74 (32 direct, 42 indirect) bytes in 2 blocks are definitely lost in loss record 4 of 53 # ==7948== at 0x4C22425: operator new(unsigned long) (vg_replace_malloc.c:167) # ==7948== by 0x4B4FFA: DMTreeFactory::getDMTree(char const*) (lDMTreeFactory.cpp:58) # ==7948== by 0x4B2C3C: DMTClientConfig::open() (lDMTClientConfig.cpp:212) # ==7948== by 0x413E18: TestEvolution::TestEvolution(std::string const&) (client-test-app.cpp:304) # ==7948== by 0x41313C: TestEvolution::TestEvolution(std::string const&) (client-test-app.cpp:217) # ==7948== by 0x414205: RegisterTestEvolution::RegisterTestEvolution() (client-test-app.cpp:649) # ==7948== by 0x40981A: __static_initialization_and_destruction_0(int, int) (client-test-app.cpp:669) # ==7948== by 0x409844: _GLOBAL__I__ZN16MacOSAddressBook11m_singletonE (client-test-app.cpp:670) # ==7948== by 0x4F9685: (within /scratch/work/sync/src/client-test) # ==7948== by 0x40820A: (within /scratch/work/sync/src/client-test) { general catchall for errors in client library files, detected because it uses new Memcheck:Leak fun:_Znwm } # ==7948== # ==7948== # ==7948== 36,301 bytes in 1,171 blocks are definitely lost in loss record 27 of 53 # ==7948== at 0x4C220C5: operator new[](unsigned long) (vg_replace_malloc.c:199) # ==7948== by 0x4E29F7: DeviceManagementNode::gotoDir(bool) (lDeviceManagementNode.cpp:108) # ==7948== by 0x4E2E73: DeviceManagementNode::update(bool) (lDeviceManagementNode.cpp:173) # ==7948== by 0x4E351F: DeviceManagementNode::DeviceManagementNode(char const*) (lDeviceManagementNode.cpp:70) # ==7948== by 0x4E1FE9: DMTree::readManagementNode(char const*) (lDMTree.cpp:87) # ==7948== by 0x4B2CA5: DMTClientConfig::open() (lDMTClientConfig.cpp:215) # ==7948== by 0x4B2F3A: DMTClientConfig::read() (lDMTClientConfig.cpp:134) # ==7948== by 0x4134E0: TestEvolution::TestEvolution(std::string const&) (client-test-app.cpp:270) # ==7948== by 0x41313C: TestEvolution::TestEvolution(std::string const&) (client-test-app.cpp:217) # ==7948== by 0x414205: RegisterTestEvolution::RegisterTestEvolution() (client-test-app.cpp:649) # ==7948== by 0x40981A: __static_initialization_and_destruction_0(int, int) (client-test-app.cpp:669) # ==7948== by 0x409844: _GLOBAL__I__ZN16MacOSAddressBook11m_singletonE (client-test-app.cpp:670) { general catchall for errors in client library files, detected because it uses new Memcheck:Leak fun:_Znam } # ==10986== 58 (16 direct, 42 indirect) bytes in 2 blocks are definitely lost in loss record 2 of 22 # ==10986== at 0x401DC55: operator new(unsigned) (vg_replace_malloc.c:163) # ==10986== by 0x80FC697: DMTreeFactory::getDMTree(char const*) (lDMTreeFactory.cpp:58) # ==10986== by 0x80FA918: DMTClientConfig::open() (lDMTClientConfig.cpp:212) # ==10986== by 0x8059AA0: TestEvolution::TestEvolution(std::string const&) (in /tmp/runtests/head/tmp/build/src/client-test) # ==10986== by 0x8058C2F: TestEvolution::TestEvolution(std::string const&) (in /tmp/runtests/head/tmp/build/src/client-test) # ==10986== by 0x8059EAA: RegisterTestEvolution::RegisterTestEvolution() (in /tmp/runtests/head/tmp/build/src/client-test) # ==10986== by 0x804F3B6: __static_initialization_and_destruction_0(int, int) (in /tmp/runtests/head/tmp/build/src/client-test) # ==10986== by 0x804F3EA: _GLOBAL__I__ZN16MacOSAddressBook11m_singletonE (in /tmp/runtests/head/tmp/build/src/client-test) # ==10986== by 0x812EB14: (within /tmp/runtests/head/tmp/build/src/client-test) # ==10986== by 0x804DD30: (within /tmp/runtests/head/tmp/build/src/client-test) # ==10986== by 0x812EAA8: __libc_csu_init (in /tmp/runtests/head/tmp/build/src/client-test) # ==10986== by 0x449FE5C: (below main) (in /lib/tls/i686/cmov/libc-2.3.6.so) { general catchall for errors in client library files, detected because it uses new Memcheck:Leak fun:_Znwj } # ==10986== 62 bytes in 2 blocks are possibly lost in loss record 4 of 22 # ==10986== at 0x401D8E5: operator new[](unsigned) (vg_replace_malloc.c:195) # ==10986== by 0x811DD33: DeviceManagementNode::gotoDir(bool) (lDeviceManagementNode.cpp:108) # ==10986== by 0x811E5F2: DeviceManagementNode::update(bool) (lDeviceManagementNode.cpp:173) # ==10986== by 0x811E9EB: DeviceManagementNode::DeviceManagementNode(char const*) (lDeviceManagementNode.cpp:70) # ==10986== by 0x811D9D9: DMTree::readManagementNode(char const*) (lDMTree.cpp:87) # ==10986== by 0x80FA954: DMTClientConfig::open() (lDMTClientConfig.cpp:215) # ==10986== by 0x80FAB0C: DMTClientConfig::read() (lDMTClientConfig.cpp:134) # ==10986== by 0x8059A5B: TestEvolution::TestEvolution(std::string const&) (in /tmp/runtests/head/tmp/build/src/client-test) # ==10986== by 0x8058C2F: TestEvolution::TestEvolution(std::string const&) (in /tmp/runtests/head/tmp/build/src/client-test) # ==10986== by 0x8059EAA: RegisterTestEvolution::RegisterTestEvolution() (in /tmp/runtests/head/tmp/build/src/client-test) # ==10986== by 0x804F3B6: __static_initialization_and_destruction_0(int, int) (in /tmp/runtests/head/tmp/build/src/client-test) # ==10986== by 0x804F3EA: _GLOBAL__I__ZN16MacOSAddressBook11m_singletonE (in /tmp/runtests/head/tmp/build/src/client-test) { general catchall for errors in client library files, detected because it uses new Memcheck:Leak fun:_Znaj } syncevolution_1.4/test/compare.xsl000066400000000000000000000347151230021373600175250ustar00rootroot00000000000000 No comparison_file, please set comparison_file by: --stringparam cmp_file [your file path] syncevolution_1.4/test/cppcheck-wrapper.sh000077500000000000000000000005171230021373600211350ustar00rootroot00000000000000#! /bin/bash set -o pipefail # We cannot rely on cppcheck --exitcode because it gets triggered by # suppressed errors. Instead look at the output. cppcheck '--template={file}:{line}: cppcheck {severity}: {id} - {message}' "$@" 2>&1 | \ perl -e '$res = 0; while (<>) { if (/: cppcheck /) { $res = 1; }; print; }; exit $res;' syncevolution_1.4/test/dbus-client-server.cpp000066400000000000000000000270651230021373600215700ustar00rootroot00000000000000#include "gdbus-cxx-bridge.h" #include #include #include #include #include #include #include #include SyncEvo::GMainLoopCXX loop; // closes child connection boost::scoped_ptr guard; class Test { GDBusCXX::DBusObjectHelper m_server; // GDBusCXX::DBusRemoteObject m_dbusAPI; // GDBusCXX::SignalWatch0 m_disconnected; public: Test(const GDBusCXX::DBusConnectionPtr &conn) : // will close connection m_server(conn, "/test", "org.example.Test", GDBusCXX::DBusObjectHelper::Callback_t(), true) // m_dbusAPI(conn, DBUS_PATH_LOCAL, DBUS_INTERFACE_LOCAL, "" /* sender? */), // m_disconnected(m_dbusAPI, "Disconnected") { m_server.add(this, &Test::hello, "Hello"); m_server.add(this, &Test::getstrings, "GetStrings"); m_server.add(this, &Test::getmixed, "GetMixed"); m_server.add(this, &Test::kill, "Kill"); } void activate() { m_server.activate(); // fails with an unspecific error inside libdbus, don't rely on "Disconnected" // m_disconnected.activate(boost::bind(&Test::disconnected, this)); // not implemented either // b_dbus_set_disconnect_function(m_server.getConnection(), // staticDisconnected, // NULL, // NULL); } std::string hello(const std::string &in) { std::cout << "hello() called with " << in << std::endl; return "world"; } void getstrings(std::string &first, std::string &second) { first = "hello"; second = "world"; } void getmixed(std::string &first, int &second, std::string &third) { first = "hello"; second = 1; third = "world"; } void kill() { std::cout << "killing myself as requested" << std::endl; abort(); } void disconnected() { std::cout << "connection disconnected"; } // static void staticDisconnected(DBusConnection*conn, void *data) // { // std::cout << "connection disconnected"; // } }; static void newClientConnection(GDBusCXX::DBusServerCXX &server, GDBusCXX::DBusConnectionPtr &conn, boost::scoped_ptr &testptr) { std::cout << "new connection" << std::endl; testptr.reset(new Test(conn.get())); testptr->activate(); } static void onChildConnect(const GDBusCXX::DBusConnectionPtr &conn, boost::scoped_ptr &testptr) { std::cout << "child is ready" << std::endl; testptr.reset(new Test(conn.get())); testptr->activate(); } static void onQuit(int status) { std::cout << "child has quit, status " << status << std::endl; // always quit the process, not just on failure g_main_loop_quit(loop.get()); } static void onFailure(const std::string &error) { std::cout << "failure, quitting now: " << error << std::endl; g_main_loop_quit(loop.get()); } class TestProxy : public GDBusCXX::DBusRemoteObject { public: TestProxy(const GDBusCXX::DBusConnectionPtr &conn) : GDBusCXX::DBusRemoteObject(conn.get(), "/test", "org.example.Test", "direct.peer"), m_hello(*this, "Hello"), m_kill(*this, "Kill") { } GDBusCXX::DBusClientCall1 m_hello; GDBusCXX::DBusClientCall0 m_kill; }; static void onChildConnectKill(const GDBusCXX::DBusConnectionPtr &conn, boost::scoped_ptr &testptr) { std::cout << "child is ready, kill it" << std::endl; testptr.reset(new Test(conn.get())); testptr->activate(); // process messages already before returning from this onConnect callback dbus_bus_connection_undelay(conn); TestProxy proxy(conn); try { proxy.m_kill(); } catch (const std::runtime_error &ex) { std::cout << "caught exception, as expected: " << ex.what() << std::endl; std::cout << "aborting..." << std::endl; abort(); } std::cout << "did not get the expected exception" << std::endl; abort(); } static void helloCB(GMainLoop *loop, const std::string &res, const std::string &error) { if (!error.empty()) { std::cout << "call failed: " << error << std::endl; } else { std::cout << "hello('hello') = " << res << std::endl; } g_main_loop_quit(loop); } static void callServer(const GDBusCXX::DBusConnectionPtr &conn) { TestProxy proxy(conn); Test test(conn.get()); test.activate(); // process messages already before returning from this onConnect callback dbus_bus_connection_undelay(conn); std::cout << "blocking call to server without callback" << std::endl; std::cout << proxy.m_hello(std::string("blocking world, II")) << std::endl; try { GDBusCXX::DBusClientCall1 nosuchcall(proxy, "nosuchcall"); std::cout << nosuchcall(std::string("ignoreme")) << std::endl; } catch (const std::runtime_error &ex) { std::cout << "caught exception, as expected: " << ex.what() << std::endl; } GDBusCXX::DBusClientCall2 getstrings(proxy, "GetStrings"); std::pair r = getstrings(); std::cout << "Got pair: (" << r.first << ", " << r.second << ")" << std::endl; GDBusCXX::DBusClientCall3 getmixed(proxy, "GetMixed"); std::cout << "Got tuple: " << getmixed() << std::endl; std::cout << "calling server" << std::endl; proxy.m_hello.start(std::string("world"), boost::bind(helloCB, loop.get(), _1, _2)); // keep connection open until child quits guard.reset(new GDBusCXX::DBusObject(conn, "foo", "bar", true)); } static void killServer(const GDBusCXX::DBusConnectionPtr &conn) { TestProxy proxy(conn); // process messages already before returning from this onConnect callback dbus_bus_connection_undelay(conn); try { proxy.m_kill(); } catch (const std::runtime_error &ex) { std::cout << "caught exception, as expected: " << ex.what() << std::endl; std::cout << "aborting..." << std::endl; abort(); } std::cout << "did not get the expected exception" << std::endl; abort(); } static void calledByServer(const GDBusCXX::DBusConnectionPtr &conn) { // run until Test::kill() is invoked by server Test test(conn.get()); test.activate(); dbus_bus_connection_undelay(conn); g_main_loop_run(loop.get()); } void signalHandler (int sig) { if (loop) { g_main_loop_quit(loop.get()); } } int main(int argc, char **argv) { int ret = 0; signal (SIGABRT, &signalHandler); signal (SIGTERM, &signalHandler); signal (SIGINT, &signalHandler); try { gboolean opt_server; gboolean opt_fork_exec; gboolean opt_fork_exec_failure; gchar *opt_address; gchar *opt_kill; GOptionContext *opt_context; // gboolean opt_allow_anonymous; SyncEvo::GErrorCXX gerror; GDBusCXX::DBusErrorCXX dbusError; GOptionEntry opt_entries[] = { { "server", 's', 0, G_OPTION_ARG_NONE, &opt_server, "Start a server instead of a client", NULL }, { "forkexec", 'e', 0, G_OPTION_ARG_NONE, &opt_fork_exec, "Use fork+exec to start the client (implies --server)", NULL }, { "forkfailure", 'f', 0, G_OPTION_ARG_NONE, &opt_fork_exec_failure, "Fork /bin/false to simulate a failure in the child (implies )", NULL }, { "forkkill", 'a', 0, G_OPTION_ARG_STRING, &opt_kill, "'child/parent' call peer which kills itself before replying (implies --forkexec)", NULL }, { "address", 'a', 0, G_OPTION_ARG_STRING, &opt_address, "D-Bus address to use", NULL }, // { "allow-anonymous", 'n', 0, G_OPTION_ARG_NONE, &opt_allow_anonymous, "Allow anonymous authentication", NULL }, { NULL} }; g_type_init(); opt_address = NULL; opt_kill = NULL; opt_server = FALSE; opt_fork_exec = FALSE; opt_fork_exec_failure = FALSE; // opt_allow_anonymous = FALSE; opt_context = g_option_context_new("peer-to-peer example"); g_option_context_add_main_entries(opt_context, opt_entries, NULL); bool success = g_option_context_parse(opt_context, &argc, &argv, gerror); g_option_context_free(opt_context); if (!success) { gerror.throwError("parsing command line options"); } // if (!opt_server && opt_allow_anonymous) { // throw stdruntime_error("The --allow-anonymous option only makes sense when used with --server."); // } loop = SyncEvo::GMainLoopStealCXX(g_main_loop_new (NULL, FALSE)); if (!loop) { throw std::runtime_error("could not allocate main loop"); } if (opt_fork_exec || opt_fork_exec_failure) { boost::scoped_ptr testptr; boost::shared_ptr forkexec = SyncEvo::ForkExecParent::create(opt_fork_exec_failure ? "/bin/false" : argv[0]); if (opt_kill) { forkexec->addEnvVar("DBUS_CLIENT_SERVER_KILL", opt_kill); } forkexec->m_onConnect.connect(g_strcmp0(opt_kill, "child") ? boost::bind(onChildConnect, _1, boost::ref(testptr)) : boost::bind(onChildConnectKill, _1, boost::ref(testptr))); forkexec->m_onQuit.connect(onQuit); forkexec->m_onFailure.connect(boost::bind(onFailure, _2)); forkexec->start(); g_main_loop_run(loop.get()); } else if (opt_server) { boost::shared_ptr server = GDBusCXX::DBusServerCXX::listen(opt_address ? opt_address : "", &dbusError); if (!server) { dbusError.throwFailure("starting server"); } std::cout << "Server is listening at: " << server->getAddress() << std::endl; boost::scoped_ptr testptr; server->setNewConnectionCallback(boost::bind(newClientConnection, _1, _2, boost::ref(testptr))); g_main_loop_run(loop.get()); } else if (SyncEvo::ForkExecChild::wasForked()) { boost::shared_ptr forkexec = SyncEvo::ForkExecChild::create(); forkexec->m_onConnect.connect(!g_strcmp0(getenv("DBUS_CLIENT_SERVER_KILL"), "child") ? calledByServer : !g_strcmp0(getenv("DBUS_CLIENT_SERVER_KILL"), "server") ? killServer : callServer); forkexec->m_onFailure.connect(boost::bind(onFailure, _2)); forkexec->connect(); g_main_loop_run(loop.get()); } else { if (!opt_address) { throw std::runtime_error("need server address"); } GDBusCXX::DBusConnectionPtr conn = dbus_get_bus_connection(opt_address, &dbusError); if (!conn) { dbusError.throwFailure("connecting to server"); } // closes connection callServer(conn); g_main_loop_run(loop.get()); } loop.reset(); } catch (const std::exception &ex) { std::cout << ex.what() << std::endl; ret = 1; loop.reset(); } std::cout << "server done" << std::endl; return ret; } syncevolution_1.4/test/dbus-server-config.py000077500000000000000000000076041230021373600214250ustar00rootroot00000000000000#! /usr/bin/python '''Get config/reports and set config. Usage: dbus-server-sync [--getconfig | --config | --reports] [config options] - configuration name ''' import dbus from dbus.mainloop.glib import DBusGMainLoop import gobject import sys DBusGMainLoop(set_as_default=True) bus = dbus.SessionBus() object = dbus.Interface(bus.get_object('org.syncevolution', '/org/syncevolution/Server'), 'org.syncevolution.Server') loop = gobject.MainLoop() sessionpath = None def SessionChanged(object, ready): print "SessionChanged:", object, ready if sessionpath == object: loop.quit() bus.add_signal_receiver(SessionChanged, 'SessionChanged', 'org.syncevolution.Server', 'org.syncevolution', None, byte_arrays=True) dummysessionpath = object.StartSession("") sessionpath = object.StartSession(sys.argv[1]) # detach from dummy session so that real session can run session = dbus.Interface(bus.get_object('org.syncevolution', dummysessionpath), 'org.syncevolution.Session') session.Detach() session = dbus.Interface(bus.get_object('org.syncevolution', sessionpath), 'org.syncevolution.Session') if sys.argv[2] == "--getconfig": print 'Get Config:' dict = session.GetConfig(0) for name, value in dict.items(): print 'name: [', name, ']' for config, confvalue in value.items(): print '\t', config, ' = ', confvalue elif sys.argv[2] == "--config": print 'Set Config:' i = 3; length = len(sys.argv) dict = {} update = 1 temporary = 0 while i < length: if sys.argv[i] == "--clear": update = 0 i = i + 1 continue elif sys.argv[i] == "--temp": temporary = 1 i = i + 1 continue elif sys.argv[i].startswith('-z'): l1 = sys.argv[i].split('=') if len(l1) == 2: name = 'source/' + l1[1] elif sys.argv[i].startswith('-y'): name = '' l2 = sys.argv[i+1].split('=') idict = {} if dict.has_key(name): idict = dict[name] idict[l2[0]] = l2[1] else: idict[l2[0]] = l2[1] dict[name] = idict i = i+2 print dict session.SetConfig(update, temporary, dict) session.Sync("", {}) elif sys.argv[2] == "--reports": reports = session.GetReports(0, 10) i = len(reports) j = 0 while j < i: print 'Report ', j, ':' dict = reports.pop() for key, value in dict.items(): print '\t[',key,'] = ' ,value j = j+1 print '' elif sys.argv[2] == "--checksource": i = 3; length = len(sys.argv) print 'CheckSource:' while i < length: try: session.CheckSource(sys.argv[i]) except dbus.exceptions.DBusException, x: print '\t[', sys.argv[i], ']: failed, ', x i = i + 1 continue print '\t[', sys.argv[i], ']: ok' i = i + 1 elif sys.argv[2] == "--getdatabases": i = 3; length = len(sys.argv) print 'GetDatabases:' while i < length: r = session.GetDatabases(sys.argv[i]) print '\t[', sys.argv[i],']' for item in r: print '\t\t','name =', item[0], ', uri = ', item[1], ', default =', item[2] i = i + 1 print '' elif sys.argv[2] == "--getconfigs": i = 3 length = len(sys.argv) r = {} if length == 3: r = session.GetConfigs(1) print ' Available configuration templates:' else: r = session.GetConfigs(0) print ' Configured servers:' for item in r: print '\t', item syncevolution_1.4/test/dbus-server-connect.py000077500000000000000000000043741230021373600216120ustar00rootroot00000000000000#! /usr/bin/python import dbus from dbus.mainloop.glib import DBusGMainLoop import gobject DBusGMainLoop(set_as_default=True) bus = dbus.SessionBus() object = dbus.Interface(bus.get_object('org.syncevolution', '/org/syncevolution/Server'), 'org.syncevolution.Server') conpath = object.Connect({'description': 'dbus-server-connection.py', 'transport': 'dummy'}, False, 0) print conpath connection = dbus.Interface(bus.get_object('org.syncevolution', conpath), 'org.syncevolution.Connection') connection.Close(False, 'die, connection, die') loop = gobject.MainLoop() conpath = object.Connect({'description': 'dbus-server-connection.py', 'transport': 'dummy'}, False, 0) connection = dbus.Interface(bus.get_object('org.syncevolution', conpath), 'org.syncevolution.Connection') def Reply(data, type, meta, final, session): print "Reply:", data, type, meta, final, session connection.Close(True, '') sessionpath = None def SessionChanged(object, ready): print "SessionChanged:", object, ready if not ready or sessionpath == object: loop.quit() bus.add_signal_receiver(Reply, 'Reply', 'org.syncevolution.Connection', 'org.syncevolution', conpath, byte_arrays=True) bus.add_signal_receiver(SessionChanged, 'SessionChanged', 'org.syncevolution.Server', 'org.syncevolution', None, byte_arrays=True) connection.Process([ 1, 2, 3, 4 ], "dummy message type") loop.run() sessionpath = object.StartSession('no_such_server') session = dbus.Interface(bus.get_object('org.syncevolution', sessionpath), 'org.syncevolution.Session') # wait for session ready loop.run() session.Close() # wait for session gone loop.run() syncevolution_1.4/test/dbus-server-http.py000077500000000000000000000055251230021373600211370ustar00rootroot00000000000000#! /usr/bin/python '''Usage: dbus-server-http.py Runs a sync session with an HTTP SyncML server configured in config.''' import dbus from dbus.mainloop.glib import DBusGMainLoop import gobject import sys import httplib, urlparse DBusGMainLoop(set_as_default=True) bus = dbus.SessionBus() object = dbus.Interface(bus.get_object('org.syncevolution', '/org/syncevolution/Server'), 'org.syncevolution.Server') conpath = object.Connect({'description': 'dbus-server-connection.py', 'transport': 'HTTP'}, False, 0) connection = dbus.Interface(bus.get_object('org.syncevolution', conpath), 'org.syncevolution.Connection') loop = gobject.MainLoop() def Abort(): print "connection went down" def Reply(data, type, meta, final, session): try: if final: print "closing connection" connection.Close(True, "") else: print ("send %d bytes of type %s, " % (len(data), type)), meta url = urlparse.urlparse(meta["URL"]) if url.scheme == "http": conn = httplib.HTTPConnection(url.netloc) elif url.scheme == "https": conn = httplib.HTTPSConnection(url.netloc) else: raise "invalid scheme " + url.scheme conn.request("POST", url.path, data, {"Content-type": type}) resp = conn.getresponse() reply = resp.read() print "received %d bytes of type %s" % (len(data), type) replytype = resp.getheader("Content-type") connection.Process(reply, replytype) except Exception as ex: print ex loop.quit() def SessionChanged(object, ready): print "SessionChanged:", object, ready if not ready: loop.quit() bus.add_signal_receiver(Abort, 'Abort', 'org.syncevolution.Connection', 'org.syncevolution', conpath, byte_arrays=True) bus.add_signal_receiver(Reply, 'Reply', 'org.syncevolution.Connection', 'org.syncevolution', conpath, byte_arrays=True) bus.add_signal_receiver(SessionChanged, 'SessionChanged', 'org.syncevolution.Server', 'org.syncevolution', None, byte_arrays=True) # start a test session with an HTTP server connection.Process(sys.argv[1], "HTTP Config") loop.run() syncevolution_1.4/test/dbus-server-sync.py000077500000000000000000000035401230021373600211270ustar00rootroot00000000000000#! /usr/bin/python '''Runs a sync. Usage: dbus-server-sync - configuration name - "", "two-way", "disabled", ... - "{}" or Python hash (like {"addressbook": "refresh-from-server"}) ''' import dbus from dbus.mainloop.glib import DBusGMainLoop import gobject import sys DBusGMainLoop(set_as_default=True) bus = dbus.SessionBus() object = dbus.Interface(bus.get_object('org.syncevolution', '/org/syncevolution/Server'), 'org.syncevolution.Server') loop = gobject.MainLoop() sessionpath = None def SessionChanged(object, ready): print "SessionChanged:", object, ready if sessionpath == object: loop.quit() bus.add_signal_receiver(SessionChanged, 'SessionChanged', 'org.syncevolution.Server', 'org.syncevolution', None, byte_arrays=True) dummysessionpath = object.StartSession("") sessionpath = object.StartSession(sys.argv[1]) # detach from dummy session so that real session can run session = dbus.Interface(bus.get_object('org.syncevolution', dummysessionpath), 'org.syncevolution.Session') session.Detach() session = dbus.Interface(bus.get_object('org.syncevolution', sessionpath), 'org.syncevolution.Session') print 'session created:', session.GetStatus(), session.GetProgress() # wait for session ready loop.run() print 'session ready:', session.GetStatus(), session.GetProgress() session.Sync(sys.argv[2], eval(sys.argv[3])) print 'sync started:', session.GetStatus(), session.GetProgress() # wait for session done loop.run() print 'done:', session.GetStatus(), session.GetProgress() syncevolution_1.4/test/dbus-session.sh000077500000000000000000000111361230021373600203140ustar00rootroot00000000000000#! /bin/sh # # Wrapper script which starts a new D-Bus session before # running a program and kills the D-Bus daemon when done. # start D-Bus session unset DBUS_SESSION_BUS_ADDRESS DBUS_SESSION_BUS_PID eval `dbus-launch` export DBUS_SESSION_BUS_ADDRESS if [ "$DBUS_SESSION_SH_SYSTEM_BUS" ]; then # Use own private bus as system bus, then start a new one. DBUS_SYSTEM_BUS_PID=$DBUS_SESSION_BUS_PID DBUS_SYSTEM_BUS_ADDRESS=$DBUS_SESSION_BUS_ADDRESS export DBUS_SYSTEM_BUS_ADDRESS eval `dbus-launch` fi # Ensure that XDG dirs exist. Otherwise some daemons do not work correctly. createxdg() { dir="$1" if [ "$dir" ]; then mkdir -p "$dir" fi } createxdg "$XDG_CONFIG_HOME" createxdg "$XDG_CACHE_HOME" createxdg "$XDG_DATA_HOME" # Work-around for GNOME keyring daemon not started # when accessed via org.freedesktop.secrets: start it # explicitly. # See https://launchpad.net/bugs/525642 and # https://bugzilla.redhat.com/show_bug.cgi?id=572137 if [ -x /usr/bin/gnome-keyring-daemon ]; then /usr/bin/gnome-keyring-daemon --start --foreground --components=secrets 1>&2 & KEYRING_PID=$! fi # kill all programs started by us atexit() { set -x [ ! "$KEYRING_PID" ] || ( echo >&2 "dbus-session.sh $$: killing keyring pid $KEYRING_PID"; kill -9 $KEYRING_PID ) [ ! "$DBUS_SESSION_SH_SYSTEM_BUS" ] || [ ! "$DBUS_SYSTEM_BUS_PID" ] || ( echo >&2 "dbus-session.sh $$: killing system bus daemon $DBUS_SYSTEM_BUS_PID"; kill -9 $DBUS_SYSTEM_BUS_PID ) [ ! $DBUS_SESSION_BUS_PID ] || ( echo >&2 "dbus-session.sh $$: killing session bus daemon $DBUS_SESSION_BUS_PID"; kill -9 $DBUS_SESSION_BUS_PID ) } trap atexit EXIT # If DBUS_SESSION_SH_EDS_BASE is set and our main program runs # under valgrind, then also check EDS. Otherwise start EDS only # for certain known operations which need EDS (syncevolution, client-test) # but not others (make, configure). # # DBUS_SESSION_SH_EDS_BASE must be the directory which contains # e-addressbook/calendar-factory. # # Similar for DBUS_SESSION_SH_AKONADI, except that we rely on akonadictl # and don't run it under valgrind. We also don't start it for test-dbus.py, # because starting it with "normal" XDG env variables and trying to # connect to it with XDG env set in test-dbus.py failed: # libakonadi Akonadi::SessionPrivate::init: Akonadi Client Session: connection config file ' akonadi/akonadiconnectionrc can not be found in ' "/data/runtests/work/lucid-amd64/build/src/temp-test-dbus/config" ' nor in any of ("/etc/xdg", "/etc") # E_CAL_PID= E_BOOK_PID= case "$@" in *valgrind*) prefix=`echo $@ | perl -p -e 's;.*?(\S*/?valgrind\S*).*;$1;'`;; *setup-syncevolution.sh*|*syncevolution\ *|*client-test\ *|*bash*|*testpim.py\ *|*test-dbus.py\ *|*gdb\ *) prefix=env;; *) prefix=;; # don't start EDS esac case "$TEST_DBUS_PREFIX" in *valgrind*) prefix="$TEST_DBUS_PREFIX";; esac akonadi=$prefix case "$@" in *test-dbus.py\ *) akonadi=;; esac if [ "$DBUS_SESSION_SH_AKONADI" ] && [ "$akonadi" ]; then akonadictl start 1>&2 SLEEP=5 else DBUS_SESSION_SH_AKONADI= fi if [ "$DBUS_SESSION_SH_EDS_BASE" ] && [ "$prefix" ]; then $prefix $DBUS_SESSION_SH_EDS_BASE/evolution-calendar-factory --keep-running 1>&2 & E_CAL_PID=$! $prefix $DBUS_SESSION_SH_EDS_BASE/evolution-addressbook-factory --keep-running 1>&2 & E_BOOK_PID=$! # give daemons some time to start and register with D-Bus SLEEP=5 else DBUS_SESSION_SH_EDS_BASE= fi [ "$SLEEP" ] && sleep $SLEEP # run program "$@" res=$? echo dbus-session.sh: program returned $res >&2 # Now also shut down EDS, if started by us. Update total result code if any of them # failed the valgrind check. shutdown () { pid=$1 program=$2 # give process 20 seconds to shut down i=0 while [ "$pid" ] && ps | grep -q "$pid" && [ $i -lt 20 ]; do sleep 1 i=`expr $i + 1` done if [ "$pid" ] && ps | grep -q "$pid"; then kill -9 "$pid" 2>/dev/null fi wait "$pid" subres=$? case $subres in 0|130|137|143) true;; # 130 and 143 indicate that it was killed, probably by us *) echo $program failed with return code $subres >&2 if [ $res -eq 0 ]; then res=$subres fi ;; esac } if [ "$DBUS_SESSION_SH_AKONADI" ]; then akonadictl stop fi if [ "$DBUS_SESSION_SH_EDS_BASE" ]; then kill "$E_CAL_PID" 2>/dev/null kill "$E_BOOK_PID" 2>/dev/null shutdown "$E_CAL_PID" "$DBUS_SESSION_SH_EDS_BASE/evolution-calendar-factory" shutdown "$E_BOOK_PID" "$DBUS_SESSION_SH_EDS_BASE/evolution-addressbook-factory" fi echo dbus-session.sh: final result $res >&2 exit $res syncevolution_1.4/test/evo.supp000066400000000000000000011506051230021373600170470ustar00rootroot00000000000000# # This file combines supressions common to all executables using Evolution libraries, # clients using libecal/libebook and evolution-data-server. ##### server ##### # ==3729== 72 bytes in 1 blocks are possibly lost in loss record 1,846 of 2,796 # ==3729== at 0x4C260C6: calloc (vg_replace_malloc.c:566) # ==3729== by 0x8632590: g_malloc0 (gmem.c:189) # ==3729== by 0x83A2368: g_closure_new_simple (gclosure.c:206) # ==3729== by 0x83A38CF: g_cclosure_new (gclosure.c:917) # ==3729== by 0x83BA91D: g_signal_connect_data (gsignal.c:2443) # ==3729== by 0x5643821: ??? (in /usr/lib/x86_64-linux-gnu/libgtk-3.so.0.200.3) # ==3729== by 0x56279DA: ??? (in /usr/lib/x86_64-linux-gnu/libgtk-3.so.0.200.3) # ==3729== by 0x8637A5F: g_option_context_parse (goption.c:2025) # ==3729== by 0x5627E3F: gtk_parse_args (in /usr/lib/x86_64-linux-gnu/libgtk-3.so.0.200.3) # ==3729== by 0x5627E98: gtk_init_check (in /usr/lib/x86_64-linux-gnu/libgtk-3.so.0.200.3) # ==3729== by 0x40162A: main (evolution-addressbook-factory.c:104) # ==3729== { GTK args leak Memcheck:Leak ... fun:gtk_parse_args fun:gtk_init_check fun:main } # ==3730== 5,424 (1,024 direct, 4,400 indirect) bytes in 2 blocks are definitely lost in loss record 2,839 of 2,903 # ==3730== at 0x4C2776B: realloc (vg_replace_malloc.c:632) # ==3730== by 0x8CD45E6: g_realloc (gmem.c:224) # ==3730== by 0x8CA3757: g_ptr_array_maybe_expand (garray.c:1093) # ==3730== by 0x8CA4712: g_ptr_array_add (garray.c:1350) # ==3730== by 0x66FDD97: read_netlink_messages (gnetworkmonitornetlink.c:237) # ==3730== by 0x66D5DE5: socket_source_dispatch (gsocket.c:3161) # ==3730== by 0x8CCE799: g_main_context_dispatch (gmain.c:2515) # ==3730== by 0x8CCEB5F: g_main_context_iterate.isra.23 (gmain.c:3123) # ==3730== by 0x8CCEF59: g_main_loop_run (gmain.c:3317) # ==3730== by 0x5085E08: e_dbus_server_run (e-dbus-server.c:253) # ==3730== by 0x401759: main (evolution-calendar-factory.c:149) # ==3730== { EDS daemon + read netlink leak Memcheck:Leak ... fun:read_netlink_messages ... fun:e_dbus_server_run fun:main } # ==4154== 144 bytes in 2 blocks are possibly lost in loss record 2,131 of 2,440 # ==4154== at 0x4C260C6: calloc (vg_replace_malloc.c:566) # ==4154== by 0x553B590: g_malloc0 (gmem.c:189) # ==4154== by 0x52AB368: g_closure_new_simple (gclosure.c:206) # ==4154== by 0x52AC8CF: g_cclosure_new (gclosure.c:917) # ==4154== by 0x52C391D: g_signal_connect_data (gsignal.c:2443) # ==4154== by 0x7E8BD72: ??? (in /usr/lib/libsoup-2.4.so.1.4.0) # ==4154== by 0x7EAA793: ??? (in /usr/lib/libsoup-2.4.so.1.4.0) # ==4154== by 0x624E546: g_simple_async_result_complete (gsimpleasyncresult.c:767) # ==4154== by 0x6254637: g_socket_client_async_connect_complete (gsocketclient.c:1316) # ==4154== by 0x62551C3: g_socket_client_connected_callback (gsocketclient.c:1516) # ==4154== by 0x624E546: g_simple_async_result_complete (gsimpleasyncresult.c:767) # ==4154== by 0x625663E: g_socket_connection_connect_callback (gsocketconnection.c:242) # ==4154== by 0x624EDE5: socket_source_dispatch (gsocket.c:3161) # ==4154== by 0x5535799: g_main_context_dispatch (gmain.c:2515) # ==4154== by 0x5535B5F: g_main_context_iterate.isra.23 (gmain.c:3123) # ==4154== by 0x5535F59: g_main_loop_run (gmain.c:3317) # ==4154== by 0x57F653B: eas_soup_thread (eas-connection.c:672) # ==4154== by 0x5557404: g_thread_proxy (gthread.c:801) # ==4154== by 0x5C41B4F: start_thread (pthread_create.c:304) # ==4154== by 0x5F2F90C: clone (clone.S:112) # ==4154== { EAS daemon connection leak Memcheck:Leak ... fun:g_socket_connection_connect_callback ... fun:eas_soup_thread } ##### common ##### # ==8596== 24 bytes in 1 blocks are definitely lost in loss record 2,027 of 5,220 # ==8596== at 0x4C28BED: malloc (vg_replace_malloc.c:263) # ==8596== by 0xA91BD50: g_malloc (gmem.c:159) # ==8596== by 0xA930C92: g_slice_alloc (gslice.c:1003) # ==8596== by 0xA9311E5: g_slice_alloc0 (gslice.c:1029) # ==8596== by 0xA925622: g_queue_copy (gqueue.c:222) # ==8596== by 0x1B0861D3: tp_proxy_poll_features (proxy.c:2202) # ==8596== by 0x1B086974: tp_proxy_emit_invalidated (proxy.c:618) # ==8596== by 0x1AF8E92A: _tp_account_manager_got_all_cb (account-manager.c:482) # ==8596== by 0x1B081F32: _tp_cli_dbus_properties_invoke_callback_get_all (tp-cli-generic-body.h:1204) # ==8596== by 0x1B08814F: tp_proxy_pending_call_idle_invoke (proxy-methods.c:155) # ==8596== by 0xA916114: g_main_context_dispatch (gmain.c:2715) # ==8596== by 0xA916447: g_main_context_iterate.isra.24 (gmain.c:3290) # ==8596== by 0xA916503: g_main_context_iteration (gmain.c:3351) # ==8596== by 0x7C7003: SyncEvo::EvolutionSyncSource::getSourceRegistry() (EvolutionSyncSource.cpp:68) # ==8596== by 0x7C8665: SyncEvo::EvolutionSyncSource::getDatabasesFromRegistry(std::vector >&, char const*, _ESource* (*)(_ESourceRegistry*)) (EvolutionSyncSource.cpp:37) # ==8596== by 0x7C1FC3: SyncEvo::EvolutionContactSource::getDatabases() (EvolutionContactSource.cpp:93) # ==8596== by 0x720BD7: SyncEvo::Manager::doSetPeer(boost::shared_ptr const&, boost::shared_ptr const&, std::string const&, std::map, std::allocator > > const&) (manager.cpp:757) # ==8596== by 0x71D89B: SyncEvo::Manager::doSession(boost::weak_ptr const&, boost::shared_ptr const&, boost::function const&)> const&) (function_template.hpp:760) # ==8596== by 0x6C21FE: boost::detail::function::void_function_obj_invoker0 const&)>, boost::_bi::list1 > > >, void>::invoke(boost::detail::function::function_buffer&) (function_template.hpp:760) # ==8596== by 0x7191F9: boost::signals2::detail::signal0_impl, int, std::less, boost::function, boost::function, boost::signals2::mutex>::operator()() (function_template.hpp:760) # ==8596== by 0x6E6A17: SyncEvo::Session::activateSession() (signal_template.hpp:695) # ==8596== by 0x6B3904: SyncEvo::Server::checkQueue() (server.cpp:643) # ==8596== by 0x6B688D: SyncEvo::Server::enqueue(boost::shared_ptr const&) (server.cpp:500) # ==8596== by 0x6B7759: SyncEvo::Server::startInternalSession(std::string const&, SyncEvo::Server::SessionFlags, boost::function const&)> const&) (server.cpp:235) # ==8596== by 0x71EF92: SyncEvo::Manager::runInSession(std::string const&, SyncEvo::Server::SessionFlags, boost::shared_ptr const&, boost::function const&)> const&) (manager.cpp:596) # ==8596== by 0x72007C: SyncEvo::Manager::setPeer(boost::shared_ptr const&, std::string const&, std::map, std::allocator > > const&) (manager.cpp:677) # ==8596== by 0x734E6C: GDBusCXX::MakeMethodEntry const&, std::string const&, std::map, std::allocator > > const&)> >::methodFunction(_GDBusConnection*, _GDBusMessage*, void*) (function_template.hpp:760) # ==8596== by 0x6C23EA: GDBusCXX::MethodHandler::handler(_GDBusConnection*, char const*, char const*, char const*, char const*, _GVariant*, _GDBusMethodInvocation*, void*) (gdbus-cxx-bridge.h:1062) # ==8596== by 0xA3E93E4: call_in_idle_cb (gdbusconnection.c:4737) # ==8596== by 0xA916114: g_main_context_dispatch (gmain.c:2715) # ==8596== { getSourceRegistry inside libedata Memcheck:Leak fun:malloc ... fun:_tp_account_manager_got_all_cb ... fun:g_main_context_iteration fun:_ZN7SyncEvo19EvolutionSyncSource17getSourceRegistryEv } { getSourceRegistry inside libedata, using EDSRegistryLoader Memcheck:Leak fun:malloc ... fun:_tp_account_manager_got_all_cb ... fun:g_main_context_iteration fun:*EDSRegistryLoader* } # ==8468== Use of uninitialised value of size 8 # ==8468== at 0x70F826E: g_utf8_offset_to_pointer (in /usr/lib/libglib-2.0.so.0.1400.1) # ==8468== by 0x50A2C7B: e_vcard_to_string (e-vcard.c:905) # ==8468== by 0x50974D1: do_add_contact (e-book.c:277) # ==8468== by 0x49626A: EvolutionContactSource::addItemThrow(SyncItem&) (EvolutionContactSource.cpp:717) # ==8468== by 0x415B1A: EvolutionSyncSource::processItem(char const*, int (EvolutionSyncSource::*)(SyncItem&), SyncItem&, bool) (EvolutionSyncSource.cpp:428) # ==8468== by 0x415C5A: EvolutionSyncSource::addItem(SyncItem&) (EvolutionSyncSource.cpp:399) # ==8468== by 0x42B17A: importItem(SyncSource*, std::string&) (ClientTest.cpp:148) # ==8468== by 0x42C7A6: ClientTest::import(ClientTest&, SyncSource&, char const*) (ClientTest.cpp:1779) # ==8468== by 0x4617F3: LocalTests::testImport() (ClientTest.cpp:668) # ==8468== by 0x48FDFB: CppUnit::TestCaller::runTest() (TestCaller.h:166) # ==8468== by 0x4E51166: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==8468== by 0x4E435C3: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) { 500509: invalid memory access due to incorrect call of g_utf8_offset_to_pointer(), I Memcheck:Value8 fun:g_utf8_offset_to_pointer fun:e_vcard_to_string* } { 500509: invalid memory access due to incorrect call of g_utf8_offset_to_pointer(), I 32bit Memcheck:Value4 fun:g_utf8_offset_to_pointer fun:e_vcard_to_string* } # ==8545== Invalid read of size 1 # ==8545== at 0x70F8279: g_utf8_offset_to_pointer (in /usr/lib/libglib-2.0.so.0.1400.1) # ==8545== by 0x50A2C7B: e_vcard_to_string (e-vcard.c:905) # ==8545== by 0x50974D1: do_add_contact (e-book.c:277) # ==8545== by 0x49626A: EvolutionContactSource::addItemThrow(SyncItem&) (EvolutionContactSource.cpp:717) # ==8545== by 0x415B1A: EvolutionSyncSource::processItem(char const*, int (EvolutionSyncSource::*)(SyncItem&), SyncItem&, bool) (EvolutionSyncSource.cpp:428) # ==8545== by 0x415C5A: EvolutionSyncSource::addItem(SyncItem&) (EvolutionSyncSource.cpp:399) # ==8545== by 0x42B17A: importItem(SyncSource*, std::string&) (ClientTest.cpp:148) # ==8545== by 0x42C7A6: ClientTest::import(ClientTest&, SyncSource&, char const*) (ClientTest.cpp:1779) # ==8545== by 0x4617F3: LocalTests::testImport() (ClientTest.cpp:668) # ==8545== by 0x48FDFB: CppUnit::TestCaller::runTest() (TestCaller.h:166) # ==8545== by 0x4E51166: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==8545== by 0x4E435C3: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==8545== Address 0xEB8E920 is 0 bytes after a block of size 128 alloc'd # ==8545== at 0x4C21D17: realloc (vg_replace_malloc.c:306) # ==8545== by 0x70D8310: g_realloc (in /usr/lib/libglib-2.0.so.0.1400.1) # ==8545== by 0x70F00F3: (within /usr/lib/libglib-2.0.so.0.1400.1) # ==8545== by 0x70F0C4E: g_string_insert_len (in /usr/lib/libglib-2.0.so.0.1400.1) # ==8545== by 0x50A2A75: e_vcard_to_string (e-vcard.c:876) # ==8545== by 0x50974D1: do_add_contact (e-book.c:277) # ==8545== by 0x49626A: EvolutionContactSource::addItemThrow(SyncItem&) (EvolutionContactSource.cpp:717) # ==8545== by 0x415B1A: EvolutionSyncSource::processItem(char const*, int (EvolutionSyncSource::*)(SyncItem&), SyncItem&, bool) (EvolutionSyncSource.cpp:428) # ==8545== by 0x415C5A: EvolutionSyncSource::addItem(SyncItem&) (EvolutionSyncSource.cpp:399) # ==8545== by 0x42B17A: importItem(SyncSource*, std::string&) (ClientTest.cpp:148) # ==8545== by 0x42C7A6: ClientTest::import(ClientTest&, SyncSource&, char const*) (ClientTest.cpp:1779) # ==8545== by 0x4617F3: LocalTests::testImport() (ClientTest.cpp:668) { 500509: invalid memory access due to incorrect call of g_utf8_offset_to_pointer(), II Memcheck:Addr1 fun:g_utf8_offset_to_pointer fun:e_vcard_to_string* } # ==10986== 800 bytes in 20 blocks are possibly lost in loss record 14 of 22 # ==10986== at 0x401C7EF: calloc (vg_replace_malloc.c:279) # ==10986== by 0x42DC9FD: g_malloc0 (gmem.c:150) # ==10986== by 0x4286FA8: type_node_any_new_W (gtype.c:342) # ==10986== by 0x4287164: type_node_fundamental_new_W (gtype.c:447) # ==10986== by 0x428771B: g_type_init_with_debug_flags (gtype.c:3417) # ==10986== by 0x4287881: g_type_init (gtype.c:3475) # ==10986== by 0x8059F6B: RegisterTestEvolution::RegisterTestEvolution() (in /tmp/runtests/head/tmp/build/src/client-test) # ==10986== by 0x804F3B6: __static_initialization_and_destruction_0(int, int) (in /tmp/runtests/head/tmp/build/src/client-test) # ==10986== by 0x804F3EA: _GLOBAL__I__ZN16MacOSAddressBook11m_singletonE (in /tmp/runtests/head/tmp/build/src/client-test) # ==10986== by 0x812EB14: (within /tmp/runtests/head/tmp/build/src/client-test) # ==10986== by 0x804DD30: (within /tmp/runtests/head/tmp/build/src/client-test) # ==10986== by 0x812EAA8: __libc_csu_init (in /tmp/runtests/head/tmp/build/src/client-test) { g_type_init, g_type_create Memcheck:Leak ... fun:g_type_* } # ==13528== 64 bytes in 1 blocks are possibly lost in loss record 489 of 780 # ==13528== at 0x4C245E2: realloc (vg_replace_malloc.c:525) # ==13528== by 0x643233E: g_realloc (in /lib/libglib-2.0.so.0.2400.1) # ==13528== by 0x5FC70BA: ??? (in /usr/lib/libgobject-2.0.so.0.2400.1) # ==13528== by 0x5FCA374: g_type_register_static (in /usr/lib/libgobject-2.0.so.0.2400.1) # ==13528== by 0x5FCA407: g_type_register_static_simple (in /usr/lib/libgobject-2.0.so.0.2400.1) # ==13528== by 0x5058230: e_book_get_type (in /usr/lib/libebook-1.2.so.9.3.1) # ==13528== by 0x505835F: e_book_new (in /usr/lib/libebook-1.2.so.9.3.1) # ==13528== by 0x50587A7: e_book_new_from_uri (in /usr/lib/libebook-1.2.so.9.3.1) # ==13528== by 0x59815F: SyncEvo::EvolutionContactSource::open() (EvolutionContactSource.cpp:168) # ==13528== by 0x4E46D8: SyncEvo::LocalTests::testOpen() (ClientTest.cpp:532) # ==13528== by 0x595B406: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==13528== by 0x594D7D3: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==13528== by 0x5957278: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==13528== by 0x5956FBB: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==13528== by 0x5962D9F: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==13528== by 0x595B09C: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==13528== by 0x5962B29: CppUnit::TestResult::runTest(CppUnit::Test*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==13528== by 0x5965121: CppUnit::TestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==13528== by 0x596813A: CppUnit::TextTestRunner::run(std::string, bool, bool, bool) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==13528== by 0x586B2B: main (client-test-main.cpp:271) { g_type_register Memcheck:Leak ... fun:g_type_register* } ##### clients ##### # ==8371== 556 bytes in 22 blocks are possibly lost in loss record 45 of 64 # ==8371== at 0x4C21C16: malloc (vg_replace_malloc.c:149) # ==8371== by 0x70D842A: g_malloc (in /usr/lib/libglib-2.0.so.0.1400.1) # ==8371== by 0x6611AEC: ORBit_alloc_string (in /usr/lib/libORBit-2.so.0.1.0) # ==8371== by 0x66117FD: CORBA_string_dup (in /usr/lib/libORBit-2.so.0.1.0) # ==8371== by 0x63D2EF4: Bonobo_ServerInfo_copy (in /usr/lib/libbonobo-activation.so.4.0.0) # ==8371== by 0x63D2FCA: Bonobo_ServerInfoList_duplicate (in /usr/lib/libbonobo-activation.so.4.0.0) # ==8371== by 0x63D12D0: bonobo_activation_query (in /usr/lib/libbonobo-activation.so.4.0.0) # ==8371== by 0x50915BF: e_book_new (e-book.c:3234) # ==8371== by 0x49851D: EvolutionContactSource::open() (EvolutionContactSource.cpp:125) # ==8371== by 0x40F6F6: TestEvolutionSyncSource::beginSync() (client-test-app.cpp:142) # ==8371== by 0x42E136: LocalTests::testIterateTwice() (ClientTest.cpp:490) # ==8371== by 0x48FDFB: CppUnit::TestCaller::runTest() (TestCaller.h:166) { server info Memcheck:Leak fun:malloc fun:g_malloc fun:ORBit_alloc_string fun:CORBA_string_dup fun:Bonobo_ServerInfo_copy fun:Bonobo_ServerInfoList_duplicate fun:bonobo_activation_query fun:e_book_new } # ==4081== 34,300 bytes in 1,071 blocks are possibly lost in loss record 43 of 53 # ==4081== at 0x401C7EF: calloc (vg_replace_malloc.c:279) # ==4081== by 0x42DD9FD: g_malloc0 (gmem.c:150) # ==4081== by 0x483E675: ORBit_alloc_by_tc (allocators.c:373) # ==4081== by 0x48399AC: ORBit_small_alloc (orbit-small.c:44) # ==4081== by 0x484524B: IOP_ObjectKey_demarshal (iop-profiles.c:1043) # ==4081== by 0x4845F80: ORBit_demarshal_IOR (iop-profiles.c:1399) # ==4081== by 0x483CCBE: ORBit_demarshal_object (corba-object.c:608) # ==4081== by 0x4843558: ORBit_demarshal_value (corba-any.c:545) # ==4081== by 0x4839D45: orbit_small_demarshal (orbit-small.c:425) # ==4081== by 0x483AB8C: ORBit_small_invoke_stub (orbit-small.c:663) # ==4081== by 0x483AD7F: ORBit_small_invoke_stub_n (orbit-small.c:575) # ==4081== by 0x4847671: ORBit_c_stub_invoke (poa.c:2643) # ==4081== by 0x412FD0F: GNOME_Evolution_Addressbook_BookFactory_getBook (Evolution-DataServer-Addressbook-stubs.c:330) # ==4081== by 0x413CDE2: fetch_corba_book (e-book.c:3246) # ==4081== by 0x413E232: e_book_new (e-book.c:3730) # ==4081== by 0x413E3FE: e_book_new_from_uri (e-book.c:3762) # ==4081== by 0x810EF8A: EvolutionContactSource::open() (EvolutionContactSource.cpp:136) # ==4081== by 0x8057875: TestEvolutionSyncSource::beginSync() (in /tmp/runtests/head/tmp/build/src/client-test) # ==4081== by 0x80ED371: LocalTests::update(CreateSource, char const*, bool) (in /tmp/runtests/head/tmp/build/src/client-test) # ==4081== by 0x80DBBC1: LocalTests::testChanges() (in /tmp/runtests/head/tmp/build/src/client-test) # ==4081== by 0x810213A: CppUnit::TestCaller::runTest() (in /tmp/runtests/head/tmp/build/src/client-test) # ==4081== by 0x4051C88: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==4081== by 0x40433ED: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==4081== by 0x404D8D2: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==4081== by 0x404D613: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==4081== by 0x405A390: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==4081== by 0x4051637: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==4081== by 0x40522FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==4081== by 0x4052229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==4081== by 0x40522FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) { e_book_new II Memcheck:Leak fun:calloc fun:g_malloc* fun:ORBit_alloc_by_tc fun:ORBit_small_alloc fun:IOP_ObjectKey_demarshal fun:ORBit_demarshal_IOR fun:ORBit_demarshal_object fun:ORBit_demarshal_value fun:orbit_small_demarshal fun:ORBit_small_invoke_stub fun:ORBit_small_invoke_stub_n fun:ORBit_c_stub_invoke fun:GNOME_Evolution_Addressbook_BookFactory_getBook fun:fetch_corba_book fun:e_book_new fun:e_book_new_from_uri } # ==4555== 33,240 bytes in 1,066 blocks are possibly lost in loss record 24 of 51 # ==4555== at 0x401C7EF: calloc (vg_replace_malloc.c:279) # ==4555== by 0x42DD9FD: g_malloc0 (gmem.c:150) # ==4555== by 0x483E675: ORBit_alloc_by_tc (allocators.c:373) # ==4555== by 0x48399AC: ORBit_small_alloc (orbit-small.c:44) # ==4555== by 0x4848F58: ORBit_POAObject_object_to_objkey (poa.c:384) # ==4555== by 0x484D621: ORBit_OAObject_object_to_objkey (orbit-adaptor.c:327) # ==4555== by 0x4846712: IOP_generate_profiles (iop-profiles.c:620) # ==4555== by 0x483C826: ORBit_marshal_object (corba-object.c:573) # ==4555== by 0x4843D06: ORBit_marshal_value (corba-any.c:173) # ==4555== by 0x483964F: orbit_small_marshal (orbit-small.c:353) # ==4555== by 0x483AB37: ORBit_small_invoke_stub (orbit-small.c:646) # ==4555== by 0x483AD7F: ORBit_small_invoke_stub_n (orbit-small.c:575) # ==4555== by 0x4847671: ORBit_c_stub_invoke (poa.c:2643) # ==4555== by 0x412FD0F: GNOME_Evolution_Addressbook_BookFactory_getBook (Evolution-DataServer-Addressbook-stubs.c:330) # ==4555== by 0x413CDE2: fetch_corba_book (e-book.c:3246) # ==4555== by 0x413E232: e_book_new (e-book.c:3730) # ==4555== by 0x413E3FE: e_book_new_from_uri (e-book.c:3762) # ==4555== by 0x810EF8A: EvolutionContactSource::open() (EvolutionContactSource.cpp:136) # ==4555== by 0x8057875: TestEvolutionSyncSource::beginSync() (in /tmp/runtests/head/tmp/build/src/client-test) # ==4555== by 0x80ED371: LocalTests::update(CreateSource, char const*, bool) (in /tmp/runtests/head/tmp/build/src/client-test) # ==4555== by 0x80E0566: LocalTests::testChanges() (in /tmp/runtests/head/tmp/build/src/client-test) # ==4555== by 0x810213A: CppUnit::TestCaller::runTest() (in /tmp/runtests/head/tmp/build/src/client-test) # ==4555== by 0x4051C88: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==4555== by 0x40433ED: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==4555== by 0x404D8D2: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==4555== by 0x404D613: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==4555== by 0x405A390: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==4555== by 0x4051637: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==4555== by 0x40522FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==4555== by 0x4052229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) { e_book_new III Memcheck:Leak fun:calloc fun:g_malloc* fun:ORBit_alloc_by_tc fun:ORBit_small_alloc fun:ORBit_POAObject_object_to_objkey fun:ORBit_OAObject_object_to_objkey fun:IOP_generate_profiles fun:ORBit_marshal_object fun:ORBit_marshal_value fun:orbit_small_marshal fun:ORBit_small_invoke_stub fun:ORBit_small_invoke_stub_n fun:ORBit_c_stub_invoke fun:GNOME_Evolution_Addressbook_BookFactory_getBook fun:fetch_corba_book fun:e_book_new } # ==10697== 769 bytes in 27 blocks are possibly lost in loss record 18 of 51 # ==10697== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==10697== by 0x42DDA95: g_malloc (gmem.c:131) # ==10697== by 0x483E95D: ORBit_alloc_string (allocators.c:228) # ==10697== by 0x483E618: CORBA_string_dup (corba-string.c:22) # ==10697== by 0x4785649: Bonobo_ServerInfo_copy (bonobo-activation-server-info.c:166) # ==10697== by 0x478573F: Bonobo_ServerInfoList_duplicate (bonobo-activation-server-info.c:212) # ==10697== by 0x4783701: bonobo_activation_query (bonobo-activation-activate.c:238) # ==10697== by 0x413C9FC: activate_factories_for_uri (e-book.c:3144) # ==10697== by 0x413CBF5: fetch_corba_book (e-book.c:3208) # ==10697== by 0x413E232: e_book_new (e-book.c:3730) # ==10697== by 0x413E3FE: e_book_new_from_uri (e-book.c:3762) # ==10697== by 0x810BCAA: EvolutionContactSource::open() (EvolutionContactSource.cpp:136) # ==10697== by 0x80558CE: TestEvolutionSyncSource::beginSync() (in /tmp/runtests/head/tmp/build/src/client-test) # ==10697== by 0x8090A29: LocalTests::testIterateTwice() (in /tmp/runtests/head/tmp/build/src/client-test) # ==10697== by 0x80FE7C6: CppUnit::TestCaller::runTest() (in /tmp/runtests/head/tmp/build/src/client-test) # ==10697== by 0x4051C88: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==10697== by 0x40433ED: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==10697== by 0x404D8D2: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==10697== by 0x404D613: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==10697== by 0x405A390: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==10697== by 0x4051637: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==10697== by 0x40522FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==10697== by 0x4052229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==10697== by 0x40522FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==10697== by 0x4052229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==10697== by 0x405A009: CppUnit::TestResult::runTest(CppUnit::Test*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==10697== by 0x405C72F: CppUnit::TestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==10697== by 0x405FF2A: CppUnit::TextTestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==10697== by 0x405FFA1: CppUnit::TextTestRunner::run(std::string, bool, bool, bool) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==10697== by 0x8102024: main (in /tmp/runtests/head/tmp/build/src/client-test) { e_book_new IV Memcheck:Leak fun:malloc fun:g_malloc fun:ORBit_alloc_string fun:CORBA_string_dup fun:Bonobo_ServerInfo_copy fun:Bonobo_ServerInfoList_duplicate fun:bonobo_activation_query fun:activate_factories_for_uri fun:fetch_corba_book fun:e_book_new } # ==5064== 33,588 bytes in 1,069 blocks are possibly lost in loss record 43 of 50 # ==5064== at 0x401C7EF: calloc (vg_replace_malloc.c:279) # ==5064== by 0x42DC994: g_malloc0 (gmem.c:151) # ==5064== by 0x483D6EC: ORBit_alloc_tcval (allocators.c:287) # ==5064== by 0x4839DB8: ORBit_small_allocbuf (orbit-small.c:51) # ==5064== by 0x4847F7D: ORBit_POAObject_object_to_objkey (poa.c:387) # ==5064== by 0x484C621: ORBit_OAObject_object_to_objkey (orbit-adaptor.c:327) # ==5064== by 0x4845712: IOP_generate_profiles (iop-profiles.c:620) # ==5064== by 0x483B826: ORBit_marshal_object (corba-object.c:573) # ==5064== by 0x4842D06: ORBit_marshal_value (corba-any.c:173) # ==5064== by 0x483864F: orbit_small_marshal (orbit-small.c:353) # ==5064== by 0x4839B37: ORBit_small_invoke_stub (orbit-small.c:646) # ==5064== by 0x4839D7F: ORBit_small_invoke_stub_n (orbit-small.c:575) # ==5064== by 0x4846671: ORBit_c_stub_invoke (poa.c:2643) # ==5064== by 0x412FD2F: GNOME_Evolution_Addressbook_BookFactory_getBook (Evolution-DataServer-Addressbook-stubs.c:330) # ==5064== by 0x413CE08: fetch_corba_book (e-book.c:3246) # ==5064== by 0x413E258: e_book_new (e-book.c:3730) # ==5064== by 0x413E424: e_book_new_from_uri (e-book.c:3762) # ==5064== by 0x8143E5F: EvolutionContactSource::open() (EvolutionContactSource.cpp:139) # ==5064== by 0x8056656: TestEvolutionSyncSource::beginSync() (in /tmp/runtests/head/tmp/build/src/client-test) # ==5064== by 0x8126377: LocalTests::insert(CreateSource, char const*) (in /tmp/runtests/head/tmp/build/src/client-test) # ==5064== by 0x80C4B9B: LocalTests::testSimpleInsert() (in /tmp/runtests/head/tmp/build/src/client-test) # ==5064== by 0x8114B68: LocalTests::testChanges() (in /tmp/runtests/head/tmp/build/src/client-test) # ==5064== by 0x8136796: CppUnit::TestCaller::runTest() (in /tmp/runtests/head/tmp/build/src/client-test) # ==5064== by 0x4051C88: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5064== by 0x40433ED: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5064== by 0x404D8D2: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5064== by 0x404D613: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5064== by 0x405A390: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5064== by 0x4051637: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5064== by 0x40522FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) { e_book_new V Memcheck:Leak fun:calloc fun:g_malloc0 fun:ORBit_alloc_tcval fun:ORBit_small_allocbuf fun:ORBit_POAObject_object_to_objkey fun:ORBit_OAObject_object_to_objkey fun:IOP_generate_profiles fun:ORBit_marshal_object fun:ORBit_marshal_value fun:orbit_small_marshal fun:ORBit_small_invoke_stub fun:ORBit_small_invoke_stub_n fun:ORBit_c_stub_invoke fun:GNOME_Evolution_Addressbook_BookFactory_getBook fun:fetch_corba_book fun:e_book_new } # ==9785== 33,744 bytes in 1,081 blocks are possibly lost in loss record 31 of 38 # ==9785== at 0x401C7EF: calloc (vg_replace_malloc.c:279) # ==9785== by 0x42C8994: g_malloc0 (gmem.c:151) # ==9785== by 0x483B6EC: ORBit_alloc_tcval (allocators.c:287) # ==9785== by 0x4837DB8: ORBit_small_allocbuf (orbit-small.c:51) # ==9785== by 0x4846323: ORBit_POA_create_object_T (poa.c:363) # ==9785== by 0x4846553: PortableServer_POA_servant_to_reference (poa.c:2330) # ==9785== by 0x473E416: bonobo_object_constructor (bonobo-object.c:791) # ==9785== by 0x41677C7: g_object_newv (gobject.c:937) # ==9785== by 0x416840E: g_object_new_valist (gobject.c:1027) # ==9785== by 0x416851F: g_object_new (gobject.c:795) # ==9785== by 0x41313D8: e_book_listener_new (e-book-listener.c:412) # ==9785== by 0x413D2E4: fetch_corba_book (e-book.c:3313) # ==9785== by 0x413E8B4: e_book_new (e-book.c:3824) # ==9785== by 0x413EA80: e_book_new_from_uri (e-book.c:3856) # ==9785== by 0x8182AA1: EvolutionContactSource::open() (EvolutionContactSource.cpp:139) # ==9785== by 0x8056932: TestEvolutionSyncSource::beginSync() (in /tmp/runtests/head-evolution-2.12/build/src/client-test) # ==9785== by 0x8100DB5: LocalTests::insert(CreateSource, char const*) (in /tmp/runtests/head-evolution-2.12/build/src/client-test) # ==9785== by 0x8099539: LocalTests::testLocalDeleteAll() (in /tmp/runtests/head-evolution-2.12/build/src/client-test) # ==9785== by 0x809DB20: LocalTests::testImport() (in /tmp/runtests/head-evolution-2.12/build/src/client-test) # ==9785== by 0x8092A6D: LocalTests::testImportDelete() (in /tmp/runtests/head-evolution-2.12/build/src/client-test) # ==9785== by 0x8173F04: CppUnit::TestCaller::runTest() (in /tmp/runtests/head-evolution-2.12/build/src/client-test) # ==9785== by 0x4051C88: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==9785== by 0x40433ED: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==9785== by 0x404D8D2: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==9785== by 0x404D613: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==9785== by 0x405A390: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==9785== by 0x4051637: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==9785== by 0x40522FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==9785== by 0x4052229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==9785== by 0x40522FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) { e_book_new VI Memcheck:Leak fun:calloc fun:g_malloc0 fun:ORBit_alloc_tcval fun:ORBit_small_allocbuf fun:ORBit_POA_create_object_T fun:PortableServer_POA_servant_to_reference fun:bonobo_object_constructor fun:g_object_newv fun:g_object_new_valist fun:g_object_new fun:e_book_listener_new fun:fetch_corba_book fun:e_book_new } # ==3335== 76,364 bytes in 2,526 blocks are possibly lost in loss record 28 of 47 # ==3335== at 0x401C7EF: calloc (vg_replace_malloc.c:279) # ==3335== by 0x4456994: g_malloc0 (gmem.c:151) # ==3335== by 0x43A36EC: ORBit_alloc_tcval (allocators.c:287) # ==3335== by 0x439FDB8: ORBit_small_allocbuf (orbit-small.c:51) # ==3335== by 0x43AE323: ORBit_POA_create_object_T (poa.c:363) # ==3335== by 0x43AE553: PortableServer_POA_servant_to_reference (poa.c:2330) # ==3335== by 0x42FE416: bonobo_object_constructor (bonobo-object.c:791) # ==3335== by 0x43ED7C7: g_object_newv (gobject.c:937) # ==3335== by 0x43EE40E: g_object_new_valist (gobject.c:1027) # ==3335== by 0x43EE51F: g_object_new (gobject.c:795) # ==3335== by 0x404B0F3: e_data_book_new (e-data-book.c:943) # ==3335== by 0x4047BA8: impl_GNOME_Evolution_Addressbook_BookFactory_getBook (e-data-book-factory.c:351) # ==3335== by 0x4038E62: _ORBIT_skel_small_GNOME_Evolution_Addressbook_BookFactory_getBook (Evolution-DataServer-Addressbook-common.c:180) # ==3335== by 0x43AC436: ORBit_POAObject_invoke (poa.c:1142) # ==3335== by 0x43B2664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==3335== by 0x439F5D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==3335== by 0x43B02B9: ORBit_POAObject_handle_request (poa.c:1351) # ==3335== by 0x43B095B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==3335== by 0x439813A: giop_thread_queue_process (giop.c:771) # ==3335== by 0x43989D7: giop_request_handler_thread (giop.c:481) # ==3335== by 0x4478306: g_thread_pool_thread_proxy (gthreadpool.c:265) # ==3335== by 0x447677E: g_thread_create_proxy (gthread.c:635) # ==3335== by 0x452623F: start_thread (in /lib/tls/i686/cmov/libpthread-2.3.6.so) # ==3335== by 0x45FE49D: clone (in /lib/tls/i686/cmov/libc-2.3.6.so) { e_book_new VII Memcheck:Leak fun:calloc fun:g_malloc0 fun:ORBit_alloc_tcval fun:ORBit_small_allocbuf fun:ORBit_POA_create_object_T fun:PortableServer_POA_servant_to_reference fun:bonobo_object_constructor fun:g_object_newv fun:g_object_new_valist fun:g_object_new fun:e_data_book_new } # ==19340== 208 bytes in 1 blocks are possibly lost in loss record 90 of 168 # ==19340== at 0x4C233A2: realloc (vg_replace_malloc.c:429) # ==19340== by 0x64EBD88: g_realloc (in /usr/lib/libglib-2.0.so.0.1600.6) # ==19340== by 0x84F720E: ORBit_realloc_tcval (in /usr/lib/libORBit-2.so.0.1.0) # ==19340== by 0x84FAF33: ORBit_sequence_append (in /usr/lib/libORBit-2.so.0.1.0) # ==19340== by 0x82B89D0: bonobo_activation_init_activation_env (in /usr/lib/libbonobo-activation.so.4.0.0) # ==19340== by 0x82BC3F2: bonobo_activation_orb_init (in /usr/lib/libbonobo-activation.so.4.0.0) # ==19340== by 0x82BC76E: bonobo_activation_init (in /usr/lib/libbonobo-activation.so.4.0.0) # ==19340== by 0x80634C0: bonobo_init_full (in /usr/lib/libbonobo-2.so.0.0.0) # ==19340== by 0x4E4243D: e_book_new (e-book.c:3792) # ==19340== by 0x4E424CE: e_book_new_from_uri (e-book.c:3856) # ==19340== by 0x6B006C: EvolutionContactSource::open() (EvolutionContactSource.cpp:140) # ==19340== by 0x52A577: TestEvolutionSyncSource::beginSync(SyncMode) (client-test-app.cpp:68) # ==19340== by 0x5CF2DA: LocalTests::testIterateTwice() (ClientTest.cpp:610) # ==19340== by 0x60CCE1: CppUnit::TestCaller::runTest() (TestCaller.h:166) # ==19340== by 0x6795846: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19340== by 0x6787C43: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19340== by 0x6791758: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19340== by 0x679149B: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19340== by 0x679D23F: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19340== by 0x67954DC: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19340== by 0x6795E3B: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19340== by 0x6795D65: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19340== by 0x6795E3B: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19340== by 0x6795D65: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19340== by 0x679CFC9: CppUnit::TestResult::runTest(CppUnit::Test*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19340== by 0x679F5C1: CppUnit::TestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19340== by 0x67A25DA: CppUnit::TextTestRunner::run(std::string, bool, bool, bool) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19340== by 0x613976: main (client-test-main.cpp:255) { e_book_new VII Memcheck:Leak fun:realloc fun:g_realloc fun:ORBit_realloc_tcval fun:ORBit_sequence_append fun:bonobo_activation_init_activation_env fun:bonobo_activation_orb_init fun:bonobo_activation_init fun:bonobo_init_full fun:e_book_new } # ==19927== 42,760 bytes in 1,070 blocks are possibly lost in loss record 25 of 38 # ==19927== at 0x4C223DC: calloc (vg_replace_malloc.c:397) # ==19927== by 0x64EBE12: g_malloc0 (in /usr/lib/libglib-2.0.so.0.1600.6) # ==19927== by 0x84F70E7: ORBit_alloc_tcval (in /usr/lib/libORBit-2.so.0.1.0) # ==19927== by 0x8500805: (within /usr/lib/libORBit-2.so.0.1.0) # ==19927== by 0x84FE48A: IOP_generate_profiles (in /usr/lib/libORBit-2.so.0.1.0) # ==19927== by 0x84F563B: ORBit_marshal_object (in /usr/lib/libORBit-2.so.0.1.0) # ==19927== by 0x84FBBA7: ORBit_marshal_value (in /usr/lib/libORBit-2.so.0.1.0) # ==19927== by 0x84F2835: (within /usr/lib/libORBit-2.so.0.1.0) # ==19927== by 0x84F3CAC: ORBit_small_invoke_stub (in /usr/lib/libORBit-2.so.0.1.0) # ==19927== by 0x4E3B0DA: GNOME_Evolution_Addressbook_BookFactory_getBook (Evolution-DataServer-Addressbook-stubs.c:330) # ==19927== by 0x4E420BE: e_book_new (e-book.c:3340) # ==19927== by 0x4E424CE: e_book_new_from_uri (e-book.c:3856) # ==19927== by 0x6B006C: EvolutionContactSource::open() (EvolutionContactSource.cpp:140) # ==19927== by 0x52A577: TestEvolutionSyncSource::beginSync(SyncMode) (client-test-app.cpp:68) # ==19927== by 0x5D002D: LocalTests::deleteAll(CreateSource) (ClientTest.cpp:389) # ==19927== by 0x52FCD2: LocalTests::testLocalDeleteAll() (ClientTest.cpp:634) # ==19927== by 0x60CCE1: CppUnit::TestCaller::runTest() (TestCaller.h:166) # ==19927== by 0x6795846: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19927== by 0x6787C43: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19927== by 0x6791758: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19927== by 0x679149B: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19927== by 0x679D23F: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19927== by 0x67954DC: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19927== by 0x6795E3B: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19927== by 0x6795D65: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19927== by 0x6795E3B: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19927== by 0x6795D65: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19927== by 0x679CFC9: CppUnit::TestResult::runTest(CppUnit::Test*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19927== by 0x679F5C1: CppUnit::TestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19927== by 0x67A25DA: CppUnit::TextTestRunner::run(std::string, bool, bool, bool) (in /usr/lib/libcppunit-1.12.so.0.0.0) { e_book_new VIII Memcheck:Leak fun:calloc fun:g_malloc0 fun:ORBit_alloc_tcval obj:* fun:IOP_generate_profiles fun:ORBit_marshal_object fun:ORBit_marshal_value obj:* fun:ORBit_small_invoke_stub fun:GNOME_Evolution_Addressbook_BookFactory_getBook fun:e_book_new } # ==14222== Syscall param writev(vector[...]) points to uninitialised byte(s) # ==14222== at 0x4000792: (within /lib/ld-2.3.6.so) # ==14222== by 0x4852E5A: write_data_T (linc-connection.c:986) # ==14222== by 0x4854233: link_connection_writev (linc-connection.c:1181) # ==14222== by 0x4833DB6: giop_send_buffer_write (giop-send-buffer.c:464) # ==14222== by 0x483869A: orbit_small_marshal (orbit-small.c:366) # ==14222== by 0x4839B37: ORBit_small_invoke_stub (orbit-small.c:646) # ==14222== by 0x4839D7F: ORBit_small_invoke_stub_n (orbit-small.c:575) # ==14222== by 0x4846671: ORBit_c_stub_invoke (poa.c:2643) # ==14222== by 0x4809C5B: ConfigServer_ping (in /usr/lib/libgconf-2.so.4.1.0) # ==14222== by 0x47F10C1: gconf_activate_server (in /usr/lib/libgconf-2.so.4.1.0) # ==14222== by 0x47FD5D1: (within /usr/lib/libgconf-2.so.4.1.0) # ==14222== by 0x47FE33A: (within /usr/lib/libgconf-2.so.4.1.0) # ==14222== by 0x4801DEF: gconf_engine_get_default (in /usr/lib/libgconf-2.so.4.1.0) # ==14222== by 0x48067DB: gconf_client_get_default (in /usr/lib/libgconf-2.so.4.1.0) # ==14222== by 0x40934D1: e_book_get_addressbooks (e-book.c:3753) # ==14222== by 0x80E9FEC: EvolutionContactSource::open() (EvolutionContactSource.cpp:104) # ==14222== by 0x8054EEB: TestEvolutionSyncSource::beginSync() (in /tmp/runtests/head/tmp/build/src/client-test) # ==14222== by 0x80744E9: LocalTests::testIterateTwice() (in /tmp/runtests/head/tmp/build/src/client-test) # ==14222== by 0x80E1FBC: CppUnit::TestCaller::runTest() (in /tmp/runtests/head/tmp/build/src/client-test) # ==14222== by 0x4051C88: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==14222== Address 0x56AEBDA is 10 bytes inside a block of size 2,048 alloc'd # ==14222== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==14222== by 0x42DCA95: g_malloc (gmem.c:131) # ==14222== by 0x4833406: get_next_indirect (giop-send-buffer.c:312) # ==14222== by 0x483352C: giop_send_buffer_append_copy (giop-send-buffer.c:334) # ==14222== by 0x4833C30: giop_send_buffer_use_request (giop-send-buffer.c:108) # ==14222== by 0x483860C: orbit_small_marshal (orbit-small.c:324) # ==14222== by 0x4839B37: ORBit_small_invoke_stub (orbit-small.c:646) # ==14222== by 0x4839D7F: ORBit_small_invoke_stub_n (orbit-small.c:575) # ==14222== by 0x4846671: ORBit_c_stub_invoke (poa.c:2643) # ==14222== by 0x4809C5B: ConfigServer_ping (in /usr/lib/libgconf-2.so.4.1.0) # ==14222== by 0x47F10C1: gconf_activate_server (in /usr/lib/libgconf-2.so.4.1.0) # ==14222== by 0x47FD5D1: (within /usr/lib/libgconf-2.so.4.1.0) # ==14222== by 0x47FE33A: (within /usr/lib/libgconf-2.so.4.1.0) # ==14222== by 0x4801DEF: gconf_engine_get_default (in /usr/lib/libgconf-2.so.4.1.0) # ==14222== by 0x48067DB: gconf_client_get_default (in /usr/lib/libgconf-2.so.4.1.0) # ==14222== by 0x40934D1: e_book_get_addressbooks (e-book.c:3753) # ==14222== by 0x80E9FEC: EvolutionContactSource::open() (EvolutionContactSource.cpp:104) # ==14222== by 0x8054EEB: TestEvolutionSyncSource::beginSync() (in /tmp/runtests/head/tmp/build/src/client-test) # ==14222== by 0x80744E9: LocalTests::testIterateTwice() (in /tmp/runtests/head/tmp/build/src/client-test) # ==14222== by 0x80E1FBC: CppUnit::TestCaller::runTest() (in /tmp/runtests/head/tmp/build/src/client-test) { gconf_activate_server Memcheck:Param writev(vector[...]) obj:*ld-2.3.6.so fun:write_data_T fun:link_connection_writev fun:giop_send_buffer_write fun:orbit_small_marshal fun:ORBit_small_invoke_stub fun:ORBit_small_invoke_stub_n fun:ORBit_c_stub_invoke fun:ConfigServer_ping fun:gconf_activate_server } # ==10992== 13 bytes in 2 blocks are possibly lost in loss record 3 of 29 # ==10992== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==10992== by 0x4457A95: g_malloc (gmem.c:131) # ==10992== by 0x43A395D: ORBit_alloc_string (allocators.c:228) # ==10992== by 0x43A3618: CORBA_string_dup (corba-string.c:22) # ==10992== by 0x43A7973: ORBit_copy_value_core (corba-any.c:922) # ==10992== by 0x43A78B8: ORBit_copy_value_core (corba-any.c:886) # ==10992== by 0x43A7CEF: ORBit_sequence_append (corba-any.c:1303) # ==10992== by 0x4340945: bonobo_activation_init_activation_env (bonobo-activation-activate.c:778) # ==10992== by 0x4344893: bonobo_activation_orb_init (bonobo-activation-init.c:604) # ==10992== by 0x415CCD2: (within /usr/lib/libgnome-2.so.0.1600.0) # ==10992== by 0x4157722: gnome_program_postinit (in /usr/lib/libgnome-2.so.0.1600.0) # ==10992== by 0x4157B64: (within /usr/lib/libgnome-2.so.0.1600.0) { bonobo init Memcheck:Leak fun:malloc fun:g_malloc fun:ORBit_alloc_string fun:CORBA_string_dup fun:ORBit_copy_value_core fun:ORBit_copy_value_core fun:ORBit_sequence_append fun:bonobo_activation_init_activation_env } # ==10992== 1,372 bytes in 40 blocks are possibly lost in loss record 21 of 29 # ==10992== at 0x401C7EF: calloc (vg_replace_malloc.c:279) # ==10992== by 0x44579FD: g_malloc0 (gmem.c:150) # ==10992== by 0x4401FA8: type_node_any_new_W (gtype.c:342) # ==10992== by 0x4402164: type_node_fundamental_new_W (gtype.c:447) # ==10992== by 0x440271B: g_type_init_with_debug_flags (gtype.c:3417) # ==10992== by 0x4402881: g_type_init (gtype.c:3475) # ==10992== by 0x4157E3A: gnome_program_init (in /usr/lib/libgnome-2.so.0.1600.0) # ==10992== by 0x804B55D: main (server.c:367) { gnome program init Memcheck:Leak fun:calloc fun:g_malloc0 fun:type_node_any_new_W fun:type_node_fundamental_new_W fun:g_type_init_with_debug_flags fun:g_type_init fun:gnome_program_init } # ==10979== Conditional jump or move depends on uninitialised value(s) # ==10979== at 0x439D49B: CORBA_ORB_object_to_string (corba-orb.c:545) # ==10979== by 0x4341E96: bonobo_activation_registration_iterate (bonobo-activation-base-service.c:157) # ==10979== by 0x4341F7A: bonobo_activation_base_service_set (bonobo-activation-base-service.c:223) # ==10979== by 0x43426E8: bonobo_activation_internal_service_get_extended (bonobo-activation-base-service.c:427) # ==10979== by 0x43427C9: bonobo_activation_service_get (bonobo-activation-base-service.c:462) # ==10979== by 0x4344DA9: bonobo_activation_activation_context_get (bonobo-activation-init.c:302) # ==10979== by 0x4344DCE: bonobo_activation_object_directory_get (bonobo-activation-init.c:316) # ==10979== by 0x4342CA2: bonobo_activation_register_active_server_ext (bonobo-activation-register.c:287) # ==10979== by 0x4343112: bonobo_activation_register_active_server (bonobo-activation-register.c:222) # ==10979== by 0x4343207: bonobo_activation_active_server_register (bonobo-activation-register.c:397) # ==10979== by 0x4047E85: e_data_book_factory_activate (e-data-book-factory.c:434) # ==10979== by 0x804B1B9: setup_books (server.c:201) { setup books Memcheck:Cond fun:CORBA_ORB_object_to_string fun:bonobo_activation_registration_iterate fun:bonobo_activation_base_service_set fun:bonobo_activation_internal_service_get_extended fun:bonobo_activation_service_get fun:bonobo_activation_activation_context_get fun:bonobo_activation_object_directory_get fun:bonobo_activation_register_active_server_ext fun:bonobo_activation_register_active_server fun:bonobo_activation_active_server_register fun:e_data_book_factory_activate fun:setup_books } # ==12548== Conditional jump or move depends on uninitialised value(s) # ==12548== at 0x401E255: strlen (mc_replace_strmem.c:246) # ==12548== by 0x4573163: vfprintf (in /lib/tls/i686/cmov/libc-2.3.6.so) # ==12548== by 0x45782E1: fprintf (in /lib/tls/i686/cmov/libc-2.3.6.so) # ==12548== by 0x43419B1: rloc_file_register (bonobo-activation-base-service.c:669) # ==12548== by 0x4341E69: bonobo_activation_registration_iterate (bonobo-activation-base-service.c:172) # ==12548== by 0x4341F7A: bonobo_activation_base_service_set (bonobo-activation-base-service.c:223) # ==12548== by 0x43426E8: bonobo_activation_internal_service_get_extended (bonobo-activation-base-service.c:427) # ==12548== by 0x43427C9: bonobo_activation_service_get (bonobo-activation-base-service.c:462) # ==12548== by 0x4344DA9: bonobo_activation_activation_context_get (bonobo-activation-init.c:302) # ==12548== by 0x4344DCE: bonobo_activation_object_directory_get (bonobo-activation-init.c:316) # ==12548== by 0x4342CA2: bonobo_activation_register_active_server_ext (bonobo-activation-register.c:287) # ==12548== by 0x4343112: bonobo_activation_register_active_server (bonobo-activation-register.c:222) # ==12548== by 0x4343207: bonobo_activation_active_server_register (bonobo-activation-register.c:397) # ==12548== by 0x4047E85: e_data_book_factory_activate (e-data-book-factory.c:434) # ==12548== by 0x804B1B9: setup_books (server.c:201) # ==12548== by 0x804B594: main (server.c:381) { setup_books Memcheck:Cond fun:strlen fun:vfprintf fun:fprintf fun:rloc_file_register fun:bonobo_activation_registration_iterate fun:bonobo_activation_base_service_set fun:bonobo_activation_internal_service_get_extended fun:bonobo_activation_service_get fun:bonobo_activation_activation_context_get fun:bonobo_activation_object_directory_get fun:bonobo_activation_register_active_server_ext fun:bonobo_activation_register_active_server fun:bonobo_activation_active_server_register fun:e_data_book_factory_activate fun:setup_books } # ==17723== 152 (88 direct, 64 indirect) bytes in 1 blocks are definitely lost in loss record 11 of 24 # ==17723== at 0x4C21C16: malloc (vg_replace_malloc.c:149) # ==17723== by 0x755E42A: g_malloc (in /usr/lib/libglib-2.0.so.0.1400.1) # ==17723== by 0x7571917: g_slice_alloc (in /usr/lib/libglib-2.0.so.0.1400.1) # ==17723== by 0x7572085: g_slice_alloc0 (in /usr/lib/libglib-2.0.so.0.1400.1) # ==17723== by 0x730D9EE: g_type_create_instance (in /usr/lib/libgobject-2.0.so.0.1400.1) # ==17723== by 0x72F522C: (within /usr/lib/libgobject-2.0.so.0.1400.1) # ==17723== by 0x66046C4: (within /usr/lib/libbonobo-2.so.0.0.0) # ==17723== by 0x72F3663: g_object_newv (in /usr/lib/libgobject-2.0.so.0.1400.1) # ==17723== by 0x72F40AB: g_object_new_valist (in /usr/lib/libgobject-2.0.so.0.1400.1) # ==17723== by 0x72F42E0: g_object_new (in /usr/lib/libgobject-2.0.so.0.1400.1) # ==17723== by 0x65FA702: bonobo_moniker_context_new (in /usr/lib/libbonobo-2.so.0.0.0) # ==17723== by 0x65FB988: bonobo_context_init (in /usr/lib/libbonobo-2.so.0.0.0) # ==17723== by 0x65FF436: bonobo_init_full (in /usr/lib/libbonobo-2.so.0.0.0) # ==17723== by 0x53568A9: e_book_new (e-book.c:3786) # ==17723== by 0x4B2686: EvolutionContactSource::open() (EvolutionContactSource.cpp:124) # ==17723== by 0x41236C: TestEvolutionSyncSource::beginSync() (client-test-app.cpp:146) # ==17723== by 0x491A02: LocalTests::insert(CreateSource, char const*) (ClientTest.cpp:202) # ==17723== by 0x43D708: LocalTests::testLocalDeleteAll() (ClientTest.cpp:513) # ==17723== by 0x473F74: LocalTests::testImport() (ClientTest.cpp:662) # ==17723== by 0x4A321B: CppUnit::TestCaller::runTest() (TestCaller.h:166) # ==17723== by 0x4E51166: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17723== by 0x4E435C3: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17723== by 0x4E4D228: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17723== by 0x4E4CF77: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17723== by 0x4E58C5F: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17723== by 0x4E50E3F: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17723== by 0x4E58A39: CppUnit::TestResult::runTest(CppUnit::Test*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17723== by 0x4E5B081: CppUnit::TestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17723== by 0x4E5E04A: CppUnit::TextTestRunner::run(std::string, bool, bool, bool) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17723== by 0x4A6644: main (client-test-main.cpp:229) { e_book_new Memcheck:Leak fun:malloc fun:g_malloc fun:g_slice_alloc fun:g_slice_alloc0 fun:g_type_create_instance obj:*libgobject-2.0.so* obj:*libbonobo-2.so* fun:g_object_newv fun:g_object_new_valist fun:g_object_new fun:bonobo_moniker_context_new fun:bonobo_context_init fun:bonobo_init_full fun:e_book_new } # ==17763== 11,376 bytes in 271 blocks are possibly lost in loss record 20 of 23 # ==17763== at 0x4C20F3F: calloc (vg_replace_malloc.c:279) # ==17763== by 0x755E3B1: g_malloc0 (in /usr/lib/libglib-2.0.so.0.1400.1) # ==17763== by 0x6A97856: ORBit_alloc_by_tc (in /usr/lib/libORBit-2.so.0.1.0) # ==17763== by 0x6AA10C6: (within /usr/lib/libORBit-2.so.0.1.0) # ==17763== by 0x6A9ED64: IOP_generate_profiles (in /usr/lib/libORBit-2.so.0.1.0) # ==17763== by 0x6A95DE0: ORBit_marshal_object (in /usr/lib/libORBit-2.so.0.1.0) # ==17763== by 0x6A9BCF7: ORBit_marshal_value (in /usr/lib/libORBit-2.so.0.1.0) # ==17763== by 0x6A92F93: (within /usr/lib/libORBit-2.so.0.1.0) # ==17763== by 0x6A9442C: ORBit_small_invoke_stub (in /usr/lib/libORBit-2.so.0.1.0) # ==17763== by 0x534F39A: GNOME_Evolution_Addressbook_BookFactory_getBook (Evolution-DataServer-Addressbook-stubs.c:330) # ==17763== by 0x53566F6: e_book_new (e-book.c:3336) # ==17763== by 0x4B2686: EvolutionContactSource::open() (EvolutionContactSource.cpp:124) # ==17763== by 0x41236C: TestEvolutionSyncSource::beginSync() (client-test-app.cpp:146) # ==17763== by 0x485481: LocalTests::testChanges() (ClientTest.cpp:619) # ==17763== by 0x4A321B: CppUnit::TestCaller::runTest() (TestCaller.h:166) # ==17763== by 0x4E51166: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17763== by 0x4E435C3: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17763== by 0x4E4D228: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17763== by 0x4E4CF77: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17763== by 0x4E58C5F: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17763== by 0x4E50E3F: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17763== by 0x4E58A39: CppUnit::TestResult::runTest(CppUnit::Test*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17763== by 0x4E5B081: CppUnit::TestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17763== by 0x4E5E04A: CppUnit::TextTestRunner::run(std::string, bool, bool, bool) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17763== by 0x4A6644: main (client-test-main.cpp:229) { e_book_new II Memcheck:Leak fun:calloc fun:g_malloc0 fun:ORBit_alloc_by_tc obj:*libORBit-2.so* fun:IOP_generate_profiles fun:ORBit_marshal_object fun:ORBit_marshal_value obj:*libORBit-2.so* fun:ORBit_small_invoke_stub fun:GNOME_Evolution_Addressbook_BookFactory_getBook fun:e_book_new } # ==17771== 11,376 bytes in 271 blocks are possibly lost in loss record 20 of 23 # ==17771== at 0x4C20F3F: calloc (vg_replace_malloc.c:279) # ==17771== by 0x755E3B1: g_malloc0 (in /usr/lib/libglib-2.0.so.0.1400.1) # ==17771== by 0x6A97856: ORBit_alloc_by_tc (in /usr/lib/libORBit-2.so.0.1.0) # ==17771== by 0x6A9DA7A: (within /usr/lib/libORBit-2.so.0.1.0) # ==17771== by 0x6A9E704: ORBit_demarshal_IOR (in /usr/lib/libORBit-2.so.0.1.0) # ==17771== by 0x6A961F4: ORBit_demarshal_object (in /usr/lib/libORBit-2.so.0.1.0) # ==17771== by 0x6A9C213: ORBit_demarshal_value (in /usr/lib/libORBit-2.so.0.1.0) # ==17771== by 0x6A9366E: (within /usr/lib/libORBit-2.so.0.1.0) # ==17771== by 0x6A94482: ORBit_small_invoke_stub (in /usr/lib/libORBit-2.so.0.1.0) # ==17771== by 0x534F39A: GNOME_Evolution_Addressbook_BookFactory_getBook (Evolution-DataServer-Addressbook-stubs.c:330) # ==17771== by 0x53566F6: e_book_new (e-book.c:3336) # ==17771== by 0x4B2686: EvolutionContactSource::open() (EvolutionContactSource.cpp:124) # ==17771== by 0x41236C: TestEvolutionSyncSource::beginSync() (client-test-app.cpp:146) # ==17771== by 0x485481: LocalTests::testChanges() (ClientTest.cpp:619) # ==17771== by 0x4A321B: CppUnit::TestCaller::runTest() (TestCaller.h:166) # ==17771== by 0x4E51166: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17771== by 0x4E435C3: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17771== by 0x4E4D228: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17771== by 0x4E4CF77: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17771== by 0x4E58C5F: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17771== by 0x4E50E3F: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17771== by 0x4E58A39: CppUnit::TestResult::runTest(CppUnit::Test*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17771== by 0x4E5B081: CppUnit::TestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17771== by 0x4E5E04A: CppUnit::TextTestRunner::run(std::string, bool, bool, bool) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17771== by 0x4A6644: main (client-test-main.cpp:229) { e_book_new III Memcheck:Leak fun:calloc fun:g_malloc0 fun:* obj:*libORBit-2.so* fun:ORBit_demarshal_IOR fun:ORBit_demarshal_object fun:ORBit_demarshal_value obj:*libORBit-2.so* fun:ORBit_small_invoke_stub fun:GNOME_Evolution_Addressbook_BookFactory_getBook fun:e_book_new } # ==17790== 22,280 bytes in 553 blocks are possibly lost in loss record 19 of 25 # ==17790== at 0x4C20F3F: calloc (vg_replace_malloc.c:279) # ==17790== by 0x755E3B1: g_malloc0 (in /usr/lib/libglib-2.0.so.0.1400.1) # ==17790== by 0x6A978D7: ORBit_alloc_tcval (in /usr/lib/libORBit-2.so.0.1.0) # ==17790== by 0x6AA141D: (within /usr/lib/libORBit-2.so.0.1.0) # ==17790== by 0x6AA1664: PortableServer_POA_servant_to_reference (in /usr/lib/libORBit-2.so.0.1.0) # ==17790== by 0x6604826: (within /usr/lib/libbonobo-2.so.0.0.0) # ==17790== by 0x72F3663: g_object_newv (in /usr/lib/libgobject-2.0.so.0.1400.1) # ==17790== by 0x72F413D: g_object_new_valist (in /usr/lib/libgobject-2.0.so.0.1400.1) # ==17790== by 0x72F42E0: g_object_new (in /usr/lib/libgobject-2.0.so.0.1400.1) # ==17790== by 0x5356627: e_book_new (e-book.c:3309) # ==17790== by 0x4B2686: EvolutionContactSource::open() (EvolutionContactSource.cpp:124) # ==17790== by 0x41236C: TestEvolutionSyncSource::beginSync() (client-test-app.cpp:146) # ==17790== by 0x493FC9: LocalTests::insert(CreateSource, char const*) (ClientTest.cpp:221) # ==17790== by 0x43D708: LocalTests::testLocalDeleteAll() (ClientTest.cpp:513) # ==17790== by 0x4760A1: LocalTests::testChanges() (ClientTest.cpp:539) # ==17790== by 0x4A321B: CppUnit::TestCaller::runTest() (TestCaller.h:166) # ==17790== by 0x4E51166: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17790== by 0x4E435C3: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17790== by 0x4E4D228: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17790== by 0x4E4CF77: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17790== by 0x4E58C5F: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17790== by 0x4E50E3F: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17790== by 0x4E5173B: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17790== by 0x4E51665: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17790== by 0x4E58A39: CppUnit::TestResult::runTest(CppUnit::Test*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17790== by 0x4E5B081: CppUnit::TestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17790== by 0x4E5E04A: CppUnit::TextTestRunner::run(std::string, bool, bool, bool) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==17790== by 0x4A6644: main (client-test-main.cpp:229) { e_book_new IV Memcheck:Leak fun:calloc fun:g_malloc0 fun:* obj:*libORBit-2.so* fun:PortableServer_POA_servant_to_reference obj:*libbonobo-2.so* fun:g_object_newv fun:g_object_new_valist fun:g_object_new fun:e_book_new } # ==12548== Syscall param write(buf) points to uninitialised byte(s) # ==12548== at 0x4000792: (within /lib/ld-2.3.6.so) # ==12548== by 0x4593384: (within /lib/tls/i686/cmov/libc-2.3.6.so) # ==12548== by 0x459361E: _IO_do_write (in /lib/tls/i686/cmov/libc-2.3.6.so) # ==12548== by 0x4594A04: _IO_file_close_it (in /lib/tls/i686/cmov/libc-2.3.6.so) # ==12548== by 0x4588F1B: fclose (in /lib/tls/i686/cmov/libc-2.3.6.so) # ==12548== by 0x43419B9: rloc_file_register (bonobo-activation-base-service.c:670) # ==12548== by 0x4341E69: bonobo_activation_registration_iterate (bonobo-activation-base-service.c:172) # ==12548== by 0x4341F7A: bonobo_activation_base_service_set (bonobo-activation-base-service.c:223) # ==12548== by 0x43426E8: bonobo_activation_internal_service_get_extended (bonobo-activation-base-service.c:427) # ==12548== by 0x43427C9: bonobo_activation_service_get (bonobo-activation-base-service.c:462) # ==12548== by 0x4344DA9: bonobo_activation_activation_context_get (bonobo-activation-init.c:302) # ==12548== by 0x4344DCE: bonobo_activation_object_directory_get (bonobo-activation-init.c:316) # ==12548== by 0x4342CA2: bonobo_activation_register_active_server_ext (bonobo-activation-register.c:287) # ==12548== by 0x4343112: bonobo_activation_register_active_server (bonobo-activation-register.c:222) # ==12548== by 0x4343207: bonobo_activation_active_server_register (bonobo-activation-register.c:397) # ==12548== by 0x4047E85: e_data_book_factory_activate (e-data-book-factory.c:434) # ==12548== by 0x804B1B9: setup_books (server.c:201) # ==12548== by 0x804B594: main (server.c:381) # ==12548== Address 0x414913A is not stack'd, malloc'd or (recently) free'd { setup_books write Memcheck:Param write(buf) obj:*ld-2.3.6.so obj:*libc-2.3.6.so fun:_IO_do_write fun:_IO_file_close_it fun:fclose fun:rloc_file_register fun:bonobo_activation_registration_iterate fun:bonobo_activation_base_service_set fun:bonobo_activation_internal_service_get_extended fun:bonobo_activation_service_get fun:bonobo_activation_activation_context_get fun:bonobo_activation_object_directory_get fun:bonobo_activation_register_active_server_ext fun:bonobo_activation_register_active_server fun:bonobo_activation_active_server_register fun:e_data_book_factory_activate fun:setup_books } # ==14222== 21,532 bytes in 564 blocks are possibly lost in loss record 24 of 39 # ==14222== at 0x401C7EF: calloc (vg_replace_malloc.c:279) # ==14222== by 0x42DC9FD: g_malloc0 (gmem.c:150) # ==14222== by 0x483D675: ORBit_alloc_by_tc (allocators.c:373) # ==14222== by 0x48389AC: ORBit_small_alloc (orbit-small.c:44) # ==14222== by 0x48482FD: ORBit_POA_create_object_T (poa.c:361) # ==14222== by 0x4848553: PortableServer_POA_servant_to_reference (poa.c:2330) # ==14222== by 0x4740416: bonobo_object_constructor (bonobo-object.c:791) # ==14222== by 0x4274308: g_object_newv (gobject.c:937) # ==14222== by 0x4274F8E: g_object_new_valist (gobject.c:1027) # ==14222== by 0x427509F: g_object_new (gobject.c:795) # ==14222== by 0x40863A8: e_book_listener_new (e-book-listener.c:412) # ==14222== by 0x4092196: fetch_corba_book (e-book.c:3305) # ==14222== by 0x4093766: e_book_new (e-book.c:3816) # ==14222== by 0x4093932: e_book_new_from_uri (e-book.c:3848) # ==14222== by 0x80EA5FA: EvolutionContactSource::open() (EvolutionContactSource.cpp:119) # ==14222== by 0x8054EEB: TestEvolutionSyncSource::beginSync() (in /tmp/runtests/head/tmp/build/src/client-test) # ==14222== by 0x80B0A45: LocalTests::testChanges() (in /tmp/runtests/head/tmp/build/src/client-test) # ==14222== by 0x80E1FBC: CppUnit::TestCaller::runTest() (in /tmp/runtests/head/tmp/build/src/client-test) # ==14222== by 0x4051C88: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==14222== by 0x40433ED: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) { fetch_corba_book Memcheck:Leak fun:calloc fun:g_malloc0 fun:ORBit_alloc_by_tc fun:ORBit_small_alloc fun:ORBit_POA_create_object_T fun:PortableServer_POA_servant_to_reference fun:bonobo_object_constructor fun:g_object_newv fun:g_object_new_valist fun:g_object_new fun:e_book_listener_new fun:fetch_corba_book } # ==3045== 5,012 bytes in 139 blocks are possibly lost in loss record 42 of 61 # ==3045== at 0x401C7EF: calloc (vg_replace_malloc.c:279) # ==3045== by 0x42DC9FD: g_malloc0 (gmem.c:150) # ==3045== by 0x483D6EC: ORBit_alloc_tcval (allocators.c:287) # ==3045== by 0x4839DB8: ORBit_small_allocbuf (orbit-small.c:51) # ==3045== by 0x484CB02: ORBit_adaptor_setup (orbit-adaptor.c:130) # ==3045== by 0x484954D: ORBit_POA_new (poa.c:809) # ==3045== by 0x48498DD: ORBit_POA_new_from (poa.c:838) # ==3045== by 0x473A526: bonobo_poa_new_from (bonobo-main.c:462) # ==3045== by 0x473A64A: bonobo_poa_get_threadedv (bonobo-main.c:400) # ==3045== by 0x473A7C6: bonobo_poa_get_threaded (bonobo-main.c:442) # ==3045== by 0x40F3977: e_cal_listener_new (e-cal-listener.c:1032) # ==3045== by 0x40D8A63: e_cal_init (e-cal.c:1056) # ==3045== by 0x428F065: g_type_create_instance (gtype.c:1569) # ==3045== by 0x4273B21: g_object_constructor (gobject.c:1046) # ==3045== by 0x4274308: g_object_newv (gobject.c:937) # ==3045== by 0x4274EE6: g_object_new_valist (gobject.c:986) # ==3045== by 0x427509F: g_object_new (gobject.c:795) # ==3045== by 0x40D9DC1: e_cal_new (e-cal.c:1411) # ==3045== by 0x40D9EB4: e_cal_new_from_uri (e-cal.c:1448) # ==3045== by 0x80ED1C3: EvolutionCalendarSource::open() (EvolutionCalendarSource.cpp:127) { e_cal_init Memcheck:Leak fun:calloc fun:g_malloc0 fun:ORBit_alloc_tcval fun:ORBit_small_allocbuf fun:ORBit_adaptor_setup fun:ORBit_POA_new fun:ORBit_POA_new_from fun:bonobo_poa_new_from fun:bonobo_poa_get_threadedv fun:bonobo_poa_get_threaded fun:e_cal_listener_new fun:e_cal_init } # ==3659== 2,804 bytes in 56 blocks are possibly lost in loss record 41 of 63 # ==3659== at 0x401C7EF: calloc (vg_replace_malloc.c:279) # ==3659== by 0x42DC9FD: g_malloc0 (gmem.c:150) # ==3659== by 0x42C7CDA: g_hash_table_new_full (ghash.c:358) # ==3659== by 0x42C7D23: g_hash_table_new (ghash.c:318) # ==3659== by 0x484952D: ORBit_POA_new (poa.c:807) # ==3659== by 0x48498DD: ORBit_POA_new_from (poa.c:838) # ==3659== by 0x473A526: bonobo_poa_new_from (bonobo-main.c:462) # ==3659== by 0x473A64A: bonobo_poa_get_threadedv (bonobo-main.c:400) # ==3659== by 0x473A7C6: bonobo_poa_get_threaded (bonobo-main.c:442) # ==3659== by 0x40F3977: e_cal_listener_new (e-cal-listener.c:1032) # ==3659== by 0x40D8A63: e_cal_init (e-cal.c:1056) # ==3659== by 0x428F065: g_type_create_instance (gtype.c:1569) # ==3659== by 0x4273B21: g_object_constructor (gobject.c:1046) # ==3659== by 0x4274308: g_object_newv (gobject.c:937) # ==3659== by 0x4274EE6: g_object_new_valist (gobject.c:986) # ==3659== by 0x427509F: g_object_new (gobject.c:795) # ==3659== by 0x40D9DC1: e_cal_new (e-cal.c:1411) # ==3659== by 0x40D9EB4: e_cal_new_from_uri (e-cal.c:1448) # ==3659== by 0x80ED1C3: EvolutionCalendarSource::open() (EvolutionCalendarSource.cpp:127) # ==3659== by 0x805676F: TestEvolutionSyncSource::beginSync() (in /tmp/runtests/head/tmp/build/src/client-test) { e_cal_init Memcheck:Leak fun:calloc fun:g_malloc0 fun:g_hash_table_new_full fun:g_hash_table_new fun:ORBit_POA_new fun:ORBit_POA_new_from fun:bonobo_poa_new_from fun:bonobo_poa_get_threadedv fun:bonobo_poa_get_threaded fun:e_cal_listener_new fun:e_cal_init } # ==24572== 1,436 bytes in 44 blocks are possibly lost in loss record 21 of 37 # ==24572== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==24572== by 0x4292A1C: g_malloc (gmem.c:131) # ==24572== by 0x481695D: ORBit_alloc_string (allocators.c:228) # ==24572== by 0x4816618: CORBA_string_dup (corba-string.c:22) # ==24572== by 0x4759691: Bonobo_ServerInfo_copy (bonobo-activation-server-info.c:171) # ==24572== by 0x475973F: Bonobo_ServerInfoList_duplicate (bonobo-activation-server-info.c:212) # ==24572== by 0x4757701: bonobo_activation_query (bonobo-activation-activate.c:238) # ==24572== by 0x404A90C: get_factories (e-cal.c:1011) # ==24572== by 0x404B8CF: fetch_corba_cal (e-cal.c:1277) # ==24572== by 0x404BDFD: e_cal_new (e-cal.c:1413) # ==24572== by 0x404BED4: e_cal_new_from_uri (e-cal.c:1448) # ==24572== by 0x81B5352: EvolutionCalendarSource::open() (EvolutionCalendarSource.cpp:139) # ==24572== by 0x8057060: TestEvolutionSyncSource::beginSync() (in /tmp/runtests/head/tmp/build/src/client-test) # ==24572== by 0x80E9A9B: LocalTests::testIterateTwice() (in /tmp/runtests/head/tmp/build/src/client-test) # ==24572== by 0x81AF4B4: CppUnit::TestCaller::runTest() (in /tmp/runtests/head/tmp/build/src/client-test) # ==24572== by 0x4375C88: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==24572== by 0x43673ED: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==24572== by 0x43718D2: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==24572== by 0x4371613: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==24572== by 0x437E390: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==24572== by 0x4375637: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==24572== by 0x43762FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==24572== by 0x4376229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==24572== by 0x43762FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==24572== by 0x4376229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==24572== by 0x437E009: CppUnit::TestResult::runTest(CppUnit::Test*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==24572== by 0x438072F: CppUnit::TestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==24572== by 0x4383F2A: CppUnit::TextTestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==24572== by 0x4383FA1: CppUnit::TextTestRunner::run(std::string, bool, bool, bool) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==24572== by 0x81B2E50: main (in /tmp/runtests/head/tmp/build/src/client-test) { e_cal_new Memcheck:Leak fun:malloc fun:g_malloc fun:ORBit_alloc_string fun:CORBA_string_dup fun:Bonobo_ServerInfo_copy fun:Bonobo_ServerInfoList_duplicate fun:bonobo_activation_query fun:get_factories fun:fetch_corba_cal fun:e_cal_new } # ==8850== 1,557 bytes in 48 blocks are possibly lost in loss record 21 of 37 # ==8850== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==8850== by 0x4292A1C: g_malloc (gmem.c:131) # ==8850== by 0x481695D: ORBit_alloc_string (allocators.c:228) # ==8850== by 0x4816618: CORBA_string_dup (corba-string.c:22) # ==8850== by 0x475956C: Bonobo_ActivationProperty_copy (bonobo-activation-server-info.c:141) # ==8850== by 0x4759601: CORBA_sequence_Bonobo_ActivationProperty_copy (bonobo-activation-server-info.c:157) # ==8850== by 0x47596A3: Bonobo_ServerInfo_copy (bonobo-activation-server-info.c:173) # ==8850== by 0x475973F: Bonobo_ServerInfoList_duplicate (bonobo-activation-server-info.c:212) # ==8850== by 0x4757701: bonobo_activation_query (bonobo-activation-activate.c:238) # ==8850== by 0x404A90C: get_factories (e-cal.c:1011) # ==8850== by 0x404B8CF: fetch_corba_cal (e-cal.c:1277) # ==8850== by 0x404BDFD: e_cal_new (e-cal.c:1413) # ==8850== by 0x404BED4: e_cal_new_from_uri (e-cal.c:1448) # ==8850== by 0x81B5352: EvolutionCalendarSource::open() (EvolutionCalendarSource.cpp:139) # ==8850== by 0x8057060: TestEvolutionSyncSource::beginSync() (in /tmp/runtests/head-evolution-svn-minimal/build/src/client-test) # ==8850== by 0x80E9A9B: LocalTests::testIterateTwice() (in /tmp/runtests/head-evolution-svn-minimal/build/src/client-test) # ==8850== by 0x81AF4B4: CppUnit::TestCaller::runTest() (in /tmp/runtests/head-evolution-svn-minimal/build/src/client-test) # ==8850== by 0x4375C88: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==8850== by 0x43673ED: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==8850== by 0x43718D2: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==8850== by 0x4371613: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==8850== by 0x437E390: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==8850== by 0x4375637: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==8850== by 0x43762FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==8850== by 0x4376229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==8850== by 0x43762FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==8850== by 0x4376229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==8850== by 0x437E009: CppUnit::TestResult::runTest(CppUnit::Test*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==8850== by 0x438072F: CppUnit::TestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==8850== by 0x4383F2A: CppUnit::TextTestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) { e_cal_new II Memcheck:Leak fun:malloc fun:g_malloc fun:ORBit_alloc_string fun:CORBA_string_dup fun:Bonobo_ActivationProperty_copy fun:CORBA_sequence_Bonobo_ActivationProperty_copy fun:Bonobo_ServerInfo_copy fun:Bonobo_ServerInfoList_duplicate fun:bonobo_activation_query fun:get_factories fun:fetch_corba_cal fun:e_cal_new } # ==8696== 2,940 (2,868 direct, 72 indirect) bytes in 18 blocks are definitely lost in loss record 29 of 36 # ==8696== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==8696== by 0x4293A1C: g_malloc (gmem.c:131) # ==8696== by 0x4715F89: bonobo_object_instance_init (bonobo-object.c:958) # ==8696== by 0x42458FB: g_type_create_instance (gtype.c:1567) # ==8696== by 0x422C571: g_object_constructor (gobject.c:1046) # ==8696== by 0x47162B0: bonobo_object_constructor (bonobo-object.c:820) # ==8696== by 0x422A7C7: g_object_newv (gobject.c:937) # ==8696== by 0x422B366: g_object_new_valist (gobject.c:986) # ==8696== by 0x422B51F: g_object_new (gobject.c:795) # ==8696== by 0x470B526: bonobo_moniker_context_new (bonobo-moniker-context.c:71) # ==8696== by 0x470CAF6: bonobo_context_init (bonobo-context.c:91) # ==8696== by 0x4710A16: bonobo_init_full (bonobo-main.c:234) # ==8696== by 0x4710B15: bonobo_init (bonobo-main.c:256) # ==8696== by 0x404BCA7: e_cal_activate (e-cal.c:1324) # ==8696== by 0x404BFDC: e_cal_new (e-cal.c:1410) # ==8696== by 0x81B7355: EvolutionCalendarSource::open() (EvolutionCalendarSource.cpp:145) # ==8696== by 0x80CCE68: EvolutionCalendarTest::testOpenDefaultCalendar() (in /tmp/runtests/head-evolution-svn-minimal/build/src/client-test) # ==8696== by 0x80C5DBC: CppUnit::TestCaller::runTest() (in /tmp/runtests/head-evolution-svn-minimal/build/src/client-test) # ==8696== by 0x4376C88: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==8696== by 0x43683ED: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==8696== by 0x43728D2: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==8696== by 0x4372613: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==8696== by 0x437F390: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==8696== by 0x4376637: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==8696== by 0x43772FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==8696== by 0x4377229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==8696== by 0x43772FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==8696== by 0x4377229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==8696== by 0x437F009: CppUnit::TestResult::runTest(CppUnit::Test*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==8696== by 0x438172F: CppUnit::TestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) { e_cal_new III Memcheck:Leak fun:malloc fun:g_malloc fun:bonobo_object_instance_init fun:g_type_create_instance fun:g_object_constructor fun:bonobo_object_constructor fun:g_object_newv fun:g_object_new_valist fun:g_object_new fun:bonobo_moniker_context_new fun:bonobo_context_init fun:bonobo_init_full fun:bonobo_init fun:e_cal_activate fun:e_cal_new } # ==5228== 1,456 (496 direct, 960 indirect) bytes in 4 blocks are definitely lost in loss record 25 of 36 # ==5228== at 0x401C7EF: calloc (vg_replace_malloc.c:279) # ==5228== by 0x4293994: g_malloc0 (gmem.c:151) # ==5228== by 0x4823428: ORBit_POA_new (poa.c:786) # ==5228== by 0x48238DD: ORBit_POA_new_from (poa.c:838) # ==5228== by 0x4710526: bonobo_poa_new_from (bonobo-main.c:462) # ==5228== by 0x471064A: bonobo_poa_get_threadedv (bonobo-main.c:400) # ==5228== by 0x47107C6: bonobo_poa_get_threaded (bonobo-main.c:442) # ==5228== by 0x4065D47: e_cal_listener_new (e-cal-listener.c:1032) # ==5228== by 0x404AC93: e_cal_init (e-cal.c:1057) # ==5228== by 0x4245AD5: g_type_create_instance (gtype.c:1575) # ==5228== by 0x422C571: g_object_constructor (gobject.c:1046) # ==5228== by 0x422A7C7: g_object_newv (gobject.c:937) # ==5228== by 0x422B366: g_object_new_valist (gobject.c:986) # ==5228== by 0x422B51F: g_object_new (gobject.c:795) # ==5228== by 0x404BFF1: e_cal_new (e-cal.c:1412) # ==5228== by 0x81B58F5: EvolutionCalendarSource::open() (EvolutionCalendarSource.cpp:145) # ==5228== by 0x80CB1FC: EvolutionCalendarTest::testOpenDefaultMemo() (in /tmp/runtests/head-evolution-svn-minimal/build/src/client-test) # ==5228== by 0x80C4FC0: CppUnit::TestCaller::runTest() (in /tmp/runtests/head-evolution-svn-minimal/build/src/client-test) # ==5228== by 0x4376C88: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x43683ED: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x43728D2: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x4372613: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x437F390: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x4376637: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x43772FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x4377229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x43772FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x4377229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x437F009: CppUnit::TestResult::runTest(CppUnit::Test*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x438172F: CppUnit::TestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) { e_cal_new IV Memcheck:Leak ... fun:g_object_* ... fun:e_cal_new* } { e_cal_new V Memcheck:Leak ... fun:g_signal* ... fun:e_cal_new* } { e_book_new + g_object Memcheck:Leak ... fun:g_object_* ... fun:e_book_new* } { e_book_new + g_signal Memcheck:Leak ... fun:g_signal_* ... fun:e_book_new* } # ==31707== 805 bytes in 13 blocks are definitely lost in loss record 2,572 of 2,697 # ==31707== at 0x4C27673: malloc (vg_replace_malloc.c:263) # ==31707== by 0x8B83C02: g_malloc (in /lib/x86_64-linux-gnu/libglib-2.0.so.0.3000.2) # ==31707== by 0x8B9A3BD: g_strdup (in /lib/x86_64-linux-gnu/libglib-2.0.so.0.3000.2) # ==31707== by 0x5DCBEF1: e_gdbus_proxy_async_method_done (e-gdbus-templates.c:1026) # ==31707== by 0x5DCC011: e_gdbus_proxy_async_method_done_string (e-gdbus-templates.c:1067) # ==31707== by 0x5DB5DFF: e_gdbus_marshallers_VOID__UINT_BOXED_STRING (e-gdbus-marshallers.c:364) # ==31707== by 0x84E8803: g_closure_invoke (in /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.3000.2) # ==31707== by 0x84FA789: ??? (in /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.3000.2) # ==31707== by 0x8503E10: g_signal_emit_valist (in /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.3000.2) # ==31707== by 0x8503FB1: g_signal_emit (in /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.3000.2) # ==31707== by 0x5DCAD84: e_gdbus_proxy_emit_signal (e-gdbus-templates.c:613) # ==31707== by 0x595EB10: g_signal (e-gdbus-cal.c:1590) # ==31707== by 0x1115E7BB: ffi_call_unix64 (in /usr/lib/x86_64-linux-gnu/libffi.so.5.0.10) # ==31707== by 0x1115E236: ffi_call (in /usr/lib/x86_64-linux-gnu/libffi.so.5.0.10) # ==31707== by 0x84E8CC6: g_cclosure_marshal_generic (in /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.3000.2) # ==31707== by 0x84E8803: g_closure_invoke (in /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.3000.2) # ==31707== by 0x84FA5BE: ??? (in /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.3000.2) # ==31707== by 0x8503E10: g_signal_emit_valist (in /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.3000.2) # ==31707== by 0x8503FB1: g_signal_emit (in /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.3000.2) # ==31707== by 0x778A0FE: ??? (in /usr/lib/x86_64-linux-gnu/libgio-2.0.so.0.3000.2) # ==31707== by 0x77789AD: ??? (in /usr/lib/x86_64-linux-gnu/libgio-2.0.so.0.3000.2) # ==31707== by 0x8B7D0CE: g_main_context_dispatch (in /lib/x86_64-linux-gnu/libglib-2.0.so.0.3000.2) # ==31707== by 0x8B7D8C7: ??? (in /lib/x86_64-linux-gnu/libglib-2.0.so.0.3000.2) # ==31707== by 0x8B7DA98: g_main_context_iteration (in /lib/x86_64-linux-gnu/libglib-2.0.so.0.3000.2) # ==31707== by 0x5DCD12E: e_gdbus_proxy_call_sync (e-gdbus-templates.c:1460) # ==31707== by 0x5DCDB2B: e_gdbus_proxy_call_sync_string__string (e-gdbus-templates.c:1626) # ==31707== by 0x595C44F: e_gdbus_cal_call_create_object_sync (e-gdbus-cal.c:575) # ==31707== by 0x592CAEE: e_cal_create_object (e-cal.c:3933) # ==31707== by 0x797F17: SyncEvo::EvolutionCalendarSource::insertItem(std::string const&, std::string const&, bool) (EvolutionCalendarSource.cpp:446) # ==31707== by 0x9102DF: SyncEvo::TrackingSyncSource::insertItemRaw(std::string const&, std::string const&) (TrackingSyncSource.cpp:151) # ==31707== { e_cal_create_object leak Memcheck:Leak fun:malloc fun:g_malloc fun:g_strdup ... fun:e_cal_create_object } # ==18590== 49 bytes in 1 blocks are definitely lost in loss record 481 of 799 # ==18590== at 0x4C244E8: malloc (vg_replace_malloc.c:236) # ==18590== by 0x6432504: g_malloc (in /lib/libglib-2.0.so.0.2400.1) # ==18590== by 0x6449D7D: g_strdup (in /lib/libglib-2.0.so.0.2400.1) # ==18590== by 0x5FAB052: g_boxed_copy (in /usr/lib/libgobject-2.0.so.0.2400.1) # ==18590== by 0x5FAB8AD: g_value_set_boxed (in /usr/lib/libgobject-2.0.so.0.2400.1) # ==18590== by 0x8BF8639: ??? (in /usr/lib/libdbus-glib-1.so.2.1.0) # ==18590== by 0x8BF6904: ??? (in /usr/lib/libdbus-glib-1.so.2.1.0) # ==18590== by 0x8BF1F04: ??? (in /usr/lib/libdbus-glib-1.so.2.1.0) # ==18590== by 0x8BF2A42: dbus_g_proxy_call (in /usr/lib/libdbus-glib-1.so.2.1.0) # ==18590== by 0x528CDB5: e_cal_new (in /usr/lib/libecal-1.2.so.7.2.2) # ==18590== by 0x528D2DB: e_cal_new_from_uri (in /usr/lib/libecal-1.2.so.7.2.2) # ==18590== by 0x5A0E8E: SyncEvo::EvolutionCalendarSource::open() (EvolutionCalendarSource.cpp:187) { libecal + D-Bus leaks Memcheck:Leak ... fun:dbus_* fun:e_cal_* } { libebook + D-Bus leaks Memcheck:Leak ... fun:dbus_* fun:e_book_* } # ==31627== 39 bytes in 1 blocks are definitely lost in loss record 4,002 of 15,340 # ==31627== at 0x4C244E8: malloc (vg_replace_malloc.c:236) # ==31627== by 0x6BDB392: g_malloc (in /lib/libglib-2.0.so.0.2800.6) # ==31627== by 0x6BF39CD: g_strdup (in /lib/libglib-2.0.so.0.2800.6) # ==31627== by 0x6C0EBCC: ??? (in /lib/libglib-2.0.so.0.2800.6) # ==31627== by 0x6C0EA59: ??? (in /lib/libglib-2.0.so.0.2800.6) # ==31627== by 0x6C0F8A8: g_variant_get_va (in /lib/libglib-2.0.so.0.2800.6) # ==31627== by 0x6C0F9F5: g_variant_get (in /lib/libglib-2.0.so.0.2800.6) # ==31627== by 0x52C6AD6: e_gdbus_cal_call_create_object_sync (in /usr/lib/libecal-1.2.so.8.2.2) # ==31627== by 0x52A84E8: e_cal_create_object (in /usr/lib/libecal-1.2.so.8.2.2) # ==31627== by 0x5DCA3B: SyncEvo::EvolutionCalendarSource::insertItem(std::string const&, std::string const&, bool) (EvolutionCalendarSource.cpp:416) # ==31627== by 0x70BECA: SyncEvo::TrackingSyncSource::insertItemRaw(std::string const&, std::string const&) (TrackingSyncSource.cpp:86) # ==31627== by 0x586756: SyncEvo::LocalTests::insert(SyncEvo::CreateSource, char const*, bool, std::string*) (ClientTest.cpp:308) # ==31627== by 0x56C12A: SyncEvo::LocalTests::testLinkedItemsChildChangesParent() (ClientTest.cpp:1109) # ==31627== by 0x5EED749: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==31627== by 0x5EE0C83: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==31627== by 0x5EE9CC6: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==31627== by 0x5EF4423: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==31627== by 0x5EED4E7: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==31627== by 0x5EEDC22: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==31627== by 0x5EEDB45: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==31627== by 0x5EF4049: CppUnit::TestResult::runTest(CppUnit::Test*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==31627== by 0x5EF6472: CppUnit::TestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==31627== by 0x5EF8FDA: CppUnit::TextTestRunner::run(std::string, bool, bool, bool) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==31627== by 0x5B048B: main (client-test-main.cpp:283) # ==31627== { e_cal_create_object Memcheck:Leak fun:malloc ... fun:g_variant_get ... fun:e_cal_create_object } # ==28758== 3 bytes in 1 blocks are possibly lost in loss record 31 of 10,032 # ==28758== at 0x4C244E8: malloc (vg_replace_malloc.c:236) # ==28758== by 0x6432504: g_malloc (in /lib/libglib-2.0.so.0.2400.1) # ==28758== by 0x6449D7D: g_strdup (in /lib/libglib-2.0.so.0.2400.1) # ==28758== by 0x54CA34C: e_source_update_from_xml_node (in /usr/lib/libedataserver-1.2.so.13.0.1) # ==28758== by 0x54CB8FD: e_source_new_from_xml_node (in /usr/lib/libedataserver-1.2.so.13.0.1) # ==28758== by 0x54C7BE7: e_source_group_new_from_xmldoc (in /usr/lib/libedataserver-1.2.so.13.0.1) # ==28758== by 0x54C8D8F: ??? (in /usr/lib/libedataserver-1.2.so.13.0.1) # ==28758== by 0x54C9874: e_source_list_new_for_gconf (in /usr/lib/libedataserver-1.2.so.13.0.1) # ==28758== by 0x5283EEB: ??? (in /usr/lib/libecal-1.2.so.7.2.2) # ==28758== by 0x5A0AED: SyncEvo::EvolutionCalendarSource::open() (EvolutionCalendarSource.cpp:173) { e_source_list_new_for_gconf Memcheck:Leak ... fun:e_source_list_new_for_gconf } # ==31023== 13,056 (6,072 direct, 6,984 indirect) bytes in 115 blocks are definitely lost in loss record 29 of 42 # ==31023== at 0x4C223DC: calloc (vg_replace_malloc.c:397) # ==31023== by 0x64EBE12: g_malloc0 (in /usr/lib/libglib-2.0.so.0.1600.6) # ==31023== by 0x8501B18: (within /usr/lib/libORBit-2.so.0.1.0) # ==31023== by 0x8501FB4: ORBit_POA_new_from (in /usr/lib/libORBit-2.so.0.1.0) # ==31023== by 0x8063091: bonobo_poa_new_from (in /usr/lib/libbonobo-2.so.0.0.0) # ==31023== by 0x8063197: bonobo_poa_get_threadedv (in /usr/lib/libbonobo-2.so.0.0.0) # ==31023== by 0x806335C: bonobo_poa_get_threaded (in /usr/lib/libbonobo-2.so.0.0.0) # ==31023== by 0x5BC130B: e_cal_listener_new (e-cal-listener.c:1032) # ==31023== by 0x5BAF560: e_cal_init (e-cal.c:1057) # ==31023== by 0x6294713: g_type_create_instance (in /usr/lib/libgobject-2.0.so.0.1600.6) # ==31023== by 0x6279F4C: (within /usr/lib/libgobject-2.0.so.0.1600.6) # ==31023== by 0x627A53F: g_object_newv (in /usr/lib/libgobject-2.0.so.0.1600.6) # ==31023== by 0x627B041: g_object_new_valist (in /usr/lib/libgobject-2.0.so.0.1600.6) # ==31023== by 0x627B180: g_object_new (in /usr/lib/libgobject-2.0.so.0.1600.6) # ==31023== by 0x5BAEECA: e_cal_new (e-cal.c:1412) # ==31023== by 0x5BAF3CB: e_cal_new_from_uri (e-cal.c:1449) # ==31023== by 0x6C45FD: EvolutionCalendarSource::open() (EvolutionCalendarSource.cpp:163) # ==31023== by 0x52C7E7: TestEvolutionSyncSource::beginSync(SyncMode) (client-test-app.cpp:68) # ==31023== by 0x5D229D: LocalTests::deleteAll(CreateSource) (ClientTest.cpp:389) # ==31023== by 0x52E4F2: SyncTests::refreshClient(SyncOptions) (ClientTest.cpp:1817) # ==31023== by 0x5DC6D6: SyncTests::testItems() (ClientTest.cpp:2531) # ==31023== by 0x60EE75: CppUnit::TestCaller::runTest() (TestCaller.h:166) # ==31023== by 0x6795846: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==31023== by 0x6787C43: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==31023== by 0x6791758: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==31023== by 0x679149B: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==31023== by 0x679D23F: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==31023== by 0x67954DC: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==31023== by 0x679CFC9: CppUnit::TestResult::runTest(CppUnit::Test*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==31023== by 0x679F5C1: CppUnit::TestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) { e_cal_init II Memcheck:Leak fun:calloc fun:g_malloc0 obj:*libORBit* fun:ORBit_POA_new_from fun:bonobo_poa_new_from fun:bonobo_poa_get_threadedv fun:bonobo_poa_get_threaded fun:e_cal_listener_new fun:e_cal_init } # ==5228== 1,668 bytes in 48 blocks are possibly lost in loss record 28 of 36 # ==5228== at 0x401C7EF: calloc (vg_replace_malloc.c:279) # ==5228== by 0x4293994: g_malloc0 (gmem.c:151) # ==5228== by 0x4817675: ORBit_alloc_by_tc (allocators.c:373) # ==5228== by 0x48129AC: ORBit_small_alloc (orbit-small.c:44) # ==5228== by 0x48222FD: ORBit_POA_create_object_T (poa.c:361) # ==5228== by 0x4822553: PortableServer_POA_servant_to_reference (poa.c:2330) # ==5228== by 0x4716416: bonobo_object_constructor (bonobo-object.c:791) # ==5228== by 0x422A7C7: g_object_newv (gobject.c:937) # ==5228== by 0x422B366: g_object_new_valist (gobject.c:986) # ==5228== by 0x422B51F: g_object_new (gobject.c:795) # ==5228== by 0x4720002: bonobo_running_context_new (bonobo-running-context.c:394) # ==5228== by 0x470CB06: bonobo_context_init (bonobo-context.c:92) # ==5228== by 0x4710A16: bonobo_init_full (bonobo-main.c:234) # ==5228== by 0x4710B15: bonobo_init (bonobo-main.c:256) # ==5228== by 0x404BCA7: e_cal_activate (e-cal.c:1324) # ==5228== by 0x404BFDC: e_cal_new (e-cal.c:1410) # ==5228== by 0x81B58F5: EvolutionCalendarSource::open() (EvolutionCalendarSource.cpp:145) # ==5228== by 0x80CC06C: EvolutionCalendarTest::testOpenDefaultCalendar() (in /tmp/runtests/head-evolution-svn-minimal/build/src/client-test) # ==5228== by 0x80C4FC0: CppUnit::TestCaller::runTest() (in /tmp/runtests/head-evolution-svn-minimal/build/src/client-test) # ==5228== by 0x4376C88: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x43683ED: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x43728D2: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x4372613: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x437F390: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x4376637: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x43772FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x4377229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x43772FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x4377229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x437F009: CppUnit::TestResult::runTest(CppUnit::Test*) (in /usr/lib/libcppunit-1.12.so.0.0.0) { e_cal_new V Memcheck:Leak fun:calloc fun:g_malloc0 fun:ORBit_alloc_by_tc fun:ORBit_small_alloc fun:ORBit_POA_create_object_T fun:PortableServer_POA_servant_to_reference fun:bonobo_object_constructor fun:g_object_newv fun:g_object_new_valist fun:g_object_new fun:bonobo_running_context_new fun:bonobo_context_init fun:bonobo_init_full fun:bonobo_init fun:e_cal_activate fun:e_cal_new } # ==5228== 2,940 (2,904 direct, 36 indirect) bytes in 18 blocks are definitely lost in loss record 29 of 36 # ==5228== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==5228== by 0x4293A1C: g_malloc (gmem.c:131) # ==5228== by 0x42A8B92: g_slice_alloc (gslice.c:824) # ==5228== by 0x42A9274: g_slice_alloc0 (gslice.c:833) # ==5228== by 0x424569B: g_type_create_instance (gtype.c:1555) # ==5228== by 0x422C571: g_object_constructor (gobject.c:1046) # ==5228== by 0x47162B0: bonobo_object_constructor (bonobo-object.c:820) # ==5228== by 0x422A7C7: g_object_newv (gobject.c:937) # ==5228== by 0x422B366: g_object_new_valist (gobject.c:986) # ==5228== by 0x422B51F: g_object_new (gobject.c:795) # ==5228== by 0x470B526: bonobo_moniker_context_new (bonobo-moniker-context.c:71) # ==5228== by 0x470CAF6: bonobo_context_init (bonobo-context.c:91) # ==5228== by 0x4710A16: bonobo_init_full (bonobo-main.c:234) # ==5228== by 0x4710B15: bonobo_init (bonobo-main.c:256) # ==5228== by 0x404BCA7: e_cal_activate (e-cal.c:1324) # ==5228== by 0x404BFDC: e_cal_new (e-cal.c:1410) # ==5228== by 0x81B58F5: EvolutionCalendarSource::open() (EvolutionCalendarSource.cpp:145) # ==5228== by 0x80CC06C: EvolutionCalendarTest::testOpenDefaultCalendar() (in /tmp/runtests/head-evolution-svn-minimal/build/src/client-test) # ==5228== by 0x80C4FC0: CppUnit::TestCaller::runTest() (in /tmp/runtests/head-evolution-svn-minimal/build/src/client-test) # ==5228== by 0x4376C88: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x43683ED: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x43728D2: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x4372613: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x437F390: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x4376637: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x43772FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x4377229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x43772FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x4377229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==5228== by 0x437F009: CppUnit::TestResult::runTest(CppUnit::Test*) (in /usr/lib/libcppunit-1.12.so.0.0.0) { e_cal_new VI Memcheck:Leak fun:malloc fun:g_malloc fun:g_slice_alloc fun:g_slice_alloc0 fun:g_type_create_instance fun:g_object_constructor fun:bonobo_object_constructor fun:g_object_newv fun:g_object_new_valist fun:g_object_new fun:bonobo_moniker_context_new fun:bonobo_context_init fun:bonobo_init_full fun:bonobo_init fun:e_cal_activate fun:e_cal_new } # ==28454== 2,714 (2,646 direct, 68 indirect) bytes in 13 blocks are definitely lost in loss record 32 of 39 # ==28454== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==28454== by 0x4293A1C: g_malloc (gmem.c:131) # ==28454== by 0x42A8B92: g_slice_alloc (gslice.c:824) # ==28454== by 0x428997A: g_list_append (glist.c:122) # ==28454== by 0x4715F74: bonobo_object_instance_init (bonobo-object.c:954) # ==28454== by 0x42458FB: g_type_create_instance (gtype.c:1567) # ==28454== by 0x422C571: g_object_constructor (gobject.c:1046) # ==28454== by 0x47162B0: bonobo_object_constructor (bonobo-object.c:820) # ==28454== by 0x422A7C7: g_object_newv (gobject.c:937) # ==28454== by 0x422B366: g_object_new_valist (gobject.c:986) # ==28454== by 0x422B51F: g_object_new (gobject.c:795) # ==28454== by 0x470B526: bonobo_moniker_context_new (bonobo-moniker-context.c:71) # ==28454== by 0x470CAF6: bonobo_context_init (bonobo-context.c:91) # ==28454== by 0x4710A16: bonobo_init_full (bonobo-main.c:234) # ==28454== by 0x4710B15: bonobo_init (bonobo-main.c:256) # ==28454== by 0x404BCA7: e_cal_activate (e-cal.c:1324) # ==28454== by 0x404BFDC: e_cal_new (e-cal.c:1410) # ==28454== by 0x81B9345: EvolutionCalendarSource::open() (EvolutionCalendarSource.cpp:151) # ==28454== by 0x80CEBC4: EvolutionCalendarTest::testOpenDefaultCalendar() (in /tmp/runtests/head-evolution-svn-minimal/build/src/client-test) # ==28454== by 0x80C7B18: CppUnit::TestCaller::runTest() (in /tmp/runtests/head-evolution-svn-minimal/build/src/client-test) # ==28454== by 0x4376C88: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==28454== by 0x43683ED: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==28454== by 0x43728D2: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==28454== by 0x4372613: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==28454== by 0x437F390: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==28454== by 0x4376637: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==28454== by 0x43772FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==28454== by 0x4377229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==28454== by 0x43772FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==28454== by 0x4377229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) { e_cal_init VII Memcheck:Leak fun:malloc fun:g_malloc fun:g_slice_alloc fun:g_list_append fun:bonobo_object_instance_init fun:g_type_create_instance fun:g_object_constructor fun:bonobo_object_constructor fun:g_object_newv fun:g_object_new_valist fun:g_object_new fun:bonobo_moniker_context_new fun:bonobo_context_init fun:bonobo_init_full fun:bonobo_init fun:e_cal_activate fun:e_cal_new } # ==23265== 1,456 bytes in 43 blocks are possibly lost in loss record 23 of 47 # ==23265== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==23265== by 0x42F7A1C: g_malloc (gmem.c:131) # ==23265== by 0x483595D: ORBit_alloc_string (allocators.c:228) # ==23265== by 0x4835618: CORBA_string_dup (corba-string.c:22) # ==23265== by 0x480A512: Bonobo_ActivationPropertyValue_copy (bonobo-activation-server-info.c:106) # ==23265== by 0x480A57D: Bonobo_ActivationProperty_copy (bonobo-activation-server-info.c:142) # ==23265== by 0x480A601: CORBA_sequence_Bonobo_ActivationProperty_copy (bonobo-activation-server-info.c:157) # ==23265== by 0x480A6A3: Bonobo_ServerInfo_copy (bonobo-activation-server-info.c:173) # ==23265== by 0x480A73F: Bonobo_ServerInfoList_duplicate (bonobo-activation-server-info.c:212) # ==23265== by 0x4808701: bonobo_activation_query (bonobo-activation-activate.c:238) # ==23265== by 0x40D789C: get_factories (e-cal.c:1011) # ==23265== by 0x40D885F: fetch_corba_cal (e-cal.c:1277) # ==23265== by 0x40D8D13: e_cal_new (e-cal.c:1406) # ==23265== by 0x40D8DEA: e_cal_new_from_uri (e-cal.c:1441) # ==23265== by 0x81A5A2F: EvolutionCalendarSource::open() (EvolutionCalendarSource.cpp:147) # ==23265== by 0x805D2B6: TestEvolutionSyncSource::beginSync() (in /tmp/runtests/head-evolution-2.12/build/src/client-test) # ==23265== by 0x808E52F: LocalTests::testIterateTwice() (in /tmp/runtests/head-evolution-2.12/build/src/client-test) # ==23265== by 0x816B7F0: CppUnit::TestCaller::runTest() (in /tmp/runtests/head-evolution-2.12/build/src/client-test) # ==23265== by 0x4050C88: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x40423ED: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x404C8D2: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x404C613: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x4059390: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x4050637: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x40512FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x4051229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x40512FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x4051229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x4059009: CppUnit::TestResult::runTest(CppUnit::Test*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x405B72F: CppUnit::TestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) { e_cal_new VIII Memcheck:Leak fun:malloc fun:g_malloc fun:ORBit_alloc_string fun:CORBA_string_dup fun:Bonobo_ActivationPropertyValue_copy fun:Bonobo_ActivationProperty_copy fun:CORBA_sequence_Bonobo_ActivationProperty_copy fun:Bonobo_ServerInfo_copy fun:Bonobo_ServerInfoList_duplicate fun:bonobo_activation_query fun:get_factories fun:fetch_corba_cal fun:e_cal_new } # ==22473== 208 bytes in 1 blocks are possibly lost in loss record 97 of 177 # ==22473== at 0x4C233A2: realloc (vg_replace_malloc.c:429) # ==22473== by 0x64EBD88: g_realloc (in /usr/lib/libglib-2.0.so.0.1600.6) # ==22473== by 0x84F720E: ORBit_realloc_tcval (in /usr/lib/libORBit-2.so.0.1.0) # ==22473== by 0x84FAF33: ORBit_sequence_append (in /usr/lib/libORBit-2.so.0.1.0) # ==22473== by 0x82B89D0: bonobo_activation_init_activation_env (in /usr/lib/libbonobo-activation.so.4.0.0) # ==22473== by 0x82BC3F2: bonobo_activation_orb_init (in /usr/lib/libbonobo-activation.so.4.0.0) # ==22473== by 0x82BC76E: bonobo_activation_init (in /usr/lib/libbonobo-activation.so.4.0.0) # ==22473== by 0x80634C0: bonobo_init_full (in /usr/lib/libbonobo-2.so.0.0.0) # ==22473== by 0x5BAF111: e_cal_new (e-cal.c:1324) # ==22473== by 0x5BAF3CB: e_cal_new_from_uri (e-cal.c:1449) # ==22473== by 0x6C45E1: EvolutionCalendarSource::open() (EvolutionCalendarSource.cpp:163) # ==22473== by 0x52C7E7: TestEvolutionSyncSource::beginSync(SyncMode) (client-test-app.cpp:68) # ==22473== by 0x5D229D: LocalTests::deleteAll(CreateSource) (ClientTest.cpp:389) # ==22473== by 0x60BD80: SyncTests::deleteAll(SyncTests::DeleteAllMode) (ClientTest.cpp:1759) # ==22473== by 0x5DC3DB: SyncTests::testItems() (ClientTest.cpp:2521) # ==22473== by 0x60EE75: CppUnit::TestCaller::runTest() (TestCaller.h:166) # ==22473== by 0x6795846: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==22473== by 0x6787C43: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==22473== by 0x6791758: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==22473== by 0x679149B: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==22473== by 0x679D23F: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==22473== by 0x67954DC: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==22473== by 0x679CFC9: CppUnit::TestResult::runTest(CppUnit::Test*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==22473== by 0x679F5C1: CppUnit::TestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==22473== by 0x67A25DA: CppUnit::TextTestRunner::run(std::string, bool, bool, bool) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==22473== by 0x615BE6: main (client-test-main.cpp:255) { e_cal_new IX Memcheck:Leak fun:realloc fun:g_realloc fun:ORBit_realloc_tcval fun:ORBit_sequence_append fun:bonobo_activation_init_activation_env fun:bonobo_activation_orb_init fun:bonobo_activation_init fun:bonobo_init_full fun:e_cal_new } # ==12803== 607 bytes in 26 blocks are possibly lost in loss record 17 of 46 # ==12803== at 0x4C2260E: malloc (vg_replace_malloc.c:207) # ==12803== by 0x6174BA2: g_malloc (in /usr/lib/libglib-2.0.so.0.1600.6) # ==12803== by 0x91F6B2C: ORBit_alloc_string (in /usr/lib/libORBit-2.so.0.1.0) # ==12803== by 0x91F683D: CORBA_string_dup (in /usr/lib/libORBit-2.so.0.1.0) # ==12803== by 0x8FB7D4D: Bonobo_ServerInfo_copy (in /usr/lib/libbonobo-activation.so.4.0.0) # ==12803== by 0x8FB7DFA: Bonobo_ServerInfoList_duplicate (in /usr/lib/libbonobo-activation.so.4.0.0) # ==12803== by 0x8FB6189: bonobo_activation_query (in /usr/lib/libbonobo-activation.so.4.0.0) # ==12803== by 0x585BABA: e_cal_new (in /usr/lib/libecal-1.2.so.7.2.0) # ==12803== by 0x585BECB: e_cal_new_from_uri (in /usr/lib/libecal-1.2.so.7.2.0) # ==12803== by 0x618B6C: EvolutionCalendarSource::open() (EvolutionCalendarSource.cpp:163) # ==12803== by 0x4648E2: TestEvolutionSyncSource::beginSync(SyncMode) (in /work/runtests/head-evolution-lenny-2.22.3.1-full/build/src/client-test) # ==12803== by 0x55492C: LocalTests::testIterateTwice() (in /work/runtests/head-evolution-lenny-2.22.3.1-full/build/src/client-test) # ==12803== by 0x5AA6DF: CppUnit::TestCaller::runTest() (in /work/runtests/head-evolution-lenny-2.22.3.1-full/build/src/client-test) # ==12803== by 0x4E51406: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==12803== by 0x4E437D3: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==12803== by 0x4E4D278: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==12803== by 0x4E4CFBB: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==12803== by 0x4E58D9F: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==12803== by 0x4E5109C: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==12803== by 0x4E519FB: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==12803== by 0x4E51925: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==12803== by 0x4E519FB: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==12803== by 0x4E51925: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==12803== by 0x4E58B29: CppUnit::TestResult::runTest(CppUnit::Test*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==12803== by 0x4E5B121: CppUnit::TestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==12803== by 0x4E5E13A: CppUnit::TextTestRunner::run(std::string, bool, bool, bool) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==12803== by 0x5B1C15: main (in /work/runtests/head-evolution-lenny-2.22.3.1-full/build/src/client-test) { e_cal_new X Memcheck:Leak fun:malloc fun:g_malloc fun:ORBit_alloc_string fun:CORBA_string_dup fun:Bonobo_ServerInfo_copy fun:Bonobo_ServerInfoList_duplicate fun:bonobo_activation_query fun:e_cal_new } # ==28640== 419 bytes in 18 blocks are possibly lost in loss record 78 of 169 # ==28640== at 0x4C2260E: malloc (vg_replace_malloc.c:207) # ==28640== by 0x6174BA2: g_malloc (in /usr/lib/libglib-2.0.so.0.1600.6) # ==28640== by 0x91F6B2C: ORBit_alloc_string (in /usr/lib/libORBit-2.so.0.1.0) # ==28640== by 0x91F683D: CORBA_string_dup (in /usr/lib/libORBit-2.so.0.1.0) # ==28640== by 0x8FB7C1B: Bonobo_ActivationPropertyValue_copy (in /usr/lib/libbonobo-activation.so.4.0.0) # ==28640== by 0x8FB7CF2: CORBA_sequence_Bonobo_ActivationProperty_copy (in /usr/lib/libbonobo-activation.so.4.0.0) # ==28640== by 0x8FB7DFA: Bonobo_ServerInfoList_duplicate (in /usr/lib/libbonobo-activation.so.4.0.0) # ==28640== by 0x8FB6189: bonobo_activation_query (in /usr/lib/libbonobo-activation.so.4.0.0) # ==28640== by 0x585BABA: e_cal_new (in /usr/lib/libecal-1.2.so.7.2.0) # ==28640== by 0x585BECB: e_cal_new_from_uri (in /usr/lib/libecal-1.2.so.7.2.0) # ==28640== by 0x618B6C: EvolutionCalendarSource::open() (EvolutionCalendarSource.cpp:163) # ==28640== by 0x4648E2: TestEvolutionSyncSource::beginSync(SyncMode) (in /work/runtests/head-evolution-lenny-2.22.3.1-full/build/src/client-test) # ==28640== by 0x54A9B1: LocalTests::insert(CreateSource, char const*, char const*, bool) (in /work/runtests/head-evolution-lenny-2.22.3.1-full/build/src/client-test) # ==28640== by 0x46C01F: LocalTests::testSimpleInsert() (in /work/runtests/head-evolution-lenny-2.22.3.1-full/build/src/client-test) # ==28640== by 0x5AA6DF: CppUnit::TestCaller::runTest() (in /work/runtests/head-evolution-lenny-2.22.3.1-full/build/src/client-test) { e_cal_new XI Memcheck:Leak fun:malloc fun:g_malloc fun:ORBit_alloc_string fun:CORBA_string_dup fun:Bonobo_ActivationPropertyValue_copy fun:CORBA_sequence_Bonobo_ActivationProperty_copy fun:Bonobo_ServerInfoList_duplicate fun:bonobo_activation_query fun:e_cal_new } # ==20460== 16 bytes in 1 blocks are definitely lost in loss record 5 of 41 # ==20460== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==20460== by 0x411BC5C: icaltzutil_fetch_timezone (icaltz-util.c:266) # ==20460== by 0x411EE74: icaltimezone_load_builtin_timezone (icaltimezone.c:1744) # ==20460== by 0x411E0AD: icaltimezone_get_tzid (icaltimezone.c:1153) # ==20460== by 0x81ABA29: syncevolution_match_tzid (e-cal-check-timezones.c:54) # ==20460== by 0x81ABDA1: syncevolution_check_timezones (e-cal-check-timezones.c:279) # ==20460== by 0x81A7EB8: EvolutionCalendarSource::insertItem(std::string const&, SyncItem const&) (EvolutionCalendarSource.cpp:298) # ==20460== by 0x81AC2F5: TrackingSyncSource::addItemThrow(SyncItem&) (TrackingSyncSource.cpp:133) # ==20460== by 0x80689A2: EvolutionCalendarTest::addItem(boost::shared_ptr, std::string&) (in /tmp/runtests/head-evolution-2.12/build/src/client-test) # ==20460== by 0x806960A: EvolutionCalendarTest::testTimezones() (in /tmp/runtests/head-evolution-2.12/build/src/client-test) # ==20460== by 0x8063220: CppUnit::TestCaller::runTest() (in /tmp/runtests/head-evolution-2.12/build/src/client-test) # ==20460== by 0x4050C88: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==20460== by 0x40423ED: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==20460== by 0x404C8D2: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==20460== by 0x404C613: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==20460== by 0x4059390: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==20460== by 0x4050637: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==20460== by 0x40512FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==20460== by 0x4051229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==20460== by 0x40512FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==20460== by 0x4051229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==20460== by 0x4059009: CppUnit::TestResult::runTest(CppUnit::Test*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==20460== by 0x405B72F: CppUnit::TestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==20460== by 0x405EF2A: CppUnit::TextTestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==20460== by 0x405EFA1: CppUnit::TextTestRunner::run(std::string, bool, bool, bool) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==20460== by 0x816E218: main (in /tmp/runtests/head-evolution-2.12/build/src/client-test) { icaltimezone_load_builtin_timezone() Memcheck:Leak fun:malloc fun:icaltzutil_fetch_timezone fun:icaltimezone_load_builtin_timezone fun:icaltimezone_get_tzid fun:syncevolution_match_tzid fun:syncevolution_check_timezones } # ==20460== 122 bytes in 10 blocks are definitely lost in loss record 18 of 41 # ==20460== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==20460== by 0x44F617F: strdup (in /lib/tls/i686/cmov/libc-2.3.6.so) # ==20460== by 0x411089D: icalmemory_strdup (icalmemory.c:236) # ==20460== by 0x40FFC9B: icalparameter_new_from_value_string (icalderivedparameter.c:1475) # ==20460== by 0x4113230: icalparser_add_line (icalparser.c:856) # ==20460== by 0x4112AC2: icalparser_parse (icalparser.c:587) # ==20460== by 0x41138B0: icalparser_parse_string (icalparser.c:1118) # ==20460== by 0x410B8CA: icalcomponent_new_from_string (icalcomponent.c:173) # ==20460== by 0x81A7E05: EvolutionCalendarSource::insertItem(std::string const&, SyncItem const&) (EvolutionCalendarSource.cpp:288) # ==20460== by 0x81AC2F5: TrackingSyncSource::addItemThrow(SyncItem&) (TrackingSyncSource.cpp:133) # ==20460== by 0x80689A2: EvolutionCalendarTest::addItem(boost::shared_ptr, std::string&) (in /tmp/runtests/head-evolution-2.12/build/src/client-test) # ==20460== by 0x806960A: EvolutionCalendarTest::testTimezones() (in /tmp/runtests/head-evolution-2.12/build/src/client-test) # ==20460== by 0x8063220: CppUnit::TestCaller::runTest() (in /tmp/runtests/head-evolution-2.12/build/src/client-test) # ==20460== by 0x4050C88: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==20460== by 0x40423ED: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==20460== by 0x404C8D2: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==20460== by 0x404C613: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==20460== by 0x4059390: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==20460== by 0x4050637: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==20460== by 0x40512FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==20460== by 0x4051229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==20460== by 0x40512FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==20460== by 0x4051229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==20460== by 0x4059009: CppUnit::TestResult::runTest(CppUnit::Test*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==20460== by 0x405B72F: CppUnit::TestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==20460== by 0x405EF2A: CppUnit::TextTestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==20460== by 0x405EFA1: CppUnit::TextTestRunner::run(std::string, bool, bool, bool) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==20460== by 0x816E218: main (in /tmp/runtests/head-evolution-2.12/build/src/client-test) { icalcomponent_new_from_string() Memcheck:Leak fun:malloc fun:strdup fun:icalmemory_strdup fun:icalparameter_new_from_value_string fun:icalparser_add_line fun:icalparser_parse fun:icalparser_parse_string fun:icalcomponent_new_from_string } # ==23265== 23,220 (16,512 direct, 6,708 indirect) bytes in 129 blocks are definitely lost in loss record 32 of 47 # ==23265== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==23265== by 0x4120638: icalvalue_new_impl (icalvalue.c:75) # ==23265== by 0x41211F0: icalvalue_new_from_string_with_error (icalvalue.c:550) # ==23265== by 0x4121628: icalvalue_new_from_string (icalvalue.c:665) # ==23265== by 0x41133FF: icalparser_add_line (icalparser.c:945) # ==23265== by 0x4112AC2: icalparser_parse (icalparser.c:587) # ==23265== by 0x41138B0: icalparser_parse_string (icalparser.c:1118) # ==23265== by 0x410B8CA: icalcomponent_new_from_string (icalcomponent.c:173) # ==23265== by 0x81A794B: EvolutionCalendarSource::insertItem(std::string const&, SyncItem const&) (EvolutionCalendarSource.cpp:294) # ==23265== by 0x81ABCC5: TrackingSyncSource::addItemThrow(SyncItem&) (TrackingSyncSource.cpp:133) # ==23265== by 0x817878B: EvolutionSyncSource::processItem(char const*, int (EvolutionSyncSource::*)(SyncItem&), SyncItem&, bool) (EvolutionSyncSource.cpp:391) # ==23265== by 0x8178A38: EvolutionSyncSource::addItem(SyncItem&) (EvolutionSyncSource.cpp:343) # ==23265== by 0x8056F28: TestEvolutionSyncSource::addItem(SyncItem&) (in /tmp/runtests/head-evolution-2.12/build/src/client-test) # ==23265== by 0x807A57D: importItem(SyncSource*, std::string&) (in /tmp/runtests/head-evolution-2.12/build/src/client-test) # ==23265== by 0x8169BD0: LocalTests::insertManyItems(CreateSource, int, int, int) (in /tmp/runtests/head-evolution-2.12/build/src/client-test) # ==23265== by 0x80E39AF: LocalTests::testManyChanges() (in /tmp/runtests/head-evolution-2.12/build/src/client-test) # ==23265== by 0x816B7F0: CppUnit::TestCaller::runTest() (in /tmp/runtests/head-evolution-2.12/build/src/client-test) # ==23265== by 0x4050C88: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x40423ED: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x404C8D2: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x404C613: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x4059390: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x4050637: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x40512FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x4051229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x40512FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x4051229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x4059009: CppUnit::TestResult::runTest(CppUnit::Test*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x405B72F: CppUnit::TestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x405EF2A: CppUnit::TextTestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) { icalcomponent_new_from_string II Memcheck:Leak fun:malloc fun:icalvalue_new_impl fun:icalvalue_new_from_string_with_error fun:icalvalue_new_from_string fun:icalparser_add_line fun:icalparser_parse fun:icalparser_parse_string } # ==23265== 30,420 (5,408 direct, 25,012 indirect) bytes in 169 blocks are definitely lost in loss record 33 of 47 # ==23265== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==23265== by 0x411397F: icalproperty_new_impl (icalproperty.c:102) # ==23265== by 0x4113A23: icalproperty_new (icalproperty.c:127) # ==23265== by 0x4113061: icalparser_add_line (icalparser.c:782) # ==23265== by 0x4112AC2: icalparser_parse (icalparser.c:587) # ==23265== by 0x41138B0: icalparser_parse_string (icalparser.c:1118) # ==23265== by 0x410B8CA: icalcomponent_new_from_string (icalcomponent.c:173) # ==23265== by 0x81A794B: EvolutionCalendarSource::insertItem(std::string const&, SyncItem const&) (EvolutionCalendarSource.cpp:294) # ==23265== by 0x81ABCC5: TrackingSyncSource::addItemThrow(SyncItem&) (TrackingSyncSource.cpp:133) # ==23265== by 0x817878B: EvolutionSyncSource::processItem(char const*, int (EvolutionSyncSource::*)(SyncItem&), SyncItem&, bool) (EvolutionSyncSource.cpp:391) # ==23265== by 0x8178A38: EvolutionSyncSource::addItem(SyncItem&) (EvolutionSyncSource.cpp:343) # ==23265== by 0x8056F28: TestEvolutionSyncSource::addItem(SyncItem&) (in /tmp/runtests/head-evolution-2.12/build/src/client-test) # ==23265== by 0x807A57D: importItem(SyncSource*, std::string&) (in /tmp/runtests/head-evolution-2.12/build/src/client-test) # ==23265== by 0x8169BD0: LocalTests::insertManyItems(CreateSource, int, int, int) (in /tmp/runtests/head-evolution-2.12/build/src/client-test) # ==23265== by 0x80E39AF: LocalTests::testManyChanges() (in /tmp/runtests/head-evolution-2.12/build/src/client-test) # ==23265== by 0x816B7F0: CppUnit::TestCaller::runTest() (in /tmp/runtests/head-evolution-2.12/build/src/client-test) # ==23265== by 0x4050C88: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x40423ED: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x404C8D2: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x404C613: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x4059390: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x4050637: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x40512FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x4051229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x40512FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x4051229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x4059009: CppUnit::TestResult::runTest(CppUnit::Test*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x405B72F: CppUnit::TestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x405EF2A: CppUnit::TextTestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==23265== by 0x405EFA1: CppUnit::TextTestRunner::run(std::string, bool, bool, bool) (in /usr/lib/libcppunit-1.12.so.0.0.0) { icalcomponent_new_from_string III Memcheck:Leak fun:malloc fun:icalproperty_new_impl fun:icalproperty_new fun:icalparser_add_line fun:icalparser_parse fun:icalparser_parse_string } { icalcomponent_new_from_string III, version 2 Memcheck:Leak fun:malloc fun:icalproperty_new_impl fun:icalparser_add_line fun:icalparser_parse fun:icalparser_parse_string } # ==1142== 20 bytes in 1 blocks are possibly lost in loss record 5 of 43 # ==1142== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==1142== by 0x40CFF65: pvl_newlist (pvl.c:59) # ==1142== by 0x40C0B70: icalproperty_new_impl (icalproperty.c:110) # ==1142== by 0x40C0BDB: icalproperty_new (icalproperty.c:127) # ==1142== by 0x40C01BB: icalparser_add_line (icalparser.c:787) # ==1142== by 0x40BFC17: icalparser_parse (icalparser.c:591) # ==1142== by 0x40C0A66: icalparser_parse_string (icalparser.c:1132) # ==1142== by 0x40B89D2: icalcomponent_new_from_string (icalcomponent.c:173) # ==1142== by 0x81DD4AB: EvolutionCalendarSource::insertItem(std::string const&, SyncItem const&) (EvolutionCalendarSource.cpp:309) # ==1142== by 0x81E221F: TrackingSyncSource::updateItemThrow(SyncItem&) (TrackingSyncSource.cpp:142) # ==1142== by 0x81B083B: EvolutionSyncSource::processItem(char const*, int (EvolutionSyncSource::*)(SyncItem&), SyncItem&, bool) (EvolutionSyncSource.cpp:391) # ==1142== by 0x81B0AA8: EvolutionSyncSource::updateItem(SyncItem&) (EvolutionSyncSource.cpp:348) # ==1142== by 0x80574FA: TestEvolutionSyncSource::updateItem(SyncItem&) (in /tmp/runtests/head-evolution-svn-minimal/build/src/client-test) # ==1142== by 0x8112FF7: updateItem(CreateSource, std::string const&, char const*) (in /tmp/runtests/head-evolution-svn-minimal/build/src/client-test) # ==1142== by 0x8139A39: LocalTests::testLinkedItemsParentUpdate() (in /tmp/runtests/head-evolution-svn-minimal/build/src/client-test) # ==1142== by 0x816BF04: CppUnit::TestCaller::runTest() (in /tmp/runtests/head-evolution-svn-minimal/build/src/client-test) # ==1142== by 0x4279C88: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==1142== by 0x426B3ED: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==1142== by 0x42758D2: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==1142== by 0x4275613: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==1142== by 0x4282390: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==1142== by 0x4279637: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==1142== by 0x427A2FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==1142== by 0x427A229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==1142== by 0x427A2FC: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==1142== by 0x427A229: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==1142== by 0x4282009: CppUnit::TestResult::runTest(CppUnit::Test*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==1142== by 0x428472F: CppUnit::TestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==1142== by 0x4287F2A: CppUnit::TextTestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==1142== by 0x4287FA1: CppUnit::TextTestRunner::run(std::string, bool, bool, bool) (in /usr/lib/libcppunit-1.12.so.0.0.0) { icalproperty pvl_newlist Memcheck:Leak fun:malloc fun:pvl_newlist fun:icalproperty_new_impl fun:icalproperty_new fun:icalparser_add_line fun:icalparser_parse fun:icalparser_parse_string } # ==21503== 2,032 bytes in 1 blocks are definitely lost in loss record 10,657 of 10,880 # ==21503== at 0x4C2B514: calloc (vg_replace_malloc.c:593) # ==21503== by 0x87FB998: g_malloc0 (gmem.c:189) # ==21503== by 0x87C88C8: thread_memory_from_self.part.12 (gslice.c:512) # ==21503== by 0x88115E4: g_slice_alloc (gslice.c:1561) # ==21503== by 0x88119DD: g_slice_alloc0 (gslice.c:1029) # ==21503== by 0x87F53EC: g_main_context_push_thread_default (gmain.c:728) # ==21503== by 0x5908DDD: cal_client_dbus_thread (in /usr/lib/libecal-1.2.so.15.0.0) # ==21503== by 0x881A1D4: g_thread_proxy (gthread.c:798) # ==21503== by 0x9A5BE0D: start_thread (pthread_create.c:311) # ==21503== by 0xA56F9EC: clone (clone.S:113) # ==21503== # # ==21503== 48 (24 direct, 24 indirect) bytes in 1 blocks are definitely lost in loss record 6,004 of 10,880 # ==21503== at 0x4C2935B: malloc (vg_replace_malloc.c:270) # ==21503== by 0x87FB940: g_malloc (gmem.c:159) # ==21503== by 0x881149D: g_slice_alloc (gslice.c:1003) # ==21503== by 0x88119DD: g_slice_alloc0 (gslice.c:1029) # ==21503== by 0x87F53EC: g_main_context_push_thread_default (gmain.c:728) # ==21503== by 0x19210A08: ??? (in /usr/lib/x86_64-linux-gnu/gio/modules/libdconfsettings.so) # ==21503== by 0x881A1D4: g_thread_proxy (gthread.c:798) # ==21503== by 0x9A5BE0D: start_thread (pthread_create.c:311) # ==21503== by 0xA56F9EC: clone (clone.S:113) # ==21503== # # Seen with GNOME 3.8. { g_main_context_push_thread_default Memcheck:Leak ... fun:g_main_context_push_thread_default } # ==21503== 16 bytes in 1 blocks are definitely lost in loss record 2,071 of 10,880 # ==21503== at 0x4C2935B: malloc (vg_replace_malloc.c:270) # ==21503== by 0x87FB940: g_malloc (gmem.c:159) # ==21503== by 0x881149D: g_slice_alloc (gslice.c:1003) # ==21503== by 0x88119DD: g_slice_alloc0 (gslice.c:1029) # ==21503== by 0x87F3019: get_dispatch (gmain.c:2705) # ==21503== by 0x87F5DA4: g_main_context_dispatch (gmain.c:2997) # ==21503== by 0x87F61F7: g_main_context_iterate.isra.22 (gmain.c:3701) # ==21503== by 0x87F65F9: g_main_loop_run (gmain.c:3895) # ==21503== by 0x54AF732: book_client_dbus_thread (in /usr/lib/libebook-1.2.so.14.3.1) # ==21503== by 0x881A1D4: g_thread_proxy (gthread.c:798) # ==21503== by 0x9A5BE0D: start_thread (pthread_create.c:311) # ==21503== by 0xA56F9EC: clone (clone.S:113) # ==21503== # ==21503== 16 bytes in 1 blocks are definitely lost in loss record 2,072 of 10,880 # ==21503== at 0x4C2B514: calloc (vg_replace_malloc.c:593) # ==21503== by 0x87FB998: g_malloc0 (gmem.c:189) # ==21503== by 0x87F6471: g_main_loop_new (gmain.c:3790) # ==21503== by 0x5908DE7: cal_client_dbus_thread (in /usr/lib/libecal-1.2.so.15.0.0) # ==21503== by 0x881A1D4: g_thread_proxy (gthread.c:798) # ==21503== by 0x9A5BE0D: start_thread (pthread_create.c:311) # ==21503== by 0xA56F9EC: clone (clone.S:113) # ==21503== # Seen with various dbus threads in GNOME 3.8. { glib inside dbus thread Memcheck:Leak ... fun:g_main_* fun:*_dbus_thread fun:g_thread_proxy } { glib inside libdconfsettings.so thread Memcheck:Leak ... fun:g_main_* obj:*/libdconfsettings.so fun:g_thread_proxy } # ==6435== 16 bytes in 1 blocks are definitely lost in loss record 2,063 of 10,849 # ==6435== at 0x4C2935B: malloc (vg_replace_malloc.c:270) # ==6435== by 0x87FB940: g_malloc (gmem.c:159) # ==6435== by 0x881149D: g_slice_alloc (gslice.c:1003) # ==6435== by 0x88119DD: g_slice_alloc0 (gslice.c:1029) # ==6435== by 0x87F3019: get_dispatch (gmain.c:2705) # ==6435== by 0x87F5DA4: g_main_context_dispatch (gmain.c:2997) # ==6435== by 0x87F61F7: g_main_context_iterate.isra.22 (gmain.c:3701) # ==6435== by 0x87F65F9: g_main_loop_run (gmain.c:3895) # ==6435== by 0x80C5608: initable_init (gdbusproxy.c:1994) # ==6435== by 0x80575B9: g_initable_new_valist (ginitable.c:231) # ==6435== by 0x805769B: g_initable_new (ginitable.c:149) # ==6435== by 0x80C6D38: g_dbus_proxy_new_sync (gdbusproxy.c:2172) # ==6435== by 0x80D3903: initable_init (gdbusobjectmanagerclient.c:1339) # ==6435== by 0x80575B9: g_initable_new_valist (ginitable.c:231) # ==6435== by 0x805769B: g_initable_new (ginitable.c:149) # ==6435== by 0x749616C: e_dbus_object_manager_client_new_for_bus_sync (in /usr/lib/libedataserver-1.2.so.17.0.0) # ==6435== by 0x747E55B: source_registry_object_manager_thread (in /usr/lib/libedataserver-1.2.so.17.0.0) # ==6435== by 0x881A1D4: g_thread_proxy (gthread.c:798) # ==6435== by 0x9A5BE0D: start_thread (pthread_create.c:311) # ==6435== by 0xA56F9EC: clone (clone.S:113) # ==6435== { source registry thread Memcheck:Leak ... fun:e_dbus_object_manager_client_new_for_bus_sync fun:source_registry_object_manager_thread } ##### evolution-data-server #### # ==32292== Syscall param pwrite64(buf) points to uninitialised byte(s) # ==32292== at 0x4000792: (within /lib/ld-2.3.6.so) # ==32292== by 0x40E0696: __os_io_eds (os_rw.c:62) # ==32292== by 0x40D6D87: __memp_pgwrite (mp_bh.c:402) # ==32292== by 0x40D6801: __memp_bhwrite_eds (mp_bh.c:160) # ==32292== by 0x40DDF04: __memp_sync_int_eds (mp_sync.c:427) # ==32292== by 0x40DD745: __memp_fsync_eds (mp_sync.c:118) # ==32292== by 0x40737ED: __db_sync_eds (db_am.c:832) # ==32292== by 0x50F384E: do_create (e-book-backend-file.c:219) # ==32292== by 0x50F392B: e_book_backend_file_create_contact (e-book-backend-file.c:243) # ==32292== by 0x404280A: e_book_backend_sync_create_contact (e-book-backend-sync.c:61) # ==32292== by 0x4043AC5: _e_book_backend_create_contact (e-book-backend-sync.c:374) # ==32292== by 0x4044943: e_book_backend_create_contact (e-book-backend.c:185) # ==32292== by 0x4049C75: impl_GNOME_Evolution_Addressbook_Book_addContact (e-data-book.c:103) # ==32292== by 0x40387E9: _ORBIT_skel_small_GNOME_Evolution_Addressbook_Book_addContact (Evolution-DataServer-Addressbook-common.c:64) # ==32292== by 0x43AC436: ORBit_POAObject_invoke (poa.c:1142) # ==32292== by 0x43B2664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==32292== by 0x439F5D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==32292== by 0x43B02B9: ORBit_POAObject_handle_request (poa.c:1351) # ==32292== by 0x43B095B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==32292== by 0x439813A: giop_thread_queue_process (giop.c:771) # ==32292== Address 0x55C7408 is 250,944 bytes inside a block of size 270,336 alloc'd # ==32292== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==32292== by 0x40DECFB: __os_malloc_eds (os_alloc.c:269) # ==32292== by 0x40E02CB: __os_r_attach_eds (os_region.c:71) # ==32292== by 0x40A448E: __db_r_attach_eds (env_region.c:956) # ==32292== by 0x40DB125: __memp_open_eds (mp_region.c:75) # ==32292== by 0x409F41A: __dbenv_open_eds (env_open.c:228) # ==32292== by 0x50F5AE2: e_book_backend_file_load_source (e-book-backend-file.c:1104) # ==32292== by 0x404436C: e_book_backend_load_source (e-book-backend.c:74) # ==32292== by 0x40445DB: e_book_backend_open (e-book-backend.c:129) # ==32292== by 0x4049A4A: impl_GNOME_Evolution_Addressbook_Book_open (e-data-book.c:40) # ==32292== by 0x4038708: _ORBIT_skel_small_GNOME_Evolution_Addressbook_Book_open (Evolution-DataServer-Addressbook-common.c:48) # ==32292== by 0x43AC436: ORBit_POAObject_invoke (poa.c:1142) # ==32292== by 0x43B2664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==32292== by 0x439F5D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==32292== by 0x43B02B9: ORBit_POAObject_handle_request (poa.c:1351) # ==32292== by 0x43B095B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==32292== by 0x439813A: giop_thread_queue_process (giop.c:771) # ==32292== by 0x43989D7: giop_request_handler_thread (giop.c:481) # ==32292== by 0x4479686: g_thread_pool_thread_proxy (gthreadpool.c:265) # ==32292== by 0x4477AFE: g_thread_create_proxy (gthread.c:635) { DB write Memcheck:Param pwrite64(buf) obj:/lib/ld-2.3.6.so fun:__os_io_eds fun:__memp_pgwrite fun:__memp_bhwrite_eds fun:__memp_sync_int_eds fun:__memp_fsync_eds fun:__db_sync_eds } # ==32743== 9,840 (1,032 direct, 8,808 indirect) bytes in 86 blocks are definitely lost in loss record 27 of 41 # ==32743== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==32743== by 0x4457A95: g_malloc (gmem.c:131) # ==32743== by 0x446CE32: g_slice_alloc (gslice.c:824) # ==32743== by 0x444D99A: g_list_append (glist.c:69) # ==32743== by 0x50F4E18: e_book_backend_file_get_changes (e-book-backend-file.c:791) # ==32743== by 0x4043322: e_book_backend_sync_get_changes (e-book-backend-sync.c:236) # ==32743== by 0x4043D79: _e_book_backend_get_changes (e-book-backend-sync.c:456) # ==32743== by 0x40453C4: e_book_backend_get_changes (e-book-backend.c:350) # ==32743== by 0x4049F2F: impl_GNOME_Evolution_Addressbook_Book_getChanges (e-data-book.c:184) # ==32743== by 0x40388EC: _ORBIT_skel_small_GNOME_Evolution_Addressbook_Book_getChanges (Evolution-DataServer-Addressbook-common.c:80) # ==32743== by 0x43AC436: ORBit_POAObject_invoke (poa.c:1142) # ==32743== by 0x43B2664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==32743== by 0x439F5D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==32743== by 0x43B02B9: ORBit_POAObject_handle_request (poa.c:1351) # ==32743== by 0x43B095B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==32743== by 0x439813A: giop_thread_queue_process (giop.c:771) # ==32743== by 0x43989D7: giop_request_handler_thread (giop.c:481) # ==32743== by 0x4479686: g_thread_pool_thread_proxy (gthreadpool.c:265) # ==32743== by 0x4477AFE: g_thread_create_proxy (gthread.c:635) # ==32743== by 0x452723F: start_thread (in /lib/tls/i686/cmov/libpthread-2.3.6.so) { e_book_backend_get_changes Memcheck:Leak fun:malloc fun:g_malloc fun:g_slice_alloc fun:g_list_append fun:e_book_backend_file_get_changes fun:e_book_backend_sync_get_changes fun:_e_book_backend_get_changes fun:e_book_backend_get_changes } # ==32743== 7,767 bytes in 1 blocks are possibly lost in loss record 33 of 41 # ==32743== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==32743== by 0x4457862: g_try_malloc (gmem.c:196) # ==32743== by 0x40DE9B5: __os_umalloc_eds (os_alloc.c:99) # ==32743== by 0x4088DBA: __db_goff_eds (db_overflow.c:122) # ==32743== by 0x40918B0: __db_ret_eds (db_ret.c:52) # ==32743== by 0x407B15C: __db_c_get_eds (db_cam.c:859) # ==32743== by 0x4072E35: __db_get_eds (db_am.c:505) # ==32743== by 0x50F4AB4: e_book_backend_file_changes_foreach_key (e-book-backend-file.c:703) # ==32743== by 0x4682927: e_dbhash_foreach_key (e-dbhash.c:141) # ==32743== by 0x50F4EF4: e_book_backend_file_get_changes (e-book-backend-file.c:806) # ==32743== by 0x4043322: e_book_backend_sync_get_changes (e-book-backend-sync.c:236) # ==32743== by 0x4043D79: _e_book_backend_get_changes (e-book-backend-sync.c:456) # ==32743== by 0x40453C4: e_book_backend_get_changes (e-book-backend.c:350) # ==32743== by 0x4049F2F: impl_GNOME_Evolution_Addressbook_Book_getChanges (e-data-book.c:184) # ==32743== by 0x40388EC: _ORBIT_skel_small_GNOME_Evolution_Addressbook_Book_getChanges (Evolution-DataServer-Addressbook-common.c:80) # ==32743== by 0x43AC436: ORBit_POAObject_invoke (poa.c:1142) # ==32743== by 0x43B2664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==32743== by 0x439F5D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==32743== by 0x43B02B9: ORBit_POAObject_handle_request (poa.c:1351) # ==32743== by 0x43B095B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) { e_book_backend_get_changes II Memcheck:Leak fun:malloc fun:g_try_malloc fun:__os_umalloc_eds fun:__db_goff_eds fun:__db_ret_eds fun:__db_c_get_eds fun:__db_get_eds fun:e_book_backend_file_changes_foreach_key fun:e_dbhash_foreach_key fun:e_book_backend_file_get_changes fun:e_book_backend_sync_get_changes fun:_e_book_backend_get_changes fun:e_book_backend_get_changes } # ==32743== 207,748 bytes in 410 blocks are definitely lost in loss record 40 of 41 # ==32743== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==32743== by 0x4457862: g_try_malloc (gmem.c:196) # ==32743== by 0x40DE9B5: __os_umalloc_eds (os_alloc.c:99) # ==32743== by 0x4091B4E: __db_retcopy_eds (db_ret.c:130) # ==32743== by 0x4091AB0: __db_ret_eds (db_ret.c:74) # ==32743== by 0x407B15C: __db_c_get_eds (db_cam.c:859) # ==32743== by 0x50F41F3: e_book_backend_file_get_contact_list (e-book-backend-file.c:465) # ==32743== by 0x4043135: e_book_backend_sync_get_contact_list (e-book-backend-sync.c:205) # ==32743== by 0x4043CFD: _e_book_backend_get_contact_list (e-book-backend-sync.c:442) # ==32743== by 0x4044F87: e_book_backend_get_contact_list (e-book-backend.c:285) # ==32743== by 0x4049B9D: impl_GNOME_Evolution_Addressbook_Book_getContactList (e-data-book.c:78) # ==32743== by 0x403891F: _ORBIT_skel_small_GNOME_Evolution_Addressbook_Book_getContactList (Evolution-DataServer-Addressbook-common.c:84) # ==32743== by 0x43AC436: ORBit_POAObject_invoke (poa.c:1142) # ==32743== by 0x43B2664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==32743== by 0x439F5D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==32743== by 0x43B02B9: ORBit_POAObject_handle_request (poa.c:1351) # ==32743== by 0x43B095B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==32743== by 0x439813A: giop_thread_queue_process (giop.c:771) # ==32743== by 0x43989D7: giop_request_handler_thread (giop.c:481) # ==32743== by 0x4479686: g_thread_pool_thread_proxy (gthreadpool.c:265) { e_book_backend_get_contact_list Memcheck:Leak fun:malloc fun:g_try_malloc fun:__os_umalloc_eds fun:__db_retcopy_eds fun:__db_ret_eds fun:__db_c_get_eds fun:e_book_backend_file_get_contact_list fun:e_book_backend_sync_get_contact_list fun:_e_book_backend_get_contact_list fun:e_book_backend_get_contact_list } # ==413== 238 bytes in 1 blocks are possibly lost in loss record 18 of 42 # ==413== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==413== by 0x4457862: g_try_malloc (gmem.c:196) # ==413== by 0x40DE9B5: __os_umalloc_eds (os_alloc.c:99) # ==413== by 0x4091B4E: __db_retcopy_eds (db_ret.c:130) # ==413== by 0x4091AB0: __db_ret_eds (db_ret.c:74) # ==413== by 0x407B15C: __db_c_get_eds (db_cam.c:859) # ==413== by 0x4072E35: __db_get_eds (db_am.c:505) # ==413== by 0x50F4AB4: e_book_backend_file_changes_foreach_key (e-book-backend-file.c:703) # ==413== by 0x4682927: e_dbhash_foreach_key (e-dbhash.c:141) # ==413== by 0x50F4EF4: e_book_backend_file_get_changes (e-book-backend-file.c:806) # ==413== by 0x4043322: e_book_backend_sync_get_changes (e-book-backend-sync.c:236) # ==413== by 0x4043D79: _e_book_backend_get_changes (e-book-backend-sync.c:456) # ==413== by 0x40453C4: e_book_backend_get_changes (e-book-backend.c:350) # ==413== by 0x4049F2F: impl_GNOME_Evolution_Addressbook_Book_getChanges (e-data-book.c:184) # ==413== by 0x40388EC: _ORBIT_skel_small_GNOME_Evolution_Addressbook_Book_getChanges (Evolution-DataServer-Addressbook-common.c:80) # ==413== by 0x43AC436: ORBit_POAObject_invoke (poa.c:1142) # ==413== by 0x43B2664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==413== by 0x439F5D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==413== by 0x43B02B9: ORBit_POAObject_handle_request (poa.c:1351) # ==413== by 0x43B095B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) { e_book_backend_file_get_changes Memcheck:Leak fun:malloc fun:g_try_malloc fun:__os_umalloc_eds fun:__db_retcopy_eds fun:__db_ret_eds fun:__db_c_get_eds fun:__db_get_eds fun:e_book_backend_file_changes_foreach_key fun:e_dbhash_foreach_key fun:e_book_backend_file_get_changes } # ==413== 12,696 (1,056 direct, 11,640 indirect) bytes in 88 blocks are definitely lost in loss record 30 of 42 # ==413== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==413== by 0x4457A95: g_malloc (gmem.c:131) # ==413== by 0x446CE32: g_slice_alloc (gslice.c:824) # ==413== by 0x444D99A: g_list_append (glist.c:69) # ==413== by 0x50F4B4C: e_book_backend_file_changes_foreach_key (e-book-backend-file.c:717) # ==413== by 0x4682927: e_dbhash_foreach_key (e-dbhash.c:141) # ==413== by 0x50F4EF4: e_book_backend_file_get_changes (e-book-backend-file.c:806) # ==413== by 0x4043322: e_book_backend_sync_get_changes (e-book-backend-sync.c:236) # ==413== by 0x4043D79: _e_book_backend_get_changes (e-book-backend-sync.c:456) # ==413== by 0x40453C4: e_book_backend_get_changes (e-book-backend.c:350) # ==413== by 0x4049F2F: impl_GNOME_Evolution_Addressbook_Book_getChanges (e-data-book.c:184) # ==413== by 0x40388EC: _ORBIT_skel_small_GNOME_Evolution_Addressbook_Book_getChanges (Evolution-DataServer-Addressbook-common.c:80) # ==413== by 0x43AC436: ORBit_POAObject_invoke (poa.c:1142) # ==413== by 0x43B2664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==413== by 0x439F5D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==413== by 0x43B02B9: ORBit_POAObject_handle_request (poa.c:1351) # ==413== by 0x43B095B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==413== by 0x439813A: giop_thread_queue_process (giop.c:771) # ==413== by 0x43989D7: giop_request_handler_thread (giop.c:481) # ==413== by 0x4479686: g_thread_pool_thread_proxy (gthreadpool.c:265) { e_book_backend_file_get_changes II Memcheck:Leak fun:malloc fun:g_malloc fun:g_slice_alloc fun:g_list_append fun:e_book_backend_file_changes_foreach_key fun:e_dbhash_foreach_key fun:e_book_backend_file_get_changes } # ==413== 214,821 bytes in 409 blocks are definitely lost in loss record 41 of 42 # ==413== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==413== by 0x4457862: g_try_malloc (gmem.c:196) # ==413== by 0x40DE9B5: __os_umalloc_eds (os_alloc.c:99) # ==413== by 0x4091B4E: __db_retcopy_eds (db_ret.c:130) # ==413== by 0x4091AB0: __db_ret_eds (db_ret.c:74) # ==413== by 0x407B15C: __db_c_get_eds (db_cam.c:859) # ==413== by 0x4072E35: __db_get_eds (db_am.c:505) # ==413== by 0x50F4AB4: e_book_backend_file_changes_foreach_key (e-book-backend-file.c:703) # ==413== by 0x4682927: e_dbhash_foreach_key (e-dbhash.c:141) # ==413== by 0x50F4EF4: e_book_backend_file_get_changes (e-book-backend-file.c:806) # ==413== by 0x4043322: e_book_backend_sync_get_changes (e-book-backend-sync.c:236) # ==413== by 0x4043D79: _e_book_backend_get_changes (e-book-backend-sync.c:456) # ==413== by 0x40453C4: e_book_backend_get_changes (e-book-backend.c:350) # ==413== by 0x4049F2F: impl_GNOME_Evolution_Addressbook_Book_getChanges (e-data-book.c:184) # ==413== by 0x40388EC: _ORBIT_skel_small_GNOME_Evolution_Addressbook_Book_getChanges (Evolution-DataServer-Addressbook-common.c:80) # ==413== by 0x43AC436: ORBit_POAObject_invoke (poa.c:1142) # ==413== by 0x43B2664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==413== by 0x439F5D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==413== by 0x43B02B9: ORBit_POAObject_handle_request (poa.c:1351) # ==413== by 0x43B095B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) { e_book_backend_file_get_changes III Memcheck:Leak fun:malloc fun:g_try_malloc fun:__os_umalloc_eds fun:__db_retcopy_eds fun:__db_ret_eds fun:__db_c_get_eds fun:__db_get_eds fun:e_book_backend_file_changes_foreach_key fun:e_dbhash_foreach_key fun:e_book_backend_file_get_changes } # ==2499== 20 bytes in 1 blocks are possibly lost in loss record 5 of 61 # ==2499== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==2499== by 0x4123F11: pvl_newlist (pvl.c:59) # ==2499== by 0x4114B18: icalproperty_new_impl (icalproperty.c:110) # ==2499== by 0x4114B83: icalproperty_new (icalproperty.c:127) # ==2499== by 0x41141C2: icalparser_add_line (icalparser.c:783) # ==2499== by 0x4113C1E: icalparser_parse (icalparser.c:587) # ==2499== by 0x4114A11: icalparser_parse_string (icalparser.c:1119) # ==2499== by 0x40F2511: build_change_list (e-cal-listener.c:521) # ==2499== by 0x40F260B: impl_notifyChanges (e-cal-listener.c:557) # ==2499== by 0x40D1F7D: _ORBIT_skel_small_GNOME_Evolution_Calendar_CalListener_notifyChanges (Evolution-DataServer-Calendar-common.c:220) # ==2499== by 0x4846436: ORBit_POAObject_invoke (poa.c:1142) # ==2499== by 0x484C664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==2499== by 0x48395D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==2499== by 0x484A2B9: ORBit_POAObject_handle_request (poa.c:1351) # ==2499== by 0x484A95B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==2499== by 0x483213A: giop_thread_queue_process (giop.c:771) # ==2499== by 0x48329D7: giop_request_handler_thread (giop.c:481) # ==2499== by 0x42FE686: g_thread_pool_thread_proxy (gthreadpool.c:265) # ==2499== by 0x42FCAFE: g_thread_create_proxy (gthread.c:635) # ==2499== by 0x425723F: start_thread (in /lib/tls/i686/cmov/libpthread-2.3.6.so) { build_change_list Memcheck:Leak fun:malloc fun:pvl_newlist fun:icalproperty_new_impl fun:icalproperty_new fun:icalparser_add_line fun:icalparser_parse fun:icalparser_parse_string fun:build_change_list } # ==4555== 1,284 bytes in 36 blocks are possibly lost in loss record 20 of 51 # ==4555== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==4555== by 0x42DDA95: g_malloc (gmem.c:131) # ==4555== by 0x42F2E32: g_slice_alloc (gslice.c:824) # ==4555== by 0x42F40AA: g_slist_append (gslist.c:69) # ==4555== by 0x40AB00C: scan_text (e-cal-component.c:653) # ==4555== by 0x40AB141: scan_property (e-cal-component.c:705) # ==4555== by 0x40AB7D5: scan_icalcomponent (e-cal-component.c:952) # ==4555== by 0x40ABC5A: e_cal_component_set_icalcomponent (e-cal-component.c:1114) # ==4555== by 0x40B855A: build_change_list (e-cal-listener.c:526) # ==4555== by 0x40B862B: impl_notifyChanges (e-cal-listener.c:557) # ==4555== by 0x4097F9D: _ORBIT_skel_small_GNOME_Evolution_Calendar_CalListener_notifyChanges (Evolution-DataServer-Calendar-common.c:220) # ==4555== by 0x4847436: ORBit_POAObject_invoke (poa.c:1142) # ==4555== by 0x484D664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==4555== by 0x483A5D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==4555== by 0x484B2B9: ORBit_POAObject_handle_request (poa.c:1351) # ==4555== by 0x484B95B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==4555== by 0x483313A: giop_thread_queue_process (giop.c:771) # ==4555== by 0x48339D7: giop_request_handler_thread (giop.c:481) # ==4555== by 0x42FF686: g_thread_pool_thread_proxy (gthreadpool.c:265) # ==4555== by 0x42FDAFE: g_thread_create_proxy (gthread.c:635) # ==4555== by 0x425823F: start_thread (in /lib/tls/i686/cmov/libpthread-2.3.6.so) # ==4555== by 0x455749D: clone (in /lib/tls/i686/cmov/libc-2.3.6.so) { build_change_list II Memcheck:Leak fun:malloc fun:g_malloc fun:g_slice_alloc fun:g_slist_append fun:scan_text fun:scan_property fun:scan_icalcomponent fun:e_cal_component_set_icalcomponent fun:build_change_list } # ==2499== 129,660 (1,040 direct, 128,620 indirect) bytes in 65 blocks are definitely lost in loss record 24 of 61 # ==2499== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==2499== by 0x4123FD7: pvl_new_element (pvl.c:105) # ==2499== by 0x4124122: pvl_push (pvl.c:181) # ==2499== by 0x410D1D7: icalcomponent_add_property (icalcomponent.c:403) # ==2499== by 0x411420E: icalparser_add_line (icalparser.c:792) # ==2499== by 0x4113C1E: icalparser_parse (icalparser.c:587) # ==2499== by 0x4114A11: icalparser_parse_string (icalparser.c:1119) # ==2499== by 0x40F2511: build_change_list (e-cal-listener.c:521) # ==2499== by 0x40F260B: impl_notifyChanges (e-cal-listener.c:557) # ==2499== by 0x40D1F7D: _ORBIT_skel_small_GNOME_Evolution_Calendar_CalListener_notifyChanges (Evolution-DataServer-Calendar-common.c:220) # ==2499== by 0x4846436: ORBit_POAObject_invoke (poa.c:1142) # ==2499== by 0x484C664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==2499== by 0x48395D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==2499== by 0x484A2B9: ORBit_POAObject_handle_request (poa.c:1351) # ==2499== by 0x484A95B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==2499== by 0x483213A: giop_thread_queue_process (giop.c:771) # ==2499== by 0x48329D7: giop_request_handler_thread (giop.c:481) # ==2499== by 0x42FE686: g_thread_pool_thread_proxy (gthreadpool.c:265) # ==2499== by 0x42FCAFE: g_thread_create_proxy (gthread.c:635) # ==2499== by 0x425723F: start_thread (in /lib/tls/i686/cmov/libpthread-2.3.6.so) { build_change_list III Memcheck:Leak fun:malloc fun:pvl_new_element fun:pvl_push fun:icalcomponent_add_property fun:icalparser_add_line fun:icalparser_parse fun:icalparser_parse_string fun:build_change_list } # ==2499== 100,156 (79,996 direct, 20,160 indirect) bytes in 3,563 blocks are definitely lost in loss record 28 of 61 # ==2499== at 0x401C7EF: calloc (vg_replace_malloc.c:279) # ==2499== by 0x42DC9FD: g_malloc0 (gmem.c:150) # ==2499== by 0x483D675: ORBit_alloc_by_tc (allocators.c:373) # ==2499== by 0x48389AC: ORBit_small_alloc (orbit-small.c:44) # ==2499== by 0x483D1CB: CORBA_exception_set_system (corba-env.c:98) # ==2499== by 0x484B49D: ORBit_POA_handle_request (poa.c:1555) # ==2499== by 0x484C802: ORBit_handle_request (orbit-adaptor.c:298) # ==2499== by 0x4835917: giop_connection_handle_input (giop-recv-buffer.c:1312) # ==2499== by 0x4853DBC: link_connection_io_handler (linc-connection.c:1412) # ==2499== by 0x485665F: link_source_dispatch (linc-source.c:159) # ==2499== by 0x42D499A: g_main_context_dispatch (gmain.c:2064) # ==2499== by 0x42D7EB5: g_main_context_iterate (gmain.c:2697) # ==2499== by 0x42D8276: g_main_loop_run (gmain.c:2905) # ==2499== by 0x485231F: link_io_thread_fn (linc.c:396) # ==2499== by 0x42FCAFE: g_thread_create_proxy (gthread.c:635) # ==2499== by 0x425723F: start_thread (in /lib/tls/i686/cmov/libpthread-2.3.6.so) # ==2499== by 0x455649D: clone (in /lib/tls/i686/cmov/libc-2.3.6.so) { CORBA exception Memcheck:Leak fun:calloc fun:g_malloc* fun:ORBit_alloc_by_tc fun:ORBit_small_alloc fun:CORBA_exception_set_system } # ==4555== 1,721,282 (450,615 direct, 1,270,667 indirect) bytes in 11,907 blocks are definitely lost in loss record 50 of 51 # ==4555== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==4555== by 0x42DDA95: g_malloc (gmem.c:131) # ==4555== by 0x483E95D: ORBit_alloc_string (allocators.c:228) # ==4555== by 0x483E618: CORBA_string_dup (corba-string.c:22) # ==4555== by 0x483E14A: CORBA_exception_set (corba-env.c:118) # ==4555== by 0x483E1F3: CORBA_exception_set_system (corba-env.c:102) # ==4555== by 0x484C49D: ORBit_POA_handle_request (poa.c:1555) # ==4555== by 0x484D802: ORBit_handle_request (orbit-adaptor.c:298) # ==4555== by 0x4836917: giop_connection_handle_input (giop-recv-buffer.c:1312) # ==4555== by 0x4854DBC: link_connection_io_handler (linc-connection.c:1412) # ==4555== by 0x485765F: link_source_dispatch (linc-source.c:159) # ==4555== by 0x42D599A: g_main_context_dispatch (gmain.c:2064) # ==4555== by 0x42D8EB5: g_main_context_iterate (gmain.c:2697) # ==4555== by 0x42D9276: g_main_loop_run (gmain.c:2905) # ==4555== by 0x485331F: link_io_thread_fn (linc.c:396) # ==4555== by 0x42FDAFE: g_thread_create_proxy (gthread.c:635) # ==4555== by 0x425823F: start_thread (in /lib/tls/i686/cmov/libpthread-2.3.6.so) # ==4555== by 0x455749D: clone (in /lib/tls/i686/cmov/libc-2.3.6.so) { CORBA exception II Memcheck:Leak fun:malloc fun:g_malloc fun:ORBit_alloc_string fun:CORBA_string_dup fun:CORBA_exception_set fun:CORBA_exception_set_system } # ==2499== 115,204 (9,728 direct, 105,476 indirect) bytes in 76 blocks are definitely lost in loss record 29 of 61 # ==2499== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==2499== by 0x4121788: icalvalue_new_impl (icalvalue.c:75) # ==2499== by 0x410A6CA: icalvalue_new_text (icalderivedvalue.c:273) # ==2499== by 0x41220E4: icalvalue_new_from_string_with_error (icalvalue.c:468) # ==2499== by 0x4122782: icalvalue_new_from_string (icalvalue.c:665) # ==2499== by 0x4114560: icalparser_add_line (icalparser.c:946) # ==2499== by 0x4113C1E: icalparser_parse (icalparser.c:587) # ==2499== by 0x4114A11: icalparser_parse_string (icalparser.c:1119) # ==2499== by 0x40F2511: build_change_list (e-cal-listener.c:521) # ==2499== by 0x40F260B: impl_notifyChanges (e-cal-listener.c:557) # ==2499== by 0x40D1F7D: _ORBIT_skel_small_GNOME_Evolution_Calendar_CalListener_notifyChanges (Evolution-DataServer-Calendar-common.c:220) # ==2499== by 0x4846436: ORBit_POAObject_invoke (poa.c:1142) # ==2499== by 0x484C664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==2499== by 0x48395D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==2499== by 0x484A2B9: ORBit_POAObject_handle_request (poa.c:1351) # ==2499== by 0x484A95B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==2499== by 0x483213A: giop_thread_queue_process (giop.c:771) # ==2499== by 0x48329D7: giop_request_handler_thread (giop.c:481) # ==2499== by 0x42FE686: g_thread_pool_thread_proxy (gthreadpool.c:265) # ==2499== by 0x42FCAFE: g_thread_create_proxy (gthread.c:635) { build_change_list IV Memcheck:Leak fun:malloc fun:icalvalue_new_impl fun:icalvalue_new_text fun:icalvalue_new_from_string_with_error fun:icalvalue_new_from_string fun:icalparser_add_line fun:icalparser_parse fun:icalparser_parse_string fun:build_change_list } # ==19927== 25,544 (15,656 direct, 9,888 indirect) bytes in 103 blocks are definitely lost in loss record 29 of 38 # ==19927== at 0x4C232CB: malloc (vg_replace_malloc.c:207) # ==19927== by 0x5BEFD19: icalvalue_new_impl (icalvalue.c:75) # ==19927== by 0x5BF03F1: icalvalue_new_from_string_with_error (icalvalue.c:550) # ==19927== by 0x5BE2A27: icalparser_add_line (icalparser.c:959) # ==19927== by 0x5BE2F6C: icalparser_parse (icalparser.c:591) # ==19927== by 0x5BE3180: icalparser_parse_string (icalparser.c:1132) # ==19927== by 0x6BAF30: EvolutionCalendarSource::insertItem(std::string const&, SyncItem const&) (EvolutionCalendarSource.cpp:288) # ==19927== by 0x6B4B2F: TrackingSyncSource::addItemThrow(SyncItem&) (TrackingSyncSource.cpp:275) # ==19927== by 0x6724F8: EvolutionSyncSource::processItem(char const*, SyncMLStatus (EvolutionSyncSource::*)(SyncItem&), SyncItem&, bool) (EvolutionSyncSource.cpp:547) # ==19927== by 0x672684: EvolutionSyncSource::addItem(SyncItem&) (EvolutionSyncSource.cpp:499) # ==19927== by 0x524F52: TestEvolutionSyncSource::addItem(SyncItem&) (client-test-app.cpp:89) # ==19927== by 0x53E34B: importItem(EvolutionSyncSource*, std::string&) (ClientTest.cpp:164) # ==19927== by 0x5D4A00: LocalTests::insertManyItems(CreateSource, int, int, int) (ClientTest.cpp:585) # ==19927== by 0x5AC1F7: LocalTests::testManyChanges() (ClientTest.cpp:823) # ==19927== by 0x60CCE1: CppUnit::TestCaller::runTest() (TestCaller.h:166) # ==19927== by 0x6795846: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19927== by 0x6787C43: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19927== by 0x6791758: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19927== by 0x679149B: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19927== by 0x679D23F: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19927== by 0x67954DC: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19927== by 0x6795E3B: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19927== by 0x6795D65: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19927== by 0x6795E3B: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19927== by 0x6795D65: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19927== by 0x679CFC9: CppUnit::TestResult::runTest(CppUnit::Test*) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19927== by 0x679F5C1: CppUnit::TestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19927== by 0x67A25DA: CppUnit::TextTestRunner::run(std::string, bool, bool, bool) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==19927== by 0x613976: main (client-test-main.cpp:255) { icalvalue_new_impl Memcheck:Leak fun:malloc fun:icalvalue_new_impl fun:icalvalue_new_from_string_with_error fun:icalparser_add_line fun:icalparser_parse fun:icalparser_parse_string } # ==2499== 27,832 (700 direct, 27,132 indirect) bytes in 35 blocks are definitely lost in loss record 31 of 61 # ==2499== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==2499== by 0x4123F11: pvl_newlist (pvl.c:59) # ==2499== by 0x410C958: icalcomponent_new_impl (icalcomponent.c:129) # ==2499== by 0x410C9C9: icalcomponent_new (icalcomponent.c:146) # ==2499== by 0x4113F82: icalparser_add_line (icalparser.c:708) # ==2499== by 0x4113C1E: icalparser_parse (icalparser.c:587) # ==2499== by 0x4114A11: icalparser_parse_string (icalparser.c:1119) # ==2499== by 0x40F2511: build_change_list (e-cal-listener.c:521) # ==2499== by 0x40F260B: impl_notifyChanges (e-cal-listener.c:557) # ==2499== by 0x40D1F7D: _ORBIT_skel_small_GNOME_Evolution_Calendar_CalListener_notifyChanges (Evolution-DataServer-Calendar-common.c:220) # ==2499== by 0x4846436: ORBit_POAObject_invoke (poa.c:1142) # ==2499== by 0x484C664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==2499== by 0x48395D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==2499== by 0x484A2B9: ORBit_POAObject_handle_request (poa.c:1351) # ==2499== by 0x484A95B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==2499== by 0x483213A: giop_thread_queue_process (giop.c:771) # ==2499== by 0x48329D7: giop_request_handler_thread (giop.c:481) # ==2499== by 0x42FE686: g_thread_pool_thread_proxy (gthreadpool.c:265) # ==2499== by 0x42FCAFE: g_thread_create_proxy (gthread.c:635) # ==2499== by 0x425723F: start_thread (in /lib/tls/i686/cmov/libpthread-2.3.6.so) { build_change_list V Memcheck:Leak fun:malloc fun:pvl_newlist fun:icalcomponent_new_impl fun:icalcomponent_new fun:icalparser_add_line fun:icalparser_parse fun:icalparser_parse_string fun:build_change_list } # ==3045== 113,348 (10,368 direct, 102,980 indirect) bytes in 81 blocks are definitely lost in loss record 37 of 61 # ==3045== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==3045== by 0x4121788: icalvalue_new_impl (icalvalue.c:75) # ==3045== by 0x412234A: icalvalue_new_from_string_with_error (icalvalue.c:550) # ==3045== by 0x4122782: icalvalue_new_from_string (icalvalue.c:665) # ==3045== by 0x4114560: icalparser_add_line (icalparser.c:946) # ==3045== by 0x4113C1E: icalparser_parse (icalparser.c:587) # ==3045== by 0x4114A11: icalparser_parse_string (icalparser.c:1119) # ==3045== by 0x40F2511: build_change_list (e-cal-listener.c:521) # ==3045== by 0x40F260B: impl_notifyChanges (e-cal-listener.c:557) # ==3045== by 0x40D1F7D: _ORBIT_skel_small_GNOME_Evolution_Calendar_CalListener_notifyChanges (Evolution-DataServer-Calendar-common.c:220) # ==3045== by 0x4846436: ORBit_POAObject_invoke (poa.c:1142) # ==3045== by 0x484C664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==3045== by 0x48395D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==3045== by 0x484A2B9: ORBit_POAObject_handle_request (poa.c:1351) # ==3045== by 0x484A95B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==3045== by 0x483213A: giop_thread_queue_process (giop.c:771) # ==3045== by 0x48329D7: giop_request_handler_thread (giop.c:481) # ==3045== by 0x42FE686: g_thread_pool_thread_proxy (gthreadpool.c:265) # ==3045== by 0x42FCAFE: g_thread_create_proxy (gthread.c:635) # ==3045== by 0x425723F: start_thread (in /lib/tls/i686/cmov/libpthread-2.3.6.so) { build_change_list VI Memcheck:Leak fun:malloc fun:icalvalue_new_impl fun:icalvalue_new_from_string_with_error fun:icalvalue_new_from_string fun:icalparser_add_line fun:icalparser_parse fun:icalparser_parse_string fun:build_change_list } # ==4994== 981 bytes in 36 blocks are possibly lost in loss record 20 of 51 # ==4994== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==4994== by 0x42DDA95: g_malloc (gmem.c:131) # ==4994== by 0x40AAFD2: scan_text (e-cal-component.c:649) # ==4994== by 0x40AB141: scan_property (e-cal-component.c:705) # ==4994== by 0x40AB7D5: scan_icalcomponent (e-cal-component.c:952) # ==4994== by 0x40ABC5A: e_cal_component_set_icalcomponent (e-cal-component.c:1114) # ==4994== by 0x40B855A: build_change_list (e-cal-listener.c:526) # ==4994== by 0x40B862B: impl_notifyChanges (e-cal-listener.c:557) # ==4994== by 0x4097F9D: _ORBIT_skel_small_GNOME_Evolution_Calendar_CalListener_notifyChanges (Evolution-DataServer-Calendar-common.c:220) # ==4994== by 0x4847436: ORBit_POAObject_invoke (poa.c:1142) # ==4994== by 0x484D664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==4994== by 0x483A5D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==4994== by 0x484B2B9: ORBit_POAObject_handle_request (poa.c:1351) # ==4994== by 0x484B95B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==4994== by 0x483313A: giop_thread_queue_process (giop.c:771) # ==4994== by 0x48339D7: giop_request_handler_thread (giop.c:481) # ==4994== by 0x42FF686: g_thread_pool_thread_proxy (gthreadpool.c:265) # ==4994== by 0x42FDAFE: g_thread_create_proxy (gthread.c:635) # ==4994== by 0x425823F: start_thread (in /lib/tls/i686/cmov/libpthread-2.3.6.so) # ==4994== by 0x455749D: clone (in /lib/tls/i686/cmov/libc-2.3.6.so) { build_change_list VII Memcheck:Leak fun:malloc fun:g_malloc fun:scan_text fun:scan_property fun:scan_icalcomponent fun:e_cal_component_set_icalcomponent fun:build_change_list } # ==4994== 1,751,365 (450,701 direct, 1,300,664 indirect) bytes in 11,902 blocks are definitely lost in loss record 50 of 51 # ==4994== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==4994== by 0x42DDA95: g_malloc (gmem.c:131) # ==4994== by 0x42F2E32: g_slice_alloc (gslice.c:824) # ==4994== by 0x42F3514: g_slice_alloc0 (gslice.c:833) # ==4994== by 0x428FC2B: g_type_create_instance (gtype.c:1549) # ==4994== by 0x4274B21: g_object_constructor (gobject.c:1046) # ==4994== by 0x4275308: g_object_newv (gobject.c:937) # ==4994== by 0x4275EE6: g_object_new_valist (gobject.c:986) # ==4994== by 0x427609F: g_object_new (gobject.c:795) # ==4994== by 0x40AA9A7: e_cal_component_new (e-cal-component.c:457) # ==4994== by 0x40B853F: build_change_list (e-cal-listener.c:525) # ==4994== by 0x40B862B: impl_notifyChanges (e-cal-listener.c:557) # ==4994== by 0x4097F9D: _ORBIT_skel_small_GNOME_Evolution_Calendar_CalListener_notifyChanges (Evolution-DataServer-Calendar-common.c:220) # ==4994== by 0x4847436: ORBit_POAObject_invoke (poa.c:1142) # ==4994== by 0x484D664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==4994== by 0x483A5D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==4994== by 0x484B2B9: ORBit_POAObject_handle_request (poa.c:1351) # ==4994== by 0x484B95B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==4994== by 0x483313A: giop_thread_queue_process (giop.c:771) # ==4994== by 0x48339D7: giop_request_handler_thread (giop.c:481) # ==4994== by 0x42FF686: g_thread_pool_thread_proxy (gthreadpool.c:265) # ==4994== by 0x42FDAFE: g_thread_create_proxy (gthread.c:635) # ==4994== by 0x425823F: start_thread (in /lib/tls/i686/cmov/libpthread-2.3.6.so) # ==4994== by 0x455749D: clone (in /lib/tls/i686/cmov/libc-2.3.6.so) { build_change_list VIII Memcheck:Leak fun:malloc fun:g_malloc fun:g_slice_alloc fun:g_slice_alloc0 fun:g_type_create_instance fun:g_object_constructor fun:g_object_newv fun:g_object_new_valist fun:g_object_new fun:e_cal_component_new fun:build_change_list } # ==10697== 150,925 (15,744 direct, 135,181 indirect) bytes in 123 blocks are definitely lost in loss record 35 of 51 # ==10697== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==10697== by 0x40E786C: icalvalue_new_impl (icalvalue.c:75) # ==10697== by 0x40D0E07: icalvalue_new_integer (icalderivedvalue.c:505) # ==10697== by 0x40E8079: icalvalue_new_from_string_with_error (icalvalue.c:440) # ==10697== by 0x40E8866: icalvalue_new_from_string (icalvalue.c:665) # ==10697== by 0x40DA641: icalparser_add_line (icalparser.c:959) # ==10697== by 0x40D9CA3: icalparser_parse (icalparser.c:591) # ==10697== by 0x40DAAF2: icalparser_parse_string (icalparser.c:1132) # ==10697== by 0x40B8531: build_change_list (e-cal-listener.c:521) # ==10697== by 0x40B862B: impl_notifyChanges (e-cal-listener.c:557) # ==10697== by 0x4097F9D: _ORBIT_skel_small_GNOME_Evolution_Calendar_CalListener_notifyChanges (Evolution-DataServer-Calendar-common.c:220) # ==10697== by 0x4847436: ORBit_POAObject_invoke (poa.c:1142) # ==10697== by 0x484D664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==10697== by 0x483A5D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==10697== by 0x484B2B9: ORBit_POAObject_handle_request (poa.c:1351) # ==10697== by 0x484B95B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==10697== by 0x483313A: giop_thread_queue_process (giop.c:771) # ==10697== by 0x48339D7: giop_request_handler_thread (giop.c:481) # ==10697== by 0x42FF686: g_thread_pool_thread_proxy (gthreadpool.c:265) # ==10697== by 0x42FDAFE: g_thread_create_proxy (gthread.c:635) # ==10697== by 0x425823F: start_thread (in /lib/tls/i686/cmov/libpthread-2.3.6.so) # ==10697== by 0x455749D: clone (in /lib/tls/i686/cmov/libc-2.3.6.so) { build_change_list IX Memcheck:Leak fun:malloc fun:icalvalue_new_impl fun:icalvalue_new_integer fun:icalvalue_new_from_string_with_error fun:icalvalue_new_from_string fun:icalparser_add_line fun:icalparser_parse fun:icalparser_parse_string fun:build_change_list } # ==2499== 768 bytes in 6 blocks are possibly lost in loss record 32 of 61 # ==2499== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==2499== by 0x4121788: icalvalue_new_impl (icalvalue.c:75) # ==2499== by 0x412184A: icalvalue_new_clone (icalvalue.c:104) # ==2499== by 0x4114C06: icalproperty_new_clone (icalproperty.c:141) # ==2499== by 0x410CAC3: icalcomponent_new_clone (icalcomponent.c:199) # ==2499== by 0x40D81C8: cal_object_list_cb (e-cal.c:742) # ==2499== by 0x40D72DF: e_cal_marshal_VOID__INT_POINTER (e-cal-marshal.c:156) # ==2499== by 0x426D2CA: g_closure_invoke (gclosure.c:490) # ==2499== by 0x427F4D2: signal_emit_unlocked_R (gsignal.c:2440) # ==2499== by 0x4280EF2: g_signal_emit_valist (gsignal.c:2199) # ==2499== by 0x4281198: g_signal_emit (gsignal.c:2243) # ==2499== by 0x40F21C5: impl_notifyObjectListRequested (e-cal-listener.c:419) # ==2499== by 0x40D1E8C: _ORBIT_skel_small_GNOME_Evolution_Calendar_CalListener_notifyObjectListRequested (Evolution-DataServer-Calendar-common.c:200) # ==2499== by 0x4846436: ORBit_POAObject_invoke (poa.c:1142) # ==2499== by 0x484C664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==2499== by 0x48395D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==2499== by 0x484A2B9: ORBit_POAObject_handle_request (poa.c:1351) # ==2499== by 0x484A95B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==2499== by 0x483213A: giop_thread_queue_process (giop.c:771) # ==2499== by 0x48329D7: giop_request_handler_thread (giop.c:481) { cal_object_list_cb Memcheck:Leak fun:malloc fun:icalvalue_new_impl fun:icalvalue_new_clone fun:icalproperty_new_clone fun:icalcomponent_new_clone fun:cal_object_list_cb } # ==2499== 8,232 bytes in 3 blocks are possibly lost in loss record 50 of 61 # ==2499== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==2499== by 0x412193A: icalvalue_new_clone (icalvalue.c:147) # ==2499== by 0x4114C06: icalproperty_new_clone (icalproperty.c:141) # ==2499== by 0x410CAC3: icalcomponent_new_clone (icalcomponent.c:199) # ==2499== by 0x40E4B67: e_cal_component_clone (e-cal-component.c:518) # ==2499== by 0x40D846E: cal_get_changes_cb (e-cal.c:843) # ==2499== by 0x40D72DF: e_cal_marshal_VOID__INT_POINTER (e-cal-marshal.c:156) # ==2499== by 0x426D2CA: g_closure_invoke (gclosure.c:490) # ==2499== by 0x427F4D2: signal_emit_unlocked_R (gsignal.c:2440) # ==2499== by 0x4280EF2: g_signal_emit_valist (gsignal.c:2199) # ==2499== by 0x4281198: g_signal_emit (gsignal.c:2243) # ==2499== by 0x40F2655: impl_notifyChanges (e-cal-listener.c:559) # ==2499== by 0x40D1F7D: _ORBIT_skel_small_GNOME_Evolution_Calendar_CalListener_notifyChanges (Evolution-DataServer-Calendar-common.c:220) # ==2499== by 0x4846436: ORBit_POAObject_invoke (poa.c:1142) # ==2499== by 0x484C664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==2499== by 0x48395D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==2499== by 0x484A2B9: ORBit_POAObject_handle_request (poa.c:1351) # ==2499== by 0x484A95B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==2499== by 0x483213A: giop_thread_queue_process (giop.c:771) # ==2499== by 0x48329D7: giop_request_handler_thread (giop.c:481) { cal_get_changes_cb Memcheck:Leak fun:malloc fun:icalvalue_new_clone fun:icalproperty_new_clone fun:icalcomponent_new_clone fun:e_cal_component_clone fun:cal_get_changes_cb } # ==2787== 20 bytes in 1 blocks are possibly lost in loss record 4 of 62 # ==2787== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==2787== by 0x4123F11: pvl_newlist (pvl.c:59) # ==2787== by 0x4114B18: icalproperty_new_impl (icalproperty.c:110) # ==2787== by 0x4114BAF: icalproperty_new_clone (icalproperty.c:134) # ==2787== by 0x410CAC3: icalcomponent_new_clone (icalcomponent.c:199) # ==2787== by 0x40D81C8: cal_object_list_cb (e-cal.c:742) # ==2787== by 0x40D72DF: e_cal_marshal_VOID__INT_POINTER (e-cal-marshal.c:156) # ==2787== by 0x426D2CA: g_closure_invoke (gclosure.c:490) # ==2787== by 0x427F4D2: signal_emit_unlocked_R (gsignal.c:2440) # ==2787== by 0x4280EF2: g_signal_emit_valist (gsignal.c:2199) # ==2787== by 0x4281198: g_signal_emit (gsignal.c:2243) # ==2787== by 0x40F21C5: impl_notifyObjectListRequested (e-cal-listener.c:419) # ==2787== by 0x40D1E8C: _ORBIT_skel_small_GNOME_Evolution_Calendar_CalListener_notifyObjectListRequested (Evolution-DataServer-Calendar-common.c:200) # ==2787== by 0x4846436: ORBit_POAObject_invoke (poa.c:1142) # ==2787== by 0x484C664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==2787== by 0x48395D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==2787== by 0x484A2B9: ORBit_POAObject_handle_request (poa.c:1351) # ==2787== by 0x484A95B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==2787== by 0x483213A: giop_thread_queue_process (giop.c:771) # ==2787== by 0x48329D7: giop_request_handler_thread (giop.c:481) { cal_object_list_cb Memcheck:Leak fun:malloc fun:pvl_newlist fun:icalproperty_new_impl fun:icalproperty_new_clone fun:icalcomponent_new_clone fun:cal_object_list_cb } # ==2787== 2,797,740 (154,999 direct, 2,642,741 indirect) bytes in 4,206 blocks are definitely lost in loss record 59 of 62 # ==2787== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==2787== by 0x42DCA95: g_malloc (gmem.c:131) # ==2787== by 0x42F5618: g_strdup (gstrfuncs.c:92) # ==2787== by 0x40D7DCB: cal_object_created_cb (e-cal.c:585) # ==2787== by 0x40D7145: e_cal_marshal_VOID__INT_STRING (e-cal-marshal.c:82) # ==2787== by 0x426D2CA: g_closure_invoke (gclosure.c:490) # ==2787== by 0x427F4D2: signal_emit_unlocked_R (gsignal.c:2440) # ==2787== by 0x4280EF2: g_signal_emit_valist (gsignal.c:2199) # ==2787== by 0x4281198: g_signal_emit (gsignal.c:2243) # ==2787== by 0x40F1C3F: impl_notifyObjectCreated (e-cal-listener.c:247) # ==2787== by 0x40D1D19: _ORBIT_skel_small_GNOME_Evolution_Calendar_CalListener_notifyObjectCreated (Evolution-DataServer-Calendar-common.c:168) # ==2787== by 0x4846436: ORBit_POAObject_invoke (poa.c:1142) # ==2787== by 0x484C664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==2787== by 0x48395D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==2787== by 0x484A2B9: ORBit_POAObject_handle_request (poa.c:1351) # ==2787== by 0x484A95B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==2787== by 0x483213A: giop_thread_queue_process (giop.c:771) # ==2787== by 0x48329D7: giop_request_handler_thread (giop.c:481) # ==2787== by 0x42FE686: g_thread_pool_thread_proxy (gthreadpool.c:265) # ==2787== by 0x42FCAFE: g_thread_create_proxy (gthread.c:635) { cal_object_created_cb Memcheck:Leak fun:malloc fun:g_malloc fun:g_strdup fun:cal_object_created_cb } # ==3291== 82,574 bytes in 476 blocks are definitely lost in loss record 42 of 43 # ==3291== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==3291== by 0x4457A95: g_malloc (gmem.c:131) # ==3291== by 0x4470618: g_strdup (gstrfuncs.c:92) # ==3291== by 0x469673B: e_xml_to_hash (e-xml-hash-utils.c:81) # ==3291== by 0x4696A1E: e_xmlhash_new (e-xml-hash-utils.c:204) # ==3291== by 0x52118B4: e_cal_backend_file_compute_changes (e-cal-backend-file.c:1614) # ==3291== by 0x5211B74: e_cal_backend_file_get_changes (e-cal-backend-file.c:1681) # ==3291== by 0x412DF3C: e_cal_backend_sync_get_changes (e-cal-backend-sync.c:606) # ==3291== by 0x412EC23: _e_cal_backend_get_changes (e-cal-backend-sync.c:926) # ==3291== by 0x4125B1F: e_cal_backend_get_changes (e-cal-backend.c:852) # ==3291== by 0x412F6B2: impl_Cal_getChanges (e-data-cal.c:267) # ==3291== by 0x411DC12: _ORBIT_skel_small_GNOME_Evolution_Calendar_Cal_getChanges (Evolution-DataServer-Calendar-common.c:100) # ==3291== by 0x43AC436: ORBit_POAObject_invoke (poa.c:1142) # ==3291== by 0x43B2664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==3291== by 0x439F5D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==3291== by 0x43B02B9: ORBit_POAObject_handle_request (poa.c:1351) # ==3291== by 0x43B095B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==3291== by 0x439813A: giop_thread_queue_process (giop.c:771) # ==3291== by 0x43989D7: giop_request_handler_thread (giop.c:481) # ==3291== by 0x4479686: g_thread_pool_thread_proxy (gthreadpool.c:265) { e_cal_backend_get_changes Memcheck:Leak fun:malloc fun:g_malloc fun:g_strdup fun:e_xml_to_hash fun:e_xmlhash_new fun:e_cal_backend_file_compute_changes fun:e_cal_backend_file_get_changes fun:e_cal_backend_sync_get_changes fun:_e_cal_backend_get_changes fun:e_cal_backend_get_changes } # ==3659== 32 bytes in 1 blocks are possibly lost in loss record 3 of 63 # ==3659== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==3659== by 0x4112935: icalparameter_new_impl (icalparameter.c:53) # ==3659== by 0x4100D4F: icalparameter_new_from_value_string (icalderivedparameter.c:1451) # ==3659== by 0x4114391: icalparser_add_line (icalparser.c:857) # ==3659== by 0x4113C1E: icalparser_parse (icalparser.c:587) # ==3659== by 0x4114A11: icalparser_parse_string (icalparser.c:1119) # ==3659== by 0x40F2511: build_change_list (e-cal-listener.c:521) # ==3659== by 0x40F260B: impl_notifyChanges (e-cal-listener.c:557) # ==3659== by 0x40D1F7D: _ORBIT_skel_small_GNOME_Evolution_Calendar_CalListener_notifyChanges (Evolution-DataServer-Calendar-common.c:220) # ==3659== by 0x4846436: ORBit_POAObject_invoke (poa.c:1142) # ==3659== by 0x484C664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==3659== by 0x48395D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==3659== by 0x484A2B9: ORBit_POAObject_handle_request (poa.c:1351) # ==3659== by 0x484A95B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==3659== by 0x483213A: giop_thread_queue_process (giop.c:771) # ==3659== by 0x48329D7: giop_request_handler_thread (giop.c:481) # ==3659== by 0x42FE686: g_thread_pool_thread_proxy (gthreadpool.c:265) # ==3659== by 0x42FCAFE: g_thread_create_proxy (gthread.c:635) # ==3659== by 0x425723F: start_thread (in /lib/tls/i686/cmov/libpthread-2.3.6.so) # ==3659== by 0x455649D: clone (in /lib/tls/i686/cmov/libc-2.3.6.so) { build_change_list Memcheck:Leak fun:malloc fun:icalparameter_new_impl fun:icalparameter_new_from_value_string fun:icalparser_add_line fun:icalparser_parse fun:icalparser_parse_string fun:build_change_list } # ==3659== 33 bytes in 1 blocks are possibly lost in loss record 4 of 63 # ==3659== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==3659== by 0x44F617F: strdup (in /lib/tls/i686/cmov/libc-2.3.6.so) # ==3659== by 0x4111A1C: icalmemory_strdup (icalmemory.c:240) # ==3659== by 0x410A76C: icalvalue_set_text (icalderivedvalue.c:289) # ==3659== by 0x410A6FA: icalvalue_new_text (icalderivedvalue.c:276) # ==3659== by 0x41220E4: icalvalue_new_from_string_with_error (icalvalue.c:468) # ==3659== by 0x4122782: icalvalue_new_from_string (icalvalue.c:665) # ==3659== by 0x4114560: icalparser_add_line (icalparser.c:946) # ==3659== by 0x4113C1E: icalparser_parse (icalparser.c:587) # ==3659== by 0x4114A11: icalparser_parse_string (icalparser.c:1119) # ==3659== by 0x40F2511: build_change_list (e-cal-listener.c:521) # ==3659== by 0x40F260B: impl_notifyChanges (e-cal-listener.c:557) # ==3659== by 0x40D1F7D: _ORBIT_skel_small_GNOME_Evolution_Calendar_CalListener_notifyChanges (Evolution-DataServer-Calendar-common.c:220) # ==3659== by 0x4846436: ORBit_POAObject_invoke (poa.c:1142) # ==3659== by 0x484C664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==3659== by 0x48395D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==3659== by 0x484A2B9: ORBit_POAObject_handle_request (poa.c:1351) # ==3659== by 0x484A95B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==3659== by 0x483213A: giop_thread_queue_process (giop.c:771) # ==3659== by 0x48329D7: giop_request_handler_thread (giop.c:481) { build_change_list Memcheck:Leak fun:malloc fun:strdup fun:icalmemory_strdup fun:icalvalue_set_text fun:icalvalue_new_text fun:icalvalue_new_from_string_with_error fun:icalvalue_new_from_string fun:icalparser_add_line fun:icalparser_parse fun:icalparser_parse_string fun:build_change_list } # ==3659== 40 bytes in 2 blocks are possibly lost in loss record 5 of 63 # ==3659== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==3659== by 0x4123F11: pvl_newlist (pvl.c:59) # ==3659== by 0x4114B18: icalproperty_new_impl (icalproperty.c:110) # ==3659== by 0x4114BAF: icalproperty_new_clone (icalproperty.c:134) # ==3659== by 0x410CAC3: icalcomponent_new_clone (icalcomponent.c:199) # ==3659== by 0x40E4B67: e_cal_component_clone (e-cal-component.c:518) # ==3659== by 0x40D846E: cal_get_changes_cb (e-cal.c:843) # ==3659== by 0x40D72DF: e_cal_marshal_VOID__INT_POINTER (e-cal-marshal.c:156) # ==3659== by 0x426D2CA: g_closure_invoke (gclosure.c:490) # ==3659== by 0x427F4D2: signal_emit_unlocked_R (gsignal.c:2440) # ==3659== by 0x4280EF2: g_signal_emit_valist (gsignal.c:2199) # ==3659== by 0x4281198: g_signal_emit (gsignal.c:2243) # ==3659== by 0x40F2655: impl_notifyChanges (e-cal-listener.c:559) # ==3659== by 0x40D1F7D: _ORBIT_skel_small_GNOME_Evolution_Calendar_CalListener_notifyChanges (Evolution-DataServer-Calendar-common.c:220) # ==3659== by 0x4846436: ORBit_POAObject_invoke (poa.c:1142) # ==3659== by 0x484C664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==3659== by 0x48395D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==3659== by 0x484A2B9: ORBit_POAObject_handle_request (poa.c:1351) # ==3659== by 0x484A95B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==3659== by 0x483213A: giop_thread_queue_process (giop.c:771) { cal_get_changes_cb Memcheck:Leak fun:malloc fun:pvl_newlist fun:icalproperty_new_impl fun:icalproperty_new_clone fun:icalcomponent_new_clone fun:e_cal_component_clone fun:cal_get_changes_cb } # ==3659== 407 bytes in 17 blocks are possibly lost in loss record 20 of 63 # ==3659== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==3659== by 0x42DCA95: g_malloc (gmem.c:131) # ==3659== by 0x42F1E32: g_slice_alloc (gslice.c:824) # ==3659== by 0x42F30AA: g_slist_append (gslist.c:69) # ==3659== by 0x40E4FEC: scan_text (e-cal-component.c:653) # ==3659== by 0x40E5121: scan_property (e-cal-component.c:705) # ==3659== by 0x40E57B5: scan_icalcomponent (e-cal-component.c:952) # ==3659== by 0x40E5C3A: e_cal_component_set_icalcomponent (e-cal-component.c:1114) # ==3659== by 0x40E4B7C: e_cal_component_clone (e-cal-component.c:519) # ==3659== by 0x40D846E: cal_get_changes_cb (e-cal.c:843) # ==3659== by 0x40D72DF: e_cal_marshal_VOID__INT_POINTER (e-cal-marshal.c:156) # ==3659== by 0x426D2CA: g_closure_invoke (gclosure.c:490) # ==3659== by 0x427F4D2: signal_emit_unlocked_R (gsignal.c:2440) # ==3659== by 0x4280EF2: g_signal_emit_valist (gsignal.c:2199) # ==3659== by 0x4281198: g_signal_emit (gsignal.c:2243) # ==3659== by 0x40F2655: impl_notifyChanges (e-cal-listener.c:559) # ==3659== by 0x40D1F7D: _ORBIT_skel_small_GNOME_Evolution_Calendar_CalListener_notifyChanges (Evolution-DataServer-Calendar-common.c:220) # ==3659== by 0x4846436: ORBit_POAObject_invoke (poa.c:1142) # ==3659== by 0x484C664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==3659== by 0x48395D8: ORBit_small_invoke_adaptor (orbit-small.c:844) { cal_get_changes_cb Memcheck:Leak fun:malloc fun:g_malloc fun:g_slice_alloc fun:g_slist_append fun:scan_text fun:scan_property fun:scan_icalcomponent fun:e_cal_component_set_icalcomponent fun:e_cal_component_clone fun:cal_get_changes_cb } # ==3807== 571 bytes in 2 blocks are possibly lost in loss record 48 of 62 # ==3807== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==3807== by 0x44F617F: strdup (in /lib/tls/i686/cmov/libc-2.3.6.so) # ==3807== by 0x4111A1C: icalmemory_strdup (icalmemory.c:240) # ==3807== by 0x41218FE: icalvalue_new_clone (icalvalue.c:135) # ==3807== by 0x4114C06: icalproperty_new_clone (icalproperty.c:141) # ==3807== by 0x410CAC3: icalcomponent_new_clone (icalcomponent.c:199) # ==3807== by 0x40D81C8: cal_object_list_cb (e-cal.c:742) # ==3807== by 0x40D72DF: e_cal_marshal_VOID__INT_POINTER (e-cal-marshal.c:156) # ==3807== by 0x426D2CA: g_closure_invoke (gclosure.c:490) # ==3807== by 0x427F4D2: signal_emit_unlocked_R (gsignal.c:2440) # ==3807== by 0x4280EF2: g_signal_emit_valist (gsignal.c:2199) # ==3807== by 0x4281198: g_signal_emit (gsignal.c:2243) # ==3807== by 0x40F21C5: impl_notifyObjectListRequested (e-cal-listener.c:419) # ==3807== by 0x40D1E8C: _ORBIT_skel_small_GNOME_Evolution_Calendar_CalListener_notifyObjectListRequested (Evolution-DataServer-Calendar-common.c:200) # ==3807== by 0x4846436: ORBit_POAObject_invoke (poa.c:1142) # ==3807== by 0x484C664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==3807== by 0x48395D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==3807== by 0x484A2B9: ORBit_POAObject_handle_request (poa.c:1351) # ==3807== by 0x484A95B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==3807== by 0x483213A: giop_thread_queue_process (giop.c:771) { cal_object_list_cb Memcheck:Leak fun:malloc fun:strdup fun:icalmemory_strdup fun:icalvalue_new_clone fun:icalproperty_new_clone fun:icalcomponent_new_clone fun:cal_object_list_cb } # ==3966== 2,524 bytes in 56 blocks are possibly lost in loss record 35 of 61 # ==3966== at 0x401C7EF: calloc (vg_replace_malloc.c:279) # ==3966== by 0x42DC9FD: g_malloc0 (gmem.c:150) # ==3966== by 0x42C7CDA: g_hash_table_new_full (ghash.c:358) # ==3966== by 0x42C7D23: g_hash_table_new (ghash.c:318) # ==3966== by 0x40E43FA: e_cal_component_init (e-cal-component.c:255) # ==3966== by 0x428F065: g_type_create_instance (gtype.c:1569) # ==3966== by 0x4273B21: g_object_constructor (gobject.c:1046) # ==3966== by 0x4274308: g_object_newv (gobject.c:937) # ==3966== by 0x4274EE6: g_object_new_valist (gobject.c:986) # ==3966== by 0x427509F: g_object_new (gobject.c:795) # ==3966== by 0x40E4987: e_cal_component_new (e-cal-component.c:457) # ==3966== by 0x40F251F: build_change_list (e-cal-listener.c:525) # ==3966== by 0x40F260B: impl_notifyChanges (e-cal-listener.c:557) # ==3966== by 0x40D1F7D: _ORBIT_skel_small_GNOME_Evolution_Calendar_CalListener_notifyChanges (Evolution-DataServer-Calendar-common.c:220) # ==3966== by 0x4846436: ORBit_POAObject_invoke (poa.c:1142) # ==3966== by 0x484C664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==3966== by 0x48395D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==3966== by 0x484A2B9: ORBit_POAObject_handle_request (poa.c:1351) # ==3966== by 0x484A95B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==3966== by 0x483213A: giop_thread_queue_process (giop.c:771) { build_change_list Memcheck:Leak fun:calloc fun:g_malloc0 fun:g_hash_table_new_full fun:g_hash_table_new fun:e_cal_component_init fun:g_type_create_instance fun:g_object_constructor fun:g_object_newv fun:g_object_new_valist fun:g_object_new fun:e_cal_component_new fun:build_change_list } # ==4120== 532 bytes in 1 blocks are possibly lost in loss record 17 of 63 # ==4120== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==4120== by 0x44F617F: strdup (in /lib/tls/i686/cmov/libc-2.3.6.so) # ==4120== by 0x4111A1C: icalmemory_strdup (icalmemory.c:240) # ==4120== by 0x41218FE: icalvalue_new_clone (icalvalue.c:135) # ==4120== by 0x4114C06: icalproperty_new_clone (icalproperty.c:141) # ==4120== by 0x410CAC3: icalcomponent_new_clone (icalcomponent.c:199) # ==4120== by 0x40E4B67: e_cal_component_clone (e-cal-component.c:518) # ==4120== by 0x40D846E: cal_get_changes_cb (e-cal.c:843) # ==4120== by 0x40D72DF: e_cal_marshal_VOID__INT_POINTER (e-cal-marshal.c:156) # ==4120== by 0x426D2CA: g_closure_invoke (gclosure.c:490) # ==4120== by 0x427F4D2: signal_emit_unlocked_R (gsignal.c:2440) # ==4120== by 0x4280EF2: g_signal_emit_valist (gsignal.c:2199) # ==4120== by 0x4281198: g_signal_emit (gsignal.c:2243) # ==4120== by 0x40F2655: impl_notifyChanges (e-cal-listener.c:559) # ==4120== by 0x40D1F7D: _ORBIT_skel_small_GNOME_Evolution_Calendar_CalListener_notifyChanges (Evolution-DataServer-Calendar-common.c:220) # ==4120== by 0x4846436: ORBit_POAObject_invoke (poa.c:1142) # ==4120== by 0x484C664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==4120== by 0x48395D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==4120== by 0x484A2B9: ORBit_POAObject_handle_request (poa.c:1351) # ==4120== by 0x484A95B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) { cal_get_changes_cb Memcheck:Leak fun:malloc fun:strdup fun:icalmemory_strdup fun:icalvalue_new_clone fun:icalproperty_new_clone fun:icalcomponent_new_clone fun:e_cal_component_clone fun:cal_get_changes_cb } # ==4120== 2,244 bytes in 52 blocks are possibly lost in loss record 42 of 63 # ==4120== at 0x401C7EF: calloc (vg_replace_malloc.c:279) # ==4120== by 0x42DC9FD: g_malloc0 (gmem.c:150) # ==4120== by 0x40E43D6: e_cal_component_init (e-cal-component.c:252) # ==4120== by 0x428F065: g_type_create_instance (gtype.c:1569) # ==4120== by 0x4273B21: g_object_constructor (gobject.c:1046) # ==4120== by 0x4274308: g_object_newv (gobject.c:937) # ==4120== by 0x4274EE6: g_object_new_valist (gobject.c:986) # ==4120== by 0x427509F: g_object_new (gobject.c:795) # ==4120== by 0x40E4987: e_cal_component_new (e-cal-component.c:457) # ==4120== by 0x40F251F: build_change_list (e-cal-listener.c:525) # ==4120== by 0x40F260B: impl_notifyChanges (e-cal-listener.c:557) # ==4120== by 0x40D1F7D: _ORBIT_skel_small_GNOME_Evolution_Calendar_CalListener_notifyChanges (Evolution-DataServer-Calendar-common.c:220) # ==4120== by 0x4846436: ORBit_POAObject_invoke (poa.c:1142) # ==4120== by 0x484C664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==4120== by 0x48395D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==4120== by 0x484A2B9: ORBit_POAObject_handle_request (poa.c:1351) # ==4120== by 0x484A95B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==4120== by 0x483213A: giop_thread_queue_process (giop.c:771) # ==4120== by 0x48329D7: giop_request_handler_thread (giop.c:481) # ==4120== by 0x42FE686: g_thread_pool_thread_proxy (gthreadpool.c:265) { build_change_list Memcheck:Leak fun:calloc fun:g_malloc0 fun:e_cal_component_init fun:g_type_create_instance fun:g_object_constructor fun:g_object_newv fun:g_object_new_valist fun:g_object_new fun:e_cal_component_new fun:build_change_list } # ==4120== 256 bytes in 2 blocks are possibly lost in loss record 47 of 63 # ==4120== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==4120== by 0x4121788: icalvalue_new_impl (icalvalue.c:75) # ==4120== by 0x412184A: icalvalue_new_clone (icalvalue.c:104) # ==4120== by 0x4114C06: icalproperty_new_clone (icalproperty.c:141) # ==4120== by 0x410CAC3: icalcomponent_new_clone (icalcomponent.c:199) # ==4120== by 0x40E4B67: e_cal_component_clone (e-cal-component.c:518) # ==4120== by 0x40D846E: cal_get_changes_cb (e-cal.c:843) # ==4120== by 0x40D72DF: e_cal_marshal_VOID__INT_POINTER (e-cal-marshal.c:156) # ==4120== by 0x426D2CA: g_closure_invoke (gclosure.c:490) # ==4120== by 0x427F4D2: signal_emit_unlocked_R (gsignal.c:2440) # ==4120== by 0x4280EF2: g_signal_emit_valist (gsignal.c:2199) # ==4120== by 0x4281198: g_signal_emit (gsignal.c:2243) # ==4120== by 0x40F2655: impl_notifyChanges (e-cal-listener.c:559) # ==4120== by 0x40D1F7D: _ORBIT_skel_small_GNOME_Evolution_Calendar_CalListener_notifyChanges (Evolution-DataServer-Calendar-common.c:220) # ==4120== by 0x4846436: ORBit_POAObject_invoke (poa.c:1142) # ==4120== by 0x484C664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==4120== by 0x48395D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==4120== by 0x484A2B9: ORBit_POAObject_handle_request (poa.c:1351) # ==4120== by 0x484A95B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==4120== by 0x483213A: giop_thread_queue_process (giop.c:771) { cal_get_changes_cb Memcheck:Leak fun:malloc fun:icalvalue_new_impl fun:icalvalue_new_clone fun:icalproperty_new_clone fun:icalcomponent_new_clone fun:e_cal_component_clone fun:cal_get_changes_cb } # ==4256== 49,125 (840 direct, 48,285 indirect) bytes in 42 blocks are definitely lost in loss record 38 of 63 # ==4256== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==4256== by 0x4123F11: pvl_newlist (pvl.c:59) # ==4256== by 0x410C958: icalcomponent_new_impl (icalcomponent.c:129) # ==4256== by 0x410CA82: icalcomponent_new_clone (icalcomponent.c:187) # ==4256== by 0x40E4B67: e_cal_component_clone (e-cal-component.c:518) # ==4256== by 0x40D846E: cal_get_changes_cb (e-cal.c:843) # ==4256== by 0x40D72DF: e_cal_marshal_VOID__INT_POINTER (e-cal-marshal.c:156) # ==4256== by 0x426D2CA: g_closure_invoke (gclosure.c:490) # ==4256== by 0x427F4D2: signal_emit_unlocked_R (gsignal.c:2440) # ==4256== by 0x4280EF2: g_signal_emit_valist (gsignal.c:2199) # ==4256== by 0x4281198: g_signal_emit (gsignal.c:2243) # ==4256== by 0x40F2655: impl_notifyChanges (e-cal-listener.c:559) # ==4256== by 0x40D1F7D: _ORBIT_skel_small_GNOME_Evolution_Calendar_CalListener_notifyChanges (Evolution-DataServer-Calendar-common.c:220) # ==4256== by 0x4846436: ORBit_POAObject_invoke (poa.c:1142) # ==4256== by 0x484C664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==4256== by 0x48395D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==4256== by 0x484A2B9: ORBit_POAObject_handle_request (poa.c:1351) # ==4256== by 0x484A95B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==4256== by 0x483213A: giop_thread_queue_process (giop.c:771) # ==4256== by 0x48329D7: giop_request_handler_thread (giop.c:481) { cal_get_changes_cb Memcheck:Leak fun:malloc fun:pvl_newlist fun:icalcomponent_new_impl fun:icalcomponent_new_clone fun:e_cal_component_clone fun:cal_get_changes_cb } # ==4256== 2,744 bytes in 1 blocks are possibly lost in loss record 50 of 63 # ==4256== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==4256== by 0x410BA2B: icalvalue_set_recur (icalderivedvalue.c:903) # ==4256== by 0x410B9CB: icalvalue_new_recur (icalderivedvalue.c:887) # ==4256== by 0x4122300: icalvalue_new_from_string_with_error (icalvalue.c:538) # ==4256== by 0x4122782: icalvalue_new_from_string (icalvalue.c:665) # ==4256== by 0x4114560: icalparser_add_line (icalparser.c:946) # ==4256== by 0x4113C1E: icalparser_parse (icalparser.c:587) # ==4256== by 0x4114A11: icalparser_parse_string (icalparser.c:1119) # ==4256== by 0x40F2511: build_change_list (e-cal-listener.c:521) # ==4256== by 0x40F260B: impl_notifyChanges (e-cal-listener.c:557) # ==4256== by 0x40D1F7D: _ORBIT_skel_small_GNOME_Evolution_Calendar_CalListener_notifyChanges (Evolution-DataServer-Calendar-common.c:220) # ==4256== by 0x4846436: ORBit_POAObject_invoke (poa.c:1142) # ==4256== by 0x484C664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==4256== by 0x48395D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==4256== by 0x484A2B9: ORBit_POAObject_handle_request (poa.c:1351) # ==4256== by 0x484A95B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==4256== by 0x483213A: giop_thread_queue_process (giop.c:771) # ==4256== by 0x48329D7: giop_request_handler_thread (giop.c:481) # ==4256== by 0x42FE686: g_thread_pool_thread_proxy (gthreadpool.c:265) # ==4256== by 0x42FCAFE: g_thread_create_proxy (gthread.c:635) { build_change_list Memcheck:Leak fun:malloc fun:icalvalue_set_recur fun:icalvalue_new_recur fun:icalvalue_new_from_string_with_error fun:icalvalue_new_from_string fun:icalparser_add_line fun:icalparser_parse fun:icalparser_parse_string fun:build_change_list } # ==8454== 123,178 (12,160 direct, 111,018 indirect) bytes in 95 blocks are definitely lost in loss record 25 of 66 # ==8454== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==8454== by 0x4121788: icalvalue_new_impl (icalvalue.c:75) # ==8454== by 0x4121B39: icalvalue_new_enum (icalvalue.c:279) # ==8454== by 0x4121F14: icalvalue_new_from_string_with_error (icalvalue.c:425) # ==8454== by 0x4122782: icalvalue_new_from_string (icalvalue.c:665) # ==8454== by 0x4114560: icalparser_add_line (icalparser.c:946) # ==8454== by 0x4113C1E: icalparser_parse (icalparser.c:587) # ==8454== by 0x4114A11: icalparser_parse_string (icalparser.c:1119) # ==8454== by 0x40F2511: build_change_list (e-cal-listener.c:521) # ==8454== by 0x40F260B: impl_notifyChanges (e-cal-listener.c:557) # ==8454== by 0x40D1F7D: _ORBIT_skel_small_GNOME_Evolution_Calendar_CalListener_notifyChanges (Evolution-DataServer-Calendar-common.c:220) # ==8454== by 0x4846436: ORBit_POAObject_invoke (poa.c:1142) { build_change_list Memcheck:Leak fun:malloc fun:icalvalue_new_impl fun:icalvalue_new_enum fun:icalvalue_new_from_string_with_error fun:icalvalue_new_from_string fun:icalparser_add_line fun:icalparser_parse fun:icalparser_parse_string fun:build_change_list } # ==8438== 300 bytes in 3 blocks are possibly lost in loss record 21 of 52 # ==8438== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==8438== by 0x401D53A: realloc (vg_replace_malloc.c:306) # ==8438== by 0x44577FA: g_try_realloc (gmem.c:221) # ==8438== by 0x439A83C: alloc_buffer (giop-recv-buffer.c:887) # ==8438== by 0x439B7FB: giop_connection_handle_input (giop-recv-buffer.c:1192) # ==8438== by 0x43B9DBC: link_connection_io_handler (linc-connection.c:1412) # ==8438== by 0x43BC65F: link_source_dispatch (linc-source.c:159) # ==8438== by 0x444F99A: g_main_context_dispatch (gmain.c:2064) # ==8438== by 0x4452EB5: g_main_context_iterate (gmain.c:2697) # ==8438== by 0x4453276: g_main_loop_run (gmain.c:2905) # ==8438== by 0x43B831F: link_io_thread_fn (linc.c:396) # ==8438== by 0x4477AFE: g_thread_create_proxy (gthread.c:635) { giop_connection_handle_input Memcheck:Leak fun:malloc fun:realloc fun:g_try_realloc fun:alloc_buffer fun:giop_connection_handle_input } # ==8438== 37,123 (1,040 direct, 36,083 indirect) bytes in 65 blocks are definitely lost in loss record 32 of 52 # ==8438== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==8438== by 0x401D53A: realloc (vg_replace_malloc.c:306) # ==8438== by 0x445795A: g_realloc (gmem.c:168) # ==8438== by 0x43E9AF8: g_object_weak_ref (gobject.c:1490) # ==8438== by 0x4045F94: e_book_backend_add_client (e-book-backend.c:567) # ==8438== by 0x4047B25: impl_GNOME_Evolution_Addressbook_BookFactory_getBook (e-data-book-factory.c:353) # ==8438== by 0x4038E02: _ORBIT_skel_small_GNOME_Evolution_Addressbook_BookFactory_getBook (Evolution-DataServer-Addressbook-common.c:180) # ==8438== by 0x43AC436: ORBit_POAObject_invoke (poa.c:1142) # ==8438== by 0x43B2664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==8438== by 0x439F5D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==8438== by 0x43B02B9: ORBit_POAObject_handle_request (poa.c:1351) # ==8438== by 0x43B095B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) { e_book_backend_add_client Memcheck:Leak fun:malloc fun:realloc fun:g_realloc fun:g_object_weak_ref fun:e_book_backend_add_client } # ==8438== 342,647 (149,497 direct, 193,150 indirect) bytes in 1,367 blocks are definitely lost in loss record 48 of 52 # ==8438== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==8438== by 0x4457A95: g_malloc (gmem.c:131) # ==8438== by 0x446CE32: g_slice_alloc (gslice.c:824) # ==8438== by 0x446D514: g_slice_alloc0 (gslice.c:833) # ==8438== by 0x4409C2B: g_type_create_instance (gtype.c:1549) # ==8438== by 0x43EEB21: g_object_constructor (gobject.c:1046) # ==8438== by 0x42FE2B0: bonobo_object_constructor (bonobo-object.c:820) # ==8438== by 0x43EF308: g_object_newv (gobject.c:937) # ==8438== by 0x43EFF8E: g_object_new_valist (gobject.c:1027) # ==8438== by 0x43F009F: g_object_new (gobject.c:795) # ==8438== by 0x404B05B: e_data_book_new (e-data-book.c:943) # ==8438== by 0x4047B10: impl_GNOME_Evolution_Addressbook_BookFactory_getBook (e-data-book-factory.c:351) { e_data_book_new Memcheck:Leak fun:malloc fun:g_malloc fun:g_slice_alloc fun:g_slice_alloc0 fun:g_type_create_instance fun:g_object_constructor fun:bonobo_object_constructor fun:g_object_newv fun:g_object_new_valist fun:g_object_new fun:e_data_book_new } # ==4068== 72,256 (30,172 direct, 42,084 indirect) bytes in 293 blocks are definitely lost in loss record 44 of 50 # ==4068== at 0x401C7EF: calloc (vg_replace_malloc.c:279) # ==4068== by 0x44579FD: g_malloc0 (gmem.c:150) # ==4068== by 0x551336C: e_cal_backend_file_events_init (e-cal-backend-file-events.c:103) # ==4068== by 0x440A065: g_type_create_instance (gtype.c:1569) # ==4068== by 0x43EEB21: g_object_constructor (gobject.c:1046) # ==4068== by 0x43EF308: g_object_newv (gobject.c:937) # ==4068== by 0x43EFF8E: g_object_new_valist (gobject.c:1027) # ==4068== by 0x43F009F: g_object_new (gobject.c:795) # ==4068== by 0x5512F1A: _events_new_backend (e-cal-backend-file-factory.c:73) # ==4068== by 0x41294B4: e_cal_backend_factory_new_backend (e-cal-backend-factory.c:98) # ==4068== by 0x4133420: impl_CalFactory_getCal (e-data-cal-factory.c:204) # ==4068== by 0x411E3BB: _ORBIT_skel_small_GNOME_Evolution_Calendar_CalFactory_getCal (Evolution-DataServer-Calendar-common.c:244) # ==4068== by 0x43AC436: ORBit_POAObject_invoke (poa.c:1142) # ==4068== by 0x43B2664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==4068== by 0x439F5D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==4068== by 0x43B02B9: ORBit_POAObject_handle_request (poa.c:1351) # ==4068== by 0x43B095B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==4068== by 0x439813A: giop_thread_queue_process (giop.c:771) # ==4068== by 0x43989D7: giop_request_handler_thread (giop.c:481) # ==4068== by 0x4479686: g_thread_pool_thread_proxy (gthreadpool.c:265) # ==4068== by 0x4477AFE: g_thread_create_proxy (gthread.c:635) # ==4068== by 0x452723F: start_thread (in /lib/tls/i686/cmov/libpthread-2.3.6.so) # ==4068== by 0x45FF49D: clone (in /lib/tls/i686/cmov/libc-2.3.6.so) { new calendar Memcheck:Leak fun:calloc fun:g_malloc0 fun:e_cal_backend_file_events_init fun:g_type_create_instance fun:g_object_constructor fun:g_object_newv fun:g_object_new_valist fun:g_object_new fun:_events_new_backend fun:e_cal_backend_factory_new_backend fun:impl_CalFactory_getCal } # ==17481== 89,664 (37,348 direct, 52,316 indirect) bytes in 362 blocks are definitely lost in loss record 45 of 50 # ==17481== at 0x401C7EF: calloc (vg_replace_malloc.c:279) # ==17481== by 0x44579FD: g_malloc0 (gmem.c:150) # ==17481== by 0x439A151: giop_recv_buffer_use_buf (giop-recv-buffer.c:1440) # ==17481== by 0x439B8E8: giop_connection_handle_input (giop-recv-buffer.c:1218) # ==17481== by 0x43B9DBC: link_connection_io_handler (linc-connection.c:1412) # ==17481== by 0x43BC65F: link_source_dispatch (linc-source.c:159) # ==17481== by 0x444F99A: g_main_context_dispatch (gmain.c:2064) # ==17481== by 0x4452EB5: g_main_context_iterate (gmain.c:2697) # ==17481== by 0x4453276: g_main_loop_run (gmain.c:2905) # ==17481== by 0x43B831F: link_io_thread_fn (linc.c:396) # ==17481== by 0x4477AFE: g_thread_create_proxy (gthread.c:635) # ==17481== by 0x452723F: start_thread (in /lib/tls/i686/cmov/libpthread-2.3.6.so) # ==17481== by 0x45FF49D: clone (in /lib/tls/i686/cmov/libc-2.3.6.so) { giop_recv_buffer_use_buf in thread Memcheck:Leak fun:calloc fun:g_malloc0 fun:giop_recv_buffer_use_buf fun:giop_connection_handle_input fun:link_connection_io_handler fun:link_source_dispatch fun:g_main_context_dispatch fun:g_main_context_iterate fun:g_main_loop_run } # ==10684== 2,203 bytes in 11 blocks are possibly lost in loss record 30 of 49 # ==10684== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==10684== by 0x4457A95: g_malloc (gmem.c:131) # ==10684== by 0x43A3925: ORBit_alloc_simple (allocators.c:241) # ==10684== by 0x43A8760: ORBit_demarshal_value (corba-any.c:683) # ==10684== by 0x43A827F: ORBit_demarshal_value (corba-any.c:561) # ==10684== by 0x43A86D6: ORBit_demarshal_value (corba-any.c:704) # ==10684== by 0x439AA83: giop_IOP_ServiceContextList_demarshal (giop-recv-buffer.c:88) # ==10684== by 0x439AADE: giop_recv_buffer_demarshal_reply_1_2 (giop-recv-buffer.c:270) # ==10684== by 0x439B89A: giop_connection_handle_input (giop-recv-buffer.c:419) # ==10684== by 0x43B9DBC: link_connection_io_handler (linc-connection.c:1412) # ==10684== by 0x43BC65F: link_source_dispatch (linc-source.c:159) # ==10684== by 0x444F99A: g_main_context_dispatch (gmain.c:2064) # ==10684== by 0x4452EB5: g_main_context_iterate (gmain.c:2697) # ==10684== by 0x4453276: g_main_loop_run (gmain.c:2905) # ==10684== by 0x43B831F: link_io_thread_fn (linc.c:396) # ==10684== by 0x4477AFE: g_thread_create_proxy (gthread.c:635) # ==10684== by 0x452723F: start_thread (in /lib/tls/i686/cmov/libpthread-2.3.6.so) # ==10684== by 0x45FF49D: clone (in /lib/tls/i686/cmov/libc-2.3.6.so) { ORBit demarshal Memcheck:Leak fun:malloc fun:g_malloc fun:ORBit_alloc_simple fun:ORBit_demarshal_value } # ==10419== 2,714 (2,646 direct, 68 indirect) bytes in 13 blocks are definitely lost in loss record 32 of 39 # ==10419== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==10419== by 0x4293A1C: g_malloc (gmem.c:131) # ==10419== by 0x42AC378: g_strdup (gstrfuncs.c:92) # ==10419== by 0x404A57D: cal_add_timezone_cb (e-cal.c:800) # ==10419== by 0x4049375: e_cal_marshal_VOID__INT_STRING (e-cal-marshal.c:82) # ==10419== by 0x42252FA: g_closure_invoke (gclosure.c:490) # ==10419== by 0x42360DC: signal_emit_unlocked_R (gsignal.c:2440) # ==10419== by 0x42375D8: g_signal_emit_valist (gsignal.c:2199) # ==10419== by 0x4237788: g_signal_emit (gsignal.c:2243) # ==10419== by 0x40647FA: impl_notifyTimezoneAdded (e-cal-listener.c:486) # ==10419== by 0x4044155: _ORBIT_skel_small_GNOME_Evolution_Calendar_CalListener_notifyTimezoneAdded (Evolution-DataServer-Calendar-common.c:212) # ==10419== by 0x4820436: ORBit_POAObject_invoke (poa.c:1142) # ==10419== by 0x4826664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==10419== by 0x48135D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==10419== by 0x48242B9: ORBit_POAObject_handle_request (poa.c:1351) # ==10419== by 0x482495B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==10419== by 0x480C13A: giop_thread_queue_process (giop.c:771) # ==10419== by 0x480C9D7: giop_request_handler_thread (giop.c:481) # ==10419== by 0x42B5306: g_thread_pool_thread_proxy (gthreadpool.c:265) # ==10419== by 0x42B377E: g_thread_create_proxy (gthread.c:635) # ==10419== by 0x420F23F: start_thread (in /lib/tls/i686/cmov/libpthread-2.3.6.so) # ==10419== by 0x455549D: clone (in /lib/tls/i686/cmov/libc-2.3.6.so) { call_add_timezone_cb Memcheck:Leak fun:malloc fun:g_malloc fun:g_strdup fun:cal_add_timezone_cb } # ==9770== 74,348 bytes in 2,506 blocks are possibly lost in loss record 36 of 53 # ==9770== at 0x401C7EF: calloc (vg_replace_malloc.c:279) # ==9770== by 0x4456994: g_malloc0 (gmem.c:151) # ==9770== by 0x43A36EC: ORBit_alloc_tcval (allocators.c:287) # ==9770== by 0x439FDB8: ORBit_small_allocbuf (orbit-small.c:51) # ==9770== by 0x43AA267: IOP_ObjectKey_demarshal (iop-profiles.c:1045) # ==9770== by 0x43AAF80: ORBit_demarshal_IOR (iop-profiles.c:1399) # ==9770== by 0x43A1CBE: ORBit_demarshal_object (corba-object.c:608) # ==9770== by 0x43A8558: ORBit_demarshal_value (corba-any.c:545) # ==9770== by 0x439F7C0: ORBit_small_invoke_adaptor (orbit-small.c:785) # ==9770== by 0x43B02B9: ORBit_POAObject_handle_request (poa.c:1351) # ==9770== by 0x43B095B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==9770== by 0x439813A: giop_thread_queue_process (giop.c:771) # ==9770== by 0x43989D7: giop_request_handler_thread (giop.c:481) # ==9770== by 0x4478306: g_thread_pool_thread_proxy (gthreadpool.c:265) # ==9770== by 0x447677E: g_thread_create_proxy (gthread.c:635) # ==9770== by 0x452623F: start_thread (in /lib/tls/i686/cmov/libpthread-2.3.6.so) # ==9770== by 0x45FE49D: clone (in /lib/tls/i686/cmov/libc-2.3.6.so) { ORBit demarshal II Memcheck:Leak fun:calloc fun:g_malloc0 fun:ORBit_alloc_tcval fun:ORBit_small_allocbuf fun:IOP_ObjectKey_demarshal fun:ORBit_demarshal_IOR } # ==4248== Conditional jump or move depends on uninitialised value(s) # ==4248== at 0x4010DFE: (within /lib/ld-2.3.6.so) # ==4248== by 0x400B729: (within /lib/ld-2.3.6.so) # ==4248== by 0x4008202: (within /lib/ld-2.3.6.so) # ==4248== by 0x46368CC: (within /lib/tls/i686/cmov/libc-2.3.6.so) # ==4248== by 0x4636A09: _dl_sym (in /lib/tls/i686/cmov/libc-2.3.6.so) # ==4248== by 0x451DEE7: (within /lib/tls/i686/cmov/libdl-2.3.6.so) # ==4248== by 0x400B44E: (within /lib/ld-2.3.6.so) # ==4248== by 0x451E42C: (within /lib/tls/i686/cmov/libdl-2.3.6.so) # ==4248== by 0x451DE7A: dlsym (in /lib/tls/i686/cmov/libdl-2.3.6.so) # ==4248== by 0x434A1E3: g_module_symbol (gmodule-dl.c:147) # ==4248== by 0x434A6F1: g_module_open (gmodule.c:484) # ==4248== by 0x4681D3A: e_data_server_module_load (e-data-server-module.c:77) # ==4248== by 0x440B367: g_type_module_use (gtypemodule.c:190) # ==4248== by 0x468207F: e_data_server_module_load_file (e-data-server-module.c:175) # ==4248== by 0x4682146: load_module_dir (e-data-server-module.c:202) # ==4248== by 0x46821AA: e_data_server_module_init (e-data-server-module.c:219) # ==4248== by 0x804B58F: main (server.c:379) { dlsym/dlopen Memcheck:Cond obj:*ld-2.3.6.so } # ==2935== Invalid read of size 4 # ==2935== at 0x4010DD3: (within /lib/ld-2.3.6.so) # ==2935== by 0x4005F5B: (within /lib/ld-2.3.6.so) # ==2935== by 0x4006431: (within /lib/ld-2.3.6.so) # ==2935== by 0x4006A51: (within /lib/ld-2.3.6.so) # ==2935== by 0x400A1F6: (within /lib/ld-2.3.6.so) # ==2935== by 0x400B44E: (within /lib/ld-2.3.6.so) # ==2935== by 0x400A3CA: (within /lib/ld-2.3.6.so) # ==2935== by 0x46334D4: (within /lib/tls/i686/cmov/libc-2.3.6.so) # ==2935== by 0x400B44E: (within /lib/ld-2.3.6.so) # ==2935== by 0x4632EDE: _dl_open (in /lib/tls/i686/cmov/libc-2.3.6.so) # ==2935== by 0x451CD8D: (within /lib/tls/i686/cmov/libdl-2.3.6.so) # ==2935== by 0x400B44E: (within /lib/ld-2.3.6.so) # ==2935== by 0x451D42C: (within /lib/tls/i686/cmov/libdl-2.3.6.so) # ==2935== by 0x451CD20: dlopen (in /lib/tls/i686/cmov/libdl-2.3.6.so) # ==2935== by 0x434A606: g_module_open (gmodule-dl.c:99) # ==2935== by 0x4680A0B: e_data_server_module_load (e-data-server-module.c:77) # ==2935== by 0x4409DB7: g_type_module_use (gtypemodule.c:190) # ==2935== by 0x4680D50: e_data_server_module_load_file (e-data-server-module.c:175) # ==2935== by 0x4680E17: load_module_dir (e-data-server-module.c:202) # ==2935== by 0x4680E7B: e_data_server_module_init (e-data-server-module.c:219) # ==2935== by 0x804B5BF: main (server.c:379) # ==2935== Address 0x4E49E84 is 132 bytes inside a block of size 134 alloc'd # ==2935== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==2935== by 0x4005DA5: (within /lib/ld-2.3.6.so) # ==2935== by 0x4005EE5: (within /lib/ld-2.3.6.so) # ==2935== by 0x4006431: (within /lib/ld-2.3.6.so) # ==2935== by 0x4006A51: (within /lib/ld-2.3.6.so) # ==2935== by 0x400A1F6: (within /lib/ld-2.3.6.so) # ==2935== by 0x400B44E: (within /lib/ld-2.3.6.so) # ==2935== by 0x400A3CA: (within /lib/ld-2.3.6.so) # ==2935== by 0x46334D4: (within /lib/tls/i686/cmov/libc-2.3.6.so) # ==2935== by 0x400B44E: (within /lib/ld-2.3.6.so) # ==2935== by 0x4632EDE: _dl_open (in /lib/tls/i686/cmov/libc-2.3.6.so) # ==2935== by 0x451CD8D: (within /lib/tls/i686/cmov/libdl-2.3.6.so) # ==2935== by 0x400B44E: (within /lib/ld-2.3.6.so) # ==2935== by 0x451D42C: (within /lib/tls/i686/cmov/libdl-2.3.6.so) # ==2935== by 0x451CD20: dlopen (in /lib/tls/i686/cmov/libdl-2.3.6.so) # ==2935== by 0x434A606: g_module_open (gmodule-dl.c:99) # ==2935== by 0x4680A0B: e_data_server_module_load (e-data-server-module.c:77) # ==2935== by 0x4409DB7: g_type_module_use (gtypemodule.c:190) # ==2935== by 0x4680D50: e_data_server_module_load_file (e-data-server-module.c:175) # ==2935== by 0x4680E17: load_module_dir (e-data-server-module.c:202) # ==2935== by 0x4680E7B: e_data_server_module_init (e-data-server-module.c:219) # ==2935== by 0x804B5BF: main (server.c:379) { dlsym/dlopen II Memcheck:Addr4 obj:*ld-2.3.6.so } # ==27142== 1,431,213 (1,332,637 direct, 98,576 indirect) bytes in 33,210 blocks are definitely lost in loss record 48 of 48 # ==27142== at 0x401D4B0: malloc (vg_replace_malloc.c:149) # ==27142== by 0x42C6A1C: g_malloc (gmem.c:131) # ==27142== by 0x42DBB92: g_slice_alloc (gslice.c:824) # ==27142== by 0x42DC274: g_slice_alloc0 (gslice.c:833) # ==27142== by 0x418069B: g_type_create_instance (gtype.c:1555) # ==27142== by 0x4167571: g_object_constructor (gobject.c:1046) # ==27142== by 0x41657C7: g_object_newv (gobject.c:937) # ==27142== by 0x4166366: g_object_new_valist (gobject.c:986) # ==27142== by 0x416651F: g_object_new (gobject.c:795) # ==27142== by 0x4095808: e_contact_new_from_vcard (e-contact.c:1227) # ==27142== by 0x4090665: e_book_handle_response (e-book.c:3094) # ==27142== by 0x416CEDA: g_cclosure_marshal_VOID__POINTER (gmarshal.c:601) # ==27142== by 0x41602FA: g_closure_invoke (gclosure.c:490) # ==27142== by 0x41710DC: signal_emit_unlocked_R (gsignal.c:2440) # ==27142== by 0x41725D8: g_signal_emit_valist (gsignal.c:2199) # ==27142== by 0x4172788: g_signal_emit (gsignal.c:2243) # ==27142== by 0x40849FA: impl_BookListener_respond_get_contact (e-book-listener.c:136) # ==27142== by 0x407FBE0: _ORBIT_skel_small_GNOME_Evolution_Addressbook_BookListener_notifyContactRequested (Evolution-DataServer-Addressbook-common.c:144) # ==27142== by 0x483E436: ORBit_POAObject_invoke (poa.c:1142) # ==27142== by 0x4844664: ORBit_OAObject_invoke (orbit-adaptor.c:338) # ==27142== by 0x48315D8: ORBit_small_invoke_adaptor (orbit-small.c:844) # ==27142== by 0x48422B9: ORBit_POAObject_handle_request (poa.c:1351) # ==27142== by 0x484295B: ORBit_POAObject_invoke_incoming_request (poa.c:1421) # ==27142== by 0x48429E0: poa_invoke_at_idle (poa.c:1469) # ==27142== by 0x42BCD90: g_idle_dispatch (gmain.c:4081) # ==27142== by 0x42BE96A: g_main_context_dispatch (gmain.c:2003) # ==27142== by 0x42C1E85: g_main_context_iterate (gmain.c:2636) # ==27142== by 0x42C2246: g_main_loop_run (gmain.c:2844) # ==27142== by 0x4092317: startup_mainloop (e-book.c:3767) # ==27142== by 0x42E677E: g_thread_create_proxy (gthread.c:635) { e_book_handle_response Memcheck:Leak fun:malloc fun:g_malloc fun:g_slice_alloc fun:g_slice_alloc0 fun:g_type_create_instance fun:g_object_constructor fun:g_object_newv fun:g_object_new_valist fun:g_object_new fun:e_contact_new_from_vcard fun:e_book_handle_response } # ==32215== 34 bytes in 34 blocks are definitely lost in loss record 988 of 2,156 # ==32215== at 0x4C244E8: malloc (vg_replace_malloc.c:236) # ==32215== by 0x6432504: g_malloc (in /lib/libglib-2.0.so.0.2400.1) # ==32215== by 0x6449D7D: g_strdup (in /lib/libglib-2.0.so.0.2400.1) # ==32215== by 0x505F75A: e_contact_get (in /usr/lib/libebook-1.2.so.9.3.1) # ==32215== by 0x505F5B0: e_contact_get_const (in /usr/lib/libebook-1.2.so.9.3.1) # ==32215== by 0x505FD9F: e_contact_new_from_vcard (in /usr/lib/libebook-1.2.so.9.3.1) # ==32215== by 0x59ADE8: SyncEvo::EvolutionContactSource::insertItem(std::string const&, std::string const&, bool) (EvolutionContactSource.cpp:318) { e_contact_new_from_vcard + g_strdup Memcheck:Leak fun:malloc fun:g_malloc fun:g_strdup fun:e_contact_get fun:e_contact_get_const fun:e_contact_new_from_vcard } # ==7097== 2,236 (116 direct, 2,120 indirect) bytes in 1 blocks are definitely lost in loss record 16 of 31 # ==7097== at 0x401C7EF: calloc (vg_replace_malloc.c:279) # ==7097== by 0x4455994: g_malloc0 (gmem.c:151) # ==7097== by 0x4398788: giop_send_buffer_use (giop-send-buffer.c:513) # ==7097== by 0x4398AD7: giop_send_buffer_use_request (giop-send-buffer.c:75) # ==7097== by 0x439D60C: orbit_small_marshal (orbit-small.c:324) # ==7097== by 0x439EB37: ORBit_small_invoke_stub (orbit-small.c:646) # ==7097== by 0x439ED7F: ORBit_small_invoke_stub_n (orbit-small.c:575) # ==7097== by 0x43AB671: ORBit_c_stub_invoke (poa.c:2643) # ==7097== by 0x4372C5B: ConfigServer_ping (in /usr/lib/libgconf-2.so.4.1.0) # ==7097== by 0x435A0C1: gconf_activate_server (in /usr/lib/libgconf-2.so.4.1.0) # ==7097== by 0x43665D1: (within /usr/lib/libgconf-2.so.4.1.0) # ==7097== by 0x436733A: (within /usr/lib/libgconf-2.so.4.1.0) # ==7097== by 0x436758D: (within /usr/lib/libgconf-2.so.4.1.0) # ==7097== by 0x43697EC: gconf_engine_get_fuller (in /usr/lib/libgconf-2.so.4.1.0) # ==7097== by 0x4369ABD: gconf_engine_get_entry (in /usr/lib/libgconf-2.so.4.1.0) # ==7097== by 0x436DD3A: (within /usr/lib/libgconf-2.so.4.1.0) # ==7097== by 0x436DFEE: (within /usr/lib/libgconf-2.so.4.1.0) # ==7097== by 0x436E99D: gconf_client_get_bool (in /usr/lib/libgconf-2.so.4.1.0) # ==7097== by 0x4156933: gnome_program_postinit (in /usr/lib/libgnome-2.so.0.1600.0) # ==7097== by 0x4156B64: (within /usr/lib/libgnome-2.so.0.1600.0) # ==7097== by 0x4156E78: gnome_program_init (in /usr/lib/libgnome-2.so.0.1600.0) # ==7097== by 0x804B58D: main (server.c:367) { ConfigServer_ping() Memcheck:Leak fun:calloc fun:g_malloc0 fun:giop_send_buffer_use fun:giop_send_buffer_use_request fun:orbit_small_marshal fun:ORBit_small_invoke_stub fun:ORBit_small_invoke_stub_n fun:ORBit_c_stub_invoke fun:ConfigServer_ping } # ==24366== 2,236 (116 direct, 2,120 indirect) bytes in 1 blocks are definitely lost in loss record 13 of 26 # ==24366== at 0x401C7EF: calloc (vg_replace_malloc.c:279) # ==24366== by 0x45CC994: g_malloc0 (gmem.c:151) # ==24366== by 0x4510788: giop_send_buffer_use (giop-send-buffer.c:513) # ==24366== by 0x4510AD7: giop_send_buffer_use_request (giop-send-buffer.c:75) # ==24366== by 0x451560C: orbit_small_marshal (orbit-small.c:324) # ==24366== by 0x4516B37: ORBit_small_invoke_stub (orbit-small.c:646) # ==24366== by 0x4516D7F: ORBit_small_invoke_stub_n (orbit-small.c:575) # ==24366== by 0x4523671: ORBit_c_stub_invoke (poa.c:2643) # ==24366== by 0x44EAD61: ConfigServer_add_client (in /usr/lib/libgconf-2.so.4.1.0) # ==24366== by 0x44DE618: (within /usr/lib/libgconf-2.so.4.1.0) # ==24366== by 0x44DF33A: (within /usr/lib/libgconf-2.so.4.1.0) # ==24366== by 0x44DF58D: (within /usr/lib/libgconf-2.so.4.1.0) # ==24366== by 0x44E17EC: gconf_engine_get_fuller (in /usr/lib/libgconf-2.so.4.1.0) # ==24366== by 0x44E1ABD: gconf_engine_get_entry (in /usr/lib/libgconf-2.so.4.1.0) # ==24366== by 0x44E5D3A: (within /usr/lib/libgconf-2.so.4.1.0) # ==24366== by 0x44E5FEE: (within /usr/lib/libgconf-2.so.4.1.0) # ==24366== by 0x44E699D: gconf_client_get_bool (in /usr/lib/libgconf-2.so.4.1.0) # ==24366== by 0x422D933: gnome_program_postinit (in /usr/lib/libgnome-2.so.0.1600.0) # ==24366== by 0x422DB64: (within /usr/lib/libgnome-2.so.0.1600.0) # ==24366== by 0x422DE78: gnome_program_init (in /usr/lib/libgnome-2.so.0.1600.0) # ==24366== by 0x804B56D: main (server.c:366) { ConfigServer_add_client Memcheck:Leak ... fun:ConfigServer_add_client } { gconf_activate_server Memcheck:Leak ... fun:gconf_activate_server } { ORBit_small_invoke_stub Memcheck:Leak ... fun:ORBit_small_invoke_stub } { gconf leaks Memcheck:Leak ... fun:gconf_client_add_dir } # ==22473== 126,995 bytes in 1,589 blocks are possibly lost in loss record 177 of 177 # ==22473== at 0x4C23B29: operator new(unsigned long) (vg_replace_malloc.c:230) # ==22473== by 0x6C5D2E0: std::string::_Rep::_S_create(unsigned long, unsigned long, std::allocator const&) (in /usr/lib/libstdc++.so.6.0.9) # ==22473== by 0x6C5E0F4: (within /usr/lib/libstdc++.so.6.0.9) # ==22473== by 0x6C5E271: std::basic_string, std::allocator >::basic_string(char const*, std::allocator const&) (in /usr/lib/libstdc++.so.6.0.9) # ==22473== by 0x6E6CC0: sysync::tzdata::tzdata() (timezones.cpp:80) # ==22473== by 0x6E25DA: __static_initialization_and_destruction_0(int, int) (timezones.cpp:85) # ==22473== by 0x6E2604: global constructors keyed to _ZN6sysync6GZones10initializeEv (timezones.cpp:1389) # ==22473== by 0x7D95D9: (within /scratch/work/eeepc/syncevolution/src/client-test) # ==22473== by 0x51CCB2: (within /scratch/work/eeepc/syncevolution/src/client-test) # ==22473== by 0x100000006EB48AF: ??? # ==22473== by 0x7D9554: __libc_csu_init (in /scratch/work/eeepc/syncevolution/src/client-test) # ==22473== by 0x737415D: (below main) (in /lib/libc-2.7.so) { sysync::tzdata::tzdata() Memcheck:Leak fun:_Znwm fun:_ZNSs4_Rep9_S_createEmmRKSaIcE obj:*libstdc++.so* fun:_ZNSsC1EPKcRKSaIcE fun:_ZN6sysync6tzdataC1Ev } # ==22471== Invalid read of size 8 # ==22471== at 0x5BEA24E: icaltzutil_fetch_timezone (icaltz-util.c:354) # ==22471== by 0x5BEB468: icaltimezone_load_builtin_timezone (icaltimezone.c:1758) # ==22471== by 0x5BEB4AA: icaltimezone_get_component (icaltimezone.c:1223) # ==22471== by 0x6EEBC5: sysync::loadSystemZoneDefinitions(sysync::GZones*) (platform_timezones.cpp:55) # ==22471== by 0x6E5430: sysync::GZones::initialize() (timezones.cpp:95) # ==22471== by 0x74F692: sysync::TSyncAppBase::TSyncAppBase() (syncappbase.cpp:1212) # ==22471== by 0x71A2A8: sysync::TSyncClientBase::TSyncClientBase() (syncclientbase.cpp:345) # ==22471== by 0x7A094C: sysync::TEngineClientBase::TEngineClientBase() (engineclientbase.cpp:97) # ==22471== by 0x6EEEF3: sysync::TCustomClientEngineBase::TCustomClientEngineBase() (clientengine_custom_Base.cpp:49) # ==22471== by 0x6EEF89: sysync::TCustomClientEngineInterface::newSyncAppBase() (clientengine_custom_Base.cpp:39) # ==22471== by 0x6CCADB: sysync::TEngineInterface::Init() (engineinterface.cpp:1016) # ==22471== by 0x6ECC7A: sysync::TEngineModuleBase::Connect(std::string, unsigned long, unsigned short) (enginemodulebase.cpp:76) # ==22471== by 0x6CD1B1: SySync_ConnectEngine (engineinterface.cpp:1757) # ==22471== by 0x6CB2BE: sysync::UI_Connect(sysync::SDK_InterfaceType*&, void*&, char const*, unsigned long, unsigned short) (UI_util.cpp:164) # ==22471== by 0x6CAB14: sysync::TEngineModuleBridge::Init() (enginemodulebridge.cpp:43) # ==22471== by 0x6ECC7A: sysync::TEngineModuleBase::Connect(std::string, unsigned long, unsigned short) (enginemodulebase.cpp:76) # ==22471== by 0x6B1AD7: SharedEngine::Connect(std::string const&, unsigned long, unsigned short) (SynthesisEngine.cpp:29) # ==22471== by 0x680CD7: EvolutionSyncClient::createEngine() (EvolutionSyncClient.cpp:1361) # ==22471== by 0x6908B9: EvolutionSyncClient::SwapEngine::SwapEngine(EvolutionSyncClient&) (EvolutionSyncClient.h:282) # ==22471== by 0x6883C9: EvolutionSyncClient::sync(SyncReport*) (EvolutionSyncClient.cpp:1409) # ==22471== by 0x528EB7: TestEvolution::doSync(int const*, std::string const&, SyncOptions const&) (client-test-app.cpp:391) # ==22471== by 0x5398BB: SyncTests::doSync(SyncOptions const&) (ClientTest.cpp:3096) # ==22471== by 0x610CC4: SyncTests::doSync(char const*, SyncOptions const&) (ClientTest.h:950) # ==22471== by 0x60BE87: SyncTests::deleteAll(SyncTests::DeleteAllMode) (ClientTest.cpp:1761) # ==22471== by 0x5DC3DB: SyncTests::testItems() (ClientTest.cpp:2521) # ==22471== by 0x60EE75: CppUnit::TestCaller::runTest() (TestCaller.h:166) # ==22471== by 0x6795846: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==22471== by 0x6787C43: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==22471== by 0x6791758: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==22471== by 0x679149B: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==22471== Address 0xece4088 is 0 bytes after a block of size 0 alloc'd # ==22471== at 0x4C223DC: calloc (vg_replace_malloc.c:397) # ==22471== by 0x5BE9DF0: icaltzutil_fetch_timezone (icaltz-util.c:259) # ==22471== by 0x5BEB468: icaltimezone_load_builtin_timezone (icaltimezone.c:1758) # ==22471== by 0x5BEB4AA: icaltimezone_get_component (icaltimezone.c:1223) # ==22471== by 0x6EEBC5: sysync::loadSystemZoneDefinitions(sysync::GZones*) (platform_timezones.cpp:55) # ==22471== by 0x6E5430: sysync::GZones::initialize() (timezones.cpp:95) # ==22471== by 0x74F692: sysync::TSyncAppBase::TSyncAppBase() (syncappbase.cpp:1212) # ==22471== by 0x71A2A8: sysync::TSyncClientBase::TSyncClientBase() (syncclientbase.cpp:345) # ==22471== by 0x7A094C: sysync::TEngineClientBase::TEngineClientBase() (engineclientbase.cpp:97) # ==22471== by 0x6EEEF3: sysync::TCustomClientEngineBase::TCustomClientEngineBase() (clientengine_custom_Base.cpp:49) # ==22471== by 0x6EEF89: sysync::TCustomClientEngineInterface::newSyncAppBase() (clientengine_custom_Base.cpp:39) # ==22471== by 0x6CCADB: sysync::TEngineInterface::Init() (engineinterface.cpp:1016) # ==22471== by 0x6ECC7A: sysync::TEngineModuleBase::Connect(std::string, unsigned long, unsigned short) (enginemodulebase.cpp:76) # ==22471== by 0x6CD1B1: SySync_ConnectEngine (engineinterface.cpp:1757) # ==22471== by 0x6CB2BE: sysync::UI_Connect(sysync::SDK_InterfaceType*&, void*&, char const*, unsigned long, unsigned short) (UI_util.cpp:164) # ==22471== by 0x6CAB14: sysync::TEngineModuleBridge::Init() (enginemodulebridge.cpp:43) # ==22471== by 0x6ECC7A: sysync::TEngineModuleBase::Connect(std::string, unsigned long, unsigned short) (enginemodulebase.cpp:76) # ==22471== by 0x6B1AD7: SharedEngine::Connect(std::string const&, unsigned long, unsigned short) (SynthesisEngine.cpp:29) # ==22471== by 0x680CD7: EvolutionSyncClient::createEngine() (EvolutionSyncClient.cpp:1361) # ==22471== by 0x6908B9: EvolutionSyncClient::SwapEngine::SwapEngine(EvolutionSyncClient&) (EvolutionSyncClient.h:282) # ==22471== by 0x6883C9: EvolutionSyncClient::sync(SyncReport*) (EvolutionSyncClient.cpp:1409) # ==22471== by 0x528EB7: TestEvolution::doSync(int const*, std::string const&, SyncOptions const&) (client-test-app.cpp:391) # ==22471== by 0x5398BB: SyncTests::doSync(SyncOptions const&) (ClientTest.cpp:3096) # ==22471== by 0x610CC4: SyncTests::doSync(char const*, SyncOptions const&) (ClientTest.h:950) # ==22471== by 0x60BE87: SyncTests::deleteAll(SyncTests::DeleteAllMode) (ClientTest.cpp:1761) # ==22471== by 0x5DC3DB: SyncTests::testItems() (ClientTest.cpp:2521) # ==22471== by 0x60EE75: CppUnit::TestCaller::runTest() (TestCaller.h:166) # ==22471== by 0x6795846: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==22471== by 0x6787C43: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==22471== by 0x6791758: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) { ical timezones Memcheck:Addr8 fun:icaltzutil_fetch_timezone fun:icaltimezone_load_builtin_timezone fun:icaltimezone_get_component fun:_ZN6sysync25loadSystemZoneDefinitionsEPNS_6GZonesE } # ==22471== Syscall param write(buf) points to uninitialised byte(s) # ==22471== at 0x741FBFB: (within /lib/libc-2.7.so) # ==22471== by 0x73C3CE2: _IO_file_write@@GLIBC_2.2.5 (in /lib/libc-2.7.so) # ==22471== by 0x73C3BC3: _IO_do_write@@GLIBC_2.2.5 (in /lib/libc-2.7.so) # ==22471== by 0x73C4248: _IO_file_sync@@GLIBC_2.2.5 (in /lib/libc-2.7.so) # ==22471== by 0x73B9229: fflush (in /lib/libc-2.7.so) # ==22471== by 0x7A0C84: sysync::TBinFile::platformFlushFile() (binfile.cpp:95) # ==22471== by 0x7A2F3A: sysync::TBinFileBase::flushHeader() (binfilebase.cpp:240) # ==22471== by 0x7A39BA: sysync::TBinFileBase::create(unsigned int, unsigned int, void*, bool) (binfilebase.cpp:187) # ==22471== by 0x7AD234: sysync::TBinfileImplDS::openChangeLog() (binfileimplds.cpp:529) # ==22471== by 0x7AF94E: sysync::TBinfileImplDS::implMakeAdminReady(char const*, char const*, char const*) (binfileimplds.cpp:1022) # ==22471== by 0x7B67E4: sysync::TCustomImplDS::implMakeAdminReady(char const*, char const*, char const*) (customimplds.cpp:1514) # ==22471== by 0x7BF61F: sysync::TStdLogicDS::logicMakeAdminReady(char const*, char const*) (stdlogicds.cpp:120) # ==22471== by 0x6D2951: sysync::TLocalEngineDS::engInitSyncAnchors(char const*, char const*) (localengineds.cpp:3165) # ==22471== by 0x6D78ED: sysync::TLocalEngineDS::engPrepareClientSyncAlert(sysync::TLocalEngineDS*) (localengineds.cpp:3207) # ==22471== by 0x7A7351: sysync::TSyncClient::NextMessage(bool&) (syncclient.cpp:900) # ==22471== by 0x7A8344: sysync::TSyncClient::generatingStep(unsigned short&, sysync::TEngineProgressType*) (syncclient.cpp:487) # ==22471== by 0x7A89F1: sysync::TSyncClient::SessionStep(unsigned short&, sysync::TEngineProgressType*) (syncclient.cpp:427) # ==22471== by 0x71A49F: sysync::TClientEngineInterface::SessionStep(sysync::SessionType*, unsigned short&, sysync::TEngineProgressType*) (syncclientbase.cpp:274) # ==22471== by 0x6CB757: SessionStep (engineentry.cpp:88) # ==22471== by 0x6C9E4A: sysync::TEngineModuleBridge::SessionStep(sysync::SessionType*, unsigned short&, sysync::TEngineProgressType*) (enginemodulebridge.cpp:108) # ==22471== by 0x6B0E6E: SharedEngine::SessionStep(boost::shared_ptr const&, unsigned short&, sysync::TEngineProgressType*) (SynthesisEngine.cpp:92) # ==22471== by 0x684949: EvolutionSyncClient::doSync() (EvolutionSyncClient.cpp:1654) # ==22471== by 0x688B65: EvolutionSyncClient::sync(SyncReport*) (EvolutionSyncClient.cpp:1459) # ==22471== by 0x528EB7: TestEvolution::doSync(int const*, std::string const&, SyncOptions const&) (client-test-app.cpp:391) # ==22471== by 0x5398BB: SyncTests::doSync(SyncOptions const&) (ClientTest.cpp:3096) # ==22471== by 0x610CC4: SyncTests::doSync(char const*, SyncOptions const&) (ClientTest.h:950) # ==22471== by 0x60BE87: SyncTests::deleteAll(SyncTests::DeleteAllMode) (ClientTest.cpp:1761) # ==22471== by 0x5DC3DB: SyncTests::testItems() (ClientTest.cpp:2521) # ==22471== by 0x60EE75: CppUnit::TestCaller::runTest() (TestCaller.h:166) # ==22471== by 0x6795846: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.0.0.0) # ==22471== Address 0x4024021 is not stack'd, malloc'd or (recently) free'd { uninitialized data in binfile Memcheck:Param write(buf) obj:*libc* fun:_IO_file_write* fun:_IO_do_write* fun:_IO_file_sync* fun:fflush fun:_ZN6sysync8TBinFile17platformFlushFileEv } # ==27326== Syscall param write(buf) points to uninitialised byte(s) # ==27326== at 0x741FBFB: (within /lib/libc-2.7.so) # ==27326== by 0x73C3CE2: _IO_file_write@@GLIBC_2.2.5 (in /lib/libc-2.7.so) # ==27326== by 0x73C3BC3: _IO_do_write@@GLIBC_2.2.5 (in /lib/libc-2.7.so) # ==27326== by 0x73C5908: _IO_switch_to_get_mode (in /lib/libc-2.7.so) # ==27326== by 0x73C3EF7: _IO_file_seekoff@@GLIBC_2.2.5 (in /lib/libc-2.7.so) # ==27326== by 0x73C0E79: fseek (in /lib/libc-2.7.so) # ==27326== by 0x7A0D9C: sysync::TBinFile::platformSeekFile(unsigned int, bool) (binfile.cpp:74) # ==27326== by 0x7A329D: sysync::TBinFileBase::newRecord(unsigned int&, void const*) (binfilebase.cpp:296) # ==27326== by 0x7B3280: sysync::TBinFileBase::newRecord(void const*) (binfilebase.h:106) # ==27326== by 0x7B0C79: sysync::TBinfileImplDS::changeLogPreflight(bool&) (binfileimplds.cpp:839) # ==27326== by 0x7B16EF: sysync::TBinfileImplDS::implGetItem(bool&, bool&, sysync::TSyncItem*&) (binfileimplds.cpp:1337) # ==27326== by 0x7BF14C: sysync::TStdLogicDS::logicGenerateSyncCommandsAsClient(std::list >&, sysync::TSmlCommand*&, char const*) (stdlogicds.cpp:939) # ==27326== by 0x6D2BA0: sysync::TLocalEngineDS::engGenerateSyncCommands(std::list >&, sysync::TSmlCommand*&, char const*) (localengineds.cpp:2859) # ==27326== by 0x711719: sysync::TSyncCommand::generateCommandsAndClose() (synccommand.cpp:972) # ... # ==27326== Address 0x40241ad is not stack'd, malloc'd or (recently) free'd # ==27326== Uninitialised value was created by a stack allocation # ==27326== at 0x7B0278: sysync::TBinfileImplDS::changeLogPreflight(bool&) (binfileimplds.cpp:596) { uninitialized data in binfile II Memcheck:Param write(buf) obj:*libc* fun:_IO_file_write* fun:_IO_do_write* fun:_IO_switch_to_get_mode fun:_IO_file_seekoff* fun:fseek fun:_ZN6sysync8TBinFile16platformSeekFileEjb } # ==12957== 2,248 bytes in 37 blocks are possibly lost in loss record 150 of 161 # ==12957== at 0x4C203E4: calloc (vg_replace_malloc.c:397) # ==12957== by 0x6174B49: g_malloc0 (in /usr/lib/libglib-2.0.so.0.1600.6) # ==12957== by 0x91F6917: ORBit_alloc_tcval (in /usr/lib/libORBit-2.so.0.1.0) # ==12957== by 0x9200035: (within /usr/lib/libORBit-2.so.0.1.0) # ==12957== by 0x91FDCBA: IOP_generate_profiles (in /usr/lib/libORBit-2.so.0.1.0) # ==12957== by 0x91F4E6B: ORBit_marshal_object (in /usr/lib/libORBit-2.so.0.1.0) # ==12957== by 0x91FB3D7: ORBit_marshal_value (in /usr/lib/libORBit-2.so.0.1.0) # ==12957== by 0x91F20BF: (within /usr/lib/libORBit-2.so.0.1.0) # ==12957== by 0x91F34EC: ORBit_small_invoke_stub (in /usr/lib/libORBit-2.so.0.1.0) # ==12957== by 0x8B1FAD8: ConfigServer_add_client (in /usr/lib/libgconf-2.so.4.1.5) # ==12957== by 0x8B134BC: (within /usr/lib/libgconf-2.so.4.1.5) # ==12957== by 0x8B13FDD: (within /usr/lib/libgconf-2.so.4.1.5) # ==12957== by 0x8B141F8: (within /usr/lib/libgconf-2.so.4.1.5) # ==12957== by 0x8B148F6: gconf_engine_notify_add (in /usr/lib/libgconf-2.so.4.1.5) # ==12957== by 0x8B1C5FF: gconf_client_add_dir (in /usr/lib/libgconf-2.so.4.1.5) # ==12957== by 0x88E4D11: e_source_list_new_for_gconf (in /usr/lib/libedataserver-1.2.so.9.1.0) # ==12957== by 0xC2855BF: e_book_get_addressbooks (in /usr/lib/libebook-1.2.so.9.1.1) # ==12957== by 0x60FF33: EvolutionContactSource::open() (EvolutionContactSource.cpp:124) # ==12957== by 0x4648E2: TestEvolutionSyncSource::beginSync(SyncMode) (in /work/runtests/head-evolution-lenny-2.22.3.1-full/build/src/client-test) # ==12957== by 0x55492C: LocalTests::testIterateTwice() (in /work/runtests/head-evolution-lenny-2.22.3.1-full/build/src/client-test) # ==12957== by 0x5AA6DF: CppUnit::TestCaller::runTest() (in /work/runtests/head-evolution-lenny-2.22.3.1-full/build/src/client-test) # ==12957== by 0x4E51406: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==12957== by 0x4E437D3: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==12957== by 0x4E4D278: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==12957== by 0x4E4CFBB: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==12957== by 0x4E58D9F: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==12957== by 0x4E5109C: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==12957== by 0x4E519FB: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==12957== by 0x4E51925: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==12957== by 0x4E519FB: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.1.0.0) { e_book_get_addressbooks Memcheck:Leak fun:calloc ... obj:*libORBit* ... fun:e_book_get_addressbooks } # ==13858== 2,248 bytes in 37 blocks are possibly lost in loss record 150 of 161 # ==13858== at 0x4C203E4: calloc (vg_replace_malloc.c:397) # ==13858== by 0x6174B49: g_malloc0 (in /usr/lib/libglib-2.0.so.0.1600.6) # ==13858== by 0x91F6896: ORBit_alloc_by_tc (in /usr/lib/libORBit-2.so.0.1.0) # ==13858== by 0x9200016: (within /usr/lib/libORBit-2.so.0.1.0) # ==13858== by 0x91FDCBA: IOP_generate_profiles (in /usr/lib/libORBit-2.so.0.1.0) # ==13858== by 0x91F4E6B: ORBit_marshal_object (in /usr/lib/libORBit-2.so.0.1.0) # ==13858== by 0x91FB3D7: ORBit_marshal_value (in /usr/lib/libORBit-2.so.0.1.0) # ==13858== by 0x91F20BF: (within /usr/lib/libORBit-2.so.0.1.0) # ==13858== by 0x91F34EC: ORBit_small_invoke_stub (in /usr/lib/libORBit-2.so.0.1.0) # ==13858== by 0x8B1FAD8: ConfigServer_add_client (in /usr/lib/libgconf-2.so.4.1.5) # ==13858== by 0x8B134BC: (within /usr/lib/libgconf-2.so.4.1.5) # ==13858== by 0x8B13FDD: (within /usr/lib/libgconf-2.so.4.1.5) # ==13858== by 0x8B141F8: (within /usr/lib/libgconf-2.so.4.1.5) # ==13858== by 0x8B148F6: gconf_engine_notify_add (in /usr/lib/libgconf-2.so.4.1.5) # ==13858== by 0x8B1C5FF: gconf_client_add_dir (in /usr/lib/libgconf-2.so.4.1.5) # ==13858== by 0x88E4D11: e_source_list_new_for_gconf (in /usr/lib/libedataserver-1.2.so.9.1.0) # ==13858== by 0xC2855BF: e_book_get_addressbooks (in /usr/lib/libebook-1.2.so.9.1.1) # ==13858== by 0x60FF33: EvolutionContactSource::open() (EvolutionContactSource.cpp:124) # ==13858== by 0x4648E2: TestEvolutionSyncSource::beginSync(SyncMode) (in /work/runtests/head-evolution-lenny-2.22.3.1-full/build/src/client-test) # ==13858== by 0x55492C: LocalTests::testIterateTwice() (in /work/runtests/head-evolution-lenny-2.22.3.1-full/build/src/client-test) # ==13858== by 0x5AA6DF: CppUnit::TestCaller::runTest() (in /work/runtests/head-evolution-lenny-2.22.3.1-full/build/src/client-test) # ==13858== by 0x4E51406: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==13858== by 0x4E437D3: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==13858== by 0x4E4D278: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==13858== by 0x4E4CFBB: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==13858== by 0x4E58D9F: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==13858== by 0x4E5109C: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==13858== by 0x4E519FB: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==13858== by 0x4E51925: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==13858== by 0x4E519FB: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.1.0.0) { e_book_get_addressbooks II Memcheck:Leak fun:calloc fun:g_malloc0 fun:ORBit_alloc_by_tc obj:*libORBit* fun:IOP_generate_profiles fun:ORBit_marshal_object fun:ORBit_marshal_value obj:*libORBit* fun:ORBit_small_invoke_stub fun:ConfigServer_add_client obj:*libgconf* obj:*libgconf* obj:*libgconf* fun:gconf_engine_notify_add fun:gconf_client_add_dir fun:e_source_list_new_for_gconf fun:e_book_get_addressbooks } # ==28640== 63,343 bytes in 735 blocks are possibly lost in loss record 163 of 169 # ==28640== at 0x4C21FCC: operator new(unsigned long) (vg_replace_malloc.c:230) # ==28640== by 0x6499450: std::string::_Rep::_S_create(unsigned long, unsigned long, std::allocator const&) (in /usr/lib/libstdc++.so.6.0.10) # ==28640== by 0x6499E14: (within /usr/lib/libstdc++.so.6.0.10) # ==28640== by 0x6499F52: std::string::string(char const*, std::allocator const&) (in /usr/lib/libstdc++.so.6.0.10) # ==28640== by 0x5FB83B: __static_initialization_and_destruction_0(int, int) (SyncEvolutionConfig.cpp:871) # ==28640== by 0x629F55: (within /work/runtests/head-evolution-lenny-2.22.3.1-full/build/src/client-test) # ==28640== by 0x454952: (within /work/runtests/head-evolution-lenny-2.22.3.1-full/build/src/client-test) # ==28640== by 0x7FF00048F: ??? # ==28640== by 0x629EE4: __libc_csu_init (in /work/runtests/head-evolution-lenny-2.22.3.1-full/build/src/client-test) # ==28640== by 0x6936131: (below main) (in /lib/libc-2.7.so) { static strings Memcheck:Leak fun:_Znwm fun:_ZNSs4_Rep9_S_createEmmRKSaIcE obj:*libstdc++* fun:_ZNSsC1EPKcRKSaIcE fun:_Z41__static_initialization_and_destruction_0ii } # ==30592== 1,794 (178 direct, 1,616 indirect) bytes in 4 blocks are definitely lost in loss record 26 of 43 # ==30592== at 0x4C2391E: malloc (vg_replace_malloc.c:207) # ==30592== by 0x662D742: g_malloc (gmem.c:131) # ==30592== by 0x66453DD: g_strdup (gstrfuncs.c:102) # ==30592== by 0x410874: add_initial_locales (in /usr/lib/bonobo-activation/bonobo-activation-server) # ==30592== by 0x4113DE: main (in /usr/lib/bonobo-activation/bonobo-activation-server) { bonobo server I Memcheck:Leak fun:malloc fun:g_malloc fun:g_strdup fun:add_initial_locales fun:main } # ==30592== # ==30592== # ==30592== 32,519 (2,104 direct, 30,415 indirect) bytes in 67 blocks are definitely lost in loss record 40 of 43 # ==30592== at 0x4C216F4: calloc (vg_replace_malloc.c:397) # ==30592== by 0x662D6E9: g_malloc0 (gmem.c:151) # ==30592== by 0x5501978: (within /usr/lib/libORBit-2.so.0.1.0) # ==30592== by 0x5501E54: ORBit_POA_new_from (in /usr/lib/libORBit-2.so.0.1.0) # ==30592== by 0x4E56231: bonobo_poa_new_from (in /usr/lib/libbonobo-2.so.0.0.0) # ==30592== by 0x4E56347: bonobo_poa_get_threadedv (in /usr/lib/libbonobo-2.so.0.0.0) # ==30592== by 0x4E5651C: bonobo_poa_get_threaded (in /usr/lib/libbonobo-2.so.0.0.0) # ==30592== by 0x411B4B: main (in /usr/lib/bonobo-activation/bonobo-activation-server) { bonobo server II Memcheck:Leak fun:calloc fun:g_malloc0 obj:/usr/lib/libORBit-2.so.0.1.0 fun:ORBit_POA_new_from fun:bonobo_poa_new_from fun:bonobo_poa_get_threadedv fun:bonobo_poa_get_threaded fun:main } # ==30565== Thread 3: # ==30565== Invalid free() / delete / delete[] # ==30565== at 0x4C2261F: free (vg_replace_malloc.c:323) # ==30565== by 0x5BAB1AA: (within /lib/libc-2.9.so) # ==30565== by 0x5BAAD41: (within /lib/libc-2.9.so) # ==30565== by 0x4A1E560: _vgnU_freeres (vg_preloaded.c:60) # ==30565== by 0xFFFFFFFE: ??? # ==30565== Address 0x402cd10 is not stack'd, malloc'd or (recently) free'd { libc I Memcheck:Free fun:free obj:/lib/libc-2.9.so obj:/lib/libc-2.9.so fun:_vgnU_freeres obj:* } # ==17808== # ==17808== # ==17808== 32,519 (2,104 direct, 30,415 indirect) bytes in 67 blocks are definitely lost in loss==17813== # ==17813== Thread 1: # ==17813== # ==17813== 49 bytes in 1 blocks are definitely lost in loss record 14 of 76 # ==17813== at 0x4C2391E: malloc (vg_replace_malloc.c:207) # ==17813== by 0x7A729E5: poptGetNextOpt (in /lib/libpopt.so.0.0.0) # ==17813== by 0x78634A7: gnome_program_parse_args (in /usr/lib/libgnome-2.so.0.2600.0) # ==17813== by 0x78645C2: (within /usr/lib/libgnome-2.so.0.2600.0) # ==17813== by 0x786484C: gnome_program_initv (in /usr/lib/libgnome-2.so.0.2600.0) # ==17813== by 0x7864943: gnome_program_init (in /usr/lib/libgnome-2.so.0.2600.0) # ==17813== by 0x403E24: main (server.c:366) { gnome poptGetNextopt Memcheck:Leak fun:malloc fun:poptGetNextOpt fun:gnome_program_parse_args obj:/usr/lib/libgnome-2.so.0.2600.0 fun:gnome_program_initv fun:gnome_program_init fun:main } # ==17813== 6,032 bytes in 1,508 blocks are definitely lost in loss record 55 of 76 # ==17813== at 0x4C2391E: malloc (vg_replace_malloc.c:207) # ==17813== by 0x7612A24: __os_umalloc (in /usr/lib/libdb-4.7.so) # ==17813== by 0x75DAE24: __db_retcopy (in /usr/lib/libdb-4.7.so) # ==17813== by 0x75DAF70: __db_ret (in /usr/lib/libdb-4.7.so) # ==17813== by 0x75BE317: __dbc_get (in /usr/lib/libdb-4.7.so) # ==17813== by 0x75C6EF3: __dbc_get_pp (in /usr/lib/libdb-4.7.so) # ==17813== by 0xEDC4029: e_book_backend_file_get_contact_list (e-book-backend-file.c:458) # ==17813== by 0x526F212: _e_book_backend_get_contact_list (e-book-backend-sync.c:442) # ==17813== by 0x8B204EF: ORBit_small_invoke_adaptor (in /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x8B30809: (within /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x8B30E39: (within /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x8B195CE: giop_thread_queue_process (in /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x8B19E87: (within /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x9D1FAD6: g_thread_pool_thread_proxy (gthreadpool.c:265) # ==17813== by 0x9D1E573: g_thread_create_proxy (gthread.c:635) # ==17813== by 0xA7CDFA9: start_thread (in /lib/libpthread-2.9.so) # ==17813== by 0xAAB029C: clone (in /lib/libc-2.9.so) { ebook get contact list Memcheck:Leak fun:malloc fun:__os_umalloc fun:__db_retcopy fun:__db_ret fun:__dbc_get fun:__dbc_get_pp fun:e_book_backend_file_get_contact_list fun:_e_book_backend_get_contact_list fun:ORBit_small_invoke_adaptor obj:/usr/lib/libORBit-2.so.0.1.0 obj:/usr/lib/libORBit-2.so.0.1.0 fun:giop_thread_queue_process obj:/usr/lib/libORBit-2.so.0.1.0 fun:g_thread_pool_thread_proxy fun:g_thread_create_proxy fun:start_thread fun:clone } # ==17813== 7,800 bytes in 78 blocks are possibly lost in loss record 57 of 76 # ==17813== at 0x4C2391E: malloc (vg_replace_malloc.c:207) # ==17813== by 0x4C23AA7: realloc (vg_replace_malloc.c:429) # ==17813== by 0x8B1BC47: (within /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x8B1CC48: giop_connection_handle_input (in /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x8B39E57: (within /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x9CF4F79: g_main_context_dispatch (gmain.c:1814) # ==17813== by 0x9CF863F: g_main_context_iterate (gmain.c:2448) # ==17813== by 0x9CF8B0C: g_main_loop_run (gmain.c:2656) # ==17813== by 0x8B381CF: (within /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x9D1E573: g_thread_create_proxy (gthread.c:635) # ==17813== by 0xA7CDFA9: start_thread (in /lib/libpthread-2.9.so) # ==17813== by 0xAAB029C: clone (in /lib/libc-2.9.so) { giop_connection_handle_input II Memcheck:Leak fun:malloc fun:realloc obj:/usr/lib/libORBit-2.so.0.1.0 fun:giop_connection_handle_input obj:/usr/lib/libORBit-2.so.0.1.0 fun:g_main_context_dispatch fun:g_main_context_iterate fun:g_main_loop_run obj:/usr/lib/libORBit-2.so.0.1.0 fun:g_thread_create_proxy fun:start_thread fun:clone } # ==17813== 1,962,124 (95,624 direct, 1,866,500 indirect) bytes in 1,687 blocks are definitely lost in loss record 62 of 76 # ==17813== at 0x4C2391E: malloc (vg_replace_malloc.c:207) # ==17813== by 0x9CFD742: g_malloc (gmem.c:131) # ==17813== by 0x9D13707: g_slice_alloc (gslice.c:824) # ==17813== by 0x9D13A35: g_slice_alloc0 (gslice.c:833) # ==17813== by 0x96617AF: g_type_create_instance (gtype.c:1654) # ==17813== by 0x964645A: g_object_constructor (gobject.c:1338) # ==17813== by 0x8690E14: (within /usr/lib/libbonobo-2.so.0.0.0) # ==17813== by 0x9646AA2: g_object_newv (gobject.c:1215) # ==17813== by 0x96474DA: g_object_new_valist (gobject.c:1319) # ==17813== by 0x964772B: g_object_new (gobject.c:1060) # ==17813== by 0x527473F: e_data_book_new (e-data-book.c:943) # ==17813== by 0x52722EA: impl_GNOME_Evolution_Addressbook_BookFactory_getBook (e-data-book-factory.c:351) # ==17813== by 0x52670B6: _ORBIT_skel_small_GNOME_Evolution_Addressbook_BookFactory_getBook (Evolution-DataServer-Addressbook-common.c:180) # ==17813== by 0x8B204EF: ORBit_small_invoke_adaptor (in /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x8B30809: (within /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x8B30E39: (within /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x8B195CE: giop_thread_queue_process (in /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x8B19E87: (within /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x9D1FAD6: g_thread_pool_thread_proxy (gthreadpool.c:265) # ==17813== by 0x9D1E573: g_thread_create_proxy (gthread.c:635) # ==17813== by 0xA7CDFA9: start_thread (in /lib/libpthread-2.9.so) # ==17813== by 0xAAB029C: clone (in /lib/libc-2.9.so) { gnome edata book new Memcheck:Leak fun:malloc fun:g_malloc fun:g_slice_alloc fun:g_slice_alloc0 fun:g_type_create_instance fun:g_object_constructor obj:/usr/lib/libbonobo-2.so.0.0.0 fun:g_object_newv fun:g_object_new_valist fun:g_object_new fun:e_data_book_new fun:impl_GNOME_Evolution_Addressbook_BookFactory_getBook fun:_ORBIT_skel_small_GNOME_Evolution_Addressbook_BookFactory_getBook fun:ORBit_small_invoke_adaptor obj:/usr/lib/libORBit-2.so.0.1.0 obj:/usr/lib/libORBit-2.so.0.1.0 fun:giop_thread_queue_process obj:/usr/lib/libORBit-2.so.0.1.0 fun:g_thread_pool_thread_proxy fun:g_thread_create_proxy fun:start_thread fun:clone } # ==17813== 450,999 (12,320 direct, 438,679 indirect) bytes in 385 blocks are definitely lost in loss record 65 of 76 # ==17813== at 0x4C2391E: malloc (vg_replace_malloc.c:207) # ==17813== by 0x4C23AA7: realloc (vg_replace_malloc.c:429) # ==17813== by 0x9CFD66D: g_realloc (gmem.c:170) # ==17813== by 0x96427CB: g_object_weak_ref (gobject.c:1983) # ==17813== by 0x707D9C8: e_cal_backend_add_client (e-cal-backend.c:394) # ==17813== by 0x708872C: impl_CalFactory_getCal (e-data-cal-factory.c:230) # ==17813== by 0x7077979: _ORBIT_skel_small_GNOME_Evolution_Calendar_CalFactory_getCal (Evolution-DataServer-Calendar-common.c:244) # ==17813== by 0x8B204EF: ORBit_small_invoke_adaptor (in /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x8B30809: (within /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x8B30E39: (within /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x8B195CE: giop_thread_queue_process (in /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x8B19E87: (within /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x9D1FAD6: g_thread_pool_thread_proxy (gthreadpool.c:265) # ==17813== by 0x9D1E573: g_thread_create_proxy (gthread.c:635) # ==17813== by 0xA7CDFA9: start_thread (in /lib/libpthread-2.9.so) # ==17813== by 0xAAB029C: clone (in /lib/libc-2.9.so) { ecal backend add client Memcheck:Leak fun:malloc fun:realloc fun:g_realloc fun:g_object_weak_ref fun:e_cal_backend_add_client fun:impl_CalFactory_getCal fun:_ORBIT_skel_small_GNOME_Evolution_Calendar_CalFactory_getCal fun:ORBit_small_invoke_adaptor obj:/usr/lib/libORBit-2.so.0.1.0 obj:/usr/lib/libORBit-2.so.0.1.0 fun:giop_thread_queue_process obj:/usr/lib/libORBit-2.so.0.1.0 fun:g_thread_pool_thread_proxy fun:g_thread_create_proxy fun:start_thread fun:clone } # ==17813== 175,504 bytes in 43 blocks are definitely lost in loss record 71 of 76 # ==17813== at 0x4C2391E: malloc (vg_replace_malloc.c:207) # ==17813== by 0x76127D7: __os_malloc (in /usr/lib/libdb-4.7.so) # ==17813== by 0x75E352E: __env_alloc (in /usr/lib/libdb-4.7.so) # ==17813== by 0x760219F: __memp_alloc (in /usr/lib/libdb-4.7.so) # ==17813== by 0x7604450: __memp_fget (in /usr/lib/libdb-4.7.so) # ==17813== by 0x75497ED: __ham_get_meta (in /usr/lib/libdb-4.7.so) # ==17813== by 0x754A9C3: __ham_open (in /usr/lib/libdb-4.7.so) # ==17813== by 0x75CFD72: __db_open (in /usr/lib/libdb-4.7.so) # ==17813== by 0x75C86C6: __db_open_pp (in /usr/lib/libdb-4.7.so) # ==17813== by 0xEDC579F: e_book_backend_file_load_source (e-book-backend-file.c:1201) # ==17813== by 0x52716C5: e_book_backend_load_source (e-book-backend.c:74) # ==17813== by 0x5271FE0: e_book_backend_open (e-book-backend.c:129) # ==17813== by 0x8B204EF: ORBit_small_invoke_adaptor (in /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x8B30809: (within /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x8B30E39: (within /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x8B195CE: giop_thread_queue_process (in /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x8B19E87: (within /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x9D1FAD6: g_thread_pool_thread_proxy (gthreadpool.c:265) # ==17813== by 0x9D1E573: g_thread_create_proxy (gthread.c:635) # ==17813== by 0xA7CDFA9: start_thread (in /lib/libpthread-2.9.so) # ==17813== by 0xAAB029C: clone (in /lib/libc-2.9.so) { ebook backend open I Memcheck:Leak fun:malloc fun:__os_malloc fun:__env_alloc fun:__memp_alloc fun:__memp_fget fun:__ham_get_meta fun:__ham_open fun:__db_open fun:__db_open_pp fun:e_book_backend_file_load_source fun:e_book_backend_load_source fun:e_book_backend_open fun:ORBit_small_invoke_adaptor obj:/usr/lib/libORBit-2.so.0.1.0 obj:/usr/lib/libORBit-2.so.0.1.0 fun:giop_thread_queue_process obj:/usr/lib/libORBit-2.so.0.1.0 fun:g_thread_pool_thread_proxy fun:g_thread_create_proxy fun:start_thread fun:clone } # ==17813== at 0x4C2391E: malloc (vg_replace_malloc.c:207) # ==17813== by 0x76127D7: __os_malloc (in /usr/lib/libdb-4.7.so) # ==17813== by 0x75E352E: __env_alloc (in /usr/lib/libdb-4.7.so) # ==17813== by 0x760CD1A: __memp_init (in /usr/lib/libdb-4.7.so) # ==17813== by 0x760D66D: __memp_open (in /usr/lib/libdb-4.7.so) # ==17813== by 0x75E926F: __env_open (in /usr/lib/libdb-4.7.so) # ==17813== by 0xEDC585F: e_book_backend_file_load_source (e-book-backend-file.c:1113) # ==17813== by 0x52716C5: e_book_backend_load_source (e-book-backend.c:74) # ==17813== by 0x5271FE0: e_book_backend_open (e-book-backend.c:129) # ==17813== by 0x8B204EF: ORBit_small_invoke_adaptor (in /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x8B30809: (within /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x8B30E39: (within /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x8B195CE: giop_thread_queue_process (in /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x8B19E87: (within /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x9D1FAD6: g_thread_pool_thread_proxy (gthreadpool.c:265) # ==17813== by 0x9D1E573: g_thread_create_proxy (gthread.c:635) # ==17813== by 0xA7CDFA9: start_thread (in /lib/libpthread-2.9.so) # ==17813== by 0xAAB029C: clone (in /lib/libc-2.9.so) { eook backend open II Memcheck:Leak fun:malloc fun:__os_malloc fun:__env_alloc fun:__memp_init fun:__memp_open fun:__env_open fun:e_book_backend_file_load_source fun:e_book_backend_load_source fun:e_book_backend_open fun:ORBit_small_invoke_adaptor obj:/usr/lib/libORBit-2.so.0.1.0 obj:/usr/lib/libORBit-2.so.0.1.0 fun:giop_thread_queue_process obj:/usr/lib/libORBit-2.so.0.1.0 fun:g_thread_pool_thread_proxy fun:g_thread_create_proxy fun:start_thread fun:clone } # ==17813== 49,700,435 (24,021,928 direct, 25,678,507 indirect) bytes in 150,579 blocks are definitely lost in loss record 76 of 76 # ==17813== at 0x4C216F4: calloc (vg_replace_malloc.c:397) # ==17813== by 0x9CFD6E9: g_malloc0 (gmem.c:151) # ==17813== by 0x8B1B567: giop_recv_buffer_use_buf (in /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x8B1CD84: giop_connection_handle_input (in /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x8B39E57: (within /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x9CF4F79: g_main_context_dispatch (gmain.c:1814) # ==17813== by 0x9CF863F: g_main_context_iterate (gmain.c:2448) # ==17813== by 0x9CF8B0C: g_main_loop_run (gmain.c:2656) # ==17813== by 0x8B381CF: (within /usr/lib/libORBit-2.so.0.1.0) # ==17813== by 0x9D1E573: g_thread_create_proxy (gthread.c:635) # ==17813== by 0xA7CDFA9: start_thread (in /lib/libpthread-2.9.so) # ==17813== by 0xAAB029C: clone (in /lib/libc-2.9.so) { giop connection handle input III Memcheck:Leak fun:calloc fun:g_malloc0 fun:giop_recv_buffer_use_buf fun:giop_connection_handle_input obj:/usr/lib/libORBit-2.so.0.1.0 fun:g_main_context_dispatch fun:g_main_context_iterate fun:g_main_loop_run obj:/usr/lib/libORBit-2.so.0.1.0 fun:g_thread_create_proxy fun:start_thread fun:clone } # ==17786== 4,144 bytes in 95 blocks are possibly lost in loss record 44 of 65 # ==17786== at 0x4C216F4: calloc (vg_replace_malloc.c:397) # ==17786== by 0x6EAF6E9: g_malloc0 (gmem.c:151) # ==17786== by 0x91EB67E: ORBit_alloc_tcval (in /usr/lib/libORBit-2.so.0.1.0) # ==17786== by 0x91F9F10: ORBit_adaptor_setup (in /usr/lib/libORBit-2.so.0.1.0) # ==17786== by 0x91F6AA7: (within /usr/lib/libORBit-2.so.0.1.0) # ==17786== by 0x91F6E54: ORBit_POA_new_from (in /usr/lib/libORBit-2.so.0.1.0) # ==17786== by 0xAD65231: bonobo_poa_new_from (in /usr/lib/libbonobo-2.so.0.0.0) # ==17786== by 0xAD65347: bonobo_poa_get_threadedv (in /usr/lib/libbonobo-2.so.0.0.0) # ==17786== by 0xAD6551C: bonobo_poa_get_threaded (in /usr/lib/libbonobo-2.so.0.0.0) # ==17786== by 0xBA2A3CF: e_book_listener_new (e-book-listener.c:411) # ==17786== by 0xBA2E3DE: e_book_new (e-book.c:3334) # ==17786== by 0xBA2E7BE: e_book_new_from_uri (e-book.c:3877) # ==17786== by 0xA6F792C: SyncEvo::EvolutionContactSource::open() (EvolutionContactSource.cpp:141) # ==17786== by 0x5648DE: SyncEvo::TestingSyncSourcePtr::TestingSyncSourcePtr(SyncEvo::TestingSyncSource*) (in /work/runtests/head-evolution-testing/build/src/.libs/lt-client-test) # ==17786== by 0x4B6E52: SyncEvo::LocalTests::insert(SyncEvo::CreateSource, char const*, bool) (in /work/runtests/head-evolution-testing/build/src/.libs/lt-client-test) # ==17786== by 0x44D846: SyncEvo::LocalTests::testSimpleInsert() (in /work/runtests/head-evolution-testing/build/src/.libs/lt-client-test) # ==17786== by 0x4813E9: SyncEvo::SyncTests::testDeleteAllRefresh() (in /work/runtests/head-evolution-testing/build/src/.libs/lt-client-test) # ==17786== by 0x55A86D: CppUnit::TestCaller::runTest() (in /work/runtests/head-evolution-testing/build/src/.libs/lt-client-test) # ==17786== by 0x4E52406: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==17786== by 0x4E447D3: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==17786== by 0x4E4E278: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==17786== by 0x4E4DFBB: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==17786== by 0x4E59D9F: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==17786== by 0x4E5209C: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==17786== by 0x4E529FB: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==17786== by 0x4E52925: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==17786== by 0x4E529FB: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==17786== by 0x4E52925: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==17786== by 0x4E59B29: CppUnit::TestResult::runTest(CppUnit::Test*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==17786== by 0x4E5C121: CppUnit::TestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) { ebook new VI Memcheck:Leak fun:calloc fun:g_malloc0 fun:ORBit_alloc_tcval fun:ORBit_adaptor_setup obj:/usr/lib/libORBit-2.so.0.1.0 fun:ORBit_POA_new_from fun:bonobo_poa_new_from fun:bonobo_poa_get_threadedv fun:bonobo_poa_get_threaded fun:e_book_listener_new fun:e_book_new fun:e_book_new_from_uri fun:_ZN7SyncEvo22EvolutionContactSource4openEv fun:_ZN7SyncEvo20TestingSyncSourcePtrC1EPNS_17TestingSyncSourceE fun:_ZN7SyncEvo10LocalTests6insertENS_12CreateSourceEPKcb fun:_ZN7SyncEvo10LocalTests16testSimpleInsertEv fun:_ZN7SyncEvo9SyncTests20testDeleteAllRefreshEv fun:_ZN7CppUnit10TestCallerIN7SyncEvo9SyncTestsEE7runTestEv fun:_ZNK7CppUnit21TestCaseMethodFunctorclEv fun:_ZN7CppUnit16DefaultProtector7protectERKNS_7FunctorERKNS_16ProtectorContextE fun:_ZNK7CppUnit14ProtectorChain14ProtectFunctorclEv fun:_ZN7CppUnit14ProtectorChain7protectERKNS_7FunctorERKNS_16ProtectorContextE fun:_ZN7CppUnit10TestResult7protectERKNS_7FunctorEPNS_4TestERKSs fun:_ZN7CppUnit8TestCase3runEPNS_10TestResultE } # ==28783== 6,326 bytes in 166 blocks are possibly lost in loss record 46 of 72 # ==28783== at 0x4C2391E: malloc (vg_replace_malloc.c:207) # ==28783== by 0x6EAF742: g_malloc (gmem.c:131) # ==28783== by 0x91EB8AC: ORBit_alloc_string (in /usr/lib/libORBit-2.so.0.1.0) # ==28783== by 0x91EB59C: CORBA_string_dup (in /usr/lib/libORBit-2.so.0.1.0) # ==28783== by 0xAFBF14F: Bonobo_ActivationProperty_copy (in /usr/lib/libbonobo-activation.so.4.0.0) # ==28783== by 0xAFBF1DA: CORBA_sequence_Bonobo_ActivationProperty_copy (in /usr/lib/libbonobo-activation.so.4.0.0) # ==28783== by 0xAFBF2EA: Bonobo_ServerInfoList_duplicate (in /usr/lib/libbonobo-activation.so.4.0.0) # ==28783== by 0xAFBD54F: bonobo_activation_query (in /usr/lib/libbonobo-activation.so.4.0.0) # ==28783== by 0xBA2E31E: e_book_new (e-book.c:3259) # ==28783== by 0xBA2E7BE: e_book_new_from_uri (e-book.c:3877) # ==28783== by 0xA6F792C: SyncEvo::EvolutionContactSource::open() (EvolutionContactSource.cpp:141) # ==28783== by 0x5648DE: SyncEvo::TestingSyncSourcePtr::TestingSyncSourcePtr(SyncEvo::TestingSyncSource*) (in /work/runtests/head-evolution-testing/build/src/.libs/lt-client-test) # ==28783== by 0x4B6E52: SyncEvo::LocalTests::insert(SyncEvo::CreateSource, char const*, bool) (in /work/runtests/head-evolution-testing/build/src/.libs/lt-client-test) # ==28783== by 0x44D846: SyncEvo::LocalTests::testSimpleInsert() (in /work/runtests/head-evolution-testing/build/src/.libs/lt-client-test) # ==28783== by 0x4813E9: SyncEvo::SyncTests::testDeleteAllRefresh() (in /work/runtests/head-evolution-testing/build/src/.libs/lt-client-test) # ==28783== by 0x55A86D: CppUnit::TestCaller::runTest() (in /work/runtests/head-evolution-testing/build/src/.libs/lt-client-test) # ==28783== by 0x4E52406: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==28783== by 0x4E447D3: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==28783== by 0x4E4E278: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==28783== by 0x4E4DFBB: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==28783== by 0x4E59D9F: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==28783== by 0x4E5209C: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==28783== by 0x4E529FB: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==28783== by 0x4E52925: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==28783== by 0x4E529FB: CppUnit::TestComposite::doRunChildTests(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==28783== by 0x4E52925: CppUnit::TestComposite::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==28783== by 0x4E59B29: CppUnit::TestResult::runTest(CppUnit::Test*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==28783== by 0x4E5C121: CppUnit::TestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==28783== by 0x4E5F13A: CppUnit::TextTestRunner::run(std::string, bool, bool, bool) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==28783== by 0x567669: main (in /work/runtests/head-evolution-testing/build/src/.libs/lt-client-test) { ebook new VII Memcheck:Leak fun:malloc fun:g_malloc fun:ORBit_alloc_string fun:CORBA_string_dup fun:Bonobo_ActivationProperty_copy fun:CORBA_sequence_Bonobo_ActivationProperty_copy fun:Bonobo_ServerInfoList_duplicate fun:bonobo_activation_query fun:e_book_new fun:e_book_new_from_uri fun:_ZN7SyncEvo22EvolutionContactSource4openEv fun:_ZN7SyncEvo20TestingSyncSourcePtrC1EPNS_17TestingSyncSourceE fun:_ZN7SyncEvo10LocalTests6insertENS_12CreateSourceEPKcb fun:_ZN7SyncEvo10LocalTests16testSimpleInsertEv fun:_ZN7SyncEvo9SyncTests20testDeleteAllRefreshEv fun:_ZN7CppUnit10TestCallerIN7SyncEvo9SyncTestsEE7runTestEv fun:_ZNK7CppUnit21TestCaseMethodFunctorclEv fun:_ZN7CppUnit16DefaultProtector7protectERKNS_7FunctorERKNS_16ProtectorContextE fun:_ZNK7CppUnit14ProtectorChain14ProtectFunctorclEv fun:_ZN7CppUnit14ProtectorChain7protectERKNS_7FunctorERKNS_16ProtectorContextE fun:_ZN7CppUnit10TestResult7protectERKNS_7FunctorEPNS_4TestERKSs fun:_ZN7CppUnit8TestCase3runEPNS_10TestResultE fun:_ZN7CppUnit13TestComposite15doRunChildTestsEPNS_10TestResultE fun:_ZN7CppUnit13TestComposite3runEPNS_10TestResultE } # ==17425== 32 bytes in 1 blocks are possibly lost in loss record 353 of 780 # ==17425== at 0x4C245E2: realloc (vg_replace_malloc.c:525) # ==17425== by 0x643233E: g_realloc (in /lib/libglib-2.0.so.0.2400.1) # ==17425== by 0x5FCA989: g_type_set_qdata (in /usr/lib/libgobject-2.0.so.0.2400.1) # ==17425== by 0x8BF88F7: ??? (in /usr/lib/libdbus-glib-1.so.2.1.0) # ==17425== by 0x8BEC964: dbus_g_bus_get (in /usr/lib/libdbus-glib-1.so.2.1.0) # ==17425== by 0x50586AC: e_book_new (in /usr/lib/libebook-1.2.so.9.3.1) # ==17425== by 0x50587A7: e_book_new_from_uri (in /usr/lib/libebook-1.2.so.9.3.1) # ==17425== by 0x59815F: SyncEvo::EvolutionContactSource::open() (EvolutionContactSource.cpp:168) # ==17425== by 0x4E46D8: SyncEvo::LocalTests::testOpen() (ClientTest.cpp:532) { ebook new VIII Memcheck:Leak ... fun:dbus_g_bus_get fun:e_book_new } # ==17187== 207,274 bytes in 11 blocks are possibly lost in loss record 70 of 71 # ==17187== at 0x4C2391E: malloc (vg_replace_malloc.c:207) # ==17187== by 0x76127D7: __os_malloc (in /usr/lib/libdb-4.7.so) # ==17187== by 0x75E352E: __env_alloc (in /usr/lib/libdb-4.7.so) # ==17187== by 0x75EBB48: __env_attach (in /usr/lib/libdb-4.7.so) # ==17187== by 0x75E8DD2: __env_open (in /usr/lib/libdb-4.7.so) # ==17187== by 0xEDC585F: e_book_backend_file_load_source (e-book-backend-file.c:1113) # ==17187== by 0x52716C5: e_book_backend_load_source (e-book-backend.c:74) # ==17187== by 0x5271FE0: e_book_backend_open (e-book-backend.c:129) # ==17187== by 0x8B204EF: ORBit_small_invoke_adaptor (in /usr/lib/libORBit-2.so.0.1.0) # ==17187== by 0x8B30809: (within /usr/lib/libORBit-2.so.0.1.0) # ==17187== by 0x8B30E39: (within /usr/lib/libORBit-2.so.0.1.0) # ==17187== by 0x8B195CE: giop_thread_queue_process (in /usr/lib/libORBit-2.so.0.1.0) # ==17187== by 0x8B19E87: (within /usr/lib/libORBit-2.so.0.1.0) # ==17187== by 0x9D1FAD6: g_thread_pool_thread_proxy (gthreadpool.c:265) # ==17187== by 0x9D1E573: g_thread_create_proxy (gthread.c:635) # ==17187== by 0xA7CDFA9: start_thread (in /lib/libpthread-2.9.so) # ==17187== by 0xAAB029C: clone (in /lib/libc-2.9.so) { ebook backend open III Memcheck:Leak fun:malloc fun:__os_malloc fun:__env_alloc fun:__env_attach fun:__env_open fun:e_book_backend_file_load_source fun:e_book_backend_load_source fun:e_book_backend_open fun:ORBit_small_invoke_adaptor obj:/usr/lib/libORBit-2.so.0.1.0 obj:/usr/lib/libORBit-2.so.0.1.0 fun:giop_thread_queue_process obj:/usr/lib/libORBit-2.so.0.1.0 fun:g_thread_pool_thread_proxy fun:g_thread_create_proxy fun:start_thread fun:clone } # ==4974== 3,897,786 (169,592 direct, 3,728,194 indirect) bytes in 2,953 blocks are definitely lost in loss record 70 of 78 # ==4974== at 0x4C2391E: malloc (vg_replace_malloc.c:207) # ==4974== by 0x9CFD742: g_malloc (gmem.c:131) # ==4974== by 0x9D13707: g_slice_alloc (gslice.c:824) # ==4974== by 0x9CE105B: g_datalist_id_set_data_full (gdataset.c:292) # ==4974== by 0x5271A13: e_book_backend_add_client (e-book-backend.c:576) # ==4974== by 0x52722F8: impl_GNOME_Evolution_Addressbook_BookFactory_getBook (e-data-book-factory.c:353) # ==4974== by 0x52670B6: _ORBIT_skel_small_GNOME_Evolution_Addressbook_BookFactory_getBook (Evolution-DataServer-Addressbook-common.c:180) # ==4974== by 0x8B204EF: ORBit_small_invoke_adaptor (in /usr/lib/libORBit-2.so.0.1.0) # ==4974== by 0x8B30809: (within /usr/lib/libORBit-2.so.0.1.0) # ==4974== by 0x8B30E39: (within /usr/lib/libORBit-2.so.0.1.0) # ==4974== by 0x8B195CE: giop_thread_queue_process (in /usr/lib/libORBit-2.so.0.1.0) # ==4974== by 0x8B19E87: (within /usr/lib/libORBit-2.so.0.1.0) # ==4974== by 0x9D1FAD6: g_thread_pool_thread_proxy (gthreadpool.c:265) # ==4974== by 0x9D1E573: g_thread_create_proxy (gthread.c:635) # ==4974== by 0xA7CDFA9: start_thread (in /lib/libpthread-2.9.so) # ==4974== by 0xAAB029C: clone (in /lib/libc-2.9.so) { ebook backend add client II Memcheck:Leak fun:malloc fun:g_malloc fun:g_slice_alloc fun:g_datalist_id_set_data_full fun:e_book_backend_add_client fun:impl_GNOME_Evolution_Addressbook_BookFactory_getBook fun:_ORBIT_skel_small_GNOME_Evolution_Addressbook_BookFactory_getBook fun:ORBit_small_invoke_adaptor obj:/usr/lib/libORBit-2.so.0.1.0 obj:/usr/lib/libORBit-2.so.0.1.0 fun:giop_thread_queue_process obj:/usr/lib/libORBit-2.so.0.1.0 fun:g_thread_pool_thread_proxy fun:g_thread_create_proxy fun:start_thread fun:clone } # ==16317== 1 bytes in 1 blocks are possibly lost in loss record 1 of 1,445 # ==16317== at 0x4C244E8: malloc (vg_replace_malloc.c:236) # ==16317== by 0x6901534: g_malloc (in /lib/libglib-2.0.so.0.2400.2) # ==16317== by 0x6918DDD: g_strdup (in /lib/libglib-2.0.so.0.2400.2) # ==16317== by 0x571CB3C: ??? (in /usr/lib/libsoup-2.4.so.1.3.0) # ==16317== by 0x571CBC6: ??? (in /usr/lib/libsoup-2.4.so.1.3.0) # ==16317== by 0x571DE45: soup_message_headers_clear (in /usr/lib/libsoup-2.4.so.1.3.0) # ==16317== by 0x571A42D: soup_message_cleanup_response (in /usr/lib/libsoup-2.4.so.1.3.0) # ==16317== by 0x571C4A8: soup_message_send_request (in /usr/lib/libsoup-2.4.so.1.3.0) # ==16317== by 0x57281FD: ??? (in /usr/lib/libsoup-2.4.so.1.3.0) # ==16317== by 0x57282C2: ??? (in /usr/lib/libsoup-2.4.so.1.3.0) # ==16317== by 0x68F86F1: g_main_context_dispatch (in /lib/libglib-2.0.so.0.2400.2) # ==16317== by 0x68FC567: ??? (in /lib/libglib-2.0.so.0.2400.2) # ==16317== by 0x68FCA74: g_main_loop_run (in /lib/libglib-2.0.so.0.2400.2) # ==16317== by 0x9C5E26: SyncEvo::SoupTransportAgent::wait(bool) (in /home/pohly/work/syncevolution/src/client-test) # ==16317== by 0x90B40C: SyncEvo::SyncContext::doSync() (in /home/pohly/work/syncevolution/src/client-test) # ==16317== by 0x9064C7: SyncEvo::SyncContext::sync(SyncEvo::SyncReport*) (in /home/pohly/work/syncevolution/src/client-test) # ==16317== by 0x65E8D8: SyncEvo::TestEvolution::doSync(int const*, std::string const&, SyncEvo::SyncOptions const&) (in /home/pohly/work/syncevolution/src/client-test) # ==16317== by 0x75F090: SyncEvo::SyncTests::doSync(SyncEvo::SyncOptions const&) (in /home/pohly/work/syncevolution/src/client-test) # ==16317== by 0x76BCD1: SyncEvo::SyncTests::doSync(char const*, SyncEvo::SyncOptions const&) (in /home/pohly/work/syncevolution/src/client-test) # ==16317== by 0x75D7AB: SyncEvo::SyncTests::testTimeout() (in /home/pohly/work/syncevolution/src/client-test) # ==16317== by 0x7737F9: CppUnit::TestCaller::runTest() (in /home/pohly/work/syncevolution/src/client-test) # ==16317== by 0x5C22406: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==16317== by 0x5C147D3: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==16317== by 0x5C1E278: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==16317== by 0x5C1DFBB: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==16317== by 0x5C29D9F: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==16317== by 0x5C2209C: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==16317== by 0x5C29B29: CppUnit::TestResult::runTest(CppUnit::Test*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==16317== by 0x5C2C121: CppUnit::TestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==16317== by 0x5C2F13A: CppUnit::TextTestRunner::run(std::string, bool, bool, bool) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==16317== { leak in libsoup Memcheck:Leak ... fun:soup_* ... fun:g_main_loop_run } { leak in libsoup II Memcheck:Leak ... fun:soup_message_set_request } # ==19190== 128 bytes in 1 blocks are possibly lost in loss record 1,181 of 1,448 # ==19190== at 0x4C244E8: malloc (vg_replace_malloc.c:236) # ==19190== by 0x4C24562: realloc (vg_replace_malloc.c:525) # ==19190== by 0x906736E: g_realloc (in /lib/libglib-2.0.so.0.2400.2) # ==19190== by 0x9038140: ??? (in /lib/libglib-2.0.so.0.2400.2) # ==19190== by 0x90381C2: g_ptr_array_add (in /lib/libglib-2.0.so.0.2400.2) # ==19190== by 0x90617EC: g_main_context_check (in /lib/libglib-2.0.so.0.2400.2) # ==19190== by 0x90622E2: ??? (in /lib/libglib-2.0.so.0.2400.2) # ==19190== by 0x9062A74: g_main_loop_run (in /lib/libglib-2.0.so.0.2400.2) # ==19190== by 0x99E3C4: SyncEvo::SoupTransportAgent::wait(bool) (SoupTransportAgent.cpp:177) # ==19190== by 0x8E384C: SyncEvo::SyncContext::doSync() (in /home/pohly/work/syncevolution/src/client-test) # ==19190== by 0x8DE907: SyncEvo::SyncContext::sync(SyncEvo::SyncReport*) (in /home/pohly/work/syncevolution/src/client-test) # ==19190== by 0x663AB8: SyncEvo::TestEvolution::doSync(int const*, std::string const&, SyncEvo::SyncOptions const&) (in /home/pohly/work/syncevolution/src/client-test) # ==19190== by 0x73A550: SyncEvo::SyncTests::doSync(SyncEvo::SyncOptions const&) (ClientTest.cpp:3705) # ==19190== by 0x741AF8: SyncEvo::SyncTests::doSync(char const*, SyncEvo::SyncOptions const&) (ClientTest.h:833) # ==19190== by 0x73993F: SyncEvo::SyncTests::testTimeout() (ClientTest.cpp:3655) # ==19190== by 0x75E23D: CppUnit::TestCaller::runTest() (TestCaller.h:166) # ==19190== by 0x8388406: CppUnit::TestCaseMethodFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==19190== by 0x837A7D3: CppUnit::DefaultProtector::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==19190== by 0x8384278: CppUnit::ProtectorChain::ProtectFunctor::operator()() const (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==19190== by 0x8383FBB: CppUnit::ProtectorChain::protect(CppUnit::Functor const&, CppUnit::ProtectorContext const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==19190== by 0x838FD9F: CppUnit::TestResult::protect(CppUnit::Functor const&, CppUnit::Test*, std::string const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==19190== by 0x838809C: CppUnit::TestCase::run(CppUnit::TestResult*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==19190== by 0x838FB29: CppUnit::TestResult::runTest(CppUnit::Test*) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==19190== by 0x8392121: CppUnit::TestRunner::run(CppUnit::TestResult&, std::string const&) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==19190== by 0x839513A: CppUnit::TextTestRunner::run(std::string, bool, bool, bool) (in /usr/lib/libcppunit-1.12.so.1.0.0) # ==19190== by 0x75EA14: main (in /home/pohly/work/syncevolution/src/client-test) # ==19190== { glib g_ptr_array_add Memcheck:Leak ... fun:g_ptr_array_add fun:g_main_context_check obj:*libglib* fun:g_main_loop_run } # ==26674== Command: /usr/lib/evolution/e-addressbook-factory # ==26674== Parent PID: 26669 # ==26674== # ==26674== Thread 3: # ==26674== Syscall param pwrite64(buf) points to uninitialised byte(s) # ==26674== at 0x5F12A33: ??? (syscall-template.S:82) # ==26674== by 0x72D2937: __os_io (in /usr/lib/libdb-5.1.so) # ==26674== by 0x72BFBCD: ??? (in /usr/lib/libdb-5.1.so) # ==26674== by 0x72BFDD6: __memp_bhwrite (in /usr/lib/libdb-5.1.so) # ==26674== by 0x72CF04F: __memp_sync_int (in /usr/lib/libdb-5.1.so) # ==26674== by 0x726C516: __db_sync (in /usr/lib/libdb-5.1.so) # ==26674== by 0x727C014: __db_sync_pp (in /usr/lib/libdb-5.1.so) # ==26674== by 0x10CF123A: e_book_backend_file_create_contact (e-book-backend-file.c:232) # ==26674== by 0x4E4083B: _e_book_backend_create_contact (e-book-backend-sync.c:375) # ==26674== by 0x4E472AA: operation_thread (e-data-book.c:115) # ==26674== by 0x58503E3: ??? (in /lib/libglib-2.0.so.0.2800.6) # ==26674== by 0x584DCF5: ??? (in /lib/libglib-2.0.so.0.2800.6) # ==26674== by 0x5F0AB3F: start_thread (pthread_create.c:304) # ==26674== by 0x61F536C: clone (clone.S:112) # ==26674== Address 0xdf5f510 is 3,824 bytes inside a block of size 4,096 alloc'd # ==26674== at 0x4C2779D: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) # ==26674== by 0x72D01D7: __os_malloc (in /usr/lib/libdb-5.1.so) # ==26674== by 0x72BFC77: ??? (in /usr/lib/libdb-5.1.so) # ==26674== by 0x72BFDD6: __memp_bhwrite (in /usr/lib/libdb-5.1.so) # ==26674== by 0x72CF04F: __memp_sync_int (in /usr/lib/libdb-5.1.so) # ==26674== by 0x726C516: __db_sync (in /usr/lib/libdb-5.1.so) # ==26674== by 0x727C014: __db_sync_pp (in /usr/lib/libdb-5.1.so) # ==26674== by 0x10CF123A: e_book_backend_file_create_contact (e-book-backend-file.c:232) # ==26674== by 0x4E4083B: _e_book_backend_create_contact (e-book-backend-sync.c:375) # ==26674== by 0x4E472AA: operation_thread (e-data-book.c:115) # ==26674== by 0x58503E3: ??? (in /lib/libglib-2.0.so.0.2800.6) # ==26674== by 0x584DCF5: ??? (in /lib/libglib-2.0.so.0.2800.6) # ==26674== by 0x5F0AB3F: start_thread (pthread_create.c:304) # ==26674== by 0x61F536C: clone (clone.S:112) # ==26674== { partially initialized data written by libdb - caused by libdb itself? Memcheck:Param pwrite64(buf) ... obj:*libdb* } # ==26674== 332 bytes in 83 blocks are definitely lost in loss record 1,605 of 1,715 # ==26674== at 0x4C2779D: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) # ==26674== by 0x72CFF54: __os_umalloc (in /usr/lib/libdb-5.1.so) # ==26674== by 0x7294C35: __db_retcopy (in /usr/lib/libdb-5.1.so) # ==26674== by 0x726EB4D: __dbc_iget (in /usr/lib/libdb-5.1.so) # ==26674== by 0x727CDCC: __dbc_get_pp (in /usr/lib/libdb-5.1.so) # ==26674== by 0x10CF090C: e_book_backend_file_get_contact_list (e-book-backend-file.c:469) # ==26674== by 0x4E4189B: _e_book_backend_get_contact_list (e-book-backend-sync.c:443) # ==26674== by 0x4E472FA: operation_thread (e-data-book.c:123) # ==26674== by 0x58503E3: ??? (in /lib/libglib-2.0.so.0.2800.6) # ==26674== by 0x584DCF5: ??? (in /lib/libglib-2.0.so.0.2800.6) # ==26674== by 0x5F0AB3F: start_thread (pthread_create.c:304) # ==26674== by 0x61F536C: clone (clone.S:112) # ==26674== ... # ==26674== 12,552 (1,272 direct, 11,280 indirect) bytes in 53 blocks are definitely lost in loss record 1,711 of 1,715 # ==26674== at 0x4C2779D: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) # ==26674== by 0x582D392: g_malloc (in /lib/libglib-2.0.so.0.2800.6) # ==26674== by 0x5843FE6: g_slice_alloc (in /lib/libglib-2.0.so.0.2800.6) # ==26674== by 0x58214BD: g_list_prepend (in /lib/libglib-2.0.so.0.2800.6) # ==26674== by 0x10CF0942: e_book_backend_file_get_contact_list (e-book-backend-file.c:478) # ==26674== by 0x4E4189B: _e_book_backend_get_contact_list (e-book-backend-sync.c:443) # ==26674== by 0x4E472FA: operation_thread (e-data-book.c:123) # ==26674== by 0x58503E3: ??? (in /lib/libglib-2.0.so.0.2800.6) # ==26674== by 0x584DCF5: ??? (in /lib/libglib-2.0.so.0.2800.6) # ==26674== by 0x5F0AB3F: start_thread (pthread_create.c:304) # ==26674== by 0x61F536C: clone (clone.S:112) # ==26674== { e_book_backend_file_get_contact_list memory leak Memcheck:Leak fun:malloc ... fun:e_book_backend_file_get_contact_list } # ==8730== 28 bytes in 1 blocks are possibly lost in loss record 1,733 of 2,913 # ==8730== at 0x50CE796: calloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so) # ==8730== by 0x738429B: g_malloc0 (in /lib/libglib-2.0.so.0.2800.6) # ==8730== by 0x921E176: ORBit_alloc_by_tc (in /usr/lib/libORBit-2.so.0.1.0) # ==8730== by 0x921871A: ORBit_small_alloc (in /usr/lib/libORBit-2.so.0.1.0) # ==8730== by 0x92240C3: ??? (in /usr/lib/libORBit-2.so.0.1.0) # ==8730== by 0x9226115: ORBit_demarshal_IOR (in /usr/lib/libORBit-2.so.0.1.0) # ==8730== by 0x921BC55: ORBit_demarshal_object (in /usr/lib/libORBit-2.so.0.1.0) # ==8730== by 0x87C4E7F: ??? (in /usr/lib/libgconf-2.so.4.1.5) # ==8730== by 0x9777BEF: ??? # ==8730== { gconf/orbit mem leak Memcheck:Leak ... obj:*libORBit* obj:*libgconf* } ## SyncEvolution ## # Occurs when running D-Bus test TestFileNotify.testRestart # Code will be rewritten soon, ignore for the moment. # #==25995== Conditional jump or move depends on uninitialised value(s) # ==25995== at 0x59B1DE: SyncEvo::AutoSyncManager::initConfig(std::string const&) (auto-term.h:130) # ==25995== by 0x59C62B: SyncEvo::AutoSyncManager::init() (auto-sync-manager.cpp:36) # ==25995== by 0x57342C: SyncEvo::Server::Server(_GMainLoop*, bool&, boost::shared_ptr&, GDBusCXX::DBusConnectionPtr const&, int) (auto-sync-manager.h:209) # ==25995== by 0x5478F6: main (main.cpp:115) # ==25995== Uninitialised value was created by a stack allocation # ==25995== at 0x54746D: main (main.cpp:62) # ==25995== { TestFileNotify.testRestart Memcheck:Cond ... fun:_ZN7SyncEvo15AutoSyncManager10initConfigERKSs fun:_ZN7SyncEvo15AutoSyncManager4initEv fun:_ZN7SyncEvo6ServerC1EP10_GMainLoopRbRN5boost10shared_ptrINS_7RestartEEERKN8GDBusCXX17DBusConnectionPtrEi fun:main } # ==8851== 7,632 bytes in 106 blocks are possibly lost in loss record 1,825 of 1,883 # ==8851== at 0x4C260C6: calloc (vg_replace_malloc.c:566) # ==8851== by 0x9906590: g_malloc0 (gmem.c:189) # ==8851== by 0x9676368: g_closure_new_simple (gclosure.c:206) # ==8851== by 0x96778CF: g_cclosure_new (gclosure.c:917) # ==8851== by 0x968E91D: g_signal_connect_data (gsignal.c:2443) # ==8851== by 0x6B8AEA: SyncEvo::GLibNotify::GLibNotify(char const*, boost::function const&) (GLibSupport.cpp:215) # ==8851== by 0x617BC5: SyncEvo::Server::run(SyncEvo::LogRedirect&) (server.cpp:338) # ==8851== by 0x5E3E7A: main (main.cpp:120) # ==8851== { potential leak in file monitoring Memcheck:Leak fun:calloc fun:g_malloc0 fun:g_closure_new_simple fun:g_cclosure_new fun:g_signal_connect_data fun:*GFileMonitorEvent* } # ==10148== 3,912 (512 direct, 3,400 indirect) bytes in 1 blocks are definitely lost in loss record 4,885 of 4,958 # ==10148== at 0x4C28CCE: realloc (vg_replace_malloc.c:632) # ==10148== by 0x9E45E7E: g_realloc (gmem.c:224) # ==10148== by 0x9E156A9: g_ptr_array_maybe_expand (garray.c:1093) # ==10148== by 0x9E16802: g_ptr_array_add (garray.c:1350) # ==10148== by 0x98ED18B: read_netlink_messages (gnetworkmonitornetlink.c:237) # ==10148== by 0x98ED3FA: g_network_monitor_netlink_initable_init (gnetworkmonitornetlink.c:143) # ==10148== by 0x98B07CE: g_initable_new_valist (ginitable.c:228) # ==10148== by 0x98B08B8: g_initable_new (ginitable.c:148) # ==10148== by 0x98B34BF: _g_io_module_get_default (giomodule.c:742) # ==10148== by 0x52DE554: backend_constructed (e-backend.c:138) # ==10148== by 0x9BBE2B2: g_object_newv (gobject.c:1741) # ==10148== by 0x9BBE62F: g_object_new_valist (gobject.c:1830) # ==10148== by 0x9BBE963: g_object_new (gobject.c:1545) # ==10148== by 0x4E4150A: book_backend_factory_new_backend (e-book-backend-factory.c:61) # ==10148== by 0x52DEC0B: e_backend_factory_new_backend (e-backend-factory.c:109) # ==10148== by 0x52E144F: e_data_factory_ref_backend (e-data-factory.c:249) # ==10148== by 0x4E54E68: data_book_factory_ref_backend (e-data-book-factory.c:109) # ==10148== by 0x4E5520E: impl_BookFactory_get_book (e-data-book-factory.c:339) # ==10148== by 0x5527A96: e_gdbus_marshallers_BOOLEAN__OBJECT_STRING (e-gdbus-marshallers.c:246) # ==10148== by 0x9BB7723: g_closure_invoke (gclosure.c:777) # ==10148== by 0x9BC87AF: signal_emit_unlocked_R (gsignal.c:3547) # ==10148== by 0x9BD034A: g_signal_emit_valist (gsignal.c:3306) # ==10148== by 0x9BD08C1: g_signal_emit (gsignal.c:3352) # ==10148== by 0x5536A52: e_gdbus_stub_handle_method_call (e-gdbus-templates.c:687) # ==10148== by 0x4E5BA9C: handle_method_call (e-gdbus-book-factory.c:145) # ==10148== by 0x9911914: call_in_idle_cb (gdbusconnection.c:4687) # ==10148== by 0x9E40204: g_main_context_dispatch (gmain.c:2539) # ==10148== by 0x9E40537: g_main_context_iterate.isra.23 (gmain.c:3146) # ==10148== by 0x9E40931: g_main_loop_run (gmain.c:3340) # ==10148== by 0x52E1AD2: dbus_server_run_server (e-dbus-server.c:222) # ==10148== { GIO networkmonitor https://bugzilla.gnome.org/show_bug.cgi?id=676265 Memcheck:Leak ... fun:read_netlink_messages ... fun:g_network_monitor_* } # ==10187== 2,060 bytes in 37 blocks are definitely lost in loss record 3,772 of 3,810 # ==10187== at 0x4C28BED: malloc (vg_replace_malloc.c:263) # ==10187== by 0x85ACDE0: g_malloc (gmem.c:159) # ==10187== by 0x85C282B: g_strdup (gstrfuncs.c:356) # ==10187== by 0x7C87D94: g_dbus_object_proxy_set_property (gdbusobjectproxy.c:118) # ==10187== by 0x83239CB: g_object_constructor (gobject.c:1352) # ==10187== by 0x8324E40: g_object_newv (gobject.c:1713) # ==10187== by 0x832562F: g_object_new_valist (gobject.c:1830) # ==10187== by 0x8325963: g_object_new (gobject.c:1545) # ==10187== by 0x7C890D2: add_interfaces (gdbusobjectmanagerclient.c:1445) # ==10187== by 0x7C89483: process_get_all_result (gdbusobjectmanagerclient.c:1628) # ==10187== by 0x7C89DA5: initable_init (gdbusobjectmanagerclient.c:1384) # ==10187== by 0x7C0D7CE: g_initable_new_valist (ginitable.c:228) # ==10187== by 0x7C0D8B8: g_initable_new (ginitable.c:148) # ==10187== by 0x6C8B3F1: e_dbus_object_manager_client_new_for_bus_sync (e-dbus-source.c:3821) # ==10187== by 0x6C73D92: source_registry_object_manager_thread (e-source-registry.c:723) # ==10187== by 0x85C9DF4: g_thread_proxy (gthread.c:801) # ==10187== by 0x95E8B4F: start_thread (pthread_create.c:304) # ==10187== by 0xA0756DC: clone (clone.S:112) # ==10187== { object_path GDBus Object Proxy https://bugzilla.gnome.org/show_bug.cgi?id=680505 Memcheck:Leak ... fun:g_dbus_object_proxy_set_property } # ==16352== 16 bytes in 1 blocks are definitely lost in loss record 1,013 of 3,623 # ==16352== at 0x4C28BED: malloc (vg_replace_malloc.c:263) # ==16352== by 0xA821D40: g_malloc (gmem.c:159) # ==16352== by 0xA836C82: g_slice_alloc (gslice.c:1003) # ==16352== by 0xA837A7D: g_slist_prepend (gslist.c:265) # ==16352== by 0xA39889A: g_object_notify_queue_add.isra.4 (gobject.c:307) # ==16352== by 0xA398B22: g_object_constructor (gobject.c:1363) # ==16352== by 0xA399F70: g_object_newv (gobject.c:1719) # ==16352== by 0xA39A75F: g_object_new_valist (gobject.c:1836) # ==16352== by 0x9C6F33D: g_initable_new_valist (ginitable.c:224) # ==16352== by 0x9C6F438: g_initable_new (ginitable.c:148) # ==16352== by 0x9CED9AE: add_interfaces (gdbusobjectmanagerclient.c:1478) # ==16352== by 0x9CEDFC3: process_get_all_result (gdbusobjectmanagerclient.c:1627) # ==16352== by 0x9CEE8E5: initable_init (gdbusobjectmanagerclient.c:1383) # ==16352== by 0x9C6F34E: g_initable_new_valist (ginitable.c:228) # ==16352== by 0x9C6F438: g_initable_new (ginitable.c:148) # ==16352== by 0x7F0D400: e_dbus_object_manager_client_new_for_bus_sync (e-dbus-source.c:5708) # ==16352== by 0x7EF7E5B: source_registry_object_manager_thread (e-source-registry.c:733) # ==16352== by 0xA83F964: g_thread_proxy (gthread.c:797) # ==16352== by 0xAAFCB4F: start_thread (pthread_create.c:304) # ==16352== by 0xB793A7C: clone (clone.S:112) # ==16352== # # Occurs very infrequently, race condition? { notify leak in ESourceRegistry thread Memcheck:Leak fun:malloc ... fun:g_object_notify_queue_add* ... fun:e_dbus_object_manager_client_new_for_bus_sync fun:source_registry_object_manager_thread } # ==19542== 104 (40 direct, 64 indirect) bytes in 1 blocks are definitely lost in loss record 5,479 of 6,315 # ==19542== at 0x4C28BED: malloc (vg_replace_malloc.c:263) # ==19542== by 0x955DDE0: g_malloc (gmem.c:159) # ==19542== by 0x95721C2: g_slice_alloc (gslice.c:1003) # ==19542== by 0x952D899: g_array_sized_new (garray.c:195) # ==19542== by 0x902C6DD: g_dbus_connection_signal_unsubscribe (gdbusconnection.c:3585) # ==19542== by 0x90357B8: g_dbus_proxy_finalize (gdbusproxy.c:223) # ==19542== by 0x6C91011: e_dbus_source_manager_proxy_finalize (e-dbus-source-manager.c:834) # ==19542== by 0x92D4697: g_object_unref (gobject.c:3018) # ==19542== by 0x6C75ECE: source_registry_dispose (e-source-registry.c:936) # ==19542== by 0x92D4603: g_object_unref (gobject.c:2981) # ==19542== by 0x9FDADF1: __run_exit_handlers (exit.c:78) # ==19542== by 0x9FDAE44: exit (exit.c:100) # ==19542== by 0x9FC2EB3: (below main) (libc-start.c:260) # ==19542== { source_registry atexit Memcheck:Leak fun:malloc ... fun:source_registry_dispose ... fun:exit fun:(below main) } # ==19714== 24 bytes in 1 blocks are definitely lost in loss record 2,265 of 6,299 # ==19714== at 0x4C28BED: malloc (vg_replace_malloc.c:263) # ==19714== by 0x955DDE0: g_malloc (gmem.c:159) # ==19714== by 0x95721C2: g_slice_alloc (gslice.c:1003) # ==19714== by 0x9554DAD: g_list_prepend (glist.c:275) # ==19714== by 0x954FBA3: g_key_file_add_group (gkeyfile.c:3714) # ==19714== by 0x9550811: g_key_file_flush_parse_buffer (gkeyfile.c:1265) # ==19714== by 0x9550B70: g_key_file_parse_data (gkeyfile.c:1415) # ==19714== by 0x9551F28: g_key_file_load_from_data (gkeyfile.c:922) # ==19714== by 0x6C617EB: source_parse_dbus_data (e-source.c:604) # ==19714== by 0x6C6306B: source_initable_init (e-source.c:1337) # ==19714== by 0x8FC87CE: g_initable_new_valist (ginitable.c:228) # ==19714== by 0x8FC88B8: g_initable_new (ginitable.c:148) # ==19714== by 0x6C638DE: e_source_new (e-source.c:1615) # ==19714== by 0x6C7508C: source_registry_new_source (e-source-registry.c:523) # ==19714== by 0x6C75842: source_registry_object_manager_thread (e-source-registry.c:758) # ==19714== by 0x957ADF4: g_thread_proxy (gthread.c:801) # ==19714== by 0x7A2FB4F: start_thread (pthread_create.c:304) # ==19714== by 0xA07C70C: clone (clone.S:112) # ==19714== { new esource Memcheck:Leak fun:malloc fun:g_malloc fun:g_slice_alloc fun:g_list_prepend fun:g_key_file_* ... fun:e_source_new ... fun:g_thread_proxy } ### folks ### # ==14458== 56 (16 direct, 40 indirect) bytes in 1 blocks are definitely lost in loss record 2,853 of 5,224 # ==14458== at 0x4C28BED: malloc (vg_replace_malloc.c:263) # ==14458== by 0xA91BD50: g_malloc (gmem.c:159) # ==14458== by 0xA930C92: g_slice_alloc (gslice.c:1003) # ==14458== by 0xA8EB059: g_static_rec_mutex_get_rec_mutex_impl.isra.1 (gthread-deprecated.c:685) # ==14458== by 0xA8EB328: g_static_rec_mutex_lock (gthread-deprecated.c:711) # ==14458== by 0x4E72371: folks_debug_dup (debug.vala:199) # ==14458== by 0x4E72422: folks_debug_dup_with_flags (debug.vala:235) # ==14458== by 0x4E49195: folks_backend_store_constructor (backend-store.vala:140) # ==14458== by 0xA695460: g_object_newv (gobject.c:1637) # ==14458== by 0xA695AAB: g_object_new (gobject.c:1547) # ==14458== by 0x4E49640: folks_backend_store_dup (backend-store.vala:133) # ==14458== by 0x865524: SyncEvo::IndividualAggregator::init(boost::shared_ptr&) (fol # ks.cpp:586) # ==14458== by 0x866E0C: SyncEvo::IndividualAggregator::create() (folks.cpp:627) # ==14458== by 0x8040EF: SyncEvo::Manager::initFolks() (manager.cpp:123) # ==14458== by 0x8088DC: SyncEvo::Manager::init() (manager.cpp:93) # ==14458== by 0x808EDB: SyncEvo::Manager::create(boost::shared_ptr const&) (manager.cpp:143) # ==14458== by 0x808F30: SyncEvo::CreateContactManager(boost::shared_ptr const&) (manager.cpp:149) # ==14458== by 0x767398: main (main.cpp:150) # ==14458== { folks mutex Memcheck:Leak fun:malloc ... fun:g_static_rec_mutex_lock fun:folks_debug_dup fun:folks_debug_dup_with_flags fun:folks_backend_store_constructor } # ==20811== 56 (16 direct, 40 indirect) bytes in 1 blocks are definitely lost in loss record 3,198 of 6,133 # ==20811== at 0x4C28BED: malloc (vg_replace_malloc.c:263) # ==20811== by 0xA99FD40: g_malloc (gmem.c:159) # ==20811== by 0xA9B4C82: g_slice_alloc (gslice.c:1003) # ==20811== by 0xA96F049: g_static_rec_mutex_get_rec_mutex_impl.isra.1 (gthread-deprecated.c:685) # ==20811== by 0xA96F318: g_static_rec_mutex_lock (gthread-deprecated.c:711) # ==20811== by 0x50ADD31: folks_avatar_cache_dup (avatar-cache.vala:75) # ==20811== by 0x4E45391: _edsf_persona_update (edsf-persona.vala:1523) # ==20811== by 0x4E47DAD: edsf_persona_constructor (edsf-persona.vala:1000) # ==20811== by 0xA517F70: g_object_newv (gobject.c:1719) # ==20811== by 0xA51875F: g_object_new_valist (gobject.c:1836) # ==20811== by 0xA518A93: g_object_new (gobject.c:1551) # ==20811== by 0x4E42BB2: edsf_persona_construct (edsf-persona.vala:949) # ==20811== by 0x4E49E6A: ___lambda4__gsource_func (edsf-persona-store.vala:2421) # ==20811== by 0x4E4BF51: __edsf_persona_store_idle_process_gsource_func (edsf-persona-store.vala:2341) # ==20811== by 0xA99A104: g_main_context_dispatch (gmain.c:2715) # ==20811== by 0xA99A437: g_main_context_iterate.isra.24 (gmain.c:3290) # ==20811== by 0xA99A831: g_main_loop_run (gmain.c:3484) # ==20811== by 0x7CDF87: SyncEvo::Server::run() (server.cpp:440) # ==20811== by 0x797255: main (main.cpp:198) # ==20811== { folks rec mutex II Memcheck:Leak fun:malloc ... fun:g_static_rec_mutex_lock fun:folks_avatar_cache_dup } # === SyncEvolution + libsynthesis === # ==25261== 160 bytes in 1 blocks are definitely lost in loss record 1,157 of 1,363 # ==25261== at 0x4C2935B: malloc (vg_replace_malloc.c:270) # ==25261== by 0x90ABB6C: dlt_register_context_ll_ts (dlt_user.c:853) # ==25261== by 0x7DE71D: sysync::ManageContexts::registerContext(DltContext*, char const*, char const*) (debuglogger.cpp:80) # ==25261== by 0x7DE81B: sysync::ManageContexts::registerContexts() (debuglogger.cpp:94) # ==25261== by 0x7DF30D: sysync::TDebugLoggerBase::DebugStartOutput() (debuglogger.cpp:1518) # ==25261== by 0x7DFF66: sysync::TDebugLoggerBase::DebugPuts(unsigned int, char const*, unsigned long, bool) (debuglogger.cpp:870) # ==25261== by 0x7DE491: sysync::TDebugLoggerBase::DebugVPrintf(unsigned int, char const*, __va_list_tag*) [clone .part.10] (debuglogger.cpp:632) # ==25261== by 0x7DEBEE: sysync::TDebugLoggerBase::DebugPrintfLastMask(char const*, ...) (debuglogger.cpp:652) # ==25261== by 0x88C8A3: sysync::TScriptContext::showVarDefs(char const*) (scriptcontext.cpp:3457) # ==25261== by 0x88EB0A: sysync::TScriptContext::ResolveIdentifiers(std::string&, sysync::TFieldListConfig*, bool, std::string*, bool) (scriptcontext.cpp:3396) # ==25261== by 0x88F147: sysync::TScriptContext::TokenizeAndResolveFunction(sysync::TSyncAppBase*, int, char const*, sysync::TUserScriptFunction&) (scriptcontext.cpp:2915) # ==25261== by 0x84975A: sysync::TConfigElement::endElement(char const*, bool) (configelement.cpp:785) # ==25261== by 0x84906D: sysync::TConfigElement::endElement(char const*, bool) (configelement.cpp:627) # ==25261== by 0x7EAA96: endElement (syncappbase.cpp:1614) # ==25261== by 0x7AB1CEF: ??? (in /lib/x86_64-linux-gnu/libexpat.so.1.6.0) # ==25261== by 0x7AB264D: ??? (in /lib/x86_64-linux-gnu/libexpat.so.1.6.0) # ==25261== by 0x7AB274B: ??? (in /lib/x86_64-linux-gnu/libexpat.so.1.6.0) # ==25261== by 0x7AB45DE: XML_ParseBuffer (in /lib/x86_64-linux-gnu/libexpat.so.1.6.0) # ==25261== by 0x7EAC22: sysync::TSyncAppBase::readXMLConfigStream(int (*)(char*, unsigned long, unsigned long*, void*), void*) (syncappbase.cpp:1664) # ==25261== by 0x7EADE7: sysync::TSyncAppBase::readXMLConfigConstant(char const*) (syncappbase.cpp:1792) # ==25261== by 0x7E5425: sysync::TEngineInterface::InitEngineXML(char const*) (engineinterface.cpp:1217) # ==25261== by 0x77A4E8: SyncEvo::SharedEngine::InitEngineXML(std::string const&) (SynthesisEngine.cpp:48) # ==25261== by 0x6F0169: SyncEvo::SyncContext::initEngine(bool) (SyncContext.cpp:3100) # ==25261== by 0x7005AD: SyncEvo::SyncContext::sync(SyncEvo::SyncReport*) (SyncContext.cpp:3364) # ==25261== by 0x677203: SyncEvo::LocalTransportAgentChild::run() (LocalTransportAgent.cpp:948) # ==25261== by 0x66E2A1: SyncEvo::LocalTransportMain(int, char**) (LocalTransportAgent.cpp:1168) # ==25261== by 0xA6F0994: (below main) (libc-start.c:260) # ==25261== { leaked DLT context names Memcheck:Leak fun:malloc fun:dlt_register_context_ll_ts ... fun:_ZN6sysync16TDebugLoggerBase16DebugStartOutputEv } syncevolution_1.4/test/generate-html.xsl000066400000000000000000001271771230021373600206400ustar00rootroot00000000000000 No cmp_result_file, please set comparison result file by: --stringparam cmp_result_file [your file path]

    Client-test Results

    Client-test Summary

    Client-test Detail

    No client-test info!

    Client::Source Test Results

    Client::Sync Test Results

    SyncEvolution NightlyTest Generate source information in a table

    Source Information

    Patches:

    No source information!
    Generate platform information in a table

    Platform Information

    Item Value
    No platform information!
    Generate preparation information in a table

    Preparation Results

    error

    Servers Interoperability Test Summary

    Server Valgrind Status Categories Total Cases Passed Known Failure Network Failure Failed Skipped Passrate Improved Regression
    failed ok ok failed 0 0
    0 0

    Client Source Test Summary

    Sources Valgrind Status Total Cases Passed Known Failure Network Failure Failed Skipped Passrate Improved Regression
    failed ok ok failed 0 0 0
    This is used to generate a table for each server test results for all PIMs
    Item
    skipped
    Total passed cases (all: )
    <tr> </tr>
    No results!
    Generate a table for a list of unit test cases
    Value
    Total passed cases (all: )
    No information!
    green red gray

    Notes:

    Red: regression Green: improvement Gray: failed but not regression
    syncevolution_1.4/test/keys/000077500000000000000000000000001230021373600163105ustar00rootroot00000000000000syncevolution_1.4/test/keys/README000066400000000000000000000012711230021373600171710ustar00rootroot00000000000000SSL keys for syncevo-http-server running on localhost. See http://twistedmatrix.com/documents/10.1.0/core/howto/ssl.html and HOWTOs like http://www.madboa.com/geek/openssl/#cert-self Debian + server on localhost: - openssl req -x509 -nodes -days 0 -newkey rsa:1024 -keyout localhost_pem.key -out localhost_pem.crt Common Name = "localhost" - cat localhost_pem* >localhost.pem - sudo cp localhost_pem.crt /usr/local/share/ca-certificates/ - sudo update-ca-certificates - ensure that "localhost" resolves to 127.0.0.1 (Twisted does not support listening to IPv6 and libsoup has no fallback to IPv4) - syncevo-http-server --server-certificate=localhost.pem https://localhost:9000/syncevolution syncevolution_1.4/test/keys/localhost.pem000066400000000000000000000032151230021373600210040ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICGzCCAYSgAwIBAgIJAPzkRiPXbaToMA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNV BAMTCWxvY2FsaG9zdDAeFw0xMDEyMjgwOTMyNTRaFw0xMTAxMjcwOTMyNTRaMBQx EjAQBgNVBAMTCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA yEtljSEL7J8o2S/X3vLbD4x/lQH5bCDPuROkyYBKaW7b2Sc4OwTbWgrrwqFky+8a 1skJ8iAkXeh4UnJDwybnmDiGEPVLIOdFp9d8M7YGTR+E1OSSH9pO2ATlMpim8yZa I6460UkPnykErD9PMuriZ6wOEGd8GRuD7DzG+2uVyZ8CAwEAAaN1MHMwHQYDVR0O BBYEFOMz6mgFQW2wEbNlLiexb7kXYWeSMEQGA1UdIwQ9MDuAFOMz6mgFQW2wEbNl Liexb7kXYWeSoRikFjAUMRIwEAYDVQQDEwlsb2NhbGhvc3SCCQD85EYj122k6DAM BgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAL8AN54hQnT2BSjadPP/XxFZ HkVI0+laO5lfOqBQXTOYEJbuOXuGsRSmPY1F9vSBPsBWuViMy2jW94HpFRJ9uP3C l9p8iAfTTKwVTSwcHqx4pGauv+HHA8BvHG2Ml14VaXD1OkRevRvG38kgS2SArgpK ComOL7jLEdw6QKETyxOH -----END CERTIFICATE----- -----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQDIS2WNIQvsnyjZL9fe8tsPjH+VAflsIM+5E6TJgEppbtvZJzg7 BNtaCuvCoWTL7xrWyQnyICRd6HhSckPDJueYOIYQ9Usg50Wn13wztgZNH4TU5JIf 2k7YBOUymKbzJlojrjrRSQ+fKQSsP08y6uJnrA4QZ3wZG4PsPMb7a5XJnwIDAQAB AoGASNOJQBBU+ptASf/oWMsqtXOba/2EyDkB7kRjNVTtOXqyezmUa3kvnIS+Bk2S jcgJlTER6bSgJHkDTs73Lnz11bDXH16fyhL7k5Z3KIIVNYWr8Ad+lcuIK4NNddxQ HoySFK+U6dTjNpWyXXZEmH/9zunSzq4oVM8/W5nUFihflbECQQD37plZ0gW57c6y 6t1+87Nc5CNlfd7K7FoZ1b7O/ct3A+ho46Zzi2bMXL8gCyhcZ53fqYIzTOxPVqZL +Ir4s40pAkEAzs/z6LRN87Wm3TmLJPOvl30gM1f3KsJBogn+NKnSlJyYyI05BGj9 5fnqr/cqUWPzAKlZf357UwCaxF12uoWxhwJBAOjPoCh70uy4pfPUH5Fqfe6oO6S+ AUtDjYfc8oOkRj7H6KE1w8OUDz+vh7krQQckNVck8SIDBZOqphWImdbXo6ECQQCh TFtlgUrS6zhrjjfR6CVpN3Pn15G0zbE22ihjlpfgxIn80PhJUkHEHjlGaLWeqR+b wnlFELbKs8wBnwu8ygz9AkBIyiUisu/XixCpWbFdrLUwOFCdoskvhe/eZJEY2oCP 9TLM4o9GokMJVm7Gta96cGs+MVV03UrApJeacrQX3JgR -----END RSA PRIVATE KEY----- syncevolution_1.4/test/keys/localhost_pem.crt000066400000000000000000000014261230021373600216560ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICGzCCAYSgAwIBAgIJAPzkRiPXbaToMA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNV BAMTCWxvY2FsaG9zdDAeFw0xMDEyMjgwOTMyNTRaFw0xMTAxMjcwOTMyNTRaMBQx EjAQBgNVBAMTCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA yEtljSEL7J8o2S/X3vLbD4x/lQH5bCDPuROkyYBKaW7b2Sc4OwTbWgrrwqFky+8a 1skJ8iAkXeh4UnJDwybnmDiGEPVLIOdFp9d8M7YGTR+E1OSSH9pO2ATlMpim8yZa I6460UkPnykErD9PMuriZ6wOEGd8GRuD7DzG+2uVyZ8CAwEAAaN1MHMwHQYDVR0O BBYEFOMz6mgFQW2wEbNlLiexb7kXYWeSMEQGA1UdIwQ9MDuAFOMz6mgFQW2wEbNl Liexb7kXYWeSoRikFjAUMRIwEAYDVQQDEwlsb2NhbGhvc3SCCQD85EYj122k6DAM BgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAL8AN54hQnT2BSjadPP/XxFZ HkVI0+laO5lfOqBQXTOYEJbuOXuGsRSmPY1F9vSBPsBWuViMy2jW94HpFRJ9uP3C l9p8iAfTTKwVTSwcHqx4pGauv+HHA8BvHG2Ml14VaXD1OkRevRvG38kgS2SArgpK ComOL7jLEdw6QKETyxOH -----END CERTIFICATE----- syncevolution_1.4/test/keys/localhost_pem.key000066400000000000000000000015671230021373600216640ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQDIS2WNIQvsnyjZL9fe8tsPjH+VAflsIM+5E6TJgEppbtvZJzg7 BNtaCuvCoWTL7xrWyQnyICRd6HhSckPDJueYOIYQ9Usg50Wn13wztgZNH4TU5JIf 2k7YBOUymKbzJlojrjrRSQ+fKQSsP08y6uJnrA4QZ3wZG4PsPMb7a5XJnwIDAQAB AoGASNOJQBBU+ptASf/oWMsqtXOba/2EyDkB7kRjNVTtOXqyezmUa3kvnIS+Bk2S jcgJlTER6bSgJHkDTs73Lnz11bDXH16fyhL7k5Z3KIIVNYWr8Ad+lcuIK4NNddxQ HoySFK+U6dTjNpWyXXZEmH/9zunSzq4oVM8/W5nUFihflbECQQD37plZ0gW57c6y 6t1+87Nc5CNlfd7K7FoZ1b7O/ct3A+ho46Zzi2bMXL8gCyhcZ53fqYIzTOxPVqZL +Ir4s40pAkEAzs/z6LRN87Wm3TmLJPOvl30gM1f3KsJBogn+NKnSlJyYyI05BGj9 5fnqr/cqUWPzAKlZf357UwCaxF12uoWxhwJBAOjPoCh70uy4pfPUH5Fqfe6oO6S+ AUtDjYfc8oOkRj7H6KE1w8OUDz+vh7krQQckNVck8SIDBZOqphWImdbXo6ECQQCh TFtlgUrS6zhrjjfR6CVpN3Pn15G0zbE22ihjlpfgxIn80PhJUkHEHjlGaLWeqR+b wnlFELbKs8wBnwu8ygz9AkBIyiUisu/XixCpWbFdrLUwOFCdoskvhe/eZJEY2oCP 9TLM4o9GokMJVm7Gta96cGs+MVV03UrApJeacrQX3JgR -----END RSA PRIVATE KEY----- syncevolution_1.4/test/log2html.py000066400000000000000000000054071230021373600174450ustar00rootroot00000000000000#!/usr/bin/python """ Converts the .log output for a client-test test into HTML, with hightlighting and local hrefs to ClientTest.html and test directories. """ import sys import re import cgi filename = sys.argv[1] if filename == '-': log = sys.stdin else: log = open(filename) out = sys.stdout # matches [DEBUG/DEVELOPER/...] tags at the start of the line, # used to mark text via class ModeSpan: mode=None @staticmethod def setMode(newmode): if ModeSpan.mode != newmode: if ModeSpan.mode: out.write('') ModeSpan.mode = newmode if ModeSpan.mode: out.write('' % ModeSpan.mode) # detect SyncEvolution line prefix tag = re.compile(r'^(\[([A-Z]+) [^\]]*\])( .*)') # hyperlink to HTML version of source code sourcefile = re.compile(r'((\w+\.(?:cpp|c|h)):(\d+))') # highlight: # - any text after *** # *** clean via source A # *** starting Client::Source::egroupware-dav_caldav::testChanges *** # - simple prefixes at the start of a line, after the [] tag (removed already) # caldav #A: # - HTTP requests # REPORT /egw/groupdav.php/syncevolution/calendar/ HTTP/1.1 # PROPFIND /egw/groupdav.php/syncevolution/calendar/1234567890%40dummy.ics HTTP/1.1 highlight = re.compile(r'(\*\*\* .*|^ [a-zA-Z0-9_\- #]*: |(?:REPORT|PUT|GET|DELETE|PROPFIND) .*HTTP/\d\.\d$)') # hyperlink to sync session directory session = re.compile(r'(Client_(?:Source|Sync)(?:_\w+)+\S+)') out.write('''
    ''')
    
    def simplifyFilename(test):
        "same as client-test-main.cpp simplifyFilename()"
        test = test.replace(':', '_')
        test = test.replace('__', '_')
        return test
    
    for line in log:
        line = line.rstrip()
        m = tag.match(line)
        if m:
            ModeSpan.setMode(m.group(2))
            out.write('%s' % m.group(1))
            line = m.group(3)
        line = cgi.escape(line)
        line = sourcefile.sub(r'\1', line)
        line = highlight.sub(r'\1', line)
        line = session.sub(r'\1', line)
        out.write(line)
        out.write('\n')
    
    # close any  opened before
    ModeSpan.setMode(None)
    
    out.write('''
    
    ''') syncevolution_1.4/test/notification-daemon.py000077500000000000000000000040521230021373600216420ustar00rootroot00000000000000#! /usr/bin/python -u # # Copyright (C) 2009 Intel Corporation # # 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) version 3. # # 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 Street, Fifth Floor, Boston, MA # 02110-1301 USA import dbus from dbus.mainloop.glib import DBusGMainLoop import dbus.service import gobject import random class Notifications (dbus.service.Object): '''fake org.freedesktop.Notifications implementation,''' '''used when there is none already registered on the session bus''' @dbus.service.method(dbus_interface='org.freedesktop.Notifications', in_signature='', out_signature='ssss') def GetServerInformation(self): return ('test-dbus', 'SyncEvolution', '0.1', '1.1') @dbus.service.method(dbus_interface='org.freedesktop.Notifications', in_signature='', out_signature='as') def GetCapabilities(self): return ['actions', 'body', 'body-hyperlinks', 'body-markup', 'icon-static', 'sound'] @dbus.service.method(dbus_interface='org.freedesktop.Notifications', in_signature='susssasa{sv}i', out_signature='u') def Notify(self, app, replaces, icon, summary, body, actions, hints, expire): return random.randint(1,100) DBusGMainLoop(set_as_default=True) bus = dbus.SessionBus() loop = gobject.MainLoop() name = dbus.service.BusName("org.freedesktop.Notifications", bus) # start dummy notification daemon, if possible; # if it fails, ignore (probably already one running) notifications = Notifications(bus, "/org/freedesktop/Notifications") loop.run() syncevolution_1.4/test/proxy.py000066400000000000000000000074541230021373600171020ustar00rootroot00000000000000from twisted.internet import reactor from twisted.web import http from twisted.web.proxy import Proxy, ProxyRequest, ProxyClient, ProxyClientFactory from twisted.python import log import sys class ContentWrapper: def __init__(self, content, parent): self.content = content self.parent = parent def seek(self, a, b): self.content.seek(a,b) def read(self): s = self.content.read() if True: log.msg("interrupt before send") # This exception will abort contacting the server and appear as # "Unhandled Error" in the proxy log. The proxy client will get # an empty reply. raise EOFError() return s def close(self): self.content.close() class MyProxyClient(ProxyClient): def __init__(self, command, rest, version, headers, data, father): ProxyClient.__init__(self, command, rest, version, headers, data, father) def connectionMade(self): ProxyClient.connectionMade(self) log.msg("message sent") # interrupt now before server can reply? if self.father.mode == MyProxyRequest.INTERRUPT_AFTER_SEND: log.msg("interrupt after sending") # finish writing, but never read self.transport.loseConnection() # Be nice and report a real error back to the proxy client. self.father.setResponseCode(501, "Gateway error") self.father.responseHeaders.addRawHeader("Content-Type", "text/plain") self.father.write("connection intentionally interrupted after sending and before receiving") class MyProxyClientFactory(ProxyClientFactory): protocol = MyProxyClient def __init__(self, command, rest, version, headers, data, father): ProxyClientFactory.__init__(self, command, rest, version, headers, data, father) class MyProxyRequest(ProxyRequest): protocols = {"http": MyProxyClientFactory} INTERRUPT_BEFORE_SEND = 1 INTERRUPT_AFTER_SEND = 2 INTERRUPT_AFTER_RECEIVE = 3 baseport = 10000 def __init__(self, channel, queued, reactor=reactor): ProxyRequest.__init__(self, channel, queued, reactor) self.mode = channel.transport.server.port - self.baseport def process(self): log.msg("mode is", self.mode) # override read() method so that we can influence the original # process() without having to copy it; just replacing # the read method inside the existing content instance # would be easier, but turned out to be impossible (read-only # attribute) if self.mode == self.INTERRUPT_BEFORE_SEND: # ContentWrapper will raise exception instead of delivering data self.content = ContentWrapper(self.content, self) ProxyRequest.process(self) def write(self, content): log.msg("reply:", content) if self.mode == self.INTERRUPT_AFTER_RECEIVE: # TODO: suppress original headers # Original headers already sent to proxy client, but we # can still suppress the actual data and close the # connection to simulate a failure. log.msg("interrupt after receive") ProxyRequest.write(self, "") self.transport.loseConnection() else: ProxyRequest.write(self, content) class MyProxy(Proxy): requestFactory = MyProxyRequest if __name__ == '__main__': log.startLogging(sys.stdout) f = http.HTTPFactory() f.protocol = MyProxy reactor.listenTCP(MyProxyRequest.baseport + 0, f) reactor.listenTCP(MyProxyRequest.baseport + MyProxyRequest.INTERRUPT_BEFORE_SEND, f) reactor.listenTCP(MyProxyRequest.baseport + MyProxyRequest.INTERRUPT_AFTER_SEND, f) reactor.listenTCP(MyProxyRequest.baseport + MyProxyRequest.INTERRUPT_AFTER_RECEIVE, f) reactor.run() syncevolution_1.4/test/resources.py000077500000000000000000000154511230021373600177320ustar00rootroot00000000000000#!/usr/bin/python -u """ Allocates resources from Murphy and/or a make jobserver while running some command. """ import time import os import re import subprocess import signal from optparse import OptionParser usage = "usage: %prog [options] [--] command arg1 arg2 ..." parser = OptionParser(usage=usage) parser.add_option("-r", "--murphy-resource", dest="resources", action="append", help="Name of a Muprhy resource which gets locked while running the command.") parser.add_option("-j", "--jobs", default=1, type='int', action="store", help="Number of jobs to allocate from job server. Ignored if not running under a job server.") (options, args) = parser.parse_args() def log(format, *args): now = time.time() print time.asctime(time.gmtime(now)), 'UTC', '(+ %.1fs / %.1fs)' % (now - log.latest, now - log.start), format % args log.latest = now log.start = time.time() log.latest = log.start # Murphy support: as a first step, lock one resource named like the # test before running the test. gobject = None if options.resources: try: import gobject except ImportError: from gi.repository import GObject as gobject import dbus from dbus.mainloop.glib import DBusGMainLoop DBusGMainLoop(set_as_default=True) if not os.environ.get('DBUS_SESSION_BUS_ADDRESS', None): # Try to set up Murphy with a murphy-launch.py helper script # which is expected to be provided by the test environment # (not included in SyncEvolution). vars = subprocess.check_output(['murphy-launch.py']) for line in vars.split('\n'): if line: var, value = line.split('=', 1) os.environ[var] = value bus = dbus.SessionBus() loop = gobject.MainLoop() murphy = dbus.Interface(bus.get_object('org.Murphy', '/org/murphy/resource'), 'org.murphy.manager') # Support mapping of resource "foo" to "bar" with RESOURCES_FOO=bar. resources = [] for name in options.resources: replacement = os.environ.get('RESOURCES_%s' % name.upper(), None) if replacement is not None: resources.extend(replacement.split(',')) else: resources.append(name) if resources != options.resources: log('replaced resource set %s with %s based on RESOURCES_* env vars', options.resources, resources) if resources: log('=== locking resource(s) %s ===', resources) resourcesetpath = murphy.createResourceSet() resourceset = dbus.Interface(bus.get_object('org.Murphy', resourcesetpath), 'org.murphy.resourceset') for name in resources: resourcepath = resourceset.addResource(name) # Allow sharing of the resource. Only works if the resource # was marked as "shareable" in the murphy config, otherwise # we get exclusive access. resource = dbus.Interface(bus.get_object('org.Murphy', resourcepath), 'org.murphy.resource') resource.setProperty('shared', dbus.Boolean(True, variant_level=1)) # Track pending request separately, because status == 'pending' # either means something else ('unknown'?) or is buggy/unreliable. # See https://github.com/01org/murphy/issues/5 pending = False def propertyChanged(prop, value): global pending log('property changed: %s = %s', prop, value) if prop == 'status': if value == 'acquired': # Success! loop.quit() elif value == 'lost': # Not yet?! log('Murphy request failed, waiting for resource to become available.') pending = False elif value == 'pending': pass elif value == 'available': if not pending: log('Murphy request may succeed now, try again.') resourceset.request() pending = True else: log('Unexpected status: %s', value) try: match = bus.add_signal_receiver(propertyChanged, 'propertyChanged', 'org.murphy.resourceset', 'org.Murphy', resourcesetpath) resourceset.request() pending = True loop.run() finally: match.remove() class Jobserver: '''Allocates the given number of job slots from the "make -j" jobserver, then runs the command and finally returns the slots. See http://mad-scientist.net/make/jobserver.html''' def __init__(self): self.havejobserver = False self.allocated = 0 # MAKEFLAGS= --jobserver-fds=3,4 -j flags = os.environ.get('MAKEFLAGS', '') m = re.search(r'--jobserver-fds=(\d+),(\d+)', flags) if m: self.receiveslots = int(m.group(1)) self.returnslots = int(m.group(2)) self.blocked = {} self.havejobserver = True log('using jobserver') else: log('not using jobserver') def active(self): return self.havejobserver def alloc(self, numjobs = 1): if not self.havejobserver: return n = 0 self._block() try: while n < numjobs: os.read(self.receiveslots, 1) n += 1 self.allocated += n n = 0 except: os.write(self.returnslots, ' ' * n) raise finally: self._unblock() def free(self, numjobs = 1): if not self.havejobserver: return try: self.allocated -= numjobs os.write(self.returnslots, ' ' * numjobs) finally: self._unblock() def _block(self): '''Block signals if not already done.''' if not self.blocked: for sig in [ signal.SIGINT, signal.SIGTERM ]: self.blocked[sig] = signal.signal(sig, signal.SIG_IGN) def _unblock(self): '''Unblock signals if blocked and we currently own no slots.''' if self.blocked and not self.allocated: for sig, handler in self.blocked.items(): signal.signal(sig, handler) self.blocked = {} jobserver = Jobserver() jobs = 0 if jobserver.active() and options.jobs: log('=== allocating %d job slot(s) ===', options.jobs) jobserver.alloc(options.jobs) log('=== allocated %d job slot(s) ===', options.jobs) jobs = options.jobs try: subprocess.check_call(args) finally: log('=== cleaning up ===') # Return job tokens. if jobs: jobserver.free(jobs) # We don't need to unlock the Murphy resource. Quitting will do # that automatically. syncevolution_1.4/test/resultchecker.py000077500000000000000000000623571230021373600205720ustar00rootroot00000000000000#!/usr/bin/python ''' Copyright (C) 2009 Intel Corporation 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) version 3. 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 Street, Fifth Floor, Boston, MA 02110-1301 USA ''' import sys,os,glob,datetime import re import fnmatch import cgi import subprocess """ resultcheck.py: tranverse the test result directory, generate an XML based test report. """ # sort more accurately on sub-second modification times os.stat_float_times(True) space=" " def check (resultdir, serverlist,resulturi, srcdir, shellprefix, backenddir): '''Entrypoint, resutldir is the test result directory to be generated, resulturi is the http uri, it will only process corresponding server's test results list in severlist''' if serverlist: servers = serverlist.split(",") else: servers = [] result = open("%s/nightly.xml" % resultdir,"w") result.write('''\n''') result.write('''\n''') indents=[space] if(os.path.isfile(resultdir+"/output.txt")==False): print "main test output file not exist!" else: indents,cont = step1(resultdir,result,indents,resultdir,resulturi, shellprefix, srcdir) if (cont): step2(resultdir,result,servers,indents,srcdir,shellprefix,backenddir) else: # compare.xsl fails if there is no element: # add an empty one result.write('''\n''') result.write('''\n''') result.close() patchsummary = re.compile('^Subject: (?:\[PATCH.*?\] )?(.*)\n') patchauthor = re.compile('^From: (.*?) <.*>\n') def extractPatchSummary(patchfile): author = "" for line in open(patchfile): m = patchauthor.match(line) if m: author = m.group(1) + " - " else: m = patchsummary.match(line) if m: return author + m.group(1) return os.path.basename(patchfile) def step1(resultdir, result, indents, dir, resulturi, shellprefix, srcdir): '''Step1 of the result checking, collect system information and check the preparation steps (fetch, compile)''' # Always keep checking, even if any of the preparation steps failed. cont = True input = os.path.join(resultdir, "output.txt") indent =indents[-1]+space indents.append(indent) # include information prepared by GitCopy in runtests.py result.write(indent+'\n') files = os.listdir(resultdir) files.sort() for source in files: m = re.match('(.*)-source.log', source) if m: name = m.group(1) result.write(' \n' % (name, open(os.path.join(resultdir, source)).read())) result.write(' \n') for patch in files: if fnmatch.fnmatch(patch, name + '-*.patch'): result.write(' %s\n' % ( patch, extractPatchSummary(os.path.join(resultdir, patch)) ) ) result.write(' \n') result.write(' \n') result.write(indent+'\n') result.write(indent+'''\n''') indent =indents[-1]+space indents.append(indent) result.write(indent+'''\n''') fout=subprocess.check_output('cat /proc/cpuinfo|grep "model name" |uniq', shell=True) result.write(indent+fout) result.write(indent+'''\n''') result.write(indent+'''\n''') fout=subprocess.check_output('cat /proc/meminfo|grep "Mem"', shell=True) for s in fout.split('\n'): result.write(indent+s) result.write(indent+'''\n''') result.write(indent+'''\n''') fout=subprocess.check_output('uname -osr'.split()) result.write(indent+fout) result.write(indent+'''\n''') if 'schroot' in shellprefix: result.write(indent+'''\n''') # Don't make assumption about the schroot invocation. Instead # extract the schroot name from the environment of the shell. name=subprocess.check_output(shellprefix + "sh -c 'echo $SCHROOT_CHROOT_NAME'", shell=True) info = re.sub(r'schroot .*', 'schroot -i -c ' + name, shellprefix) fout=subprocess.check_output(info, shell=True) s = [] for line in fout.split('\n'): m = re.match(r'^\s+(Name|Description)\s+(.*)', line) if m: s.append(indent + m.group(1) + ': ' + m.group(2)) result.write('\n'.join(s)) result.write(indent+'''\n''') result.write(indent+'''\n''') libs = ['libsoup-2.4', 'evolution-data-server-1.2', 'glib-2.0','dbus-glib-1'] s='' for lib in libs: try: fout=subprocess.check_output(shellprefix+' pkg-config --modversion '+lib +' |grep -v pkg-config', shell=True) s = s + lib +': '+fout +' ' except subprocess.CalledProcessError: pass result.write(indent+s) result.write(indent+'''\n''') indents.pop() indent = indents[-1] result.write(indent+'''\n''') result.write(indent+'''\n''') indent =indent+space indents.append(indent) tags=['libsynthesis', 'syncevolution', 'activesyncd', 'compile', 'dist', 'distcheck'] tagsp={'libsynthesis':'libsynthesis-source', 'syncevolution':'syncevolution-source', 'activesyncd':'activesyncd-source', 'compile':'compile', 'distcheck': 'distcheck', 'dist':'dist'} for tag in tags: result.write(indent+'''<'''+tagsp[tag]) fout=subprocess.check_output('find `dirname '+input+'` -type d -name *'+tag, shell=True) s = fout.rpartition('/')[2].rpartition('\n')[0] result.write(' path ="'+s+'">') '''check the result''' if 0 == os.system("grep -q '^"+tag+": .*: failed' "+input): result.write("failed") elif 0 == os.system ("grep -q '^"+tag+" successful' "+input): result.write("okay") elif 0 == os.system("grep -q '^"+tag+".* disabled in configuration$' "+input): result.write("skipped") else: # Not listed at all? Fail. result.write("failed") result.write('''\n''') indents.pop() indent = indents[-1] result.write(indent+'''\n''') result.write(indent+'''\n''') indent =indent+space indents.append(indent) result.write(indent+''''''+resulturi+'''\n''') indents.pop() indent = indents[-1] result.write(indent+'''\n''') indents.pop() indent = indents[-1] return (indents, cont) def step2(resultdir, result, servers, indents, srcdir, shellprefix, backenddir): '''Step2 of the result checking, for each server listed in servers, tranverse the corresponding result folder, process each log file to decide the status of the testcase''' '''Read the runtime parameter for each server ''' params = {} if servers: cmd='sed -n ' for server in servers: cmd+= '-e /^'+server+'/p ' print "Analyzing overall result %s" % (resultdir+'/output.txt') cmd = cmd +resultdir+'/output.txt' fout=subprocess.check_output(cmd, shell=True) for line in fout.split('\n'): for server in servers: # Find first line with "foobar successful" or "foobar: ", # ignore "skipped". if (line.startswith(server + ":") or line.startswith(server + " ")) and server not in params: t = line.partition(server)[2] if(t.startswith(':')): t=t.partition(':')[2] t = t.strip() if t != 'skipped: disabled in configuration': print "Result for %s: %s" % (server, t) params[server]=t indent =indents[-1]+space indents.append(indent) '''start of testcase results ''' result.write(indent+'''\n''') runservers = os.listdir(resultdir) #list source test servers statically, we have no idea how to differenciate #automatically whether the server is a source test or sync test. sourceServers = ['evolution', 'evolution-prebuilt-build', 'yahoo', 'owndrive', 'davical', 'googlecalendar', 'googlecontacts', 'googleeas', 'apple', 'egroupware-dav', 'oracle', 'exchange', 'pim', 'dbus'] sourceServersRun = 0 haveSource = False #Only process servers listed in the input parameter and in the sourceServer #list and have a result folder for server in servers: matched = False for rserver in runservers: for source in sourceServers: if (rserver.find('-')!=-1 and server == rserver.partition('-')[2] and server == source): matched = True break if(matched): #put the servers at the front of the servers list, so that we will #process test first servers.remove(server) servers.insert (0, server); sourceServersRun = sourceServersRun+1; haveSource = True #process source tests first if (haveSource) : indent +=space indents.append(indent) result.write(indent+'''\n''') haveSync = False for server in servers: matched = False '''Only process servers listed in the input parametr''' for rserver in runservers: if(rserver.find('-')!= -1 and rserver.partition('-')[2] == server): matched = True break; if(matched): sourceServersRun = sourceServersRun -1; if (sourceServersRun == -1): haveSync = True '''generate a template which lists all test cases we supply, this helps generate a comparable table and track potential uncontentional skipping of test cases''' templates=[] oldpath = os.getcwd() # Get list of Client::Sync tests one source at a time (because # the result might depend on CLIENT_TEST_SOURCES and which source # is listed there first) and combine the result for the common # data types (because some tests are only enable for contacts, others # only for events). # The order of the tests matters, so don't use a hash and start with # a source which has only the common tests enabled. Additional tests # then get added at the end. clientSync = re.compile(r' +Client::Sync::(.*?)::(?:(Suspend|Resend|Retry)::)?([^:]+)') for source in ('file_task', 'file_event', 'file_contact', 'eds_contact', 'eds_event'): cmd = shellprefix + " env LD_LIBRARY_PATH=%s/build-synthesis/src/.libs SYNCEVOLUTION_BACKEND_DIR=%s CLIENT_TEST_PEER_CAN_RESTART=1 CLIENT_TEST_RETRY=t CLIENT_TEST_RESEND=t CLIENT_TEST_SUSPEND=t CLIENT_TEST_SOURCES=%s %s/client-test -h" % (srcdir, backenddir, source, srcdir) fout=subprocess.check_output(cmd, shell=True) for line in fout.split('\n'): m = clientSync.match(line) if m: if m.group(2): # special sub-grouping test = m.group(2) + '__' + m.group(3) else: test = m.group(3) if test and test not in templates: templates.append(test) indent +=space indents.append(indent) result.write(indent+'\n') result.write(indent+space+'\n') indent +=space indents.append(indent) result.write(indent+'<'+server+' path="' +rserver+'" ') #valgrind check resutls if not params.get(server, None): # Unknown result, treat as failure. result.write('result="1"') else: m = re.search(r'return code (\d+)', params[server]) if m: result.write('result="%s"' % m.group(1)) result.write('>\n') # sort files by creation time, to preserve run order logs = map(lambda file: (os.stat(file).st_mtime, file), glob.glob(resultdir+'/'+rserver+'/*.log')) logs.sort() logs = map(lambda entry: entry[1], logs) logdic ={} logprefix ={} if server in ('dbus', 'pim'): # Extract tests and their results from output.txt, # which contains Python unit test output. Example follows. # Note that there can be arbitrary text between the test name # and "ok" resp. "FAIL/ERROR". Therefore failed tests # are identified not by those words but rather by the separate # error reports at the end of the output. Those reports # are split out into separate .log files for easy viewing # via the .html report. # # TestDBusServer.testCapabilities - Server.Capabilities() ... ok # TestDBusServer.testGetConfigScheduleWorld - Server.GetConfigScheduleWorld() ... ok # TestDBusServer.testGetConfigsEmpty - Server.GetConfigsEmpty() ... ok # TestDBusServer.testGetConfigsTemplates - Server.GetConfigsTemplates() ... FAIL # TestDBusServer.testInvalidConfig - Server.NoSuchConfig exception ... ok # TestDBusServer.testVersions - Server.GetVersions() ... ok # #====================================================================== # FAIL: TestDBusServer.testGetConfigsTemplates - Server.GetConfigsTemplates() # --------------------------------------------------------------------- # # More recent Python 2.7 produces: # FAIL: testSyncSecondSession (__main__.TestSessionAPIsReal) # first build list of all tests, assuming that they pass dbustests = {} test_start = re.compile(r'''^Test(?P.*)\.test(?P[^ ]*).*ok(?:ay)?$''') # FAIL/ERROR + description of test (old Python) test_fail = re.compile(r'''(?PFAIL|ERROR): Test(?P.*)\.test(?P[^ ]*)''') # FAIL/ERROR + function name of test (Python 2.7) test_fail_27 = re.compile(r'''(?PFAIL|ERROR): test(?P[^ ]*) \(.*\.(?:Test(?P.*))\)''') name = None logfile = None htmlfile = None linetype = None for line in open(rserver + "/output.txt"): m = test_start.search(line) if m: is_okay = True else: m = test_fail.search(line) or test_fail_27.search(line) is_okay = False if m: # create new (?!) log file cl = m.group("cl") func = m.group("func") newname = rserver + "/" + cl + "_" + func + ".log" if newname != name: name = newname logfile = open(name, "w") if htmlfile: htmlfile.write('
    ''')
                                if not dbustests.get(cl):
                                    dbustests[cl] = {}
                                if is_okay:
                                    # okay: write a single line with the full test description
                                    dbustests[cl][func] = "okay"
                                    logfile.write(line)
                                    logfile = None
                                    htmlfile.write('%s
    ' % cgi.escape(line)) htmlfile.close() htmlfile = None else: # failed: start writing lines into separate log file dbustests[cl][func] = m.group("type") linetype = "ERROR" htmlfile.write('D-Bus traffic output\n\n') if logfile: logfile.write(line) if line == 'D-Bus traffic:\n': linetype = "DBUS" htmlfile.write('

    D-Bus traffic:

    ') elif line == 'server output:\n': linetype = "OUT" htmlfile.write('

    server output:

    ') else: htmlfile.write('%s' % (linetype, cgi.escape(line))) if htmlfile: htmlfile.write('\n' % (indent, testclass)) indent +=space indents.append(indent) for testfunc in dbustests[testclass]: result.write('%s<%s>%s\n' % (indent, testfunc, dbustests[testclass][testfunc], testfunc)) indents.pop() indent = indents[-1] result.write('%s\n' % (indent, testclass)) indents.pop() indent = indents[-1] else: for log in logs: logname = os.path.basename(log) if logname in ['____compare.log', 'syncevo.log', # D-Bus server output 'dbus.log', # dbus-monitor output ]: continue # /Client_Sync_eds_contact_testItems.log # /SyncEvo_CmdlineTest_testConfigure.log # /N7SyncEvo11CmdlineTestE_testConfigure.log - C++ name mangling? m = re.match(r'^(Client_Source_|Client_Sync_|N7SyncEvo\d+|[^_]*_)(.*)_([^_]*)\.log', logname) if not m or logname.endswith('.server.log'): print "skipping", logname continue # Client_Sync_, Client_Source_, SyncEvo_, ... prefix = m.group(1) # eds_contact, CmdlineTest, ... format = m.group(2) # testImport casename = m.group(3) # special case grouping of some tests: include group inside casename instead of # format, example: # /Client_Source_apple_caldav_LinkedItemsDefault_testLinkedItemsParent m = re.match(r'(.*)_(LinkedItems\w+|Suspend|Resend|Retry)', format) if m: format = m.group(1) casename = m.group(2) + '::' + casename if '.' in casename: # Ignore sub logs. print "skipping", logname continue # Another special case: suspend/resend/retry uses an intermediate grouping # which we can ignore because the name is repeated in the test case name. # m = re.match(r'(.*)_(Suspend|Resend|Retry)', format) # if m: # format = m.group(1) # # Case name *not* extended, in contrast to the # # LinkedItems case above. # if '.' in casename: # # Ignore sub logs. # print "skipping", logname # continue print "analyzing log %s: prefix %s, subset %s, testcase %s" % (logname, prefix, format, casename) if(format not in logdic): logdic[format]=[] logdic[format].append((casename, log)) logprefix[format]=prefix for format in logdic.keys(): indent +=space indents.append(indent) prefix = logprefix[format] qformat = format; # avoid + sign in element name (not allowed by XML); # code reading XML must replace _- with + and __ with _ qformat = qformat.replace("_", "__"); qformat = qformat.replace("+", "_-"); result.write(indent+'<'+qformat+' prefix="'+prefix+'">\n') for casename, log in logdic[format]: indent +=space indents.append(indent) # must avoid :: in XML tag = casename.replace('::', '__') result.write(indent+'<'+tag+'>') match=format+'::'+casename matchOk=match+": okay \*\*\*" matchKnownFailure=match+": \*\*\* failure ignored \*\*\*" if not os.system("grep -q '" + matchKnownFailure + "' "+log): result.write('knownfailure') elif not os.system("tail -10 %s | grep -q 'external transport failure (local, status 20043)'" % log): result.write('network') elif os.system("grep -q '" + matchOk + "' "+log): result.write('failed') else: result.write('okay') result.write('\n') indents.pop() indent = indents[-1] result.write(indent+'\n') indents.pop() indent = indents[-1] result.write(indent+'\n') indents.pop() indent = indents[-1] if(sourceServersRun == 0): #all source servers have been processed, end the source tag and #start the sync tags result.write(indent+'''\n''') indents.pop() indent = indents[-1] if(haveSync): result.write(indent+'
    \n') indents.pop() indent=indents[-1] result.write(indent+'''
    \n''') indents.pop() indents = indents[-1] if(__name__ == "__main__"): if (len(sys.argv)!=7): # srcdir and basedir must be usable inside the shell started by shellprefix (typically # the chroot). print "usage: python resultchecker.py resultdir servers resulturi srcdir shellprefix backenddir" else: check(*sys.argv[1:]) syncevolution_1.4/test/run_src_client_test.sh000077500000000000000000000004101230021373600217370ustar00rootroot00000000000000#!/bin/sh # This script is run by `make check'. Since `make check' is run from top source # directory and `client-test' is expected to be run from `src' directory, this # script have to be employed, so `client-test' can find some files. cd 'src' && ./client-test syncevolution_1.4/test/runtests.py000077500000000000000000003521051230021373600176070ustar00rootroot00000000000000#!/usr/bin/python -u """ The general idea is that tests to run are defined as a list of actions. Each action has a unique name and can depend on other actions to have run successfully before. Most work is executed in directories defined and owned by these actions. The framework only manages one directory which represents the result of each action: - an overview file which lists the result of each action - for each action a directory with stderr/out and additional files that the action can put there """ import os, sys, popen2, traceback, re, time, smtplib, optparse, stat, shutil, StringIO, MimeWriter import shlex import subprocess import fnmatch import copy import errno import signal import stat def log(format, *args): now = time.time() print time.asctime(time.gmtime(now)), 'UTC', '(+ %.1fs / %.1fs)' % (now - log.latest, now - log.start), format % args log.latest = now log.start = time.time() log.latest = log.start try: import gzip havegzip = True except: havegzip = False def cd(path): """Enter directories, creating them if necessary.""" if not os.access(path, os.F_OK): os.makedirs(path) os.chdir(path) def abspath(path): """Absolute path after expanding vars and user.""" return os.path.abspath(os.path.expanduser(os.path.expandvars(path))) def findInPaths(name, dirs): """find existing item in one of the directories, return None if no directories give, absolute path to existing item or (as fallbac) last dir + name""" fullname = None for dir in dirs: fullname = os.path.join(abspath(dir), name) if os.access(fullname, os.F_OK): break return fullname def del_dir(path): # Preserve XDG dirs, if we were set up like that by caller. # These dirs might already contain some relevant data. xdgdirs = list(os.environ.get(x, None) for x in ("XDG_CONFIG_HOME", "XDG_DATA_HOME", "XDG_CACHE_HOME")) if path in xdgdirs: return if not os.access(path, os.F_OK): return for file in os.listdir(path): file_or_dir = os.path.join(path,file) # ensure directory is writable os.chmod(path, os.stat(path)[stat.ST_MODE] | stat.S_IRWXU) if os.path.isdir(file_or_dir) and not os.path.islink(file_or_dir): del_dir(file_or_dir) #it's a directory recursive call to function again else: os.remove(file_or_dir) #it's a file, delete it # We might have skipped deleting something, allow that. try: os.rmdir(path) except OSError, ex: if ex.errno != errno.ENOTEMPTY: raise def copyLog(filename, dirname, htaccess, lineFilter=None): """Make a gzipped copy (if possible) with the original time stamps and find the most severe problem in it. That line is then added as description in a .htaccess AddDescription. For directories just copy the whole directory tree. """ info = os.stat(filename) outname = os.path.join(dirname, os.path.basename(filename)) if os.path.isdir(filename): # copy whole directory, without any further processing at the moment shutil.copytree(filename, outname, symlinks=True) return # .out files are typically small nowadays, so don't compress if False: outname = outname + ".gz" out = gzip.open(outname, "wb") else: out = file(outname, "w") error = None for line in file(filename, "r").readlines(): if not error and line.find("ERROR") >= 0: error = line if lineFilter: line = lineFilter(line) out.write(line) out.close() os.utime(outname, (info[stat.ST_ATIME], info[stat.ST_MTIME])) if error: error = error.strip().replace("\"", "'").replace("<", "<").replace(">",">") htaccess.write("AddDescription \"%s\" %s\n" % (error, os.path.basename(filename))) return error def TryKill(pid, signal): try: os.kill(pid, signal) except OSError, ex: # might have quit in the meantime, deal with the race # condition if ex.errno != 3: raise ex def ShutdownSubprocess(popen, timeout): start = time.time() if popen.poll() == None: TryKill(popen.pid, signal.SIGTERM) while popen.poll() == None and start + timeout >= time.time(): time.sleep(0.01) if popen.poll() == None: TryKill(popen.pid, signal.SIGKILL) while popen.poll() == None and start + timeout + 1 >= time.time(): time.sleep(0.01) return False return True class Jobserver: '''Allocates the given number of job slots from the "make -j" jobserver, then runs the command and finally returns the slots. See http://mad-scientist.net/make/jobserver.html''' def __init__(self): self.havejobserver = False self.allocated = 0 # MAKEFLAGS= --jobserver-fds=3,4 -j flags = os.environ.get('MAKEFLAGS', '') m = re.search(r'--jobserver-fds=(\d+),(\d+)', flags) if m: self.receiveslots = int(m.group(1)) self.returnslots = int(m.group(2)) self.blocked = {} self.havejobserver = True log('using jobserver') else: log('not using jobserver') def active(self): return self.havejobserver def alloc(self, numjobs = 1): if not self.havejobserver: return n = 0 self._block() try: while n < numjobs: os.read(self.receiveslots, 1) n += 1 self.allocated += n n = 0 except: os.write(self.returnslots, ' ' * n) raise finally: self._unblock() def free(self, numjobs = 1): if not self.havejobserver: return try: self.allocated -= numjobs os.write(self.returnslots, ' ' * numjobs) finally: self._unblock() def _block(self): '''Block signals if not already done.''' if not self.blocked: for sig in [ signal.SIGINT, signal.SIGTERM ]: self.blocked[sig] = signal.signal(sig, signal.SIG_IGN) def _unblock(self): '''Unblock signals if blocked and we currently own no slots.''' if self.blocked and not self.allocated: for sig, handler in self.blocked.items(): signal.signal(sig, handler) self.blocked = {} jobserver = Jobserver() # must be set before instantiating some of the following classes context = None class Action: """Base class for all actions to be performed.""" DONE = "0 DONE" WARNINGS = "1 WARNINGS" FAILED = "2 FAILED" TODO = "3 TODO" SKIPPED = "4 SKIPPED" RUNNING = "5 RUNNING" COMPLETED = (DONE, WARNINGS) def __init__(self, name): self.name = name self.status = self.TODO self.summary = "" self.dependencies = [] self.isserver = False; # Assume that the action does not need its own HOME directory. self.needhome = False # Child PID of forked process executing the action while it is # running. self.worker_pid = None def execute(self): """Runs action. Throws an exeception if anything fails. Will be called by tryexecution() with stderr/stdout redirected into a file and the current directory set to an empty temporary directory. """ raise Exception("not implemented") def nop(self): pass def tryexecution(self, step, logs): """wrapper around execute which handles exceptions, directories and stdout""" log('*** starting action %s', self.name) sys.stderr.flush() sys.stdout.flush() child = None res = 0 try: child = os.fork() if child == 0: # We are the child executing the action. try: subdirname = "%d-%s" % (step, self.name) cd(subdirname) if logs: # Append, in case that we run multiple times for the same platform. # The second run will typically have fake libsynthesis/syncevolution/compile # runs which must not overwrite previous results. The new operations must # be added at the end of main output.txt, too. fd = os.open("output.txt", os.O_WRONLY|os.O_CREAT|os.O_APPEND) os.dup2(fd, 1) os.dup2(fd, 2) sys.stdout = os.fdopen(fd, "w", 0) # unbuffered output! sys.stderr = sys.stdout if self.needhome and context.home_template: home = os.path.join(context.tmpdir, 'home', self.name) if not os.path.isdir(home): # Ignore special files like sockets (for example, # .cache/keyring-5sj9Qz/control). def ignore(path, entries): exclude = [] for entry in entries: mode = os.lstat(os.path.join(path, entry)).st_mode if not (stat.S_ISDIR(mode) or stat.S_ISREG(mode) or stat.S_ISLNK(mode)): exclude.append(entry) return exclude shutil.copytree(context.home_template, home, symlinks=True, ignore=ignore) os.environ['HOME'] = context.stripSchrootDir(home) for old, new, name in [('.cache', 'cache', 'XDG_CACHE_HOME'), ('.config', 'config', 'XDG_CONFIG_HOME'), ('.local/share', 'data', 'XDG_DATA_HOME')]: newdir = os.path.join(home, new) olddir = os.path.join(home, old) if not os.path.isdir(olddir): os.makedirs(olddir) # Use simpler directory layout to comply with testpim.py expectations. print 'old', olddir, 'new', newdir os.rename(olddir, newdir) # Keep the old names as symlinks, just in case. os.symlink(newdir, olddir) # Now use it via XDG env var *without* the schrootdir. os.environ[name] = context.stripSchrootDir(newdir) log('=== starting %s ===', self.name) self.execute() except: traceback.print_exc() # We can't just exit() here because that ends up raising an exception # which would get caught in the outer try/except. res = 1 else: # Parent. self.worker_pid = child self.status = Action.RUNNING # Can we really parallelize? if self.needhome and not context.home_template: self.wait_for_completion() except Exception, inst: # fork() error handling in parent. traceback.print_exc() self.status = Action.FAILED self.summary = str(inst) if child == 0: # Child must quit. exit(res) else: # Parent must return. return self.status def wait_for_completion(self): log('*** waiting for %s (pid %d)', self.name, self.worker_pid) pid, exitcode = os.waitpid(self.worker_pid, 0) log('*** %s: %d', self.name, exitcode) if exitcode == 0: self.status = Action.DONE else: self.status = Action.FAILED self.summary = 'return code %d: failed' % exitcode class Context: """Provides services required by actions and handles running them.""" def __init__(self, tmpdir, resultdir, uri, workdir, mailtitle, sender, recipients, mailhost, enabled, skip, nologs, setupcmd, make, sanitychecks, lastresultdir, datadir): # preserve normal stdout because stdout/stderr will be redirected self.out = os.fdopen(os.dup(1), "w", 0) # unbuffered self.todo = [] self.actions = {} self.tmpdir = abspath(tmpdir) self.resultdir = abspath(resultdir) self.uri = uri self.workdir = abspath(workdir) self.summary = [] self.mailtitle = mailtitle self.sender = sender self.recipients = recipients self.mailhost = mailhost self.enabled = enabled self.skip = skip self.nologs = nologs self.setupcmd = setupcmd self.make = make self.sanitychecks = sanitychecks self.lastresultdir = lastresultdir self.datadir = datadir self.schrootdir = None def stripSchrootDir(self, path): if self.schrootdir and path.startswith(self.schrootdir + '/'): return path[len(self.schrootdir):] else: return path def findTestFile(self, name): """find item in SyncEvolution test directory, first using the generated source of the current test, then the bootstrapping code""" return findInPaths(name, (os.path.join(sync.basedir, "test"), self.datadir)) def runCommand(self, cmdstr, dumpCommands=False, runAsIs=False, resources=[], jobs=1): """Log and run the given command, throwing an exception if it fails.""" cmd = shlex.split(cmdstr) if "valgrindcheck.sh" in cmdstr: cmd.insert(0, "VALGRIND_LOG=%s" % os.getenv("VALGRIND_LOG", "")) cmd.insert(0, "VALGRIND_ARGS=%s" % os.getenv("VALGRIND_ARGS", "")) cmd.insert(0, "VALGRIND_LEAK_CHECK_ONLY_FIRST=%s" % os.getenv("VALGRIND_LEAK_CHECK_ONLY_FIRST", "")) cmd.insert(0, "VALGRIND_LEAK_CHECK_SKIP=%s" % os.getenv("VALGRIND_LEAK_CHECK_SKIP", "")) # move "sudo" or "env" command invocation in front of # all the leading env variable assignments: necessary # because sudo ignores them otherwise command = 0 isenv = re.compile(r'[a-zA-Z0-9_]*=.*') while isenv.match(cmd[command]): command = command + 1 if cmd[command] in ("env", "sudo"): cmd.insert(0, cmd[command]) del cmd[command + 1] elif isenv.match(cmd[0]): # We did not insert env or sudo before the initial # variable assignment. Don't rely on the shell to # handle that (breaks for 'foo="x" "y"'), instead # use env. cmd.insert(0, 'env') if not runAsIs: cmdstr = " ".join(map(lambda x: (' ' in x or '(' in x or '\\' in x or x == '') and ("'" in x and '"%s"' or "'%s'") % x or x, cmd)) if dumpCommands: cmdstr = "set -x; " + cmdstr cwd = os.getcwd() # Most commands involving schroot need to run with paths as seen inside the chroot. # Detect that in a hackish way by checking for "schroot" and then adapting # paths with search/replace. Exception is resultchecker.py, which runs outside # the chroot, but gets passed "schroot" as parameter. if not runAsIs and 'schroot ' in cmdstr and options.schrootdir and not 'resultchecker.py' in cmdstr: if cwd.startswith(options.schrootdir): relcwd = cwd[len(options.schrootdir):] cmdstr = cmdstr.replace('schroot ', 'schroot -d %s ' % relcwd) cmdstr = cmdstr.replace(options.schrootdir + '/', '/') if jobs or resources: helper = self.findTestFile("resources.py") cmdstr = helper + \ (jobs and (' -j %d' % jobs) or '') + \ ''.join([' -r ' + resource for resource in resources]) + \ ' -- ' + \ cmdstr relevantenv = [ "LD_LIBRARY_PATH", "PATH", "HOME", "XDG_CONFIG_HOME", "XDG_DATA_HOME", "XDG_CACHE_HOME", ] log('*** ( cd %s; export %s; unset %s; %s )', cwd, " ".join(["'%s=%s'" % (x, os.getenv(x)) for x in relevantenv if os.getenv(x, None) is not None]), " ".join([x for x in relevantenv if os.getenv(x, None) is None]), cmdstr) sys.stdout.flush() result = os.system(cmdstr) if result != 0: raise Exception("%s: failed (return code %d)" % (cmd, result>>8)) def add(self, action): """Add an action for later execution. Order is important, fifo...""" self.todo.append(action) self.actions[action.name] = action def required(self, actionname): """Returns true if the action is required by one which is enabled.""" if actionname in self.enabled: return True for action in self.todo: if actionname in action.dependencies and self.required(action.name): return True return False def execute(self): cd(self.resultdir) # Append instead of overwriting, as for other output.txt files, too. s = open("output.txt", "a+") status = Action.DONE step = 0 run_servers = [] started = [] def check_action(action, global_status): if action.status == Action.FAILED: result = ': %s' % action.summary elif action.status == Action.WARNINGS: result = ' done, but check the warnings' else: result = ' successful' log('*** action %s completed, status%s', action.name, result) if action.status > global_status: global_status = action.status self.summary.append('%s%s' % (action.name, result)) return global_status while len(self.todo) > 0: try: step = step + 1 # get action action = self.todo.pop(0) # check whether it actually needs to be executed if self.enabled and \ not action.name in self.enabled and \ not self.required(action.name): # disabled action.status = Action.SKIPPED self.summary.append("%s skipped: disabled in configuration" % (action.name)) elif action.name in self.skip: # assume that it was done earlier action.status = Action.SKIPPED self.summary.append("%s assumed to be done: requested by configuration" % (action.name)) else: # check dependencies log('*** checking dependencies %s of %s', action.dependencies, action.name) for depend in action.dependencies: while self.actions[depend].status == Action.RUNNING: self.actions[depend].wait_for_completion() status = check_action(self.actions[depend], status) if not self.actions[depend].status in Action.COMPLETED: action.status = Action.SKIPPED self.summary.append("%s skipped: required %s has not been executed" % (action.name, depend)) break if action.status != Action.SKIPPED: # execute it if action.isserver: run_servers.append(action.name); action.tryexecution(step, not self.nologs) started.append(action) except Exception, inst: traceback.print_exc() self.summary.append("%s failed: %s" % (action.name, inst)) # Now wait for each running action. for action in started: if action.status == Action.RUNNING: action.wait_for_completion() status = check_action(action, status) # append all parameters to summary self.summary.append("") self.summary.extend(sys.argv) # update summary s.write("%s\n" % ("\n".join(self.summary))) s.close() # copy information about sources for source in self.actions.keys(): action = self.actions[source] basedir = getattr(action, 'basedir', None) if basedir and os.path.isdir(basedir): for file in os.listdir(os.path.join(basedir, "..")): if fnmatch.fnmatch(file, source + '[.-]*'): shutil.copyfile(os.path.join(basedir, "..", file), os.path.join(self.resultdir, file)) # run testresult checker testdir = compile.testdir backenddir = os.path.join(compile.installdir, "usr/lib/syncevolution/backends") # resultchecker doesn't need valgrind, remove it shell = re.sub(r'\S*valgrind\S*', '', options.shell) # When using schroot, run it in /tmp, because the host's directory might # not exist in the chroot. shell = shell.replace('schroot ', 'schroot -d /tmp ', 1) prefix = re.sub(r'\S*valgrind\S*', '', options.testprefix) uri = self.uri or ("file:///" + self.resultdir) resultchecker = self.findTestFile("resultchecker.py") compare = self.findTestFile("compare.xsl") generateHTML = self.findTestFile("generate-html.xsl") commands = [] # produce nightly.xml from plain text log files backenddir = context.stripSchrootDir(backenddir) testdir = context.stripSchrootDir(testdir) commands.append(resultchecker + " " +self.resultdir+" "+"\""+",".join(run_servers)+"\""+" "+uri +" "+testdir + " \"" + shell + " " + testprefix +" \""+" \"" +backenddir + "\"") previousxml = os.path.join(self.lastresultdir, "nightly.xml") if os.path.exists(previousxml): # compare current nightly.xml against previous file commands.append("xsltproc -o " + self.resultdir + "/cmp_result.xml --stringparam cmp_file " + previousxml + " " + compare + " " + self.resultdir + "/nightly.xml") # produce HTML with URLs relative to current directory of the nightly.html commands.append("xsltproc -o " + self.resultdir + "/nightly.html --stringparam url . --stringparam cmp_result_file " + self.resultdir + "/cmp_result.xml " + generateHTML + " "+ self.resultdir+"/nightly.xml") self.runCommand(" && ".join(commands)) # report result by email if self.recipients: server = smtplib.SMTP(self.mailhost) msg='' try: msg = open(self.resultdir + "/nightly.html").read() except IOError: msg = '''

    Error: No HTML report generated!

    \n''' # insert absolute URL into hrefs so that links can be opened directly in # the mail reader msg = re.sub(r'href="([a-zA-Z0-9./])', 'href="' + uri + r'/\1', msg) body = StringIO.StringIO() writer = MimeWriter.MimeWriter (body) writer.addheader("From", self.sender) for recipient in self.recipients: writer.addheader("To", recipient) writer.addheader("Subject", self.mailtitle + ": " + os.path.basename(self.resultdir)) writer.addheader("MIME-Version", "1.0") writer.flushheaders() writer.startbody("text/html;charset=ISO-8859-1").write(msg) failed = server.sendmail(self.sender, self.recipients, body.getvalue()) if failed: log('could not send to: %s', failed) sys.exit(1) else: log('%s\n', '\n'.join(self.summary)) if status in Action.COMPLETED: sys.exit(0) else: sys.exit(1) class CVSCheckout(Action): """Does a CVS checkout (if directory does not exist yet) or an update (if it does).""" def __init__(self, name, workdir, runner, cvsroot, module, revision): """workdir defines the directory to do the checkout in, cvsroot the server, module the path to the files, revision the tag to checkout""" Action.__init__(self,name) self.workdir = workdir self.runner = runner self.cvsroot = cvsroot self.module = module self.revision = revision self.basedir = os.path.join(abspath(workdir), module) def execute(self): cd(self.workdir) if os.access(self.module, os.F_OK): os.chdir(self.module) context.runCommand("cvs update -d -r %s" % (self.revision)) elif self.revision == "HEAD": context.runCommand("cvs -d %s checkout %s" % (self.cvsroot, self.module)) os.chdir(self.module) else: context.runCommand("cvs -d %s checkout -r %s %s" % (self.cvsroot, self.revision, self.module)) os.chdir(self.module) if os.access("autogen.sh", os.F_OK): context.runCommand("%s ./autogen.sh" % (self.runner)) class SVNCheckout(Action): """Does a Subversion checkout (if directory does not exist yet) or a switch (if it does).""" def __init__(self, name, workdir, runner, url, module): """workdir defines the directory to do the checkout in, URL the server and path inside repository, module the path to the files in the checked out copy""" Action.__init__(self,name) self.workdir = workdir self.runner = runner self.url = url self.module = module self.basedir = os.path.join(abspath(workdir), module) def execute(self): cd(self.workdir) if os.access(self.module, os.F_OK): cmd = "switch" else: cmd = "checkout" context.runCommand("svn %s %s %s" % (cmd, self.url, self.module)) os.chdir(self.module) if os.access("autogen.sh", os.F_OK): context.runCommand("%s ./autogen.sh" % (self.runner)) class GitCheckoutBase: """Just sets some common properties for all Git checkout classes: workdir, basedir""" def __init__(self, name, workdir): self.workdir = workdir self.basedir = os.path.join(abspath(workdir), name) class GitCheckout(GitCheckoutBase, Action): """Does a git clone (if directory does not exist yet) or a fetch+checkout (if it does).""" def __init__(self, name, workdir, runner, url, revision): """workdir defines the directory to do the checkout in with 'name' as name of the sub directory, URL the server and repository, revision the desired branch or tag""" Action.__init__(self, name) GitCheckoutBase.__init__(self, name) self.runner = runner self.url = url self.revision = revision def execute(self): if os.access(self.basedir, os.F_OK): cmd = "cd %s && git fetch" % (self.basedir) else: cmd = "git clone %s %s && chmod -R g+w %s && cd %s && git config core.sharedRepository group " % (self.url, self.basedir, self.basedir, self.basedir) context.runCommand(cmd) context.runCommand("set -x; cd %(dir)s && git show-ref &&" "((git tag -l | grep -w -q %(rev)s) && git checkout %(rev)s ||" "((git branch -l | grep -w -q %(rev)s) && git checkout %(rev)s || git checkout -b %(rev)s origin/%(rev)s) && git merge origin/%(rev)s)" % {"dir": self.basedir, "rev": self.revision}, runAsIs=True) os.chdir(self.basedir) if os.access("autogen.sh", os.F_OK): context.runCommand("%s ./autogen.sh" % (self.runner)) class GitCopy(GitCheckoutBase, Action): """Copy existing git repository and update it to the requested branch, with local changes stashed before updating and restored again afterwards. Automatically merges all branches with / as prefix, skips those which do not apply cleanly.""" def __init__(self, name, workdir, runner, sourcedir, revision): """workdir defines the directory to create/update the repo in with 'name' as name of the sub directory, sourcedir a directory which must contain such a repo already, revision the desired branch or tag""" Action.__init__(self, name) GitCheckoutBase.__init__(self, name, workdir) self.runner = runner self.sourcedir = sourcedir self.revision = revision self.patchlog = os.path.join(abspath(workdir), name + "-source.log") self.__getitem__ = lambda x: getattr(self, x) def execute(self): if not os.access(self.basedir, os.F_OK): context.runCommand("(mkdir -p %s && cp -a -l %s/%s %s) || ( rm -rf %s && false )" % (self.workdir, self.sourcedir, self.name, self.workdir, self.basedir)) os.chdir(self.basedir) cmd = " && ".join([ 'rm -f %(patchlog)s', 'echo "save local changes with stash under a fixed name -nightly"', 'rev=$(git stash create)', 'git branch -f %(revision)s-nightly ${rev:-HEAD}', 'echo "check out branch as "nightly" and integrate all proposed patches (= /... branches)"', # switch to detached head, to allow removal of branches 'git checkout -q $( git show-ref --head --hash | head -1 )', 'if git branch | grep -q -w "^..%(revision)s$"; then git branch -D %(revision)s; fi', 'if git branch | grep -q -w "^..nightly$"; then git branch -D nightly; fi', # fetch 'echo "remove stale merge branches and fetch anew"', 'git branch -r -D $( git branch -r | grep -e "/for-%(revision)s/" ) ', 'git branch -D $( git branch | grep -e "^ for-%(revision)s/" ) ', 'git fetch', 'git fetch --tags', # pick tag or remote branch 'if git tag | grep -q -w %(revision)s; then base=%(revision)s; git checkout -f -b nightly %(revision)s; ' \ 'else base=origin/%(revision)s; git checkout -f -b nightly origin/%(revision)s; fi', # integrate remote branches first, followed by local ones; # the hope is that local branches apply cleanly on top of the remote ones 'for patch in $( (git branch -r --no-merged origin/%(revision)s; git branch --no-merged origin/%(revision)s) | sed -e "s/^..//" | grep -e "^for-%(revision)s/" -e "/for-%(revision)s/" ); do ' \ 'if git merge $patch; then echo >>%(patchlog)s $patch: okay; ' \ 'else echo >>%(patchlog)s $patch: failed to apply; git reset --hard; fi; done', 'echo "restore -nightly and create permanent branch -nightly-before--