From 77bb14c90287fba1612a3a8cf34a9b149488af52 Mon Sep 17 00:00:00 2001 From: Nicolas VIVIEN Date: Tue, 27 Jan 2009 18:45:08 -0500 Subject: [PATCH] Applied Nicolas Vivien's opensync-plugin-0.4x patch --- opensync-plugin-0.4x/.cvsignore | 1 + opensync-plugin-0.4x/AUTHORS | 9 + opensync-plugin-0.4x/COPYING | 340 ++++ opensync-plugin-0.4x/ChangeLog | 2 + opensync-plugin-0.4x/Makefile.am | 11 + opensync-plugin-0.4x/NEWS | 0 opensync-plugin-0.4x/README | 85 + opensync-plugin-0.4x/buildgen.sh | 18 + opensync-plugin-0.4x/configure.ac | 52 + opensync-plugin-0.4x/m4/ax_c_check_flag.m4 | 89 + opensync-plugin-0.4x/m4/ax_cxx_check_flag.m4 | 89 + opensync-plugin-0.4x/patches/README | 3 + .../patches/kitchensync-svn-20070712-enable.patch | 22 + ...nc-plugin-evolution2-0.22-file-path-fudge.patch | 34 + opensync-plugin-0.4x/src/Makefile.am | 47 + opensync-plugin-0.4x/src/barry-sync | 47 + opensync-plugin-0.4x/src/barry_sync.cc | 771 +++++++ opensync-plugin-0.4x/src/barry_sync.h | 37 + opensync-plugin-0.4x/src/environment.cc | 303 +++ opensync-plugin-0.4x/src/environment.h | 122 ++ opensync-plugin-0.4x/src/idmap.cc | 166 ++ opensync-plugin-0.4x/src/idmap.h | 85 + opensync-plugin-0.4x/src/trace.h | 70 + opensync-plugin-0.4x/src/vbase.cc | 269 +++ opensync-plugin-0.4x/src/vbase.h | 198 ++ opensync-plugin-0.4x/src/vcard.cc | 590 ++++++ opensync-plugin-0.4x/src/vcard.h | 116 ++ opensync-plugin-0.4x/src/vevent.cc | 581 ++++++ opensync-plugin-0.4x/src/vevent.h | 115 ++ opensync-plugin-0.4x/src/vformat.c | 2125 ++++++++++++++++++++ opensync-plugin-0.4x/src/vformat.h | 167 ++ 31 files changed, 6564 insertions(+) create mode 100644 opensync-plugin-0.4x/.cvsignore create mode 100644 opensync-plugin-0.4x/AUTHORS create mode 100644 opensync-plugin-0.4x/COPYING create mode 100644 opensync-plugin-0.4x/ChangeLog create mode 100644 opensync-plugin-0.4x/Makefile.am create mode 100644 opensync-plugin-0.4x/NEWS create mode 100644 opensync-plugin-0.4x/README create mode 100755 opensync-plugin-0.4x/buildgen.sh create mode 100644 opensync-plugin-0.4x/configure.ac create mode 100644 opensync-plugin-0.4x/m4/ax_c_check_flag.m4 create mode 100644 opensync-plugin-0.4x/m4/ax_cxx_check_flag.m4 create mode 100644 opensync-plugin-0.4x/patches/README create mode 100644 opensync-plugin-0.4x/patches/kitchensync-svn-20070712-enable.patch create mode 100644 opensync-plugin-0.4x/patches/libopensync-plugin-evolution2-0.22-file-path-fudge.patch create mode 100644 opensync-plugin-0.4x/src/Makefile.am create mode 100644 opensync-plugin-0.4x/src/barry-sync create mode 100644 opensync-plugin-0.4x/src/barry_sync.cc create mode 100644 opensync-plugin-0.4x/src/barry_sync.h create mode 100644 opensync-plugin-0.4x/src/environment.cc create mode 100644 opensync-plugin-0.4x/src/environment.h create mode 100644 opensync-plugin-0.4x/src/idmap.cc create mode 100644 opensync-plugin-0.4x/src/idmap.h create mode 100644 opensync-plugin-0.4x/src/trace.h create mode 100644 opensync-plugin-0.4x/src/vbase.cc create mode 100644 opensync-plugin-0.4x/src/vbase.h create mode 100644 opensync-plugin-0.4x/src/vcard.cc create mode 100644 opensync-plugin-0.4x/src/vcard.h create mode 100644 opensync-plugin-0.4x/src/vevent.cc create mode 100644 opensync-plugin-0.4x/src/vevent.h create mode 100644 opensync-plugin-0.4x/src/vformat.c create mode 100644 opensync-plugin-0.4x/src/vformat.h diff --git a/opensync-plugin-0.4x/.cvsignore b/opensync-plugin-0.4x/.cvsignore new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/opensync-plugin-0.4x/.cvsignore @@ -0,0 +1 @@ + diff --git a/opensync-plugin-0.4x/AUTHORS b/opensync-plugin-0.4x/AUTHORS new file mode 100644 index 00000000..2f8176a7 --- /dev/null +++ b/opensync-plugin-0.4x/AUTHORS @@ -0,0 +1,9 @@ +Porting by Nicolas VIVIEN + based on the example plugin skeleton from opensync 0.4x + and on the Chris Frey's opensync plugin + +Written by Chris Frey + based on the example plugin skeleton from opensync + +See ../AUTHORS for project-wide list. + diff --git a/opensync-plugin-0.4x/COPYING b/opensync-plugin-0.4x/COPYING new file mode 100644 index 00000000..3912109b --- /dev/null +++ b/opensync-plugin-0.4x/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU 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 + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/opensync-plugin-0.4x/ChangeLog b/opensync-plugin-0.4x/ChangeLog new file mode 100644 index 00000000..f1d0e812 --- /dev/null +++ b/opensync-plugin-0.4x/ChangeLog @@ -0,0 +1,2 @@ + - see ../ChangeLog for details on progress + diff --git a/opensync-plugin-0.4x/Makefile.am b/opensync-plugin-0.4x/Makefile.am new file mode 100644 index 00000000..3fd39678 --- /dev/null +++ b/opensync-plugin-0.4x/Makefile.am @@ -0,0 +1,11 @@ +ACLOCAL_FLAGS = -I m4 + +SUBDIRS = src + +EXTRA_DIST = \ + buildgen.sh \ + patches + +dist-hook: + rm -rf `find $(distdir) -name CVS` + diff --git a/opensync-plugin-0.4x/NEWS b/opensync-plugin-0.4x/NEWS new file mode 100644 index 00000000..e69de29b diff --git a/opensync-plugin-0.4x/README b/opensync-plugin-0.4x/README new file mode 100644 index 00000000..edbe9a8c --- /dev/null +++ b/opensync-plugin-0.4x/README @@ -0,0 +1,85 @@ + +***************************************************************************** +* CAUTION: This is alpha software. Please make complete backups of your * +* Blackberry device before experimenting. While the author has * +* had good success with this software, that is no guarantee that * +* you will. Please make backups. * +***************************************************************************** + +Development note: this plugin is in flux, not only from pure development, + but also because the OpenSync libraries themselves are getting + close to releasing version 0.4x, which changes some internals + from 0.22. This plugin is currently based on 0.4x, but that may + change in the future. + +Introduction: +------------- +This directory contains a plugin for the OpenSync framework, which can +be used to synchronize between handhelds and desktop management software, +such as Evolution and Mozilla Sunbird. + +Please note that this plugin has the potential to not only change the +data in your Blackberry handheld, but also, through sending sync ADD, +MODIFY and DELETE changes, can change the data in your address book +and calendar in your desktop software of choice. As this plugin is +in development, please exercise caution, and make backups of your +Blackberry (see the ../gui directory) *and* backups of your desktop +software's data. + + +You can find more information on the OpenSync framework, as well +as tarballs for the version 0.4x library and plugins, at the following +website: + + http://www.opensync.org/ + + + +Library Dependencies: +--------------------- + +You will need the following packages installed on your system, including +any dev or devel packages that come along with them. + + pkg-config Used by the configure scripts + libopensync-0.4x OpenSync framework library + sqlite Required by OpenSync + glib2 Required by OpenSync + libxml2 Required by OpenSync + libbarry 0.x In the same tarball as this code. + libusb Required by libbarry + +In addition to libopensync, you will need a plugin for your desktop +software of choice, or for other handheld devices, such as the Palm. +Please see the OpenSync website for a list of available plugins. + +If you've installed Barry or OpenSync in a non-standard spot, use something +like the following: + + export PKG_CONFIG_PATH="/home/cdfrey/Barry/lib/pkgconfig:/home/cdfrey/software/lib/pkgconfig" + ./configure --prefix=/home/cdfrey/Barry + + + +Installation: +------------- + + cd opensync-plugin + ./configure + + (pkg-config will be used to find required libraries... + make sure they are installed in standard locations, + or that PKG_CONFIG_PATH is set appropriately) + + make + su + make install + + +Please report any bugs you find to the Barry mailing list. + +Thanks! + +Last modified : January 2009 +Written : August 2007 + diff --git a/opensync-plugin-0.4x/buildgen.sh b/opensync-plugin-0.4x/buildgen.sh new file mode 100755 index 00000000..069e24b5 --- /dev/null +++ b/opensync-plugin-0.4x/buildgen.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +# +# Generates the build system. +# + +if [ "$1" = "clean" ] ; then + rm -rf autom4te.cache + rm -f Makefile.in aclocal.m4 config.guess config.h.in config.h.in~ \ + config.sub \ + configure depcomp install-sh ltmain.sh missing \ + src/Makefile.in INSTALL \ + config.h.in~ +else + autoreconf -if + #autoreconf -ifv +fi + diff --git a/opensync-plugin-0.4x/configure.ac b/opensync-plugin-0.4x/configure.ac new file mode 100644 index 00000000..1ff435db --- /dev/null +++ b/opensync-plugin-0.4x/configure.ac @@ -0,0 +1,52 @@ +dnl Process this file with autoconf to produce a configure script. + +AC_PREREQ(2.59) +AC_INIT([Barry OpenSync Plugin], 0.2, [], barryplugin) +AM_INIT_AUTOMAKE(foreign) +AC_CONFIG_SRCDIR(src/barry_sync.cc) +AC_CONFIG_HEADER(config.h) + +pkg_modules="libopensync glib-2.0" +PKG_CHECK_MODULES(PACKAGE, [$pkg_modules]) +AC_SUBST(PACKAGE_CFLAGS) +AC_SUBST(PACKAGE_LIBS) + +AC_ISC_POSIX +AC_PROG_CC +AM_PROG_CC_STDC +AC_HEADER_STDC +AC_PROG_CXX +AC_DISABLE_STATIC +AC_PROG_LIBTOOL + +AX_C_CHECK_FLAG([-fvisibility=hidden], [], [], + [HAVE_C_GCCVISIBILITY=1], + [HAVE_C_GCCVISIBILITY=0]) +AX_CXX_CHECK_FLAG([-fvisibility=hidden], [], [], + [HAVE_CXX_GCCVISIBILITY=1], + [HAVE_CXX_GCCVISIBILITY=0]) +AM_CONDITIONAL([WITH_GCCVISIBILITY], [test "$HAVE_C_GCCVISIBILITY" = "1" -a "$HAVE_CXX_GCCVISIBILITY" = "1"]) + +AC_LANG([C++]) + +PKG_CHECK_MODULES([BARRY], [libbarry-0 libusb]) + +# Carry the special tree build environment variables from parent configure, +# just in case user is doing a complete tree build with --enable-opensync-plugin +AC_SUBST(TREE_BUILD_CXXFLAGS) +AC_SUBST(TREE_BUILD_LDFLAGS) + +OPENSYNC_CONFIGDIR=$(pkg-config --variable=configdir opensync-1.0) +OPENSYNC_PLUGINDIR=$(pkg-config --variable=plugindir opensync-1.0) +OPENSYNC_FORMATSDIR=$(pkg-config --variable=formatsdir opensync-1.0) +OPENSYNC_HEADERDIR=$(pkg-config --variable=headerdir opensync-1.0) + +AC_SUBST(OPENSYNC_CONFIGDIR) ## This is the place where you can install the default configuration files of your plugin +AC_SUBST(OPENSYNC_PLUGINDIR) ## This is the dir where you plugin will be installed +AC_SUBST(OPENSYNC_FORMATSDIR) ## Here are format plugins installed (if any) +AC_SUBST(OPENSYNC_HEADERDIR) ## Here are the headers that a user interface may need (if any) + +AC_OUTPUT([ +Makefile +src/Makefile +]) diff --git a/opensync-plugin-0.4x/m4/ax_c_check_flag.m4 b/opensync-plugin-0.4x/m4/ax_c_check_flag.m4 new file mode 100644 index 00000000..479bd165 --- /dev/null +++ b/opensync-plugin-0.4x/m4/ax_c_check_flag.m4 @@ -0,0 +1,89 @@ +##### http://autoconf-archive.cryp.to/ax_c_check_flag.html +# +# SYNOPSIS +# +# AX_C_CHECK_FLAG(FLAG-TO-CHECK,[PROLOGUE],[BODY],[ACTION-IF-SUCCESS],[ACTION-IF-FAILURE]) +# +# DESCRIPTION +# +# This macro tests if the C compiler supports the flag FLAG-TO-CHECK. +# If successfull execute ACTION-IF-SUCCESS otherwise +# ACTION-IF-FAILURE. PROLOGUE and BODY are optional and should be +# used as in AC_LANG_PROGRAM macro. +# +# This code is inspired from KDE_CHECK_COMPILER_FLAG macro. Thanks to +# Bogdan Drozdowski for testing and bug fixes. +# +# LAST MODIFICATION +# +# 2007-11-26 +# +# COPYLEFT +# +# Copyright (c) 2007 Francesco Salvestrini +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# As a special exception, the respective Autoconf Macro's copyright +# owner gives unlimited permission to copy, distribute and modify the +# configure scripts that are the output of Autoconf when processing +# the Macro. 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 the Macro appear in them. The GNU General +# Public License (GPL) does govern all other use of the material that +# constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the +# Autoconf Macro released by the Autoconf Macro Archive. When you +# make and distribute a modified version of the Autoconf Macro, you +# may extend this special exception to the GPL to apply to your +# modified version as well. + +AC_DEFUN([AX_C_CHECK_FLAG],[ + AC_PREREQ([2.61]) + AC_REQUIRE([AC_PROG_CC]) + AC_REQUIRE([AC_PROG_SED]) + + flag=`echo "$1" | $SED 'y% .=/+-(){}<>:*,%_______________%'` + + AC_CACHE_CHECK([whether the C compiler accepts the $1 flag], + [ax_cv_c_check_flag_$flag],[ + + AC_LANG_PUSH([C]) + + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $1" + AC_COMPILE_IFELSE([ + AC_LANG_PROGRAM([$2],[$3]) + ],[ + eval "ax_cv_c_check_flag_$flag=yes" + ],[ + eval "ax_cv_c_check_flag_$flag=no" + ]) + + CFLAGS="$save_CFLAGS" + + AC_LANG_POP + + ]) + + AS_IF([eval "test \"`echo '$ax_cv_c_check_flag_'$flag`\" = yes"],[ + : + $4 + ],[ + : + $5 + ]) +]) diff --git a/opensync-plugin-0.4x/m4/ax_cxx_check_flag.m4 b/opensync-plugin-0.4x/m4/ax_cxx_check_flag.m4 new file mode 100644 index 00000000..6e3f442b --- /dev/null +++ b/opensync-plugin-0.4x/m4/ax_cxx_check_flag.m4 @@ -0,0 +1,89 @@ +##### http://autoconf-archive.cryp.to/ax_cxx_check_flag.html +# +# SYNOPSIS +# +# AX_CXX_CHECK_FLAG(FLAG-TO-CHECK,[PROLOGUE],[BODY],[ACTION-IF-SUCCESS],[ACTION-IF-FAILURE]) +# +# DESCRIPTION +# +# This macro tests if the C++ compiler supports the flag +# FLAG-TO-CHECK. If successfull execute ACTION-IF-SUCCESS otherwise +# ACTION-IF-FAILURE. PROLOGUE and BODY are optional and should be +# used as in AC_LANG_PROGRAM macro. +# +# This code is inspired from KDE_CHECK_COMPILER_FLAG macro. Thanks to +# Bogdan Drozdowski for testing and bug fixes. +# +# LAST MODIFICATION +# +# 2007-11-26 +# +# COPYLEFT +# +# Copyright (c) 2007 Francesco Salvestrini +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# As a special exception, the respective Autoconf Macro's copyright +# owner gives unlimited permission to copy, distribute and modify the +# configure scripts that are the output of Autoconf when processing +# the Macro. 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 the Macro appear in them. The GNU General +# Public License (GPL) does govern all other use of the material that +# constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the +# Autoconf Macro released by the Autoconf Macro Archive. When you +# make and distribute a modified version of the Autoconf Macro, you +# may extend this special exception to the GPL to apply to your +# modified version as well. + +AC_DEFUN([AX_CXX_CHECK_FLAG],[ + AC_PREREQ([2.61]) + AC_REQUIRE([AC_PROG_CXX]) + AC_REQUIRE([AC_PROG_SED]) + + flag=`echo "$1" | $SED 'y% .=/+-(){}<>:*,%_______________%'` + + AC_CACHE_CHECK([whether the C++ compiler accepts the $1 flag], + [ax_cv_cxx_check_flag_$flag],[ + + AC_LANG_PUSH([C++]) + + save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS $1" + AC_COMPILE_IFELSE([ + AC_LANG_PROGRAM([$2],[$3]) + ],[ + eval "ax_cv_cxx_check_flag_$flag=yes" + ],[ + eval "ax_cv_cxx_check_flag_$flag=no" + ]) + + CXXFLAGS="$save_CXXFLAGS" + + AC_LANG_POP + + ]) + + AS_IF([eval "test \"`echo '$ax_cv_cxx_check_flag_'$flag`\" = yes"],[ + : + $4 + ],[ + : + $5 + ]) +]) diff --git a/opensync-plugin-0.4x/patches/README b/opensync-plugin-0.4x/patches/README new file mode 100644 index 00000000..fc2f5015 --- /dev/null +++ b/opensync-plugin-0.4x/patches/README @@ -0,0 +1,3 @@ +This directory contains various patches used during development +to make the installation and syncing process smoother. + diff --git a/opensync-plugin-0.4x/patches/kitchensync-svn-20070712-enable.patch b/opensync-plugin-0.4x/patches/kitchensync-svn-20070712-enable.patch new file mode 100644 index 00000000..8bcf3479 --- /dev/null +++ b/opensync-plugin-0.4x/patches/kitchensync-svn-20070712-enable.patch @@ -0,0 +1,22 @@ +Chris Frey + +This patch enables objtypes, which is useful when running the file-sync +plugin from the KitchenSync GUI. Without this patch, syncing +against a file-sync for testing tends to end up with an empty +file-sync directory. + +This patch applies to the 2007/07/12 snapshot of the KitchenSync SVN tree. + + +Index: kitchensync/src/syncprocess.cpp +=================================================================== +--- kitchensync/src/syncprocess.cpp (revision 687137) ++++ kitchensync/src/syncprocess.cpp (working copy) +@@ -98,6 +98,7 @@ + * + * mGroup.setObjectTypeEnabled( objectTypes[ i ], true ); + */ ++ mGroup.setObjectTypeEnabled( objectTypes[ i ], true ); + } else { + kdDebug() << "Disabled object type: " << objectTypes[ i ] << endl; + mGroup.setObjectTypeEnabled( objectTypes[ i ], false ); diff --git a/opensync-plugin-0.4x/patches/libopensync-plugin-evolution2-0.22-file-path-fudge.patch b/opensync-plugin-0.4x/patches/libopensync-plugin-evolution2-0.22-file-path-fudge.patch new file mode 100644 index 00000000..d2350c95 --- /dev/null +++ b/opensync-plugin-0.4x/patches/libopensync-plugin-evolution2-0.22-file-path-fudge.patch @@ -0,0 +1,34 @@ +Chris Frey + +This patch checks for the presense of a forward slash at the beginning of +each path. If found, it forces the prefix to "file://". + +This is helpful when running the evolution2 plugin with the KitchenSync +GUI (snapshot 2007/07/12), since the GUI seems to strip off the file:// +prefix no matter what you type into the fields, and the plugin needs +the prefix to operate properly. + +An example of a path: + file:///home/cdfrey/.evolution/addressbook/local/system + + +diff -ru libopensync-plugin-evolution2-0.22/src/evolution2_xml.c libopensync-plugin-evolution2-0.22-cdf/src/evolution2_xml.c +--- libopensync-plugin-evolution2-0.22/src/evolution2_xml.c 2007-03-27 07:49:55.000000000 -0400 ++++ libopensync-plugin-evolution2-0.22-cdf/src/evolution2_xml.c 2007-07-27 22:47:43.000000000 -0400 +@@ -59,13 +59,13 @@ + char *str = (char*)xmlNodeGetContent(cur); + if (str) { + if (!xmlStrcmp(cur->name, (const xmlChar *)"address_path")) { +- env->addressbook_path = g_strdup(str); ++ env->addressbook_path = g_strdup_printf("%s%s", (str[0] == '/') ? "file://" : "", str); + } + if (!xmlStrcmp(cur->name, (const xmlChar *)"calendar_path")) { +- env->calendar_path = g_strdup(str); ++ env->calendar_path = g_strdup_printf("%s%s", (str[0] == '/') ? "file://" : "", str); + } + if (!xmlStrcmp(cur->name, (const xmlChar *)"tasks_path")) { +- env->tasks_path = g_strdup(str); ++ env->tasks_path = g_strdup_printf("%s%s", (str[0] == '/') ? "file://" : "", str); + } + xmlFree(str); + } diff --git a/opensync-plugin-0.4x/src/Makefile.am b/opensync-plugin-0.4x/src/Makefile.am new file mode 100644 index 00000000..779b1d04 --- /dev/null +++ b/opensync-plugin-0.4x/src/Makefile.am @@ -0,0 +1,47 @@ +barry_syncdir=@OPENSYNC_PLUGINDIR@ +configdir=@OPENSYNC_CONFIGDIR@ +#formatsdir=@OPENSYNC_FORMATSDIR@ +opensyncheaderdir=@OPENSYNC_HEADERDIR@ + +INCLUDES = @TREE_BUILD_CXXFLAGS@ @PACKAGE_CFLAGS@ @BARRY_CFLAGS@ +LIBS = @TREE_BUILD_LDFLAGS@ + +AM_CFLAGS = -Wall -Werror +AM_CXXFLAGS = -Wall -Werror +AM_LDFLAGS = +if WITH_GCCVISIBILITY +AM_CFLAGS += -D__BARRY_HAVE_GCCVISIBILITY__ -fvisibility=hidden +#AM_CXXFLAGS += -D__BARRY_HAVE_GCCVISIBILITY__ -fvisibility=hidden -fvisibility-inlines-hidden +AM_CXXFLAGS += -D__BARRY_HAVE_GCCVISIBILITY__ -fvisibility=hidden +#AM_LDFLAGS += -fvisibility=hidden -fvisibility-inlines-hidden +AM_LDFLAGS += -fvisibility=hidden +endif + +EXTRA_DIST = barry_sync.h + +dist_config_DATA = barry-sync + +opensyncheader_HEADER = barry_sync.h + +barry_sync_LTLIBRARIES = barry_sync.la + +barry_sync_la_SOURCES = \ + barry_sync.cc barry_sync.h \ + environment.cc environment.h \ + vbase.cc vbase.h \ + vevent.cc vevent.h \ + vcard.cc vcard.h \ + idmap.cc idmap.h \ + vformat.c vformat.h \ + trace.h + +barry_sync_la_LDFLAGS = -avoid-version -export-dynamic -module +barry_sync_la_LIBADD = @PACKAGE_LIBS@ @LIBS@ @BARRY_LIBS@ + +## if you plan to install format libraries: ## +#formats_LTLIBRARIES = format.la +# +#format_la_SOURCES = format.c +#format_la_LDFLAGS = -avoid-version -export-dynamic -module +#format_la_LIBADD = @PACKAGE_LIBS@ @LIBS@ + diff --git a/opensync-plugin-0.4x/src/barry-sync b/opensync-plugin-0.4x/src/barry-sync new file mode 100644 index 00000000..3d27da01 --- /dev/null +++ b/opensync-plugin-0.4x/src/barry-sync @@ -0,0 +1,47 @@ + + + + + PIN number + 2147483647 + 2147483647 + PinCode + string + + + + Debug mode + 2147483647 + 2147483647 + Debug + bool + 1 + + + + + + + + 1 + + + vcard30 + + + Contacts + contact + + + 1 + + + vevent20 + + + Event + event + + + + diff --git a/opensync-plugin-0.4x/src/barry_sync.cc b/opensync-plugin-0.4x/src/barry_sync.cc new file mode 100644 index 00000000..24d9ecef --- /dev/null +++ b/opensync-plugin-0.4x/src/barry_sync.cc @@ -0,0 +1,771 @@ +// +// \file barry_sync.cc +// Opensync module for the USB Blackberry handheld +// + +/* + Copyright (C) 2006-2009, Net Direct Inc. (http://www.netdirect.ca/) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License in the COPYING file at the + root directory of this project for more details. +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "barry_sync.h" +#include "environment.h" +#include "vevent.h" +#include "vcard.h" +#include "trace.h" +#include +#include +#include +#include + +// All functions that are callable from outside must look like C +extern "C" { + static void *initialize(OSyncPlugin *plugin, OSyncPluginInfo *info, OSyncError **error); + static osync_bool discover(void *userdata, OSyncPluginInfo *info, OSyncError **error); + static void connect(void *userdata, OSyncPluginInfo *info, OSyncContext *ctx); + static void get_changes(void *userdata, OSyncPluginInfo *info, OSyncContext *ctx); + static void commit_change(void *userdata, OSyncPluginInfo *info, OSyncContext *ctx, OSyncChange *change); + static void sync_done(void *userdata, OSyncPluginInfo *info, OSyncContext *ctx); + static void disconnect(void *userdata, OSyncPluginInfo *info, OSyncContext *ctx); + static void finalize(void *userdata); + BXEXPORT osync_bool get_sync_info(OSyncPluginEnv *env, OSyncError **error); +} + + +////////////////////////////////////////////////////////////////////////////// +// +// Support functions and classes +// + + +void GetChanges(OSyncPluginInfo *info, OSyncContext *ctx, BarryEnvironment *env, + DatabaseSyncState *pSync, + const char *DBDBName, + const char *ObjTypeName, const char *FormatName, + GetData_t getdata) +{ + Trace trace("GetChanges"); + + OSyncError *error = NULL; + + // shortcut references + using namespace Barry; + using Barry::RecordStateTable; + Mode::Desktop &desktop = *env->m_pDesktop; + + // find the matching cache, state table, and id map for this change + DatabaseSyncState::cache_type &cache = pSync->m_Cache; + idmap &map = pSync->m_IdMap; + + // check if slow sync has been requested, and if so, empty the + // cache and id map and start fresh + if (osync_objtype_sink_get_slowsync(pSync->sink)) { + trace.log("GetChanges: slow sync request detected, clearing cache and id map"); + cache.clear(); + map.clear(); + } + + // fetch state table + unsigned int dbId = desktop.GetDBID(DBDBName); + RecordStateTable &table = pSync->m_Table; + desktop.GetRecordStateTable(dbId, table); + + OSyncFormatEnv *formatenv = osync_plugin_info_get_format_env(info); + +// OSyncObjTypeSink *sink = osync_plugin_info_get_sink(info); + + + // cycle through the state table... + // - if not in cache, it is added. + // - if in cache, check Blackberry's dirty flag + RecordStateTable::StateMapType::const_iterator i = table.StateMap.begin(); + for( ; i != table.StateMap.end(); ++i ) { + + OSyncChange *change = 0; + const RecordStateTable::IndexType &index = i->first; + const RecordStateTable::State &state = i->second; + + // search the idmap for the UID + std::string uid = pSync->Map2Uid(state.RecordId); + + // search the cache + DatabaseSyncState::cache_type::const_iterator c = cache.find(state.RecordId); + if( c == cache.end() ) { + // not in cache, this is a new item + trace.log("found an ADDED change"); + change = osync_change_new(&error); + osync_change_set_changetype(change, OSYNC_CHANGE_TYPE_ADDED); + } + else { + // in the cache... dirty? + if( state.Dirty ) { + // modified + trace.log("found a MODIFIED change"); + change = osync_change_new(&error); + osync_change_set_changetype(change, OSYNC_CHANGE_TYPE_MODIFIED); + } + else { + trace.log("no change detected"); + } + } + + // finish filling out the change object + if( change ) { +// osync_change_set_member(change, env->member); +// osync_change_set_objformat_string(change, FormatName); + + osync_change_set_uid(change, uid.c_str()); + trace.logf("change record ID: %s", uid.c_str()); + + OSyncObjFormat *format = osync_format_env_find_objformat(formatenv, FormatName); + + // Now you can set the data for the object + // Set the last argument to FALSE if the real data + // should be queried later in a "get_data" function + char *data = (*getdata)(env, dbId, index); + OSyncData *odata = osync_data_new(data, strlen(data), format, &error); + +// osync_data_set_objtype(odata, osync_objtype_sink_get_name(sink)); + + osync_change_set_data(change, odata); + osync_data_unref(odata); + + // just report the change via + osync_context_report_change(ctx, change); + + osync_change_unref(change); + + // map our IDs for later + map.Map(uid, state.RecordId); + } + } + + // now cycle through the cache... any objects in the cache + // but not found in the state table means that they have been + // deleted in the device + DatabaseSyncState::cache_type::const_iterator c = cache.begin(); + for( ; c != cache.end(); ++c ) { + uint32_t recordId = c->first; + + // search the idmap for the UID + std::string uid = pSync->Map2Uid(recordId); + + // search the state table + i = table.StateMap.begin(); + for( ; i != table.StateMap.end(); ++i ) { + + if( i->second.RecordId == recordId ) + break; // found + } + + // check if not found... + if( i == table.StateMap.end() ) { + // register a DELETE, no data + trace.log("found DELETE change"); + + OSyncChange *change = osync_change_new(&error); + + osync_change_set_changetype(change, OSYNC_CHANGE_TYPE_DELETED); + +// osync_change_set_member(change, env->member); +// osync_change_set_objformat_string(change, FormatName); + + osync_change_set_uid(change, uid.c_str()); + trace.log(uid.c_str()); + + OSyncObjFormat *format = osync_format_env_find_objformat(formatenv, FormatName); + + OSyncData *odata = osync_data_new(NULL, 0, format, &error); + +// osync_data_set_objtype(odata, osync_objtype_sink_get_name(sink)); + + osync_change_set_data(change, odata); + osync_data_unref(odata); + + // report the change + osync_context_report_change(ctx, change); + + osync_change_unref(change); + } + } + + // finally, cycle through the state map again, and overwrite the + // cache with the current state table. Memory only... if successful, + // it will be written back to disk later on. + + // start fresh + cache.clear(); + + for( i = table.StateMap.begin(); i != table.StateMap.end(); ++i ) { + const RecordStateTable::State &state = i->second; + cache[state.RecordId] = false; + } +} + +CommitData_t GetCommitFunction(OSyncChange *change) +{ + const char *name = osync_change_get_objtype(change); + + if( strcmp(name, "event") == 0 ) { + return &VEventConverter::CommitRecordData; + } + else if( strcmp(name, "contact") == 0 ) { + return &VCardConverter::CommitRecordData; + } + else { + return 0; + } +} + +bool FinishSync(OSyncContext *ctx, BarryEnvironment *env, DatabaseSyncState *pSync) +{ + Trace trace("FinishSync()"); + + if( !pSync->m_Sync ) { + // this mode is disabled in config, skip + return true; + } + + Barry::Mode::Desktop &desktop = *env->m_pDesktop; + + // get the state table again, so we can update + // the cache properly + desktop.GetRecordStateTable(pSync->m_dbId, pSync->m_Table); + + // update the cache + if( !pSync->SaveCache() ) { + osync_context_report_error(ctx, OSYNC_ERROR_IO_ERROR, + "Error saving calendar cache"); + return false; + } + + // save the id map + pSync->CleanupMap(); + if( !pSync->SaveMap() ) { + osync_context_report_error(ctx, OSYNC_ERROR_IO_ERROR, + "Error saving calendar id map"); + return false; + } + + // clear all dirty flags in device + env->ClearDirtyFlags(pSync->m_Table, pSync->m_dbName); + return true; +} + + +static bool barry_contact_initialize(DatabaseSyncState *env, OSyncPluginInfo *info, OSyncError **error) +{ + OSyncObjTypeSink *sink = osync_plugin_info_find_objtype(info, "contact"); + if (!sink) + return false; + osync_bool sinkEnabled = osync_objtype_sink_is_enabled(sink); + if (!sinkEnabled) + return false; + + OSyncObjTypeSinkFunctions functions; + memset(&functions, 0, sizeof(functions)); + + functions.connect = connect; + functions.disconnect = disconnect; + functions.get_changes = get_changes; + functions.commit = commit_change; + functions.sync_done = sync_done; + + OSyncPluginConfig *config = osync_plugin_info_get_config(info); + OSyncPluginResource *resource = osync_plugin_config_find_active_resource(config, "contact"); + + OSyncList *objformatsinks = osync_plugin_resource_get_objformat_sinks(resource); + + bool hasObjFormat = false; + + OSyncList *r; + for(r = objformatsinks;r;r = r->next) { + OSyncObjFormatSink *objformatsink = (OSyncObjFormatSink *) r->data; + + if(!strcmp("vcard30", osync_objformat_sink_get_objformat(objformatsink))) { + hasObjFormat = true; + break; + } + } + + if (!hasObjFormat) { + return false; + } + +// OSyncFormatEnv *formatenv = osync_plugin_info_get_format_env(info); + +// env->format = osync_format_env_find_objformat(formatenv, "vcard30"); + env->sink = sink; + + osync_objtype_sink_set_functions(sink, functions, NULL); + + return true; +} + + +static bool barry_calendar_initialize(DatabaseSyncState *env, OSyncPluginInfo *info, OSyncError **error) +{ + OSyncObjTypeSink *sink = osync_plugin_info_find_objtype(info, "event"); + if (!sink) + return false; + osync_bool sinkEnabled = osync_objtype_sink_is_enabled(sink); + if (!sinkEnabled) + return false; + + OSyncObjTypeSinkFunctions functions; + memset(&functions, 0, sizeof(functions)); + + functions.connect = connect; + functions.disconnect = disconnect; + functions.get_changes = get_changes; + functions.commit = commit_change; + functions.sync_done = sync_done; + + OSyncPluginConfig *config = osync_plugin_info_get_config(info); + OSyncPluginResource *resource = osync_plugin_config_find_active_resource(config, "event"); + + OSyncList *objformatsinks = osync_plugin_resource_get_objformat_sinks(resource); + + bool hasObjFormat = false; + + OSyncList *r; + for(r = objformatsinks;r;r = r->next) { + OSyncObjFormatSink *objformatsink = (OSyncObjFormatSink *) r->data; + + if(!strcmp("vevent20", osync_objformat_sink_get_objformat(objformatsink))) { + hasObjFormat = true; + break; + } + } + + if (!hasObjFormat) { + return false; + } + +// OSyncFormatEnv *formatenv = osync_plugin_info_get_format_env(info); + +// env->format = osync_format_env_find_objformat(formatenv, "vevent20"); + env->sink = sink; + + osync_objtype_sink_set_functions(sink, functions, NULL); + + return true; +} + + + + +////////////////////////////////////////////////////////////////////////////// +// +// OpenSync API +// + +static void *initialize(OSyncPlugin *plugin, OSyncPluginInfo *info, OSyncError **error) +{ + Trace trace("initialize"); + + BarryEnvironment *env = 0; + + try { + env = new BarryEnvironment(info); + + /* + * Read plugin config + */ + OSyncPluginConfig *config = osync_plugin_info_get_config(info); + + if (!config) { + osync_error_set(error, OSYNC_ERROR_GENERIC, "Unable to get config."); + + delete env; + + return NULL; + } + + /* + * Process plugin specific advanced options + */ + OSyncList *optslist = osync_plugin_config_get_advancedoptions(config); + for (; optslist; optslist = optslist->next) { + OSyncPluginAdvancedOption *option = (OSyncPluginAdvancedOption *) optslist->data; + + const char *val = osync_plugin_advancedoption_get_value(option); + const char *name = osync_plugin_advancedoption_get_name(option); + + if (!strcmp(name, "PinCode")) { + env->m_pin = atoi(val); + } + else if (!strcmp(name, "Debug")) { + env->m_DebugMode = (!strcmp(val, "1")) ? true : false; + } + } + + OSyncPluginAuthentication *optauth = osync_plugin_config_get_authentication(config); + + if (osync_plugin_authentication_option_is_supported(optauth, OSYNC_PLUGIN_AUTHENTICATION_PASSWORD)) { + const char *val = osync_plugin_authentication_get_password(optauth); + + env->m_password = val; + } + + + // FIXME - near the end of release, do a run with + // this set to true, and look for USB protocol + // inefficiencies. + Barry::Init(env->m_DebugMode); + + + /* + * Process Ressource options + */ + if (!barry_calendar_initialize(&env->m_CalendarSync, info, error)) { + env->m_CalendarSync.LoadCache(); + env->m_CalendarSync.LoadMap(); + } + else + env->m_CalendarSync.m_Sync = false; + + if (!barry_contact_initialize(&env->m_ContactsSync, info, error)) { + env->m_ContactsSync.LoadCache(); + env->m_ContactsSync.LoadMap(); + } + else + env->m_ContactsSync.m_Sync = false; + + + return (void *) env; + } + catch( std::exception &e ) { + delete env; + + return NULL; + } +} + + +static osync_bool discover(void *userdata, OSyncPluginInfo *info, OSyncError **error) +{ + int i, numobjs = osync_plugin_info_num_objtypes(info); + + for (i = 0; i < numobjs; i++) { + OSyncObjTypeSink *sink = osync_plugin_info_nth_objtype(info, i); + + osync_objtype_sink_set_available(sink, true); + } + + OSyncVersion *version = osync_version_new(error); + osync_version_set_plugin(version, "Barry"); + osync_version_set_modelversion(version, "1"); + //osync_version_set_firmwareversion(version, "firmwareversion"); + //osync_version_set_softwareversion(version, "softwareversion"); + //osync_version_set_hardwareversion(version, "hardwareversion"); + osync_plugin_info_set_version(info, version); + osync_version_unref(version); + + return true; +} + + +static void connect(void *userdata, OSyncPluginInfo *info, OSyncContext *ctx) +{ + Trace trace("connect"); + + try { + + // Each time you get passed a context (which is used to track + // calls to your plugin) you can get the data your returned in + // initialize via this call: + BarryEnvironment *env = (BarryEnvironment *) userdata; + + // Probe for available devices + Barry::Probe probe; + int nIndex = probe.FindActive(env->m_pin); + if( nIndex == -1 ) { + osync_context_report_error(ctx, OSYNC_ERROR_NO_CONNECTION, "Unable to find PIN %lx", env->m_pin); + return; + } + env->m_ProbeResult = probe.Get(nIndex); + + env->Connect(probe.Get(nIndex)); + + // Success! + osync_context_report_success(ctx); + + } + // Don't let exceptions escape to the C modules + catch( std::bad_alloc &ba ) { + osync_context_report_error(ctx, OSYNC_ERROR_INITIALIZATION, + "Unable to allocate memory for controller: %s", ba.what()); + } + catch( std::exception &e ) { + osync_context_report_error(ctx, OSYNC_ERROR_INITIALIZATION, + "%s", e.what()); + } +} + + +static void get_changes(void *userdata, OSyncPluginInfo *info, OSyncContext *ctx) +{ + Trace trace("get_changeinfo"); + + try { + + BarryEnvironment *env = (BarryEnvironment *) userdata; + + if( env->m_CalendarSync.m_Sync ) { + GetChanges(info, ctx, env, &env->m_CalendarSync, + "Calendar", "event", "vevent20", + &VEventConverter::GetRecordData); + } + + if( env->m_ContactsSync.m_Sync ) { + GetChanges(info, ctx, env, &env->m_ContactsSync, + "Address Book", "contact", "vcard30", + &VCardConverter::GetRecordData); + } + + // Success! + osync_context_report_success(ctx); + } + // don't let exceptions escape to the C modules + catch( std::exception &e ) { + osync_context_report_error(ctx, OSYNC_ERROR_IO_ERROR, "%s", e.what()); + } +} + +static void commit_change(void *userdata, OSyncPluginInfo *info, OSyncContext *ctx, OSyncChange *change) +{ + Trace trace("commit_change"); + + // We can rely on a valid record state table, since get_changeinfo() + // will be called first, and will fill the table. + + try { + + BarryEnvironment *env = (BarryEnvironment *) userdata; + + // find the needed commit function, based on objtype of the change + CommitData_t CommitData = GetCommitFunction(change); + if( !CommitData ) { + osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, + "unable to get commit function pointer"); + return; + } + + // find the matching cache, state table, and id map for this change + DatabaseSyncState *pSync = env->GetSyncObject(change); + if( !pSync ) { + osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, + "unable to get sync object that matches change type"); + return; + } + + // is syncing turned on for this type? + if( !pSync->m_Sync ) { + osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, + "This object type is disabled in the barry-sync config"); + return; + } + + // make references instead of pointers + DatabaseSyncState::cache_type &cache = pSync->m_Cache; + Barry::RecordStateTable &table = pSync->m_Table; + idmap &map = pSync->m_IdMap; + Barry::Mode::Desktop &desktop = *env->m_pDesktop; + unsigned int dbId = pSync->m_dbId; + + + // extract RecordId from change's UID, + // and update the ID map if necessary + const char *uid = osync_change_get_uid(change); + trace.logf("uid from change: %s", uid); + if( strlen(uid) == 0 ) { + osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, + "uid from change object is blank!"); + } + unsigned long RecordId = pSync->GetMappedRecordId(uid); + + // search for the RecordId in the state table, to find the + // index... we only need the index if we are deleting or + // modifying + Barry::RecordStateTable::IndexType StateIndex; + if( osync_change_get_changetype(change) != OSYNC_CHANGE_TYPE_ADDED ) { + if( !table.GetIndex(RecordId, &StateIndex) ) { + osync_context_report_error(ctx, OSYNC_ERROR_GENERIC, + "unable to get state table index for RecordId: %lu", + RecordId); + return; + } + } + + std::string errmsg; + bool status; + + OSyncData *odata = NULL; + char *plain = NULL; + + switch( osync_change_get_changetype(change) ) + { + case OSYNC_CHANGE_TYPE_DELETED: + desktop.DeleteRecord(dbId, StateIndex); + cache.erase(RecordId); + map.UnmapUid(uid); + break; + + case OSYNC_CHANGE_TYPE_ADDED: + odata = osync_change_get_data(change); + osync_data_get_data(odata, &plain, NULL); + status = (*CommitData)(env, dbId, StateIndex, RecordId, + plain, true, errmsg); + if( !status ) { + trace.logf("CommitData() for ADDED state returned false: %s", errmsg.c_str()); + osync_context_report_error(ctx, OSYNC_ERROR_PARAMETER, "%s", errmsg.c_str()); + map.UnmapUid(uid); + return; + } + cache[RecordId] = false; + break; + + case OSYNC_CHANGE_TYPE_MODIFIED: + odata = osync_change_get_data(change); + osync_data_get_data(odata, &plain, NULL); + status = (*CommitData)(env, dbId, StateIndex, RecordId, + plain, false, errmsg); + if( !status ) { + trace.logf("CommitData() for MODIFIED state returned false: %s", errmsg.c_str()); + osync_context_report_error(ctx, OSYNC_ERROR_PARAMETER, "%s", errmsg.c_str()); + map.UnmapUid(uid); + return; + } + break; + + default: + trace.log("Unknown change type"); + break; + } + + // Answer the call + osync_context_report_success(ctx); + return; + + + } + catch( std::exception &e ) { + osync_context_report_error(ctx, OSYNC_ERROR_IO_ERROR, "%s", e.what()); + + // we don't worry about unmapping ids here, as there + // is still a possibility that the record was added... + // plus, the map might not get written out to disk anyway + // in a plugin error state + + return; + } +} + +static void sync_done(void *userdata, OSyncPluginInfo *info, OSyncContext *ctx) +{ + // + // This function will only be called if the sync was successfull + // + + Trace trace("sync_done"); + + try { + + BarryEnvironment *env = (BarryEnvironment *) userdata; + + // we reconnect to the device here, since dirty flags + // for records we've just touched do not show up until + // a disconnect... as far as I can tell. + env->Reconnect(); + + // do cleanup for each database + if( FinishSync(ctx, env, &env->m_CalendarSync) && + FinishSync(ctx, env, &env->m_ContactsSync) ) + { + // Success + osync_context_report_success(ctx); + } + + } + catch( std::exception &e ) { + osync_context_report_error(ctx, OSYNC_ERROR_IO_ERROR, "%s", e.what()); + } +} + +static void disconnect(void *userdata, OSyncPluginInfo *info, OSyncContext *ctx) +{ + Trace trace("disconnect"); + + // Disconnect the controller, which closes our connection + BarryEnvironment *env = (BarryEnvironment *) userdata; + env->Disconnect(); + + // Done! + osync_context_report_success(ctx); +} + +static void finalize(void *userdata) +{ + Trace trace("finalize"); + + BarryEnvironment *env = (BarryEnvironment *) userdata; + + delete env; +} + + +osync_bool get_sync_info(OSyncPluginEnv *env, OSyncError **error) +{ + // Create a new OpenSync plugin + OSyncPlugin *plugin = osync_plugin_new(error); + + if (!plugin) { + osync_error_unref(error); + + return false; + } + + // Describe our plugin + osync_plugin_set_name(plugin, "barry-sync"); + osync_plugin_set_longname(plugin, "Barry OpenSync plugin v0.15 for the Blackberry handheld"); + osync_plugin_set_description(plugin, "Plugin to synchronize calendar and contact entries on USB Blackberry handhelds"); + + // Set the callback functions + osync_plugin_set_initialize(plugin, initialize); + osync_plugin_set_finalize(plugin, finalize); + osync_plugin_set_discover(plugin, discover); + + osync_plugin_env_register_plugin(env, plugin); + osync_plugin_unref(plugin); + + return true; +} + + +int get_version(void) +{ + return 1; +} + diff --git a/opensync-plugin-0.4x/src/barry_sync.h b/opensync-plugin-0.4x/src/barry_sync.h new file mode 100644 index 00000000..80c0a5dd --- /dev/null +++ b/opensync-plugin-0.4x/src/barry_sync.h @@ -0,0 +1,37 @@ +// +// \file barry_sync.h +// Opensync module for the USB Blackberry handheld +// + +/* + Copyright (C) 2006-2009, Net Direct Inc. (http://www.netdirect.ca/) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License in the COPYING file at the + root directory of this project for more details. +*/ + +#ifndef __BARRY_SYNC_H__ +#define __BARRY_SYNC_H__ + +#include +#include + +class BarryEnvironment; + +typedef char* (*GetData_t)(BarryEnvironment *env, unsigned int dbId, + Barry::RecordStateTable::IndexType); +typedef bool (*CommitData_t)(BarryEnvironment *env, unsigned int dbId, + Barry::RecordStateTable::IndexType StateIndex, uint32_t recordId, + const char *data, bool add, std::string &errmsg); + +#endif + diff --git a/opensync-plugin-0.4x/src/environment.cc b/opensync-plugin-0.4x/src/environment.cc new file mode 100644 index 00000000..fdc7e458 --- /dev/null +++ b/opensync-plugin-0.4x/src/environment.cc @@ -0,0 +1,303 @@ +// +// \file environment.cc +// Container / environment class for the sync module. +// + +/* + Copyright (C) 2006-2009, Net Direct Inc. (http://www.netdirect.ca/) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License in the COPYING file at the + root directory of this project for more details. +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "environment.h" +#include "trace.h" +#include +#include +#include +#include +#include +#include + + +////////////////////////////////////////////////////////////////////////////// +// DatabaseSyncState + +DatabaseSyncState::DatabaseSyncState(OSyncPluginInfo *info, const char *description) + : m_dbId(0), + m_Sync(false), + m_Desc(description) +{ + m_CacheFilename = m_MapFilename = osync_plugin_info_get_configdir(info); + m_CacheFilename += "/barry_" + m_Desc + "_cache.txt"; + m_MapFilename += "/barry_" + m_Desc + "_idmap.txt"; +} + +DatabaseSyncState::~DatabaseSyncState() +{ +} + +bool DatabaseSyncState::LoadCache() +{ + Trace trace("LoadCache", m_Desc.c_str()); + + m_Cache.clear(); + std::ifstream ifs(m_CacheFilename.c_str()); + if( !ifs ) + return false; + + while( ifs ) { + uint32_t recordId = 0; + ifs >> recordId; + if( recordId ) { + m_Cache[recordId] = false; + } + } + + if( !ifs.eof() ) { + m_Cache.clear(); // assume full sync + trace.log("Load failed!"); + return false; + } + return true; +} + +bool DatabaseSyncState::SaveCache() +{ + Trace trace("SaveCache", m_Desc.c_str()); + + std::ofstream ofs(m_CacheFilename.c_str()); + if( !ofs ) + return false; + + cache_type::const_iterator i = m_Cache.begin(); + for( ; i != m_Cache.end(); ++i ) { + ofs << i->first << std::endl; + } + return !ofs.bad() && !ofs.fail(); +} + +bool DatabaseSyncState::LoadMap() +{ + return m_IdMap.Load(m_MapFilename.c_str()); +} + +bool DatabaseSyncState::SaveMap() +{ + return m_IdMap.Save(m_MapFilename.c_str()); +} + +// cycle through the map and search the state table for each rid, +// and remove any that do not exist +void DatabaseSyncState::CleanupMap() +{ + idmap::iterator i = m_IdMap.begin(); + for( ; i != m_IdMap.end(); ++i ) { + if( !m_Table.GetIndex(i->second) ) { + // Record Id does not exist in state table, so it is + // not needed anymore in the ID map + m_IdMap.Unmap(i); + } + } +} + +// +// Map2Uid +// +/// Searches for the given record ID, and returns the mapped UID. If not +/// found, it creates a new UID and returns it without mapping it. +/// +std::string DatabaseSyncState::Map2Uid(uint32_t recordId) const +{ + // search the idmap for the UID + std::string uid; + idmap::const_iterator mapped_id; + if( m_IdMap.RidExists(recordId, &mapped_id) ) { + uid = mapped_id->first; + } + else { + // not mapped, map it ourselves + char *puid = g_strdup_printf("%s-%u", m_Desc.c_str(), recordId); + uid = puid; + g_free(puid); + } + return uid; +} + +unsigned long DatabaseSyncState::GetMappedRecordId(const std::string &uid) +{ + Trace trace("DatabaseSyncState::GetMappedRecordId()", m_Desc.c_str()); + + // if already in map, use the matching rid + idmap::const_iterator it; + if( m_IdMap.UidExists(uid, &it) ) { + trace.logf("found existing uid in map: %lu", it->second); + return it->second; + } + + // nothing in the map, so try to convert the string to a number + unsigned long RecordId; + if( sscanf(uid.c_str(), "%lu", &RecordId) != 0 ) { + trace.logf("parsed uid as: %lu", RecordId); + if( m_IdMap.Map(uid, RecordId) != m_IdMap.end() ) + return RecordId; + + trace.logf("parsed uid already exists in map, skipping"); + } + + // create one of our own, if we get here... + // do this in a loop to keep going until we find an ID that's unique + do { + RecordId = m_Table.MakeNewRecordId(); + } while( m_IdMap.Map(uid, RecordId) == m_IdMap.end() ); + + trace.logf("made new record id: %lu", RecordId); + return RecordId; +} + + + +////////////////////////////////////////////////////////////////////////////// +// BarryEnvironment Public API + +BarryEnvironment::BarryEnvironment(OSyncPluginInfo *info): + info(info), + m_pin(-1), + m_DebugMode(false), + m_password(""), + m_IConverter("UTF-8"), + m_pCon(0), + m_pDesktop(0), + m_CalendarSync(info, "calendar"), + m_ContactsSync(info, "contacts") +{ +} + +BarryEnvironment::~BarryEnvironment() +{ + delete m_pDesktop; + delete m_pCon; +} + +void BarryEnvironment::DoConnect() +{ + // Create controller + m_pCon = new Barry::Controller(m_ProbeResult); + m_pDesktop = new Barry::Mode::Desktop(*m_pCon, m_IConverter); + m_pDesktop->Open(m_password.c_str()); + + // Save the DBIDs and DBNames of the databases we will work with + m_CalendarSync.m_dbName = Barry::Calendar::GetDBName(); + m_CalendarSync.m_dbId = m_pDesktop->GetDBID(Barry::Calendar::GetDBName()); + + m_ContactsSync.m_dbId = m_pDesktop->GetDBID(Barry::Contact::GetDBName()); + m_ContactsSync.m_dbName = Barry::Contact::GetDBName(); +} + +void BarryEnvironment::Connect(const Barry::ProbeResult &result) +{ + Disconnect(); + + // save result in case we need to reconnect later + m_ProbeResult = result; + + DoConnect(); +} + +void BarryEnvironment::Reconnect() +{ + Disconnect(); + + // FIXME - temporary fix for odd reconnect message... without this + // probe, the reconnect will often fail on newer Blackberries + // due to an unexpected close socket message. It is unclear + // if this is really a message from the device, but until then, + // we add this probe. + { + Barry::Probe probe; + int i = probe.FindActive(m_ProbeResult.m_pin); + if( i != -1 ) + m_ProbeResult = probe.Get(i); + } + + DoConnect(); +} + +void BarryEnvironment::Disconnect() +{ + delete m_pDesktop; + m_pDesktop = 0; + + delete m_pCon; + m_pCon = 0; +} + +void BarryEnvironment::ClearDirtyFlags(Barry::RecordStateTable &table, + const std::string &dbname) +{ + Trace trace("ClearDirtyFlags"); + + unsigned int dbId = m_pDesktop->GetDBID(dbname); + + Barry::RecordStateTable::StateMapType::const_iterator i = table.StateMap.begin(); + for( ; i != table.StateMap.end(); ++i ) { + if( i->second.Dirty ) { + trace.logf("Clearing dirty flag for db %u, index %u", + dbId, i->first); + m_pDesktop->ClearDirty(dbId, i->first); + } + } +} + +void BarryEnvironment::ClearCalendarDirtyFlags() +{ + Trace trace("ClearCalendarDirtyFlags"); + ClearDirtyFlags(m_CalendarSync.m_Table, Barry::Calendar::GetDBName()); +} + +void BarryEnvironment::ClearContactsDirtyFlags() +{ + Trace trace("ClearContactsDirtyFlags"); + ClearDirtyFlags(m_ContactsSync.m_Table, Barry::Contact::GetDBName()); +} + +DatabaseSyncState* BarryEnvironment::GetSyncObject(OSyncChange *change) +{ + Trace trace("BarryEnvironment::GetSyncObject()"); + + const char *name = osync_change_get_objtype(change); + + if( strcmp(name, "event") == 0 ) { + return &m_CalendarSync; + } + else if( strcmp(name, "contact") == 0 ) { + return &m_ContactsSync; + } + else { + return 0; + } +} + + +//void BarryEnvironment::BuildConfig() +//{ +// FIXME - build back into one long string +//} + diff --git a/opensync-plugin-0.4x/src/environment.h b/opensync-plugin-0.4x/src/environment.h new file mode 100644 index 00000000..85fc3f94 --- /dev/null +++ b/opensync-plugin-0.4x/src/environment.h @@ -0,0 +1,122 @@ +// +// \file environment.h +// Container / environment class for the sync module. +// + +/* + Copyright (C) 2006-2009, Net Direct Inc. (http://www.netdirect.ca/) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License in the COPYING file at the + root directory of this project for more details. +*/ + +#ifndef __BARRY_SYNC_ENVIRONMENT_H__ +#define __BARRY_SYNC_ENVIRONMENT_H__ + +#include +#include +#include +#include +#include "idmap.h" + + +struct DatabaseSyncState +{ +public: + OSyncObjTypeSink *sink; + + // cache is a map of record ID to bool... the bool doesn't mean + // anything... the mere existence of the ID means it belongs + // in the cache + typedef std::map cache_type; + +public: + // cache data + std::string m_CacheFilename; + cache_type m_Cache; + + // id map data + std::string m_MapFilename; + idmap m_IdMap; + + // device data + unsigned int m_dbId; + std::string m_dbName; + Barry::RecordStateTable m_Table; + + bool m_Sync; + +private: + std::string m_Desc; + +public: + DatabaseSyncState(OSyncPluginInfo *info, const char *description); + ~DatabaseSyncState(); + + bool LoadCache(); + bool SaveCache(); + + bool LoadMap(); + bool SaveMap(); + + void CleanupMap(); + void ClearDirtyFlags(); + + std::string Map2Uid(uint32_t recordId) const; + unsigned long GetMappedRecordId(const std::string &uid); +}; + + +struct BarryEnvironment +{ +public: + OSyncMember *member; + OSyncPluginInfo *info; + + // user config data + std::string m_ConfigData; + uint32_t m_pin; + bool m_DebugMode; + std::string m_password; + + // device communication + Barry::IConverter m_IConverter; + Barry::ProbeResult m_ProbeResult; + Barry::Controller *m_pCon; + Barry::Mode::Desktop *m_pDesktop; + + // sync data + DatabaseSyncState m_CalendarSync, m_ContactsSync; + +protected: + void DoConnect(); + +public: +// BarryEnvironment(OSyncMember *pm); + BarryEnvironment(OSyncPluginInfo *info); + ~BarryEnvironment(); + + void Connect(const Barry::ProbeResult &result); + void Reconnect(); + void Disconnect(); + + DatabaseSyncState* GetSyncObject(OSyncChange *change); + + void BuildConfig(); + + void ClearDirtyFlags(Barry::RecordStateTable &table, const std::string &dbname); + void ClearCalendarDirtyFlags(); + void ClearContactsDirtyFlags(); +}; + +#endif + diff --git a/opensync-plugin-0.4x/src/idmap.cc b/opensync-plugin-0.4x/src/idmap.cc new file mode 100644 index 00000000..20297312 --- /dev/null +++ b/opensync-plugin-0.4x/src/idmap.cc @@ -0,0 +1,166 @@ +// +// \file idmap.cc +// Class that maps opensync UID strings to Blackberry +// Record ID's and back. +// + +/* + Copyright (C) 2007-2009, Net Direct Inc. (http://www.netdirect.ca/) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License in the COPYING file at the + root directory of this project for more details. +*/ + +#include "idmap.h" +#include +#include "trace.h" + +idmap::idmap() + : m_blank_uid(""), + m_blank_rid(0) +{ +} + +idmap::~idmap() +{ +} + +bool idmap::Load(const char *filename) +{ + // start fresh + m_map.clear(); + + std::ifstream ifs(filename); + if( !ifs ) + return false; + + std::string line; + uint32_t recordId; + while( ifs ) { + recordId = 0; + ifs >> recordId >> std::ws; + std::getline(ifs, line); + if( ifs && recordId && line.size() ) { + Map(line, recordId); + } + } + return ifs.eof(); +} + +bool idmap::Save(const char *filename) const +{ + std::ofstream ofs(filename); + if( !ofs ) + return false; + + const_iterator i = m_map.begin(); + for( ; i != m_map.end(); ++i ) { + ofs << i->second << " " << i->first << std::endl; + } + return !ofs.bad() && !ofs.fail(); +} + +bool idmap::UidExists(const uid_type &uid, const_iterator *it) const +{ + const_iterator i = m_map.find(uid); + if( it ) + *it = i; + return i != m_map.end(); +} + +bool idmap::RidExists(const rid_type &rid, const_iterator *it) const +{ + const_iterator i = m_map.begin(); + for( ; i != m_map.end(); ++i ) { + if( i->second == rid ) { + if( it ) + *it = i; + return true; + } + } + if( it ) + *it = m_map.end(); + return false; +} + +const idmap::uid_type& idmap::GetUid(const rid_type &rid) const +{ + const_iterator i = m_map.begin(); + for( ; i != m_map.end(); ++i ) { + if( i->second == rid ) + return i->first; + } + return m_blank_uid; +} + +const idmap::rid_type& idmap::GetRid(const uid_type &uid) const +{ + const_iterator i = m_map.find(uid); + return i->second; +} + +const idmap::uid_type& idmap::operator[] (const rid_type &rid) const +{ + return GetUid(rid); +} + +const idmap::rid_type& idmap::operator[] (const uid_type &uid) const +{ + return GetRid(uid); +} + +// returns map::end() if either id already exists, otherwise +// returns newly mapped item iterator. +// +// The other versions of the function are to make conversion +// between different types easier, giving ability to map +// with any reasonable type, and then access the real +// values through the iterator if needed. +idmap::const_iterator idmap::Map(const uid_type &uid, const rid_type &rid) +{ + // neither id can be blank + if( uid.size() == 0 || rid == 0 ) + return m_map.end(); + + // neither id must already exist + if( UidExists(uid) || RidExists(rid) ) + return m_map.end(); + + return m_map.insert(m_map.begin(), make_pair(uid, rid)); +} + +/* +idmap::const_iterator idmap::Map(unsigned long uid_number, const rid_type &rid) +{ +} + +idmap::const_iterator idmap::Map(unsigned long uid_number, const std::string &rid_string) +{ +} +*/ + +void idmap::UnmapUid(const uid_type &uid) +{ + m_map.erase(uid); +} + +void idmap::UnmapRid(const rid_type &rid) +{ + iterator i = m_map.begin(); + for( ; i != m_map.end(); ++i ) { + if( i->second == rid ) { + m_map.erase(i); + return; + } + } +} + diff --git a/opensync-plugin-0.4x/src/idmap.h b/opensync-plugin-0.4x/src/idmap.h new file mode 100644 index 00000000..2b5e9af0 --- /dev/null +++ b/opensync-plugin-0.4x/src/idmap.h @@ -0,0 +1,85 @@ +// +// \file uidmap.h +// Class that maps opensync UID strings to Blackberry +// Record ID's and back. +// + +/* + Copyright (C) 2007-2009, Net Direct Inc. (http://www.netdirect.ca/) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License in the COPYING file at the + root directory of this project for more details. +*/ + +#ifndef __BARRY_SYNC_UIDMAP_H__ +#define __BARRY_SYNC_UIDMAP_H__ + +#include +#include +#include + +class idmap +{ +public: + typedef std::string uid_type; + typedef uint32_t rid_type; // record ID + typedef std::map map_type; + typedef map_type::iterator iterator; + typedef map_type::const_iterator const_iterator; + +private: + // blank objects, so references work + uid_type m_blank_uid; + rid_type m_blank_rid; + + // the map data + map_type m_map; + +public: + idmap(); + ~idmap(); + + bool Load(const char *filename); + bool Save(const char *filename) const; + + bool UidExists(const uid_type &uid, const_iterator *it = 0) const; + bool RidExists(const rid_type &rid, const_iterator *it = 0) const; + + const uid_type& GetUid(const rid_type &rid) const; + const rid_type& GetRid(const uid_type &uid) const; + + const uid_type& operator[] (const rid_type &rid) const; + const rid_type& operator[] (const uid_type &uid) const; + + // returns map::end() if either id already exists, otherwise + // returns newly mapped item iterator. + // + // The other versions of the function are to make conversion + // between different types easier, giving ability to map + // with any reasonable type, and then access the real + // values through the iterator if needed. + const_iterator Map(const uid_type &uid, const rid_type &rid); + const_iterator Map(unsigned long uid_number, const rid_type &rid); + const_iterator Map(unsigned long uid_number, const std::string &rid_string); + + void Unmap(iterator i) { m_map.erase(i); } + void UnmapUid(const uid_type &uid); + void UnmapRid(const rid_type &rid); + + // some stl-like functions + iterator begin() { return m_map.begin(); } + iterator end() { return m_map.end(); } + void clear() { m_map.clear(); } +}; + +#endif + diff --git a/opensync-plugin-0.4x/src/trace.h b/opensync-plugin-0.4x/src/trace.h new file mode 100644 index 00000000..12017ad7 --- /dev/null +++ b/opensync-plugin-0.4x/src/trace.h @@ -0,0 +1,70 @@ +// +// \file trace.h +// RAII class for trace logging. +// + +/* + Copyright (C) 2006-2009, Net Direct Inc. (http://www.netdirect.ca/) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License in the COPYING file at the + root directory of this project for more details. +*/ + +#ifndef __BARRY_SYNC_TRACE_H__ +#define __BARRY_SYNC_TRACE_H__ + +#include +#include + +class Trace +{ + const char *text, *tag; +public: + explicit Trace(const char *t) : text(t), tag(0) + { + osync_trace(TRACE_ENTRY, "barry_sync: %s", text); + } + + Trace(const char *t, const char *tag) : text(t), tag(tag) + { + osync_trace(TRACE_ENTRY, "barry_sync (%s): %s", tag, text); + } + + ~Trace() + { + if( tag ) + osync_trace(TRACE_EXIT, "barry_sync (%s): %s", tag, text); + else + osync_trace(TRACE_EXIT, "barry_sync: %s", text); + } + + void log(const char *t) + { + osync_trace(TRACE_INTERNAL, "barry_sync: %s", t); + } + + void logf(const char *t, ...) + { + va_list vl; + va_start(vl, t); + char buffer[2048]; + int n = vsnprintf(buffer, sizeof(buffer), t, vl); + va_end(vl); + if( n > -1 && n < (int)sizeof(buffer) ) + osync_trace(TRACE_INTERNAL, "barry_sync: %s", buffer); + else + osync_trace(TRACE_INTERNAL, "barry_sync: (trace error, output too long for buffer: %s)", t); + } +}; + +#endif + diff --git a/opensync-plugin-0.4x/src/vbase.cc b/opensync-plugin-0.4x/src/vbase.cc new file mode 100644 index 00000000..60140ac9 --- /dev/null +++ b/opensync-plugin-0.4x/src/vbase.cc @@ -0,0 +1,269 @@ +// +// \file vbase.cc +// vformat support routines in base class +// + +/* + Copyright (C) 2006-2009, Net Direct Inc. (http://www.netdirect.ca/) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License in the COPYING file at the + root directory of this project for more details. +*/ + +#include "vbase.h" +#include "trace.h" +#include "vformat.h" // comes from opensync, but not a public header yet +#include +#include +#include + + +////////////////////////////////////////////////////////////////////////////// +// vAttr + +std::string vAttr::GetName() +{ + std::string ret; + + if( !m_attr ) + return ret; + + const char *name = b_vformat_attribute_get_name(m_attr); + if( name ) + ret = name; + return ret; +} + +std::string vAttr::GetValue(int nth) +{ + std::string ret; + const char *value = 0; + + if( m_attr ) { + if( b_vformat_attribute_is_single_valued(m_attr) ) { + if( nth == 0 ) + value = b_vformat_attribute_get_value(m_attr); + } + else { + value = b_vformat_attribute_get_nth_value(m_attr, nth); + } + } + + if( value ) + ret = value; + + return ret; +} + +std::string vAttr::GetParam(const char *name, int nth) +{ + std::string ret; + + if( !m_attr ) + return ret; + + b_VFormatParam *param = b_vformat_attribute_find_param(m_attr, name, 0); + if( !param ) + return ret; + + const char *value = b_vformat_attribute_param_get_nth_value(param, nth); + if( value ) + ret = value; + + return ret; +} + +/// Does an exhaustive search through the attribute, searching for all +/// param values that exist for the given name, and returns all values +/// in a comma delimited string. +std::string vAttr::GetAllParams(const char *name) +{ + std::string ret; + + if( !m_attr ) + return ret; + + b_VFormatParam *param = 0; + for( int level = 0; + (param = b_vformat_attribute_find_param(m_attr, name, level)); + level++ ) + { + const char *value = 0; + for( int nth = 0; + (value = b_vformat_attribute_param_get_nth_value(param, nth)); + nth++ ) + { + if( ret.size() ) + ret += ","; + ret += value; + } + } + + return ret; +} + + +////////////////////////////////////////////////////////////////////////////// +// vCalendar + +vBase::vBase() + : m_format(0) +{ +} + +vBase::~vBase() +{ + if( m_format ) { + b_vformat_free(m_format); + } +} + +void vBase::SetFormat(b_VFormat *format) +{ + if( m_format ) { + b_vformat_free(m_format); + m_format = 0; + } + m_format = format; +} + +void vBase::Clear() +{ + if( m_format ) { + b_vformat_free(m_format); + m_format = 0; + } +} + +vAttrPtr vBase::NewAttr(const char *name) +{ + Trace trace("vBase::NewAttr"); + + trace.logf("creating valueless attr: %s", name); + + vAttrPtr attr(b_vformat_attribute_new(NULL, name)); + if( !attr.Get() ) + throw ConvertError("resource error allocating vformat attribute"); + return attr; +} + +vAttrPtr vBase::NewAttr(const char *name, const char *value) +{ + Trace trace("vBase::NewAttr"); + +/* +some vCard values are positional (like name), so blank should be allowed... + + if( strlen(value) == 0 ) { + trace.logf("attribute '%s' contains no data, skipping", name); + return vAttrPtr(); + } +*/ + + trace.logf("creating attr: %s, %s", name, value); + + vAttrPtr attr(b_vformat_attribute_new(NULL, name)); + if( !attr.Get() ) + throw ConvertError("resource error allocating vformat attribute"); + + b_vformat_attribute_add_value(attr.Get(), value); + return attr; +} + +void vBase::AddAttr(vAttrPtr attr) +{ + Trace trace("vBase::AddAttr"); + + if( !attr.Get() ) { + trace.log("attribute contains no data, skipping"); + return; + } + + b_vformat_add_attribute(m_format, attr.Extract()); +} + +void vBase::AddValue(vAttrPtr &attr, const char *value) +{ + Trace trace("vBase::AddValue"); + if( !attr.Get() ) { + trace.log("attribute pointer contains no data, skipping"); + return; + } +/* + if( strlen(value) == 0 ) { + trace.log("attribute value is empty, skipping"); + return; + } +*/ + b_vformat_attribute_add_value(attr.Get(), value); +} + +void vBase::AddParam(vAttrPtr &attr, const char *name, const char *value) +{ + Trace trace("vBase::AddParam"); + + if( !attr.Get() ) { + trace.log("attribute pointer contains no data, skipping"); + return; + } +/* + if( strlen(value) == 0 ) { + trace.log("parameter value is empty, skipping"); + return; + } +*/ + + b_VFormatParam *pParam = b_vformat_attribute_param_new(name); + b_vformat_attribute_param_add_value(pParam, value); + b_vformat_attribute_add_param(attr.Get(), pParam); +} + +std::string vBase::GetAttr(const char *attrname, const char *block) +{ + Trace trace("vBase::GetAttr"); + trace.logf("getting attr: %s", attrname); + + std::string ret; + const char *value = 0; + + bool needs_freeing = false; + + b_VFormatAttribute *attr = b_vformat_find_attribute(m_format, attrname, 0, block); + if( attr ) { + if( b_vformat_attribute_is_single_valued(attr) ) { + value = b_vformat_attribute_get_value(attr); + needs_freeing = true; + } + else { + // FIXME, this is hardcoded + value = b_vformat_attribute_get_nth_value(attr, 0); + } + } + + if( value ) + ret = value; + + if( needs_freeing ) + g_free((char *)value); + + trace.logf("attr value: %s", ret.c_str()); + return ret; +} + +vAttr vBase::GetAttrObj(const char *attrname, int nth, const char *block) +{ + Trace trace("vBase::GetAttrObj"); + trace.logf("getting attr: %s", attrname); + + return vAttr(b_vformat_find_attribute(m_format, attrname, nth, block)); +} + diff --git a/opensync-plugin-0.4x/src/vbase.h b/opensync-plugin-0.4x/src/vbase.h new file mode 100644 index 00000000..76602873 --- /dev/null +++ b/opensync-plugin-0.4x/src/vbase.h @@ -0,0 +1,198 @@ +/// +/// \file vbase.h +/// Base class for vformat support +/// + +/* + Copyright (C) 2006-2009, Net Direct Inc. (http://www.netdirect.ca/) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License in the COPYING file at the + root directory of this project for more details. +*/ + +#ifndef __BARRY_SYNC_VBASE_H__ +#define __BARRY_SYNC_VBASE_H__ + +#include +#include +#include "vformat.h" + +// forward declarations +class BarryEnvironment; + + +// FIXME - possibly scrap this in favour of opensync's xml time routines, +// but this will require an overhaul of the plugin itself. +class vTimeZone +{ +public: + struct ZoneBlock + { + bool m_daylightSaving; + std::string m_name; + std::string m_dtstart; + std::string m_tzoffsetfrom; + std::string m_tzoffsetto; + }; + +private: + bool m_valid; + +public: + std::string m_id; + +public: + vTimeZone(); + ~vTimeZone(); + + void Clear(); + + bool IsValid(); + + /// used for comparing by TZID + bool operator==(const std::string &tzid) const; +}; + + +// A special smart pointer for vformat pointer handling. +// Behaves like std::auto_ptr<> in that only one object +// at a time owns the pointer, and destruction frees it. +template +class vSmartPtr +{ + mutable T *m_pt; + +public: + vSmartPtr() : m_pt(0) {} + vSmartPtr(T *pt) : m_pt(pt) {} + vSmartPtr(const vSmartPtr &sp) : m_pt(sp.m_pt) + { + sp.m_pt = 0; + } + ~vSmartPtr() + { + if( m_pt ) + FreeFunc(m_pt); + } + + vSmartPtr& operator=(T *pt) + { + Extract(); + m_pt = pt; + return *this; + } + + vSmartPtr& operator=(const vSmartPtr &sp) + { + Extract(); + m_pt = sp.Extract(); + return *this; + } + + T* Extract() + { + T *rp = m_pt; + m_pt = 0; + return rp; + } + + T* Get() + { + return m_pt; + } +}; + +typedef vSmartPtr vAttrPtr; +typedef vSmartPtr vParamPtr; +typedef vSmartPtr gStringPtr; + + +// +// vAttr +// +/// Class for reading a b_VFormatAttribute. Reading does not require +/// memory management, so none is done. +/// +class vAttr +{ + b_VFormatAttribute *m_attr; + +public: + vAttr() + : m_attr(0) + { + } + + vAttr(b_VFormatAttribute *attr) + : m_attr(attr) + { + } + + vAttr& operator=(b_VFormatAttribute *attr) + { + m_attr = attr; + return *this; + } + + b_VFormatAttribute* Get() { return m_attr; } + + // These functions do not throw an error if the value + // is NULL or does not exist (for example, if you ask for + // value #5 and there are only 4). + std::string GetName(); + std::string GetValue(int nth = 0); + std::string GetParam(const char *name, int nth = 0); + std::string GetAllParams(const char *name); +}; + + +// +// vBase +// +/// Base class containing vformat helper API. +/// +class vBase +{ + // internal data for managing the vformat + b_VFormat *m_format; + +public: + // FIXME - if you put this class in the Barry library, + // you'll need to change the class hierarchy + class ConvertError : public std::runtime_error + { + public: + ConvertError(const std::string &msg) : std::runtime_error(msg) {} + }; + +protected: + vBase(); + virtual ~vBase(); + + b_VFormat* Format() { return m_format; } + const b_VFormat* Format() const { return m_format; } + void SetFormat(b_VFormat *format); + + void Clear(); + + vAttrPtr NewAttr(const char *name); + vAttrPtr NewAttr(const char *name, const char *value); + void AddAttr(vAttrPtr attr); + void AddValue(vAttrPtr &attr, const char *value); + void AddParam(vAttrPtr &attr, const char *name, const char *value); + + std::string GetAttr(const char *attrname, const char *block = 0); + vAttr GetAttrObj(const char *attrname, int nth = 0, const char *block = 0); +}; + +#endif + diff --git a/opensync-plugin-0.4x/src/vcard.cc b/opensync-plugin-0.4x/src/vcard.cc new file mode 100644 index 00000000..505fa07c --- /dev/null +++ b/opensync-plugin-0.4x/src/vcard.cc @@ -0,0 +1,590 @@ +/// +/// \file vcard.cc +/// Conversion routines for vcards +/// + +/* + Copyright (C) 2006-2009, Net Direct Inc. (http://www.netdirect.ca/) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License in the COPYING file at the + root directory of this project for more details. +*/ + +#include "vcard.h" +#include "environment.h" +#include "trace.h" +#include "vformat.h" // comes from opensync, but not a public header yet +#include +#include +#include +#include +#include + + +////////////////////////////////////////////////////////////////////////////// +// Utility functions + +void ToLower(std::string &str) +{ + size_t i = 0; + while( i < str.size() ) { + str[i] = tolower(str[i]); + i++; + } +} + +////////////////////////////////////////////////////////////////////////////// +// vCard + +vCard::vCard() + : m_gCardData(0) +{ +} + +vCard::~vCard() +{ + if( m_gCardData ) { + g_free(m_gCardData); + } +} + +void vCard::AddAddress(const char *rfc_type, const Barry::PostalAddress &address) +{ + // add label first + vAttrPtr label = NewAttr("LABEL"); + AddParam(label, "TYPE", rfc_type); + AddValue(label, address.GetLabel().c_str()); + AddAttr(label); + + // add breakout address form + vAttrPtr adr = NewAttr("ADR"); // RFC 2426, 3.2.1 + AddParam(adr, "TYPE", rfc_type); + AddValue(adr, address.Address3.c_str()); // PO Box + AddValue(adr, address.Address2.c_str()); // Extended address + AddValue(adr, address.Address1.c_str()); // Street address + AddValue(adr, address.City.c_str()); // Locality (city) + AddValue(adr, address.Province.c_str()); // Region (province) + AddValue(adr, address.PostalCode.c_str()); // Postal code + AddValue(adr, address.Country.c_str()); // Country name + AddAttr(adr); +} + +void vCard::AddCategories(const Barry::CategoryList &categories) +{ + if( !categories.size() ) + return; + + vAttrPtr cat = NewAttr("CATEGORIES"); // RFC 2426, 3.6.1 + Barry::CategoryList::const_iterator i = categories.begin(); + for( ; i < categories.end(); ++i ) { + AddValue(cat, i->c_str()); + } + AddAttr(cat); +} + +/// Add phone conditionally, only if phone has data in it. This version +/// does not add a TYPE parameter to the item. +void vCard::AddPhoneCond(const std::string &phone) +{ + if( phone.size() ) { + vAttrPtr tel = NewAttr("TEL", phone.c_str()); + AddAttr(tel); + } +} + +/// Add phone conditionally, only if phone has data in it +void vCard::AddPhoneCond(const char *rfc_type, const std::string &phone) +{ + if( phone.size() ) { + vAttrPtr tel = NewAttr("TEL", phone.c_str()); + AddParam(tel, "TYPE", rfc_type); + AddAttr(tel); + } +} + +void vCard::ParseAddress(vAttr &adr, Barry::PostalAddress &address) +{ + // RFC 2426, 3.2.1 + address.Address3 = adr.GetValue(0); // PO Box + address.Address2 = adr.GetValue(1); // Extended address + address.Address1 = adr.GetValue(2); // Street address + address.City = adr.GetValue(3); // Locality (city) + address.Province = adr.GetValue(4); // Region (province) + address.PostalCode = adr.GetValue(5); // Postal code + address.Country = adr.GetValue(6); // Country name +} + +void vCard::ParseCategories(vAttr &cat, Barry::CategoryList &cats) +{ + int i = 0; + std::string value = cat.GetValue(i); + while( value.size() ) { + cats.push_back(value); + i++; + value = cat.GetValue(i); + } +} + + + +// Main conversion routine for converting from Barry::Contact to +// a vCard string of data. +const std::string& vCard::ToVCard(const Barry::Contact &con) +{ + Trace trace("vCard::ToVCard"); + std::ostringstream oss; + con.Dump(oss); + trace.logf("ToVCard, initial Barry record: %s", oss.str().c_str()); + + // start fresh + Clear(); + SetFormat( b_vformat_new() ); + if( !Format() ) + throw ConvertError("resource error allocating vformat"); + + // store the Barry object we're working with + m_BarryContact = con; + + // + // begin building vCard data + // + + AddAttr(NewAttr("PRODID", "-//OpenSync//NONSGML Barry Contact Record//EN")); + + std::string fullname = con.GetFullName(); + if( fullname.size() ) { + AddAttr(NewAttr("FN", fullname.c_str())); + } + else { + // + // RFC 2426, 3.1.1 states that FN MUST be present in the + // vcard object. Unfortunately, the Blackberry doesn't + // require a name, only a name or company name. + // + // In this case we do nothing, and generate an invalid + // vcard, since if we try to fix our output here, we'll + // likely end up with duplicated company names in the + // Blackberry record after a few syncs. + // + } + + if( con.FirstName.size() || con.LastName.size() ) { + vAttrPtr name = NewAttr("N"); // RFC 2426, 3.1.2 + AddValue(name, con.LastName.c_str()); // Family Name + AddValue(name, con.FirstName.c_str()); // Given Name + AddValue(name, ""); // Additional Names + AddValue(name, con.Prefix.c_str()); // Honorific Prefixes + AddValue(name, ""); // Honorific Suffixes + AddAttr(name); + } + + if( con.WorkAddress.HasData() ) + AddAddress("work", con.WorkAddress); + if( con.HomeAddress.HasData() ) + AddAddress("home", con.HomeAddress); + + // add all applicable phone numbers... there can be multiple + // TEL fields, even with the same TYPE value... therefore, the + // second TEL field with a TYPE=work, will be stored in WorkPhone2 + AddPhoneCond("pref", con.Phone); + AddPhoneCond("fax", con.Fax); + AddPhoneCond("work", con.WorkPhone); + AddPhoneCond("work", con.WorkPhone2); + AddPhoneCond("home", con.HomePhone); + AddPhoneCond("home", con.HomePhone2); + AddPhoneCond("cell", con.MobilePhone); + AddPhoneCond("pager", con.Pager); + AddPhoneCond(con.OtherPhone); + + // add all email addresses, marking first one as "pref" + Barry::Contact::EmailList::const_iterator eai = con.EmailAddresses.begin(); + for( unsigned int i = 0; eai != con.EmailAddresses.end(); ++eai, ++i ) { + const std::string& e = con.GetEmail(i); + if( e.size() ) { + vAttrPtr email = NewAttr("EMAIL", e.c_str()); + if( i == 0 ) { + AddParam(email, "TYPE", "internet,pref"); + } + else { + AddParam(email, "TYPE", "internet"); + } + AddAttr(email); + } + } + + if( con.JobTitle.size() ) { + AddAttr(NewAttr("TITLE", con.JobTitle.c_str())); + AddAttr(NewAttr("ROLE", con.JobTitle.c_str())); + } + + // Image not supported, since vformat routines probably don't + // support binary VCARD fields.... + + if( con.Company.size() ) { + // RFC 2426, 3.5.5 + vAttrPtr org = NewAttr("ORG", con.Company.c_str()); // Organization name + AddValue(org, ""); // Division name + AddAttr(org); + } + + if( con.Birthday.HasData() ) + AddAttr(NewAttr("BDAY", con.Birthday.ToYYYYMMDD().c_str())); + + if( con.Notes.size() ) + AddAttr(NewAttr("NOTE", con.Notes.c_str())); + if( con.URL.size() ) + AddAttr(NewAttr("URL", con.URL.c_str())); + if( con.Categories.size() ) + AddCategories(con.Categories); + + // generate the raw VCARD data + m_gCardData = b_vformat_to_string(Format(), VFORMAT_CARD_30); + m_vCardData = m_gCardData; + + trace.logf("ToVCard, resulting vcard data: %s", m_vCardData.c_str()); + return m_vCardData; +} + +// Main conversion routine for converting from vCard data string +// to a Barry::Contact object. +const Barry::Contact& vCard::ToBarry(const char *vcard, uint32_t RecordId) +{ + using namespace std; + + Trace trace("vCard::ToBarry"); + trace.logf("ToBarry, working on vcard data: %s", vcard); + + // start fresh + Clear(); + + // store the vCard raw data + m_vCardData = vcard; + + // create format parser structures + SetFormat( b_vformat_new_from_string(vcard) ); + if( !Format() ) + throw ConvertError("resource error allocating vformat"); + + + // + // Parse the vcard data + // + + Barry::Contact &con = m_BarryContact; + con.SetIds(Barry::Contact::GetDefaultRecType(), RecordId); + + vAttr name = GetAttrObj("N"); + if( name.Get() ) { + // RFC 2426, 3.1.2 + con.LastName = name.GetValue(0); // Family Name + con.FirstName = name.GetValue(1); // Given Name + con.Prefix = name.GetValue(3); // Honorific Prefixes + } + + vAttr adr = GetAttrObj("ADR"); + for( int i = 0; adr.Get(); adr = GetAttrObj("ADR", ++i) ) + { + std::string type = adr.GetAllParams("TYPE"); + ToLower(type); + + // do not use "else" here, since TYPE can have multiple keys + if( strstr(type.c_str(), "work") ) + ParseAddress(adr, con.WorkAddress); + if( strstr(type.c_str(), "home") ) + ParseAddress(adr, con.HomeAddress); + } + + + // add all applicable phone numbers... there can be multiple + // TEL fields, even with the same TYPE value... therefore, the + // second TEL field with a TYPE=work, will be stored in WorkPhone2 + vAttr tel = GetAttrObj("TEL"); + for( int i = 0; tel.Get(); tel = GetAttrObj("TEL", ++i) ) + { + // grab all parameter values for this param name + std::string stype = tel.GetAllParams("TYPE"); + + // turn to lower case for comparison + // FIXME - is this i18n safe? + ToLower(stype); + + // state + const char *type = stype.c_str(); + bool used = false; + + // Do not use "else" here, since TYPE can have multiple keys + // Instead, only fill in a field if it is empty + if( strstr(type, "pref") && con.Phone.size() == 0 ) { + con.Phone = tel.GetValue(); + used = true; + } + + if( strstr(type, "fax") && con.Fax.size() == 0 ) { + con.Fax = tel.GetValue(); + used = true; + } + + if( strstr(type, "work") ) { + if( con.WorkPhone.size() == 0 ) { + con.WorkPhone = tel.GetValue(); + used = true; + } + else if( con.WorkPhone2.size() == 0 ) { + con.WorkPhone2 = tel.GetValue(); + used = true; + } + } + + if( strstr(type, "home") ) { + if( con.HomePhone.size() == 0 ) { + con.HomePhone = tel.GetValue(); + used = true; + } + else if( con.HomePhone2.size() == 0 ) { + con.HomePhone2 = tel.GetValue(); + used = true; + } + } + + if( strstr(type, "cell") && con.MobilePhone.size() == 0 ) { + con.MobilePhone = tel.GetValue(); + used = true; + } + + if( (strstr(type, "pager") || strstr(type, "msg")) && con.Pager.size() == 0 ) { + con.Pager = tel.GetValue(); + used = true; + } + + // if this value has not been claimed by any of the + // cases above, claim it now as "OtherPhone" + if( !used && con.OtherPhone.size() == 0 ) { + con.OtherPhone = tel.GetValue(); + } + } + // Final check: If Phone is blank, and OtherPhone has a value, swap. + if( con.OtherPhone.size() && !con.Phone.size() ) { + con.Phone.swap(con.OtherPhone); + } + + // scan for all email addresses... append addresses to the + // list by default, but prepend if its type is set to "pref" + // i.e. we want the preferred email address first + vAttr email = GetAttrObj("EMAIL"); + for( int i = 0; email.Get(); email = GetAttrObj("EMAIL", ++i) ) + { + std::string type = email.GetAllParams("TYPE"); + ToLower(type); + + bool of_interest = (i == 0 || strstr(type.c_str(), "pref")); + bool x400 = strstr(type.c_str(), "x400"); + + if( of_interest && !x400 ) { + con.EmailAddresses.insert(con.EmailAddresses.begin(), email.GetValue()); + } + else { + con.EmailAddresses.push_back( email.GetValue() ); + } + } + + // figure out which company title we want to keep... + // favour the TITLE field, but if it's empty, use ROLE + con.JobTitle = GetAttr("TITLE"); + if( !con.JobTitle.size() ) + con.JobTitle = GetAttr("ROLE"); + + con.Company = GetAttr("ORG"); + con.Notes = GetAttr("NOTE"); + con.URL = GetAttr("URL"); + if( GetAttr("BDAY").size() && !con.Birthday.FromYYYYMMDD( GetAttr("BDAY") ) ) + throw ConvertError("Unable to parse BDAY field"); + + vAttr cat = GetAttrObj("CATEGORIES"); + if( cat.Get() ) + ParseCategories(cat, con.Categories); + + // Last sanity check: Blackberry requires that at least + // name or Company has data. + if( !con.GetFullName().size() && !con.Company.size() ) + throw ConvertError("FN and ORG fields both blank in VCARD data"); + + return m_BarryContact; +} + +// Transfers ownership of m_gCardData to the caller. +char* vCard::ExtractVCard() +{ + char *ret = m_gCardData; + m_gCardData = 0; + return ret; +} + +void vCard::Clear() +{ + vBase::Clear(); + m_vCardData.clear(); + m_BarryContact.Clear(); + + if( m_gCardData ) { + g_free(m_gCardData); + m_gCardData = 0; + } +} + + + +////////////////////////////////////////////////////////////////////////////// +// + +VCardConverter::VCardConverter() + : m_Data(0) +{ +} + +VCardConverter::VCardConverter(uint32_t newRecordId) + : m_Data(0), + m_RecordId(newRecordId) +{ +} + +VCardConverter::~VCardConverter() +{ + if( m_Data ) + g_free(m_Data); +} + +// Transfers ownership of m_Data to the caller +char* VCardConverter::ExtractData() +{ + Trace trace("VCardConverter::ExtractData"); + char *ret = m_Data; + m_Data = 0; + return ret; +} + +bool VCardConverter::ParseData(const char *data) +{ + Trace trace("VCardConverter::ParseData"); + + try { + + vCard vcard; + m_Contact = vcard.ToBarry(data, m_RecordId); + + } + catch( vCard::ConvertError &ce ) { + trace.logf("ERROR: vCard::ConvertError exception: %s", ce.what()); + return false; + } + + return true; +} + +// Barry storage operator +void VCardConverter::operator()(const Barry::Contact &rec) +{ + Trace trace("VCardConverter::operator()"); + + // Delete data if some already exists + if( m_Data ) { + g_free(m_Data); + m_Data = 0; + } + + try { + + vCard vcard; + vcard.ToVCard(rec); + m_Data = vcard.ExtractVCard(); + + } + catch( vCard::ConvertError &ce ) { + trace.logf("ERROR: vCard::ConvertError exception: %s", ce.what()); + } +} + +// Barry builder operator +bool VCardConverter::operator()(Barry::Contact &rec, unsigned int dbId) +{ + Trace trace("VCardConverter::builder operator()"); + + rec = m_Contact; + return true; +} + +// Handles calling of the Barry::Controller to fetch a specific +// record, indicated by index (into the RecordStateTable). +// Returns a g_malloc'd string of data containing the vcard30 +// data. It is the responsibility of the caller to free it. +// This is intended to be passed into the GetChanges() function. +char* VCardConverter::GetRecordData(BarryEnvironment *env, unsigned int dbId, + Barry::RecordStateTable::IndexType index) +{ + Trace trace("VCardConverter::GetRecordData()"); + + using namespace Barry; + + VCardConverter contact2vcard; + RecordParser parser(contact2vcard); + env->m_pDesktop->GetRecord(dbId, index, parser); + return contact2vcard.ExtractData(); +} + +bool VCardConverter::CommitRecordData(BarryEnvironment *env, unsigned int dbId, + Barry::RecordStateTable::IndexType StateIndex, uint32_t recordId, + const char *data, bool add, std::string &errmsg) +{ + Trace trace("VCardConverter::CommitRecordData()"); + + uint32_t newRecordId; + if( add ) { + // use given id if possible + if( recordId && !env->m_ContactsSync.m_Table.GetIndex(recordId) ) { + // recordId is unique and non-zero + newRecordId = recordId; + } + else { + trace.log("Can't use recommended recordId, generating new one."); + newRecordId = env->m_ContactsSync.m_Table.MakeNewRecordId(); + } + } + else { + newRecordId = env->m_ContactsSync.m_Table.StateMap[StateIndex].RecordId; + } + trace.logf("newRecordId: %lu", newRecordId); + + VCardConverter convert(newRecordId); + if( !convert.ParseData(data) ) { + std::ostringstream oss; + oss << "unable to parse change data for new RecordId: " + << newRecordId << " data: " << data; + errmsg = oss.str(); + trace.logf(errmsg.c_str()); + return false; + } + + Barry::RecordBuilder builder(convert); + + if( add ) { + trace.log("adding record"); + env->m_pDesktop->AddRecord(dbId, builder); + } + else { + trace.log("setting record"); + env->m_pDesktop->SetRecord(dbId, StateIndex, builder); + trace.log("clearing dirty flag"); + env->m_pDesktop->ClearDirty(dbId, StateIndex); + } + + return true; +} + diff --git a/opensync-plugin-0.4x/src/vcard.h b/opensync-plugin-0.4x/src/vcard.h new file mode 100644 index 00000000..3c14f95f --- /dev/null +++ b/opensync-plugin-0.4x/src/vcard.h @@ -0,0 +1,116 @@ +/// +/// \file vcard.h +/// Conversion routines for vcards +/// + +/* + Copyright (C) 2006-2009, Net Direct Inc. (http://www.netdirect.ca/) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License in the COPYING file at the + root directory of this project for more details. +*/ + +#ifndef __BARRY_SYNC_VCARD_H__ +#define __BARRY_SYNC_VCARD_H__ + +#include +#include +#include +#include "vbase.h" +#include "vformat.h" + +// forward declarations +class BarryEnvironment; + + +// +// vCard +// +/// Class for converting between RFC 2425/2426 vCard data format, +/// and the Barry::Contact class. +/// +class vCard : public vBase +{ + // data to pass to external requests + char *m_gCardData; // dynamic memory returned by vformat()... can + // be used directly by the plugin, without + // overmuch allocation and freeing (see Extract()) + std::string m_vCardData;// copy of m_gCardData, for C++ use + Barry::Contact m_BarryContact; + +protected: + void AddAddress(const char *rfc_type, const Barry::PostalAddress &addr); + void AddCategories(const Barry::CategoryList &categories); + void AddPhoneCond(const std::string &phone); + void AddPhoneCond(const char *rfc_type, const std::string &phone); + + void ParseAddress(vAttr &adr, Barry::PostalAddress &address); + void ParseCategories(vAttr &cat, Barry::CategoryList &cats); + +public: + vCard(); + ~vCard(); + + const std::string& ToVCard(const Barry::Contact &con); + const Barry::Contact& ToBarry(const char *vcal, uint32_t RecordId); + + const std::string& GetVCard() const { return m_vCardData; } + const Barry::Contact& GetBarryContact() const { return m_BarryContact; } + + char* ExtractVCard(); + + void Clear(); +}; + + +class VCardConverter +{ + char *m_Data; + Barry::Contact m_Contact; + uint32_t m_RecordId; + +public: + VCardConverter(); + explicit VCardConverter(uint32_t newRecordId); + ~VCardConverter(); + + // Transfers ownership of m_Data to the caller + char* ExtractData(); + + // Parses vevent data + bool ParseData(const char *data); + + // Barry storage operator + void operator()(const Barry::Contact &rec); + + // Barry builder operator + bool operator()(Barry::Contact &rec, unsigned int dbId); + + // Handles calling of the Barry::Controller to fetch a specific + // record, indicated by index (into the RecordStateTable). + // Returns a g_malloc'd string of data containing the vevent20 + // data. It is the responsibility of the caller to free it. + // This is intended to be passed into the GetChanges() function. + static char* GetRecordData(BarryEnvironment *env, unsigned int dbId, + Barry::RecordStateTable::IndexType index); + + // Handles either adding or overwriting a calendar record, + // given vevent20 data in data, and the proper environmebnt, + // dbId, StateIndex. Set add to true if adding. + static bool CommitRecordData(BarryEnvironment *env, unsigned int dbId, + Barry::RecordStateTable::IndexType StateIndex, uint32_t recordId, + const char *data, bool add, std::string &errmsg); +}; + + +#endif + diff --git a/opensync-plugin-0.4x/src/vevent.cc b/opensync-plugin-0.4x/src/vevent.cc new file mode 100644 index 00000000..fffccc88 --- /dev/null +++ b/opensync-plugin-0.4x/src/vevent.cc @@ -0,0 +1,581 @@ +// +// \file vevent.cc +// Conversion routines for vevents (VCALENDAR, etc) +// + +/* + Copyright (C) 2006-2009, Net Direct Inc. (http://www.netdirect.ca/) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License in the COPYING file at the + root directory of this project for more details. +*/ + +#include +#include + +#include "vevent.h" +#include "environment.h" +#include "trace.h" +#include "vformat.h" // comes from opensync, but not a public header yet +#include +#include +#include +#include + + +////////////////////////////////////////////////////////////////////////////// +// vCalendar + +vCalendar::vCalendar() + : m_gCalData(0) +{ +} + +vCalendar::~vCalendar() +{ + if( m_gCalData ) { + g_free(m_gCalData); + } +} + +const char *vCalendar::WeekDays[] = { "SU", "MO", "TU", "WE", "TH", "FR", "SA" }; + +unsigned short vCalendar::GetWeekDayIndex(const char *dayname) +{ + for( int i = 0; i < 7; i++ ) { + if( strcasecmp(dayname, WeekDays[i]) == 0 ) + return i; + } + return 0; +} + +bool vCalendar::HasMultipleVEvents() const +{ + int count = 0; + b_VFormat *format = const_cast(Format()); + GList *attrs = format ? b_vformat_get_attributes(format) : 0; + for( ; attrs; attrs = attrs->next ) { + b_VFormatAttribute *attr = (b_VFormatAttribute*) attrs->data; + if( strcasecmp(b_vformat_attribute_get_name(attr), "BEGIN") == 0 && + strcasecmp(b_vformat_attribute_get_nth_value(attr, 0), "VEVENT") == 0 ) + { + count++; + } + } + return count > 1; +} + +void vCalendar::RecurToVCal() +{ + using namespace Barry; + using namespace std; + Barry::Calendar &cal = m_BarryCal; + + if( !cal.Recurring ) + return; + + vAttrPtr attr = NewAttr("RRULE"); + + switch( cal.RecurringType ) + { + case Calendar::Day: // eg. every day + AddParam(attr, "FREQ", "DAILY"); + break; + + case Calendar::MonthByDate: // eg. every month on the 12th + // see: DayOfMonth + AddParam(attr, "FREQ", "MONTHLY"); + { + ostringstream oss; + oss << cal.DayOfMonth; + AddParam(attr, "BYMONTHDAY", oss.str().c_str()); + } + break; + + case Calendar::MonthByDay: // eg. every month on 3rd Wed + // see: DayOfWeek and WeekOfMonth + AddParam(attr, "FREQ", "MONTHLY"); + if( cal.DayOfWeek <= 6 ) { // DayOfWeek is unsigned + ostringstream oss; + oss << cal.WeekOfMonth << WeekDays[cal.DayOfWeek]; + AddParam(attr, "BYDAY", oss.str().c_str()); + } + break; + + case Calendar::YearByDate: // eg. every year on March 5 + // see: DayOfMonth and MonthOfYear + AddParam(attr, "FREQ", "YEARLY"); + { + ostringstream oss; + oss << cal.MonthOfYear; + AddParam(attr, "BYMONTH", oss.str().c_str()); + } + { + ostringstream oss; + oss << cal.DayOfMonth; + AddParam(attr, "BYMONTHDAY", oss.str().c_str()); + } + break; + + case Calendar::YearByDay: // eg. every year on 3rd Wed of Jan + // see: DayOfWeek, WeekOfMonth, and + // MonthOfYear + AddParam(attr, "FREQ", "YEARLY"); + if( cal.DayOfWeek <= 6 ) { // DayOfWeek is unsigned + ostringstream oss; + oss << cal.WeekOfMonth << WeekDays[cal.DayOfWeek]; + AddParam(attr, "BYDAY", oss.str().c_str()); + + oss.str(""); + oss << cal.MonthOfYear; + AddParam(attr, "BYMONTH", oss.str().c_str()); + } + break; + + case Calendar::Week: // eg. every week on Mon and Fri + // see: WeekDays + AddParam(attr, "FREQ", "WEEKLY"); + { + ostringstream oss; + for( int i = 0, bm = 1, cnt = 0; i < 7; i++, bm <<= 1 ) { + if( cal.WeekDays & bm ) { + if( cnt ) + oss << ","; + oss << WeekDays[i]; + cnt++; + } + } + AddParam(attr, "BYDAY", oss.str().c_str()); + } + break; + + default: + throw ConvertError("Unknown RecurringType in Barry Calendar object"); + } + + // add some common parameters + if( cal.Interval > 1 ) { + ostringstream oss; + oss << cal.Interval; + AddParam(attr, "INTERVAL", oss.str().c_str()); + } + if( !cal.Perpetual ) { + gStringPtr rend(osync_time_unix2vtime(&cal.RecurringEndTime)); + AddParam(attr, "UNTIL", rend.Get()); + } + + AddAttr(attr); + +/* + bool AllDayEvent; + + /// + /// Recurring data + /// + /// Note: interval can be used on all of these recurring types to + /// make it happen "every other time" or more, etc. + /// + + bool Recurring; + RecurringCodeType RecurringType; + unsigned short Interval; // must be >= 1 + time_t RecurringEndTime; // only pertains if Recurring is true + // sets the date and time when + // recurrence of this appointment + // should no longer occur + // If a perpetual appointment, this + // is 0xFFFFFFFF in the low level data + // Instead, set the following flag. + bool Perpetual; // if true, this will always recur + unsigned short TimeZoneCode; // the time zone originally used + // for the recurrence data... + // seems to have little use, but + // set to your current time zone + // as a good default + + unsigned short // recurring details, depending on type + DayOfWeek, // 0-6 + WeekOfMonth, // 1-5 + DayOfMonth, // 1-31 + MonthOfYear; // 1-12 + unsigned char WeekDays; // bitmask, bit 0 = sunday + + #define CAL_WD_SUN 0x01 + #define CAL_WD_MON 0x02 + #define CAL_WD_TUE 0x04 + #define CAL_WD_WED 0x08 + #define CAL_WD_THU 0x10 + #define CAL_WD_FRI 0x20 + #define CAL_WD_SAT 0x40 + +*/ + +} + +void vCalendar::RecurToBarryCal() +{ + // FIXME - needs to be implemented + + // GetWeekDayIndex() +} + +// Main conversion routine for converting from Barry::Calendar to +// a vCalendar string of data. +const std::string& vCalendar::ToVCal(const Barry::Calendar &cal) +{ + Trace trace("vCalendar::ToVCal"); + std::ostringstream oss; + cal.Dump(oss); + trace.logf("ToVCal, initial Barry record: %s", oss.str().c_str()); + + // start fresh + Clear(); + SetFormat( b_vformat_new() ); + if( !Format() ) + throw ConvertError("resource error allocating vformat"); + + // store the Barry object we're working with + m_BarryCal = cal; + + // begin building vCalendar data + AddAttr(NewAttr("PRODID", "-//OpenSync//NONSGML Barry Calendar Record//EN")); + AddAttr(NewAttr("BEGIN", "VEVENT")); + AddAttr(NewAttr("SEQUENCE", "0")); + AddAttr(NewAttr("SUMMARY", cal.Subject.c_str())); + AddAttr(NewAttr("DESCRIPTION", cal.Notes.c_str())); + AddAttr(NewAttr("LOCATION", cal.Location.c_str())); + + gStringPtr start(osync_time_unix2vtime(&cal.StartTime)); + gStringPtr end(osync_time_unix2vtime(&cal.EndTime)); + gStringPtr notify(osync_time_unix2vtime(&cal.NotificationTime)); + + AddAttr(NewAttr("DTSTART", start.Get())); + AddAttr(NewAttr("DTEND", end.Get())); + // FIXME - add a truly globally unique "UID" string? + + + AddAttr(NewAttr("BEGIN", "VALARM")); + AddAttr(NewAttr("ACTION", "AUDIO")); + + // notify must be UTC, when specified in DATE-TIME + vAttrPtr trigger = NewAttr("TRIGGER", notify.Get()); + AddParam(trigger, "VALUE", "DATE-TIME"); + AddAttr(trigger); + + AddAttr(NewAttr("END", "VALARM")); + + + if( cal.Recurring ) { + RecurToVCal(); + } + + AddAttr(NewAttr("END", "VEVENT")); + + // generate the raw VCALENDAR data + m_gCalData = b_vformat_to_string(Format(), VFORMAT_EVENT_20); + m_vCalData = m_gCalData; + + trace.logf("ToVCal, resulting vcal data: %s", m_vCalData.c_str()); + return m_vCalData; +} + +// Main conversion routine for converting from vCalendar data string +// to a Barry::Calendar object. +const Barry::Calendar& vCalendar::ToBarry(const char *vcal, uint32_t RecordId) +{ + using namespace std; + + Trace trace("vCalendar::ToBarry"); + trace.logf("ToBarry, working on vcal data: %s", vcal); + + // we only handle vCalendar data with one vevent block + if( HasMultipleVEvents() ) + throw ConvertError("vCalendar data contains more than one VEVENT block, unsupported"); + + // start fresh + Clear(); + + // store the vCalendar raw data + m_vCalData = vcal; + + // create format parser structures + SetFormat( b_vformat_new_from_string(vcal) ); + if( !Format() ) + throw ConvertError("resource error allocating vformat"); + + string start = GetAttr("DTSTART", "/vevent"); + trace.logf("DTSTART attr retrieved: %s", start.c_str()); + string end = GetAttr("DTEND", "/vevent"); + trace.logf("DTEND attr retrieved: %s", end.c_str()); + string subject = GetAttr("SUMMARY", "/vevent"); + trace.logf("SUMMARY attr retrieved: %s", subject.c_str()); + if( subject.size() == 0 ) { + subject = ""; + trace.logf("ERROR: bad data, blank SUMMARY: %s", vcal); + } + vAttr trigger_obj = GetAttrObj("TRIGGER", 0, "/valarm"); + + string location = GetAttr("LOCATION", "/vevent"); + trace.logf("LOCATION attr retrieved: %s", location.c_str()); + + string notes = GetAttr("DESCRIPTION", "/vevent"); + trace.logf("DESCRIPTION attr retrieved: %s", notes.c_str()); + + + // + // Now, run checks and convert into Barry object + // + + + // FIXME - we are assuming that any non-UTC timestamps + // in the vcalendar record will be in the current timezone... + // This is wrong! fix this later. + // + // Also, we current ignore any time zone + // parameters that might be in the vcalendar format... this + // must be fixed. + // + time_t now = time(NULL); + int zoneoffset = osync_time_timezone_diff(localtime(&now)); + + Barry::Calendar &rec = m_BarryCal; + rec.SetIds(Barry::Calendar::GetDefaultRecType(), RecordId); + + if( !start.size() ) + throw ConvertError("Blank DTSTART"); + rec.StartTime = osync_time_vtime2unix(start.c_str(), zoneoffset); + + if( !end.size() ) { + // DTEND is actually optional! According to the + // RFC, a DTSTART with no DTEND should be treated + // like a "special day" like an anniversary, which occupies + // no time. + // + // Since the Blackberry doesn't really map well to this + // case, we'll set the end time to 1 day past start. + // + rec.EndTime = rec.StartTime + 24 * 60 * 60; + } + else { + rec.EndTime = osync_time_vtime2unix(end.c_str(), zoneoffset); + } + + rec.Subject = subject; + rec.Location = location; + rec.Notes = notes; + + // convert trigger time into notification time + // assume no notification, by default + rec.NotificationTime = 0; + if( trigger_obj.Get() ) { + string trigger_type = trigger_obj.GetParam("VALUE"); + string trigger = trigger_obj.GetValue(); + + if( trigger.size() == 0 ) { + trace.logf("ERROR: no TRIGGER found in calendar entry, assuming notification time as 15 minutes before start."); + } + else if( trigger_type == "DATE-TIME" ) { + rec.NotificationTime = osync_time_vtime2unix(trigger.c_str(), zoneoffset); + } + else if( trigger_type == "DURATION" || trigger_type.size() == 0 ) { + // default is DURATION (RFC 4.8.6.3) + string related = trigger_obj.GetParam("RELATED"); + + // default to relative to start time + time_t *relative = &rec.StartTime; + if( related == "END" ) + relative = &rec.EndTime; + + rec.NotificationTime = *relative + osync_time_alarmdu2sec(trigger.c_str()); + } + else { + throw ConvertError("Unknown TRIGGER VALUE"); + } + } + else { + trace.logf("ERROR: no TRIGGER found in calendar entry, assuming notification time as 15 minutes before start."); + } + + std::ostringstream oss; + m_BarryCal.Dump(oss); + trace.logf("ToBarry, resulting Barry record: %s", oss.str().c_str()); + return m_BarryCal; +} + +// Transfers ownership of m_gCalData to the caller. +char* vCalendar::ExtractVCal() +{ + char *ret = m_gCalData; + m_gCalData = 0; + return ret; +} + +void vCalendar::Clear() +{ + vBase::Clear(); + m_vCalData.clear(); + m_BarryCal.Clear(); + + if( m_gCalData ) { + g_free(m_gCalData); + m_gCalData = 0; + } +} + + + +////////////////////////////////////////////////////////////////////////////// +// + +VEventConverter::VEventConverter() + : m_Data(0) +{ +} + +VEventConverter::VEventConverter(uint32_t newRecordId) + : m_Data(0), + m_RecordId(newRecordId) +{ +} + +VEventConverter::~VEventConverter() +{ + if( m_Data ) + g_free(m_Data); +} + +// Transfers ownership of m_Data to the caller +char* VEventConverter::ExtractData() +{ + Trace trace("VEventConverter::ExtractData"); + char *ret = m_Data; + m_Data = 0; + return ret; +} + +bool VEventConverter::ParseData(const char *data) +{ + Trace trace("VEventConverter::ParseData"); + + try { + + vCalendar vcal; + m_Cal = vcal.ToBarry(data, m_RecordId); + + } + catch( vCalendar::ConvertError &ce ) { + trace.logf("ERROR: vCalendar::ConvertError exception: %s", ce.what()); + return false; + } + + return true; +} + +// Barry storage operator +void VEventConverter::operator()(const Barry::Calendar &rec) +{ + Trace trace("VEventConverter::operator()"); + + // Delete data if some already exists + if( m_Data ) { + g_free(m_Data); + m_Data = 0; + } + + try { + + vCalendar vcal; + vcal.ToVCal(rec); + m_Data = vcal.ExtractVCal(); + + } + catch( vCalendar::ConvertError &ce ) { + trace.logf("ERROR: vCalendar::ConvertError exception: %s", ce.what()); + } +} + +// Barry builder operator +bool VEventConverter::operator()(Barry::Calendar &rec, unsigned int dbId) +{ + Trace trace("VEventConverter::builder operator()"); + + rec = m_Cal; + return true; +} + +// Handles calling of the Barry::Controller to fetch a specific +// record, indicated by index (into the RecordStateTable). +// Returns a g_malloc'd string of data containing the vevent20 +// data. It is the responsibility of the caller to free it. +// This is intended to be passed into the GetChanges() function. +char* VEventConverter::GetRecordData(BarryEnvironment *env, unsigned int dbId, + Barry::RecordStateTable::IndexType index) +{ + Trace trace("VEventConverter::GetRecordData()"); + + using namespace Barry; + + VEventConverter cal2event; + RecordParser parser(cal2event); + env->m_pDesktop->GetRecord(dbId, index, parser); + return cal2event.ExtractData(); +} + +bool VEventConverter::CommitRecordData(BarryEnvironment *env, unsigned int dbId, + Barry::RecordStateTable::IndexType StateIndex, uint32_t recordId, + const char *data, bool add, std::string &errmsg) +{ + Trace trace("VEventConverter::CommitRecordData()"); + + uint32_t newRecordId; + if( add ) { + // use given id if possible + if( recordId && !env->m_CalendarSync.m_Table.GetIndex(recordId) ) { + // recordId is unique and non-zero + newRecordId = recordId; + } + else { + trace.log("Can't use recommended recordId, generating new one."); + newRecordId = env->m_CalendarSync.m_Table.MakeNewRecordId(); + } + } + else { + newRecordId = env->m_CalendarSync.m_Table.StateMap[StateIndex].RecordId; + } + trace.logf("newRecordId: %lu", newRecordId); + + VEventConverter convert(newRecordId); + if( !convert.ParseData(data) ) { + std::ostringstream oss; + oss << "unable to parse change data for new RecordId: " + << newRecordId << " data: " << data; + errmsg = oss.str(); + trace.logf(errmsg.c_str()); + return false; + } + + Barry::RecordBuilder builder(convert); + + if( add ) { + trace.log("adding record"); + env->m_pDesktop->AddRecord(dbId, builder); + } + else { + trace.log("setting record"); + env->m_pDesktop->SetRecord(dbId, StateIndex, builder); + trace.log("clearing dirty flag"); + env->m_pDesktop->ClearDirty(dbId, StateIndex); + } + + return true; +} + diff --git a/opensync-plugin-0.4x/src/vevent.h b/opensync-plugin-0.4x/src/vevent.h new file mode 100644 index 00000000..80c2427b --- /dev/null +++ b/opensync-plugin-0.4x/src/vevent.h @@ -0,0 +1,115 @@ +// +// \file vevent.h +// Conversion routines for vevents (VCALENDAR, etc) +// + +/* + Copyright (C) 2006-2009, Net Direct Inc. (http://www.netdirect.ca/) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License in the COPYING file at the + root directory of this project for more details. +*/ + +#ifndef __BARRY_SYNC_VEVENT_H__ +#define __BARRY_SYNC_VEVENT_H__ + +#include +#include +#include +#include "vbase.h" +#include "vformat.h" + +// forward declarations +class BarryEnvironment; + +// +// vCalendar +// +/// Class for converting between RFC 2445 iCalendar data format, +/// and the Barry::Calendar class. +/// +class vCalendar : public vBase +{ + // data to pass to external requests + char *m_gCalData; // dynamic memory returned by vformat()... can + // be used directly by the plugin, without + // overmuch allocation and freeing (see Extract()) + std::string m_vCalData; // copy of m_gCalData, for C++ use + Barry::Calendar m_BarryCal; + + static const char *WeekDays[7]; + +protected: + void RecurToVCal(); + void RecurToBarryCal(); + + static unsigned short GetWeekDayIndex(const char *dayname); + bool HasMultipleVEvents() const; + +public: + vCalendar(); + ~vCalendar(); + + const std::string& ToVCal(const Barry::Calendar &cal); + const Barry::Calendar& ToBarry(const char *vcal, uint32_t RecordId); + + const std::string& GetVCal() const { return m_vCalData; } + const Barry::Calendar& GetBarryCal() const { return m_BarryCal; } + + char* ExtractVCal(); + + void Clear(); +}; + + +class VEventConverter +{ + char *m_Data; + Barry::Calendar m_Cal; + uint32_t m_RecordId; + +public: + VEventConverter(); + explicit VEventConverter(uint32_t newRecordId); + ~VEventConverter(); + + // Transfers ownership of m_Data to the caller + char* ExtractData(); + + // Parses vevent data + bool ParseData(const char *data); + + // Barry storage operator + void operator()(const Barry::Calendar &rec); + + // Barry builder operator + bool operator()(Barry::Calendar &rec, unsigned int dbId); + + // Handles calling of the Barry::Controller to fetch a specific + // record, indicated by index (into the RecordStateTable). + // Returns a g_malloc'd string of data containing the vevent20 + // data. It is the responsibility of the caller to free it. + // This is intended to be passed into the GetChanges() function. + static char* GetRecordData(BarryEnvironment *env, unsigned int dbId, + Barry::RecordStateTable::IndexType index); + + // Handles either adding or overwriting a calendar record, + // given vevent20 data in data, and the proper environmebnt, + // dbId, StateIndex. Set add to true if adding. + static bool CommitRecordData(BarryEnvironment *env, unsigned int dbId, + Barry::RecordStateTable::IndexType StateIndex, uint32_t recordId, + const char *data, bool add, std::string &errmsg); +}; + + +#endif + diff --git a/opensync-plugin-0.4x/src/vformat.c b/opensync-plugin-0.4x/src/vformat.c new file mode 100644 index 00000000..6bc1b211 --- /dev/null +++ b/opensync-plugin-0.4x/src/vformat.c @@ -0,0 +1,2125 @@ +/* + * Copyright (C) 2003 Ximian, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Author: Chris Toshok (toshok@ximian.com) + * Author: Armin Bauer (armin.bauer@opensync.org) + * + */ + +#include "vformat.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include + +static size_t base64_encode_step(unsigned char *in, size_t len, gboolean break_lines, unsigned char *out, int *state, int *save); +static size_t base64_decode_step(unsigned char *in, size_t len, unsigned char *out, int *state, unsigned int *save); +static size_t base64_decode_simple (char *data, size_t len); +static char *base64_encode_simple (const char *data, size_t len); + +static size_t quoted_decode_simple (char *data, size_t len); +static char *quoted_encode_simple (const unsigned char *string, int len); + + +/** + * _helper_is_base64 is helper function to check i a string is "b" or "base64" + * @param check_string string that should be compared with "b" or "base64" + * @return 0 if check_string is not base64 and 1 if it is + */ +static int _helper_is_base64(const char *check_string) +{ + if(!g_ascii_strcasecmp ((char *) check_string, "BASE64") || + !g_ascii_strcasecmp ((char *) check_string, "b") ) + return (1); + return (0); +} + +time_t b_vformat_time_to_unix(const char *inptime) +{ + char *date = NULL; + char *time = NULL; + char *ftime = NULL; + if ((ftime = g_strrstr(inptime, "T"))) { + + date = g_strndup(inptime, ftime - inptime); + if (ftime[3] == ':') + time = g_strndup(ftime + 1, 8); + else + time = g_strndup(ftime + 1, 6); + } else { + date = g_strdup(inptime); + } + + struct tm btime; + memset(&btime, 0, sizeof(struct tm)); + btime.tm_isdst = -1; + + if (strlen(date) == 10) { + btime.tm_year = date[0] * 1000 + date[1] * 100 + date[2] * 10 + date[3] - '0' * 1111 - 1900; + btime.tm_mon = date[5] * 10 + date[6] - '0' * 11 - 1; + btime.tm_mday = date[8] * 10 + date[9] - '0' * 11; + } else { + btime.tm_year = date[0] * 1000 + date[1] * 100 + date[2] * 10 + date[3] - '0' * 1111- 1900; + btime.tm_mon = date[4] * 10 + date[5] - '0' * 11 - 1; + btime.tm_mday = date[6] * 10 + date[7] - '0' * 11; + } + + if (time && strlen(time) == 8) { + //Time + btime.tm_hour = time[0] * 10 + time[1] - '0' * 11; + btime.tm_min = time[3] * 10 + time[4] - '0' * 11; + btime.tm_sec = time[6] * 10 + time[7] - '0' * 11; + } else if (time && strlen(time) == 6) { + btime.tm_hour = time[0] * 10 + time[1] - '0' * 11; + btime.tm_min = time[2] * 10 + time[3] - '0' * 11; + btime.tm_sec = time[4] * 10 + time[5] - '0' * 11; + } + + time_t utime = mktime(&btime); + return utime; +} + +static char *_fold_lines (char *buf) +{ + GString *str = g_string_new (""); + GString *line = g_string_new (""); + char *p = buf; + char *next, *next2, *q; + gboolean newline = TRUE; + gboolean quotedprintable = FALSE; + + /* + * We're pretty liberal with line folding here. We handle + * lines folded with \r\n, \n\r, \n, =\r\n and =\n\r. + * We also turn single \r's and \n's not followed by into \r\n's. + */ + + while (*p) { + + /* search new lines for quoted printable encoding */ + if (newline) { + for (q=p; *q != '\n' && *q != '\0'; q++) + line = g_string_append_unichar (line, g_utf8_get_char (q)); + + if (strstr(line->str, "ENCODING=QUOTED-PRINTABLE")) + quotedprintable = TRUE; + + g_string_free(line, TRUE); + line = g_string_new (""); + + newline = FALSE; + } + + + if ((quotedprintable && *p == '=') || *p == '\r' || *p == '\n') { + next = g_utf8_next_char (p); + if (*next == '\n' || *next == '\r') { + next2 = g_utf8_next_char (next); + if (*next2 == '\n' || *next2 == '\r' || *next2 == ' ' || *next2 == '\t') { + p = g_utf8_next_char (next2); + } + else { + str = g_string_append (str, CRLF); + p = g_utf8_next_char (next); + newline = TRUE; + quotedprintable = FALSE; + } + } + else if (*p == '=') { + str = g_string_append_unichar (str, g_utf8_get_char (p)); + p = g_utf8_next_char (p); + } + else if (*next == ' ' || *next == '\t') { + p = g_utf8_next_char (next); + } + else { + str = g_string_append (str, CRLF); + p = g_utf8_next_char (p); + newline = TRUE; + quotedprintable = FALSE; + } + } + else { + str = g_string_append_unichar (str, g_utf8_get_char (p)); + p = g_utf8_next_char (p); + } + } + + g_free (buf); + g_string_free(line, TRUE); + + return g_string_free (str, FALSE); +} + +/* skip forward until we hit the CRLF, or \0 */ +static void _skip_to_next_line (char **p) +{ + char *lp; + lp = *p; + + while (*lp != '\r' && *lp != '\0') + lp = g_utf8_next_char (lp); + + if (*lp == '\r') { + lp = g_utf8_next_char (lp); /* \n */ + lp = g_utf8_next_char (lp); /* start of the next line */ + } + + *p = lp; +} + +/* skip forward until we hit a character in @s, CRLF, or \0. leave *p + pointing at the character that causes us to stop */ +static void _skip_until (char **p, char *s) +{ + char *lp; + + lp = *p; + + while (*lp != '\r' && *lp != '\0') { + gboolean s_matches = FALSE; + char *ls; + for (ls = s; *ls; ls = g_utf8_next_char (ls)) { + if (g_utf8_get_char (ls) == g_utf8_get_char (lp)) { + s_matches = TRUE; + break; + } + } + + if (s_matches) + break; + lp++; + } + + *p = lp; +} + +static void _read_attribute_value_add (b_VFormatAttribute *attr, GString *str, GString *charset) +{ + /* don't convert empty strings */ + if (str->len == 0) { + b_vformat_attribute_add_value(attr, str->str); + return; + } + + char *inbuf, *outbuf, *p; + size_t inbytesleft, outbytesleft; + + inbuf = str->str; + p = outbuf = malloc(str->len*2); + inbytesleft = str->len; + outbytesleft = str->len*2; + + iconv_t cd; + + /* if a CHARSET was given, let's try to convert inbuf to UTF-8 */ + if (charset) { + + cd = iconv_open("UTF-8", charset->str); +#ifdef SOLARIS + if (iconv(cd, (const char**)&inbuf, &inbytesleft, &p, &outbytesleft) != (size_t)(-1)) { +#else + if (iconv(cd, &inbuf, &inbytesleft, &p, &outbytesleft) != (size_t)(-1)) { +#endif + *p = 0; + b_vformat_attribute_add_value(attr, outbuf); + + } else { + + /* hmm, should not happen */ + b_vformat_attribute_add_value(attr, str->str); + + } + + iconv_close(cd); + + } else { + + /* no CHARSET was given, if inbuf is already UTF-8 we add str->str */ + if (g_utf8_validate (inbuf, -1, NULL)) { + + b_vformat_attribute_add_value (attr, str->str); + + } else { + + /* because inbuf is not UTF-8, we think it is ISO-8859-1 */ + cd = iconv_open("UTF-8", "ISO-8859-1"); +#ifdef SOLARIS + if (iconv(cd, (const char**)&inbuf, &inbytesleft, &p, &outbytesleft) != (size_t)(-1)) { +#else + if (iconv(cd, &inbuf, &inbytesleft, &p, &outbytesleft) != (size_t)(-1)) { +#endif + *p = 0; + b_vformat_attribute_add_value (attr, outbuf); + + } else { + + b_vformat_attribute_add_value (attr, str->str); + + } + + iconv_close(cd); + + } + + } + + free(outbuf); + +} + +static void _read_attribute_value (b_VFormatAttribute *attr, char **p, int format_encoding, GString *charset) +{ + char *lp = *p; + GString *str; + + /* read in the value */ + str = g_string_new (""); + while (*lp != '\r' && *lp != '\0') { + if (*lp == '=' && format_encoding == VF_ENCODING_QP) { + char a, b, x1=0, x2=0; + + if ((a = *(++lp)) == '\0') break; + if ((b = *(++lp)) == '\0') break; + + if (isalnum(a)) { + if (isalnum(b)) { + /* e.g. ...N=C3=BCrnberg\r\n + * ^^^ + */ + x1=a; + x2=b; + } + else if (b == '=') { + /* e.g. ...N=C=\r\n + * ^^^ + * 3=BCrnberg... + * ^ + */ + char *tmplp = lp; + if (*(++tmplp) == '\r' && *(++tmplp) == '\n' && isalnum(*(++tmplp))) { + x1 = a; + x2 = *tmplp; + lp = tmplp; + } + } + else { + /* append malformed input, and + continue parsing */ + str = g_string_append_c(str, a); + str = g_string_append_c(str, b); + } + } + else if (a == '=') { + char *tmplp = lp; + char c, d, e; + c = *(++tmplp); + d = *(++tmplp); + e = *(++tmplp); + if (b == '\r' && c == '\n' && isalnum(d) && isalnum(e)) { + x1 = d; + x2 = e; + lp = tmplp; + } + else { + /* append malformed input, and + continue parsing */ + str = g_string_append_c(str, a); + str = g_string_append_c(str, b); + } + } + else { + /* append malformed input, and + continue parsing */ + str = g_string_append_c(str, a); + str = g_string_append_c(str, b); + } + if (x1 && x2) { + char c; + + a = tolower (x1); + b = tolower (x2); + + c = (((a>='a'?a-'a'+10:a-'0')&0x0f) << 4) + | ((b>='a'?b-'a'+10:b-'0')&0x0f); + + str = g_string_append_c (str, c); + } + lp++; + x1 = x2 = 0; + } + else if (format_encoding == VF_ENCODING_BASE64) { + if((*lp != ' ') && (*lp != '\t') ) + str = g_string_append_unichar (str, g_utf8_get_char (lp)); + lp = g_utf8_next_char(lp); + } + else if (*lp == '\\') { + /* convert back to the non-escaped version of + the characters */ + lp = g_utf8_next_char(lp); + if (*lp == '\0') { + str = g_string_append_c (str, '\\'); + break; + } + switch (*lp) { + case 'n': str = g_string_append_c (str, '\n'); break; + case 'r': str = g_string_append_c (str, '\r'); break; + case ';': str = g_string_append_c (str, ';'); break; + case ',': + if (!g_ascii_strcasecmp (attr->name, "CATEGORIES")) { + //We need to handle categories here to work + //aroung a bug in evo2 + _read_attribute_value_add (attr, str, charset); + g_string_assign (str, ""); + } else + str = g_string_append_c (str, ','); + break; + case '\\': str = g_string_append_c (str, '\\'); break; + case '"': str = g_string_append_c (str, '"'); break; + /* \t is (incorrectly) used by kOrganizer, so handle it here */ + case 't': str = g_string_append_c (str, '\t'); break; + default: + osync_trace(TRACE_INTERNAL, "invalid escape, passing it through. escaped char was %i", *lp); + str = g_string_append_c (str, '\\'); + str = g_string_append_unichar (str, g_utf8_get_char(lp)); + break; + } + lp = g_utf8_next_char(lp); + } + else if ((*lp == ';') || + (*lp == ',' && !g_ascii_strcasecmp (attr->name, "CATEGORIES"))) { + _read_attribute_value_add (attr, str, charset); + g_string_assign (str, ""); + lp = g_utf8_next_char(lp); + } + else { + str = g_string_append_unichar (str, g_utf8_get_char (lp)); + lp = g_utf8_next_char(lp); + } + } + if (str) { + _read_attribute_value_add (attr, str, charset); + g_string_free (str, TRUE); + } + + if (*lp == '\r') { + lp = g_utf8_next_char (lp); /* \n */ + lp = g_utf8_next_char (lp); /* start of the next line */ + } + + *p = lp; +} + +static void _read_attribute_params(b_VFormatAttribute *attr, char **p, int *format_encoding, GString **charset) +{ + char *lp = *p; + GString *str; + b_VFormatParam *param = NULL; + gboolean in_quote = FALSE; + str = g_string_new (""); + + while (*lp != '\0') { + if (*lp == '"') { + in_quote = !in_quote; + lp = g_utf8_next_char (lp); + } + else if (in_quote || g_unichar_isalnum (g_utf8_get_char (lp)) || *lp == '-' || *lp == '_' || *lp == '/' || *lp == '.' || *lp == ' ') { + str = g_string_append_unichar (str, g_utf8_get_char (lp)); + lp = g_utf8_next_char (lp); + } + /* accumulate until we hit the '=' or ';'. If we hit + * a '=' the string contains the parameter name. if + * we hit a ';' the string contains the parameter + * value and the name is either ENCODING (if value == + * QUOTED-PRINTABLE) or TYPE (in any other case.) + */ + else if (*lp == '=') { + if (str->len > 0) { + param = b_vformat_attribute_param_new (str->str); + g_string_assign (str, ""); + lp = g_utf8_next_char (lp); + } + else { + _skip_until (&lp, ":;"); + if (*lp == '\r') { + lp = g_utf8_next_char (lp); /* \n */ + lp = g_utf8_next_char (lp); /* start of the next line */ + break; + } + else if (*lp == ';') + lp = g_utf8_next_char (lp); + } + } + else if (*lp == ';' || *lp == ':' || *lp == ',') { + gboolean colon = (*lp == ':'); + gboolean comma = (*lp == ','); + + if (param) { + if (str->len > 0) { + b_vformat_attribute_param_add_value (param, str->str); + g_string_assign (str, ""); + if (!colon) + lp = g_utf8_next_char (lp); + } + else { + /* we've got a parameter of the form: + * PARAM=(.*,)?[:;] + * so what we do depends on if there are already values + * for the parameter. If there are, we just finish + * this parameter and skip past the offending character + * (unless it's the ':'). If there aren't values, we free + * the parameter then skip past the character. + */ + if (!param->values) { + b_vformat_attribute_param_free (param); + param = NULL; + if (!colon) + lp = g_utf8_next_char (lp); + } + } + + if (param + && !g_ascii_strcasecmp (param->name, "encoding")) { + if (!g_ascii_strcasecmp (param->values->data, "quoted-printable")) { + *format_encoding = VF_ENCODING_QP; + b_vformat_attribute_param_free (param); + param = NULL; + } else if ( _helper_is_base64(param->values->data)) { + *format_encoding = VF_ENCODING_BASE64; +// b_vformat_attribute_param_free (param); +// param = NULL; + } + } else if (param && !g_ascii_strcasecmp(param->name, "charset")) { + *charset = g_string_new(param->values->data); + b_vformat_attribute_param_free (param); + param = NULL; + } + } + else { + if (str->len > 0) { + char *param_name; + if (!g_ascii_strcasecmp (str->str, + "quoted-printable")) { + param_name = "ENCODING"; + *format_encoding = VF_ENCODING_QP; + } + /* apple's broken addressbook app outputs naked BASE64 + parameters, which aren't even vcard 3.0 compliant. */ + else if (!g_ascii_strcasecmp (str->str, + "base64")) { + param_name = "ENCODING"; + g_string_assign (str, "b"); + *format_encoding = VF_ENCODING_BASE64; + } + else { + param_name = "TYPE"; + } + + if (param_name) { + param = b_vformat_attribute_param_new (param_name); + b_vformat_attribute_param_add_value (param, str->str); + } + g_string_assign (str, ""); + if (!colon) + lp = g_utf8_next_char (lp); + } + else { + /* we've got an attribute with a truly empty + attribute parameter. So it's of the form: + + ATTR;[PARAM=value;]*;[PARAM=value;]*: + + (note the extra ';') + + the only thing to do here is, well.. nothing. + we skip over the character if it's not a colon, + and the rest is handled for us: We'll either + continue through the loop again if we hit a ';', + or we'll break out correct below if it was a ':' */ + if (!colon) + lp = g_utf8_next_char (lp); + } + } + if (param && !comma) { + b_vformat_attribute_add_param (attr, param); + param = NULL; + } + if (colon) + break; + } + else { + osync_trace(TRACE_INTERNAL, "invalid character found in parameter spec: \"%i\" String so far: %s", lp[0], str->str); + g_string_assign (str, ""); + _skip_until (&lp, ":;"); + } + } + + if (str) + g_string_free (str, TRUE); + + *p = lp; +} + +/* reads an entire attribute from the input buffer, leaving p pointing + at the start of the next line (past the \r\n) */ +static b_VFormatAttribute *_read_attribute (char **p) +{ + char *attr_group = NULL; + char *attr_name = NULL; + b_VFormatAttribute *attr = NULL; + GString *str, *charset = NULL; + char *lp = *p; + + gboolean is_qp = FALSE; + + /* first read in the group/name */ + str = g_string_new (""); + while (*lp != '\r' && *lp != '\0') { + if (*lp == ':' || *lp == ';') { + if (str->len != 0) { + /* we've got a name, break out to the value/attribute parsing */ + attr_name = g_string_free (str, FALSE); + break; + } + else { + /* a line of the form: + * (group.)?[:;] + * + * since we don't have an attribute + * name, skip to the end of the line + * and try again. + */ + g_string_free (str, TRUE); + *p = lp; + _skip_to_next_line(p); + goto lose; + } + } + else if (*lp == '.') { + if (attr_group) { + osync_trace(TRACE_INTERNAL, "extra `.' in attribute specification. ignoring extra group `%s'", + str->str); + g_string_free (str, TRUE); + str = g_string_new (""); + } + if (str->len != 0) { + attr_group = g_string_free (str, FALSE); + str = g_string_new (""); + } + } + else if (g_unichar_isalnum (g_utf8_get_char (lp)) || *lp == '-' || *lp == '_' || *lp == '/') { + str = g_string_append_unichar (str, g_utf8_get_char (lp)); + } + else { + osync_trace(TRACE_INTERNAL, "invalid character found in attribute group/name: \"%i\" String so far: %s", lp[0], str->str); + g_string_free (str, TRUE); + *p = lp; + _skip_to_next_line(p); + goto lose; + } + + lp = g_utf8_next_char(lp); + } + + if (!attr_name) { + _skip_to_next_line (p); + goto lose; + } + + attr = b_vformat_attribute_new (attr_group, attr_name); + g_free (attr_group); + g_free (attr_name); + + if (*lp == ';') { + /* skip past the ';' */ + lp = g_utf8_next_char(lp); + _read_attribute_params (attr, &lp, &is_qp, &charset); + } + if (*lp == ':') { + /* skip past the ':' */ + lp = g_utf8_next_char(lp); + _read_attribute_value (attr, &lp, is_qp, charset); + } + + if (charset) g_string_free(charset, TRUE); + *p = lp; + + if (!attr->values) + goto lose; + + return attr; + lose: + if (attr) + b_vformat_attribute_free (attr); + return NULL; +} + +static void open_block(char **block, const char *block_name) +{ + char *start = *block ? *block : ""; + char *result = NULL; + + result = g_strconcat(start, "/", block_name, NULL); + if( *block ) + g_free(*block); + *block = result; +} + +static void close_block(char **block, const char *block_name) +{ + int name_len = strlen(block_name); + int block_len = *block ? strlen(*block) : 0; + char *cmp_start = NULL; + + if( block_len < name_len + 1 ) + return; + + cmp_start = *block + (block_len - name_len - 1); + if( cmp_start[0] == '/' && + g_ascii_strcasecmp(cmp_start+1, block_name) == 0 ) + { + // end of block hierarchy contains block name, + // so safe to remove + + // cut off the end of the string... no need to free/realloc + *cmp_start = '\0'; + } +} + +/* we try to be as forgiving as we possibly can here - this isn't a + * validator. Almost nothing is considered a fatal error. We always + * try to return *something*. + */ +static void _parse(b_VFormat *evc, const char *str) +{ + char *buf = g_strdup (str); + char *p, *end; + b_VFormatAttribute *attr; + + /* first validate the string is valid utf8 */ + if (!g_utf8_validate (buf, -1, (const char **)&end)) { + /* if the string isn't valid, we parse as much as we can from it */ + osync_trace(TRACE_INTERNAL, "invalid utf8 passed to b_VFormat. Limping along."); + *end = '\0'; + } + + buf = _fold_lines (buf); + + p = buf; + + attr = _read_attribute (&p); + if (!attr) + attr = _read_attribute (&p); + + if (!attr || attr->group || g_ascii_strcasecmp (attr->name, "begin")) { + osync_trace(TRACE_INTERNAL, "vformat began without a BEGIN\n"); + } + if (attr && !g_ascii_strcasecmp (attr->name, "begin")) + b_vformat_attribute_free (attr); + else if (attr) + b_vformat_add_attribute (evc, attr); + + char *block = NULL; + while (*p) { + b_VFormatAttribute *next_attr = _read_attribute (&p); + + if (next_attr) { + if( g_ascii_strcasecmp(next_attr->name, "begin") == 0 ) { + // add to block hierarchy string + char *value = b_vformat_attribute_get_value(next_attr); + open_block(&block, value); + //osync_trace(TRACE_INTERNAL, "open block: %s", block); + g_free(value); + } + else if( g_ascii_strcasecmp(next_attr->name, "end") == 0 ) { + // close off the block + char *value = b_vformat_attribute_get_value(next_attr); + close_block(&block, value); + //osync_trace(TRACE_INTERNAL, "close block: %s", block); + g_free(value); + } + + // apply the block to the attr + next_attr->block = g_strdup(block); + + // add! + b_vformat_add_attribute (evc, next_attr); + attr = next_attr; + } + } + + if (!attr || attr->group || g_ascii_strcasecmp (attr->name, "end")) { + osync_trace(TRACE_INTERNAL, "vformat ended without END"); + } + + g_free (buf); + g_free (block); +} + +char *b_vformat_escape_string (const char *s, b_VFormatType type) +{ + GString *str; + const char *p; + + str = g_string_new (""); + + /* Escape a string as described in RFC2426, section 5 */ + for (p = s; p && *p; p++) { + switch (*p) { + case '\n': + str = g_string_append (str, "\\n"); + break; + case '\r': + if (*(p+1) == '\n') + p++; + str = g_string_append (str, "\\n"); + break; + case ';': + str = g_string_append (str, "\\;"); + break; + case ',': + if (type == VFORMAT_CARD_30 || type == VFORMAT_EVENT_20 || type == VFORMAT_TODO_20) + str = g_string_append (str, "\\,"); + else + str = g_string_append_c (str, *p); + break; + case '\\': + /** + * We won't escape backslashes + * on vcard 2.1, unless it is in the end of a value. + * See comments above for a better explanation + **/ + if (*p != '\0' && type == VFORMAT_CARD_21) { + osync_trace(TRACE_INTERNAL, "[%s]We won't escape backslashes", __func__); + str = g_string_append_c(str, *p); + } + else { + osync_trace(TRACE_INTERNAL, "[%s] escape backslashes!!", __func__); + str = g_string_append (str, "\\\\"); + } + break; + default: + str = g_string_append_c (str, *p); + break; + } + } + + return g_string_free (str, FALSE); +} + +char* +b_vformat_unescape_string (const char *s) +{ + GString *str; + const char *p; + + g_return_val_if_fail (s != NULL, NULL); + + str = g_string_new (""); + + /* Unescape a string as described in RFC2426, section 4 (Formal Grammar) */ + for (p = s; *p; p++) { + if (*p == '\\') { + p++; + if (*p == '\0') { + str = g_string_append_c (str, '\\'); + break; + } + switch (*p) { + case 'n': str = g_string_append_c (str, '\n'); break; + case 'r': str = g_string_append_c (str, '\r'); break; + case ';': str = g_string_append_c (str, ';'); break; + case ',': str = g_string_append_c (str, ','); break; + case '\\': str = g_string_append_c (str, '\\'); break; + case '"': str = g_string_append_c (str, '"'); break; + /* \t is (incorrectly) used by kOrganizer, so handle it here */ + case 't': str = g_string_append_c (str, '\t'); break; + default: + osync_trace(TRACE_INTERNAL, "invalid escape, passing it through. escaped char was %s", *p); + str = g_string_append_c (str, '\\'); + str = g_string_append_unichar (str, g_utf8_get_char(p)); + break; + } + } + } + + return g_string_free (str, FALSE); +} + +void +b_vformat_construct (b_VFormat *evc, const char *str) +{ + g_return_if_fail (str != NULL); + + if (*str) + _parse (evc, str); +} + +void b_vformat_free(b_VFormat *format) +{ + g_list_foreach (format->attributes, (GFunc)b_vformat_attribute_free, NULL); + g_list_free (format->attributes); + g_free(format); +} + +b_VFormat *b_vformat_new_from_string (const char *str) +{ + g_return_val_if_fail (str != NULL, NULL); + b_VFormat *evc = g_malloc0(sizeof(b_VFormat)); + + b_vformat_construct (evc, str); + + return evc; +} + +b_VFormat *b_vformat_new(void) +{ + return b_vformat_new_from_string (""); +} + +static int _block_match(b_VFormatAttribute *attr, const char *block) +{ + // a block matches if the end of the attribute's block + // string matches a case insensitive compare with block + // + // for example, a calendar may or may not start with a + // BEGIN: VCALENDAR, so DTSTART's block string could be + // "/vcalendar/vevent" or just "/vevent". By passing + // "/vevent" or even "vevent" as the block argument above, + // we should get a match for any of the above. + + int attr_len = attr->block ? strlen(attr->block) : 0; + int block_len = block ? strlen(block) : 0; + + if( block == NULL ) + return 1; // if block is null, match everything + + if( attr_len < block_len ) + return 0; // not enough string to compare + + if( attr_len == 0 && block_len == 0 ) + return 1; // empty and null strings match + + if( attr->block == NULL ) + return 0; // don't compare if one side is null + + return g_ascii_strcasecmp(&attr->block[attr_len - block_len], block) == 0; +} + +b_VFormatAttribute *b_vformat_find_attribute(b_VFormat *vcard, const char *name, int nth, const char *block) +{ + GList *attributes = b_vformat_get_attributes(vcard); + GList *a = NULL; + int i = 0; + for (a = attributes; a; a = a->next) { + b_VFormatAttribute *attr = a->data; + if (!g_ascii_strcasecmp(b_vformat_attribute_get_name(attr), name)) { + if( block == NULL || _block_match(attr, block) ) { + if( i == nth ) + return attr; + i++; + } + } + } + return NULL; +} + +/* +b_VFormatAttribute *b_vformat_find_attribute_next(b_VFormatAttribute *last, + const char *name, + int nth) +{ + GList *attributes = last ? last->next : 0; + GList *a = NULL; + int i = 0; + for (a = attributes; a; a = a->next) { + b_VFormatAttribute *attr = a->data; + if (!g_ascii_strcasecmp(b_vformat_attribute_get_name(attr), name)) { + if( i == nth ) + return attr; + i++; + } + } + return NULL; +} +*/ + +char *b_vformat_to_string (b_VFormat *evc, b_VFormatType type) +{ + osync_trace(TRACE_ENTRY, "%s(%p, %i)", __func__, type); + GList *l; + GList *v; + + GString *str = g_string_new (""); + + switch (type) { + case VFORMAT_CARD_21: + str = g_string_append (str, "BEGIN:VCARD\r\nVERSION:2.1\r\n"); + break; + case VFORMAT_CARD_30: + str = g_string_append (str, "BEGIN:VCARD\r\nVERSION:3.0\r\n"); + break; + case VFORMAT_TODO_10: + case VFORMAT_EVENT_10: + str = g_string_append (str, "BEGIN:VCALENDAR\r\nVERSION:1.0\r\n"); + break; + case VFORMAT_TODO_20: + case VFORMAT_EVENT_20: + str = g_string_append (str, "BEGIN:VCALENDAR\r\nVERSION:2.0\r\n"); + break; + case VFORMAT_NOTE: + str = g_string_append (str, "BEGIN:VNOTE\r\nVERSION:1.1\r\n"); + break; + } + + for (l = evc->attributes; l; l = l->next) { + GList *p; + b_VFormatAttribute *attr = l->data; + GString *attr_str; + int l; + int format_encoding = VF_ENCODING_RAW; + + attr_str = g_string_new (""); + + /* From rfc2425, 5.8.2 + * + * contentline = [group "."] name *(";" param) ":" value CRLF + */ + + if (attr->group) { + attr_str = g_string_append (attr_str, attr->group); + attr_str = g_string_append_c (attr_str, '.'); + } + attr_str = g_string_append (attr_str, attr->name); + /* handle the parameters */ + for (p = attr->params; p; p = p->next) { + b_VFormatParam *param = p->data; + /* 5.8.2: + * param = param-name "=" param-value *("," param-value) + */ + if( type == VFORMAT_CARD_30 || type == VFORMAT_TODO_20 + || type == VFORMAT_EVENT_20) { + + /** + * Character set can only be specified on the CHARSET + * parameter on the Content-Type MIME header field. + **/ + if (!g_ascii_strcasecmp (param->name, "CHARSET")) + continue; + attr_str = g_string_append_c (attr_str, ';'); + attr_str = g_string_append (attr_str, param->name); + if (param->values) { + attr_str = g_string_append_c (attr_str, '='); + } + for (v = param->values; v; v = v->next) { + if (_helper_is_base64((const char *) v->data)) { + format_encoding = VF_ENCODING_BASE64; + /*Only the "B" encoding of [RFC 2047] is an allowed*/ + v->data="B"; + } + /** + * QUOTED-PRINTABLE inline encoding has been + * eliminated. + **/ + if (!g_ascii_strcasecmp (param->name, "ENCODING") && !g_ascii_strcasecmp ((char *) v->data, "QUOTED-PRINTABLE")) { + osync_trace(TRACE_ERROR, "%s false encoding QUOTED-PRINTABLE is not allowed", __func__); + format_encoding = VF_ENCODING_QP; + } + attr_str = g_string_append (attr_str, v->data); + + if (v->next) + attr_str = g_string_append_c (attr_str, ','); + } + } + else { + attr_str = g_string_append_c (attr_str, ';'); + /** + * The "TYPE=" is optional skip it. + * LOGO, PHOTO and SOUND multimedia formats MUST + * have a "TYPE=" parameter + **/ + gboolean must_have_type = FALSE; + if (!g_ascii_strcasecmp (attr->name, "PHOTO") || !g_ascii_strcasecmp (attr->name, "LOGO") || !g_ascii_strcasecmp (attr->name, "SOUND") ) + must_have_type = TRUE; + if ( must_have_type || g_ascii_strcasecmp (param->name, "TYPE") ) + attr_str = g_string_append (attr_str, param->name); + if ( param->values && (must_have_type || g_ascii_strcasecmp (param->name, "TYPE")) ) + attr_str = g_string_append_c (attr_str, '='); + for (v = param->values; v; v = v->next) { + // check for quoted-printable encoding + if (!g_ascii_strcasecmp (param->name, "ENCODING") && !g_ascii_strcasecmp ((char *) v->data, "QUOTED-PRINTABLE")) + format_encoding = VF_ENCODING_QP; + // check for base64 encoding + if (_helper_is_base64((const char *) v->data)) { + format_encoding = VF_ENCODING_BASE64; + v->data="BASE64"; + } + attr_str = g_string_append (attr_str, v->data); + if (v->next) + attr_str = g_string_append_c (attr_str, ','); + } + } + } + + attr_str = g_string_append_c (attr_str, ':'); + + for (v = attr->values; v; v = v->next) { + char *value = v->data; + char *escaped_value = NULL; + + if (!g_ascii_strcasecmp (attr->name, "RRULE") && + strstr (value, "BYDAY") == v->data) { + attr_str = g_string_append (attr_str, value); + } else { + escaped_value = b_vformat_escape_string (value, type); + attr_str = g_string_append (attr_str, escaped_value); + } + + if (v->next) { + + /* XXX toshok - i hate you, rfc 2426. + why doesn't CATEGORIES use a ; like + a normal list attribute? */ + if (!g_ascii_strcasecmp (attr->name, "CATEGORIES")) + attr_str = g_string_append_c (attr_str, ','); + else + attr_str = g_string_append_c (attr_str, ';'); + } + + g_free (escaped_value); + } + + /* Folding lines: + * ^^^^^^^^^^^^^^ + * + * rfc 2426 (vCard), 2.6 Line Delimiting and Folding: + * After generating a content line, + * lines longer than 75 characters SHOULD be folded according to the + * folding procedure described in [MIME-DIR]. + * + * rfc 2445 (iCalendar), 4.1 Content Lines: + * Lines of text SHOULD NOT be longer than 75 octets, excluding the line + * break. Long content lines SHOULD be split into a multiple line + * representations using a line "folding" technique. That is, a long + * line can be split between any two characters by inserting a CRLF + * immediately followed by a single linear white space character (i.e., + * SPACE, US-ASCII decimal 32 or HTAB, US-ASCII decimal 9). Any sequence + * of CRLF followed immediately by a single linear white space character + * is ignored (i.e., removed) when processing the content type. + * + * SUMMARY: When generating a content line, lines longer then 75 characters SHOULD be folded! + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * + * Differences between encodings: + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * + * rfc 2425 [MIME-DIR], 5.8.1: + * A logical line MAY be continued on the next physical line anywhere + * between two characters by inserting a CRLF immediately followed by a + * single (white space) character. + * + * rfc 2045, 6.7, chapter 5: + * The quoted-printable specs says that softbreaks should be generated by inserting a =\r\n + * without follwing + * + * UTF-8 + * ^^^^^ + * + * Note that all the line folding above is described in terms of characters + * not bytes. In particular, it would be an error to put a line break + * within a UTF-8 character. + */ + + l = 0; + do { + if (g_utf8_strlen(attr_str->str, attr_str->len) - l > 75) { + l += 75; + + /* If using QP, must be sure that we do not fold within a quote sequence */ + if (format_encoding == VF_ENCODING_QP) { + if (g_utf8_get_char(g_utf8_offset_to_pointer(attr_str->str, l-1)) == '=') l--; + else if (g_utf8_get_char(g_utf8_offset_to_pointer(attr_str->str, l-2)) == '=') l -= 2; + } + + char *p = g_utf8_offset_to_pointer(attr_str->str, l); + + if (format_encoding == VF_ENCODING_QP) + attr_str = g_string_insert_len (attr_str, p - attr_str->str, "=" CRLF "", sizeof ("=" CRLF "") - 1); + else + attr_str = g_string_insert_len (attr_str, p - attr_str->str, CRLF " ", sizeof (CRLF " ") - 1); + } + else + break; + } while (l < g_utf8_strlen(attr_str->str, attr_str->len)); + + attr_str = g_string_append (attr_str, CRLF); + /** + * base64= + * the end of the text is marked with two CRLF sequences + * this results in one blank line before the start of the + * next property + **/ + if( format_encoding == VF_ENCODING_BASE64 + && (type == VFORMAT_CARD_21)) + attr_str = g_string_append (attr_str, CRLF); + + str = g_string_append (str, attr_str->str); + g_string_free (attr_str, TRUE); + } + + switch (type) { + case VFORMAT_CARD_21: + str = g_string_append (str, "END:VCARD\r\n"); + break; + case VFORMAT_CARD_30: + str = g_string_append (str, "END:VCARD\r\n"); + break; + case VFORMAT_TODO_10: + case VFORMAT_EVENT_10: + str = g_string_append (str, "END:VCALENDAR\r\n"); + break; + case VFORMAT_TODO_20: + case VFORMAT_EVENT_20: + str = g_string_append (str, "END:VCALENDAR\r\n"); + break; + case VFORMAT_NOTE: + str = g_string_append (str, "END:VNOTE\r\n"); + break; + } + + osync_trace(TRACE_EXIT, "%s(%p, %i)", __func__, type); + return g_string_free (str, FALSE); +} + +void b_vformat_dump_structure (b_VFormat *evc) +{ + GList *a; + GList *v; + int i; + + printf ("b_VFormat\n"); + for (a = evc->attributes; a; a = a->next) { + GList *p; + b_VFormatAttribute *attr = a->data; + printf ("+-- %s\n", attr->name); + if (attr->params) { + printf (" +- params=\n"); + + for (p = attr->params, i = 0; p; p = p->next, i++) { + b_VFormatParam *param = p->data; + printf (" | [%d] = %s", i,param->name); + printf ("("); + for (v = param->values; v; v = v->next) { + char *value = b_vformat_escape_string ((char*)v->data, VFORMAT_CARD_21); + printf ("%s", value); + if (v->next) + printf (","); + g_free (value); + } + + printf (")\n"); + } + } + printf (" +- values=\n"); + for (v = attr->values, i = 0; v; v = v->next, i++) { + printf (" [%d] = `%s'\n", i, (char*)v->data); + } + } +} + +b_VFormatAttribute *b_vformat_attribute_new (const char *attr_group, const char *attr_name) +{ + b_VFormatAttribute *attr; + + attr = g_new0 (b_VFormatAttribute, 1); + + attr->group = g_strdup (attr_group); + attr->name = g_strdup (attr_name); + + return attr; +} + +void +b_vformat_attribute_free (b_VFormatAttribute *attr) +{ + g_return_if_fail (attr != NULL); + + g_free (attr->block); + g_free (attr->group); + g_free (attr->name); + + b_vformat_attribute_remove_values (attr); + + b_vformat_attribute_remove_params (attr); + + g_free (attr); +} + +b_VFormatAttribute* +b_vformat_attribute_copy (b_VFormatAttribute *attr) +{ + b_VFormatAttribute *a; + GList *p; + + g_return_val_if_fail (attr != NULL, NULL); + + a = b_vformat_attribute_new (b_vformat_attribute_get_group (attr), + b_vformat_attribute_get_name (attr)); + + for (p = attr->values; p; p = p->next) + b_vformat_attribute_add_value (a, p->data); + + for (p = attr->params; p; p = p->next) + b_vformat_attribute_add_param (a, b_vformat_attribute_param_copy (p->data)); + + return a; +} + +void +b_vformat_remove_attributes (b_VFormat *evc, const char *attr_group, const char *attr_name) +{ + GList *attr; + + g_return_if_fail (attr_name != NULL); + + attr = evc->attributes; + while (attr) { + GList *next_attr; + b_VFormatAttribute *a = attr->data; + + next_attr = attr->next; + + if (((!attr_group && !a->group) || + (attr_group && !g_ascii_strcasecmp (attr_group, a->group))) && + ((!attr_name && !a->name) || !g_ascii_strcasecmp (attr_name, a->name))) { + + /* matches, remove/delete the attribute */ + evc->attributes = g_list_remove_link (evc->attributes, attr); + + b_vformat_attribute_free (a); + } + + attr = next_attr; + } +} + +void +b_vformat_remove_attribute (b_VFormat *evc, b_VFormatAttribute *attr) +{ + g_return_if_fail (attr != NULL); + + evc->attributes = g_list_remove (evc->attributes, attr); + b_vformat_attribute_free (attr); +} + +void +b_vformat_add_attribute (b_VFormat *evc, b_VFormatAttribute *attr) +{ + g_return_if_fail (attr != NULL); + + evc->attributes = g_list_append (evc->attributes, attr); +} + +void +b_vformat_add_attribute_with_value (b_VFormat *b_VFormat, + b_VFormatAttribute *attr, const char *value) +{ + g_return_if_fail (attr != NULL); + + b_vformat_attribute_add_value (attr, value); + + b_vformat_add_attribute (b_VFormat, attr); +} + +void +b_vformat_add_attribute_with_values (b_VFormat *b_VFormat, b_VFormatAttribute *attr, ...) +{ + va_list ap; + char *v; + + g_return_if_fail (attr != NULL); + + va_start (ap, attr); + + while ((v = va_arg (ap, char*))) { + b_vformat_attribute_add_value (attr, v); + } + + va_end (ap); + + b_vformat_add_attribute (b_VFormat, attr); +} + +void +b_vformat_attribute_add_value (b_VFormatAttribute *attr, const char *value) +{ + g_return_if_fail (attr != NULL); + + attr->values = g_list_append (attr->values, g_strdup (value)); +} + +void +b_vformat_attribute_add_value_decoded (b_VFormatAttribute *attr, const char *value, int len) +{ + g_return_if_fail (attr != NULL); + + switch (attr->encoding) { + case VF_ENCODING_RAW: + osync_trace(TRACE_INTERNAL, "can't add_value_decoded with an attribute using RAW encoding. you must set the ENCODING parameter first"); + break; + case VF_ENCODING_BASE64: { + char *b64_data = base64_encode_simple (value, len); + GString *decoded = g_string_new_len (value, len); + + /* make sure the decoded list is up to date */ + b_vformat_attribute_get_values_decoded (attr); + + attr->values = g_list_append (attr->values, b64_data); + attr->decoded_values = g_list_append (attr->decoded_values, decoded); + break; + } + case VF_ENCODING_QP: { + char *qp_data = quoted_encode_simple ((unsigned char*)value, len); + GString *decoded = g_string_new (value); + + /* make sure the decoded list is up to date */ + b_vformat_attribute_get_values_decoded (attr); + + attr->values = g_list_append (attr->values, qp_data); + attr->decoded_values = g_list_append (attr->decoded_values, decoded); + break; + } + case VF_ENCODING_8BIT: { + char *data = g_strdup(value); + GString *decoded = g_string_new (value); + + /* make sure the decoded list is up to date */ + b_vformat_attribute_get_values_decoded (attr); + + attr->values = g_list_append (attr->values, data); + attr->decoded_values = g_list_append (attr->decoded_values, decoded); + break; + } + } +} + +void +b_vformat_attribute_add_values (b_VFormatAttribute *attr, ...) +{ + va_list ap; + char *v; + + g_return_if_fail (attr != NULL); + + va_start (ap, attr); + + while ((v = va_arg (ap, char*))) { + b_vformat_attribute_add_value (attr, v); + } + + va_end (ap); +} + +static void +free_gstring (GString *str) +{ + g_string_free (str, TRUE); +} + +void +b_vformat_attribute_remove_values (b_VFormatAttribute *attr) +{ + g_return_if_fail (attr != NULL); + + g_list_foreach (attr->values, (GFunc)g_free, NULL); + g_list_free (attr->values); + attr->values = NULL; + + g_list_foreach (attr->decoded_values, (GFunc)free_gstring, NULL); + g_list_free (attr->decoded_values); + attr->decoded_values = NULL; +} + +void +b_vformat_attribute_remove_params (b_VFormatAttribute *attr) +{ + g_return_if_fail (attr != NULL); + + g_list_foreach (attr->params, (GFunc)b_vformat_attribute_param_free, NULL); + g_list_free (attr->params); + attr->params = NULL; + + /* also remove the cached encoding on this attribute */ + attr->encoding_set = FALSE; + attr->encoding = VF_ENCODING_RAW; +} + +b_VFormatParam* +b_vformat_attribute_param_new (const char *name) +{ + b_VFormatParam *param = g_new0 (b_VFormatParam, 1); + param->name = g_strdup (name); + + return param; +} + +void +b_vformat_attribute_param_free (b_VFormatParam *param) +{ + g_return_if_fail (param != NULL); + + g_free (param->name); + + b_vformat_attribute_param_remove_values (param); + + g_free (param); +} + +b_VFormatParam* +b_vformat_attribute_param_copy (b_VFormatParam *param) +{ + b_VFormatParam *p; + GList *l; + + g_return_val_if_fail (param != NULL, NULL); + + p = b_vformat_attribute_param_new (b_vformat_attribute_param_get_name (param)); + + for (l = param->values; l; l = l->next) { + b_vformat_attribute_param_add_value (p, l->data); + } + + return p; +} + +void +b_vformat_attribute_add_param (b_VFormatAttribute *attr, + b_VFormatParam *param) +{ + g_return_if_fail (attr != NULL); + g_return_if_fail (param != NULL); + + attr->params = g_list_append (attr->params, param); + + /* we handle our special encoding stuff here */ + + if (!g_ascii_strcasecmp (param->name, "ENCODING")) { + if (attr->encoding_set) { + osync_trace(TRACE_INTERNAL, "ENCODING specified twice"); + return; + } + + if (param->values && param->values->data) { + if (_helper_is_base64((const char*)param->values->data)) + attr->encoding = VF_ENCODING_BASE64; + else if (!g_ascii_strcasecmp ((char*)param->values->data, "QUOTED-PRINTABLE")) + attr->encoding = VF_ENCODING_QP; + else if (!g_ascii_strcasecmp ((char *)param->values->data, "8BIT")) + attr->encoding = VF_ENCODING_8BIT; + else { + osync_trace(TRACE_INTERNAL, "Unknown value `%s' for ENCODING parameter. values will be treated as raw", + (char*)param->values->data); + } + + attr->encoding_set = TRUE; + } + else { + osync_trace(TRACE_INTERNAL, "ENCODING parameter added with no value"); + } + } +} + +b_VFormatParam *b_vformat_attribute_find_param(b_VFormatAttribute *attr, const char *name, int level) +{ + g_return_val_if_fail (attr != NULL, NULL); + GList *p = NULL; + for (p = attr->params; p; p = p->next) { + b_VFormatParam *param = p->data; + if (!g_ascii_strcasecmp (param->name, name)) { + if( level == 0 ) + return param; + else + level--; + } + } + return NULL; +} + +void +b_vformat_attribute_set_value (b_VFormatAttribute *attr, + int nth, const char *value) +{ + GList *param = g_list_nth(attr->values, nth); + g_free(param->data); + param->data = g_strdup(value); +} + +void +b_vformat_attribute_param_add_value (b_VFormatParam *param, + const char *value) +{ + g_return_if_fail (param != NULL); + + param->values = g_list_append (param->values, g_strdup (value)); +} + +void +b_vformat_attribute_param_add_values (b_VFormatParam *param, + ...) +{ + va_list ap; + char *v; + + g_return_if_fail (param != NULL); + + va_start (ap, param); + + while ((v = va_arg (ap, char*))) { + b_vformat_attribute_param_add_value (param, v); + } + + va_end (ap); +} + +void +b_vformat_attribute_add_param_with_value (b_VFormatAttribute *attr, const char *name, const char *value) +{ + g_return_if_fail (attr != NULL); + g_return_if_fail (name != NULL); + + if (!value) + return; + + b_VFormatParam *param = b_vformat_attribute_param_new(name); + + b_vformat_attribute_param_add_value (param, value); + + b_vformat_attribute_add_param (attr, param); +} + +void +b_vformat_attribute_add_param_with_values (b_VFormatAttribute *attr, + b_VFormatParam *param, ...) +{ + va_list ap; + char *v; + + g_return_if_fail (attr != NULL); + g_return_if_fail (param != NULL); + + va_start (ap, param); + + while ((v = va_arg (ap, char*))) { + b_vformat_attribute_param_add_value (param, v); + } + + va_end (ap); + + b_vformat_attribute_add_param (attr, param); +} + +void +b_vformat_attribute_param_remove_values (b_VFormatParam *param) +{ + g_return_if_fail (param != NULL); + + g_list_foreach (param->values, (GFunc)g_free, NULL); + g_list_free (param->values); + param->values = NULL; +} + +GList* +b_vformat_get_attributes (b_VFormat *format) +{ + return format->attributes; +} + +const char* +b_vformat_attribute_get_group (b_VFormatAttribute *attr) +{ + g_return_val_if_fail (attr != NULL, NULL); + + return attr->group; +} + +const char* +b_vformat_attribute_get_name (b_VFormatAttribute *attr) +{ + g_return_val_if_fail (attr != NULL, NULL); + + return attr->name; +} + +const char* +b_vformat_attribute_get_block (b_VFormatAttribute *attr) +{ + g_return_val_if_fail (attr != NULL, NULL); + + return attr->block; +} + +GList* +b_vformat_attribute_get_values (b_VFormatAttribute *attr) +{ + g_return_val_if_fail (attr != NULL, NULL); + + return attr->values; +} + +GList* +b_vformat_attribute_get_values_decoded (b_VFormatAttribute *attr) +{ + g_return_val_if_fail (attr != NULL, NULL); + + if (!attr->decoded_values) { + GList *l; + switch (attr->encoding) { + case VF_ENCODING_RAW: + case VF_ENCODING_8BIT: + for (l = attr->values; l; l = l->next) + attr->decoded_values = g_list_append (attr->decoded_values, g_string_new ((char*)l->data)); + break; + case VF_ENCODING_BASE64: + for (l = attr->values; l; l = l->next) { + char *decoded = g_strdup ((char*)l->data); + int len = base64_decode_simple (decoded, strlen (decoded)); + attr->decoded_values = g_list_append (attr->decoded_values, g_string_new_len (decoded, len)); + g_free (decoded); + } + break; + case VF_ENCODING_QP: + for (l = attr->values; l; l = l->next) { + if (!(l->data)) + continue; + char *decoded = g_strdup ((char*)l->data); + int len = quoted_decode_simple (decoded, strlen (decoded)); + attr->decoded_values = g_list_append (attr->decoded_values, g_string_new_len (decoded, len)); + g_free (decoded); + } + break; + } + } + + return attr->decoded_values; +} + +gboolean +b_vformat_attribute_is_single_valued (b_VFormatAttribute *attr) +{ + g_return_val_if_fail (attr != NULL, FALSE); + + if (attr->values == NULL + || attr->values->next != NULL) + return FALSE; + + return TRUE; +} + +char* +b_vformat_attribute_get_value (b_VFormatAttribute *attr) +{ + GList *values; + + g_return_val_if_fail (attr != NULL, NULL); + + values = b_vformat_attribute_get_values (attr); + + if (!b_vformat_attribute_is_single_valued (attr)) + osync_trace(TRACE_INTERNAL, "b_vformat_attribute_get_value called on multivalued attribute"); + + return values ? g_strdup ((char*)values->data) : NULL; +} + +GString* +b_vformat_attribute_get_value_decoded (b_VFormatAttribute *attr) +{ + GList *values; + GString *str = NULL; + + g_return_val_if_fail (attr != NULL, NULL); + + values = b_vformat_attribute_get_values_decoded (attr); + + if (!b_vformat_attribute_is_single_valued (attr)) + osync_trace(TRACE_INTERNAL, "b_vformat_attribute_get_value_decoded called on multivalued attribute"); + + if (values) + str = values->data; + + return str ? g_string_new_len (str->str, str->len) : NULL; +} + +const char *b_vformat_attribute_get_nth_value(b_VFormatAttribute *attr, int nth) +{ + GList *values = b_vformat_attribute_get_values_decoded(attr); + if (!values) + return NULL; + GString *retstr = (GString *)g_list_nth_data(values, nth); + if (!retstr) + return NULL; + + if (!g_utf8_validate(retstr->str, -1, NULL)) { + values = b_vformat_attribute_get_values(attr); + if (!values) + return NULL; + return g_list_nth_data(values, nth); + } + + return retstr->str; +} + +gboolean +b_vformat_attribute_has_type (b_VFormatAttribute *attr, const char *typestr) +{ + GList *params; + GList *p; + + g_return_val_if_fail (attr != NULL, FALSE); + g_return_val_if_fail (typestr != NULL, FALSE); + + params = b_vformat_attribute_get_params (attr); + + for (p = params; p; p = p->next) { + b_VFormatParam *param = p->data; + + if (!strcasecmp (b_vformat_attribute_param_get_name (param), "TYPE")) { + GList *values = b_vformat_attribute_param_get_values (param); + GList *v; + + for (v = values; v; v = v->next) { + if (!strcasecmp ((char*)v->data, typestr)) + return TRUE; + } + } + } + + return FALSE; +} + + +gboolean b_vformat_attribute_has_param(b_VFormatAttribute *attr, const char *name) +{ + g_return_val_if_fail (attr != NULL, FALSE); + g_return_val_if_fail (name != NULL, FALSE); + + GList *params = b_vformat_attribute_get_params(attr); + GList *p; + for (p = params; p; p = p->next) { + b_VFormatParam *param = p->data; + if (!strcasecmp(name, b_vformat_attribute_param_get_name(param))) + return TRUE; + } + return FALSE; +} + +GList* +b_vformat_attribute_get_params (b_VFormatAttribute *attr) +{ + g_return_val_if_fail (attr != NULL, NULL); + + return attr->params; +} + +const char* +b_vformat_attribute_param_get_name (b_VFormatParam *param) +{ + g_return_val_if_fail (param != NULL, NULL); + + return param->name; +} + +GList* +b_vformat_attribute_param_get_values (b_VFormatParam *param) +{ + g_return_val_if_fail (param != NULL, NULL); + + return param->values; +} + +const char *b_vformat_attribute_param_get_nth_value(b_VFormatParam *param, int nth) +{ + const char *ret = NULL; + GList *values = b_vformat_attribute_param_get_values(param); + if (!values) + return NULL; + ret = g_list_nth_data(values, nth); + return ret; +} + +static const char *base64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +//static unsigned char _evc_base64_rank[256]; + +static void base64_init(char *rank) +{ + int i; + + memset(rank, 0xff, sizeof(rank)); + for (i=0;i<64;i++) { + rank[(unsigned int)base64_alphabet[i]] = i; + } + rank['='] = 0; +} + +/* call this when finished encoding everything, to + flush off the last little bit */ +static size_t base64_encode_close(unsigned char *in, size_t inlen, gboolean break_lines, unsigned char *out, int *state, int *save) +{ + int c1, c2; + unsigned char *outptr = out; + + if (inlen>0) + outptr += base64_encode_step(in, inlen, break_lines, outptr, state, save); + + c1 = ((unsigned char *)save)[1]; + c2 = ((unsigned char *)save)[2]; + + switch (((char *)save)[0]) { + case 2: + outptr[2] = base64_alphabet[ ( (c2 &0x0f) << 2 ) ]; + g_assert(outptr[2] != 0); + goto skip; + case 1: + outptr[2] = '='; + skip: + outptr[0] = base64_alphabet[ c1 >> 2 ]; + outptr[1] = base64_alphabet[ c2 >> 4 | ( (c1&0x3) << 4 )]; + outptr[3] = '='; + outptr += 4; + break; + } + if (break_lines) + *outptr++ = '\n'; + + *save = 0; + *state = 0; + + return outptr-out; +} + +/* + performs an 'encode step', only encodes blocks of 3 characters to the + output at a time, saves left-over state in state and save (initialise to + 0 on first invocation). +*/ +static size_t base64_encode_step(unsigned char *in, size_t len, gboolean break_lines, unsigned char *out, int *state, int *save) +{ + register unsigned char *inptr, *outptr; + + if (len<=0) + return 0; + + inptr = in; + outptr = out; + + if (len + ((char *)save)[0] > 2) { + unsigned char *inend = in+len-2; + register int c1, c2, c3; + register int already; + + already = *state; + + switch (((char *)save)[0]) { + case 1: c1 = ((unsigned char *)save)[1]; goto skip1; + case 2: c1 = ((unsigned char *)save)[1]; + c2 = ((unsigned char *)save)[2]; goto skip2; + } + + /* yes, we jump into the loop, no i'm not going to change it, it's beautiful! */ + while (inptr < inend) { + c1 = *inptr++; + skip1: + c2 = *inptr++; + skip2: + c3 = *inptr++; + *outptr++ = base64_alphabet[ c1 >> 2 ]; + *outptr++ = base64_alphabet[ c2 >> 4 | ( (c1&0x3) << 4 ) ]; + *outptr++ = base64_alphabet[ ( (c2 &0x0f) << 2 ) | (c3 >> 6) ]; + *outptr++ = base64_alphabet[ c3 & 0x3f ]; + /* this is a bit ugly ... */ + if (break_lines && (++already)>=19) { + *outptr++='\n'; + already = 0; + } + } + + ((char *)save)[0] = 0; + len = 2-(inptr-inend); + *state = already; + } + + if (len>0) { + register char *saveout; + + /* points to the slot for the next char to save */ + saveout = & (((char *)save)[1]) + ((char *)save)[0]; + + /* len can only be 0 1 or 2 */ + switch(len) { + case 2: *saveout++ = *inptr++; + case 1: *saveout++ = *inptr++; + } + ((char *)save)[0]+=len; + } + + return outptr-out; +} + + +/** + * base64_decode_step: decode a chunk of base64 encoded data + * @in: input stream + * @len: max length of data to decode + * @out: output stream + * @state: holds the number of bits that are stored in @save + * @save: leftover bits that have not yet been decoded + * + * Decodes a chunk of base64 encoded data + **/ +static size_t base64_decode_step(unsigned char *in, size_t len, unsigned char *out, int *state, unsigned int *save) +{ + unsigned char base64_rank[256]; + base64_init((char*)base64_rank); + + register unsigned char *inptr, *outptr; + unsigned char *inend, c; + register unsigned int v; + int i; + + inend = in+len; + outptr = out; + + /* convert 4 base64 bytes to 3 normal bytes */ + v=*save; + i=*state; + inptr = in; + while (inptr>16; + *outptr++ = v>>8; + *outptr++ = v; + i=0; + } + } + } + + *save = v; + *state = i; + + /* quick scan back for '=' on the end somewhere */ + /* fortunately we can drop 1 output char for each trailing = (upto 2) */ + i=2; + while (inptr>in && i) { + inptr--; + if (base64_rank[*inptr] != 0xff) { + if (*inptr == '=' && outptr>out) + outptr--; + i--; + } + } + + /* if i!= 0 then there is a truncation error! */ + return outptr-out; +} + +static char *base64_encode_simple (const char *data, size_t len) +{ + unsigned char *out; + int state = 0, outlen; + unsigned int save = 0; + + g_return_val_if_fail (data != NULL, NULL); + + out = g_malloc (len * 4 / 3 + 5); + outlen = base64_encode_close ((unsigned char *)data, len, FALSE, + out, &state, (int*)&save); + out[outlen] = '\0'; + return (char *)out; +} + +static size_t base64_decode_simple (char *data, size_t len) +{ + int state = 0; + unsigned int save = 0; + + g_return_val_if_fail (data != NULL, 0); + + return base64_decode_step ((unsigned char *)data, len, + (unsigned char *)data, &state, &save); +} + +static char *quoted_encode_simple(const unsigned char *string, int len) +{ + GString *tmp = g_string_new(""); + + int i = 0; + while(string[i] != 0) { + if (string[i] > 127 || string[i] == 13 || string[i] == 10 || string[i] == '=') { + g_string_append_printf(tmp, "=%02X", string[i]); + } else { + g_string_append_c(tmp, string[i]); + } + i++; + } + + char *ret = tmp->str; + g_string_free(tmp, FALSE); + return ret; +} + + +static size_t quoted_decode_simple (char *data, size_t len) +{ + g_return_val_if_fail (data != NULL, 0); + + GString *string = g_string_new(data); + if (!string) + return 0; + + char hex[5]; + hex[4] = 0; + + while (1) { + //Get the index of the next encoded char + int i = strcspn(string->str, "="); + if (i >= strlen(string->str)) + break; + + strcpy(hex, "0x"); + strncat(hex, &string->str[i + 1], 2); + char rep = ((int)(strtod(hex, NULL))); + g_string_erase(string, i, 2); + g_string_insert_c(string, i, rep); + } + + memset(data, 0, strlen(data)); + strcpy(data, string->str); + g_string_free(string, 1); + + return strlen(data); +} diff --git a/opensync-plugin-0.4x/src/vformat.h b/opensync-plugin-0.4x/src/vformat.h new file mode 100644 index 00000000..cd97f2ac --- /dev/null +++ b/opensync-plugin-0.4x/src/vformat.h @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2003 Ximian, Inc. 2005 Armin Bauer + * + * Copyright (C) 2003 Ximian, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Author: Chris Toshok (toshok@ximian.com) + * Author: Armin Bauer (armin.bauer@opensync.org) + * + */ + +#ifndef _VFORMAT_H +#define _VFORMAT_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + VFORMAT_CARD_21, + VFORMAT_CARD_30, + VFORMAT_NOTE, + VFORMAT_EVENT_10, + VFORMAT_EVENT_20, + VFORMAT_TODO_10, + VFORMAT_TODO_20 +} b_VFormatType; + +typedef struct b_VFormat { + //b_VFormatType type; + GList *attributes; +} b_VFormat; + +#define CRLF "\r\n" + +typedef enum { + VF_ENCODING_RAW, /* no encoding */ + VF_ENCODING_BASE64, /* base64 */ + VF_ENCODING_QP, /* quoted-printable */ + VF_ENCODING_8BIT +} b_VFormatEncoding; + +typedef struct b_VFormatAttribute { + char *block; /* "vtimezone/standard", or "vevent", depending on + current begin/end location... may be null */ + char *group; + char *name; + GList *params; /* b_VFormatParam */ + GList *values; + GList *decoded_values; + b_VFormatEncoding encoding; + gboolean encoding_set; +} b_VFormatAttribute; + +typedef struct b_VFormatParam { + char *name; + GList *values; /* GList of char*'s*/ +} b_VFormatParam; + + +/*b_VFormat *vcard_new(b_VFormatType type); +b_VFormat *vcard_new_from_string (const char *str, b_VFormatType type); +//char *vcard_to_string(VFormat *card, VFormatType format); + +VFormat *vnote_new(void); +VFormat *vnote_new_from_string(const char *str); +//char *vnote_to_string(VFormat *note); + + +VFormat *vevent_new(void); +VFormat *vevent_new_from_string(const char *str); +//char *vevent_to_string(VFormat *event); + +VFormat *vtodo_new(void); +VFormat *vtodo_new_from_string(const char *str);*/ +//char *vtodo_to_string(VFormat *todo); + +/* mostly for debugging */ +b_VFormat *b_vformat_new(void); +b_VFormat *b_vformat_new_from_string(const char *str); +void b_vformat_dump_structure(b_VFormat *format); +char *b_vformat_to_string(b_VFormat *evc, b_VFormatType type); +time_t b_vformat_time_to_unix(const char *inptime); +void b_vformat_free(b_VFormat *format); + +/* attributes */ +b_VFormatAttribute *b_vformat_attribute_new (const char *attr_group, const char *attr_name); +void b_vformat_attribute_free (b_VFormatAttribute *attr); +b_VFormatAttribute *b_vformat_attribute_copy (b_VFormatAttribute *attr); +void b_vformat_remove_attributes (b_VFormat *vformat, const char *attr_group, const char *attr_name); +void b_vformat_remove_attribute (b_VFormat *vformat, b_VFormatAttribute *attr); +void b_vformat_add_attribute (b_VFormat *vformat, b_VFormatAttribute *attr); +void b_vformat_add_attribute_with_value (b_VFormat *vformat, b_VFormatAttribute *attr, const char *value); +void b_vformat_add_attribute_with_values (b_VFormat *vformat, b_VFormatAttribute *attr, ...); +void b_vformat_attribute_add_value (b_VFormatAttribute *attr, const char *value); +void b_vformat_attribute_set_value (b_VFormatAttribute *attr, int nth, const char *value); +void b_vformat_attribute_add_value_decoded (b_VFormatAttribute *attr, const char *value, int len); +void b_vformat_attribute_add_values (b_VFormatAttribute *attr, ...); +void b_vformat_attribute_remove_values (b_VFormatAttribute *attr); +void b_vformat_attribute_remove_params (b_VFormatAttribute *attr); +b_VFormatAttribute *b_vformat_find_attribute (b_VFormat *evc, const char *name, int nth, const char *block); + +/* attribute parameters */ +b_VFormatParam* b_vformat_attribute_param_new (const char *param_name); +void b_vformat_attribute_param_free (b_VFormatParam *param); +b_VFormatParam* b_vformat_attribute_param_copy (b_VFormatParam *param); +void b_vformat_attribute_add_param (b_VFormatAttribute *attr, b_VFormatParam *param); +b_VFormatParam *b_vformat_attribute_find_param(b_VFormatAttribute *attr, const char *name, int level); +void b_vformat_attribute_add_param_with_value (b_VFormatAttribute *attr, const char *name, const char *value); +void b_vformat_attribute_add_param_with_values (b_VFormatAttribute *attr, + b_VFormatParam *param, ...); + +void b_vformat_attribute_param_add_value (b_VFormatParam *param, + const char *value); +void b_vformat_attribute_param_add_values (b_VFormatParam *param, + ...); +void b_vformat_attribute_param_remove_values (b_VFormatParam *param); +gboolean b_vformat_attribute_has_param(b_VFormatAttribute *attr, const char *name); + +/* b_VFormat* accessors. nothing returned from these functions should be + freed by the caller. */ +GList* b_vformat_get_attributes (b_VFormat *vformat); +const char* b_vformat_attribute_get_group (b_VFormatAttribute *attr); +const char* b_vformat_attribute_get_name (b_VFormatAttribute *attr); +const char* b_vformat_attribute_get_block (b_VFormatAttribute *attr); +GList* b_vformat_attribute_get_values (b_VFormatAttribute *attr); /* GList elements are of type char* */ +GList* b_vformat_attribute_get_values_decoded (b_VFormatAttribute *attr); /* GList elements are of type GString* */ +const char *b_vformat_attribute_get_nth_value(b_VFormatAttribute *attr, int nth); + +/* special accessors for single valued attributes */ +gboolean b_vformat_attribute_is_single_valued (b_VFormatAttribute *attr); +char* b_vformat_attribute_get_value (b_VFormatAttribute *attr); +GString* b_vformat_attribute_get_value_decoded (b_VFormatAttribute *attr); + +GList* b_vformat_attribute_get_params (b_VFormatAttribute *attr); +const char* b_vformat_attribute_param_get_name (b_VFormatParam *param); +GList* b_vformat_attribute_param_get_values (b_VFormatParam *param); +const char *b_vformat_attribute_param_get_nth_value(b_VFormatParam *param, int nth); + +/* special TYPE= parameter predicate (checks for TYPE=@typestr */ +gboolean b_vformat_attribute_has_type (b_VFormatAttribute *attr, const char *typestr); + +/* Utility functions. */ +char* b_vformat_escape_string (const char *str, b_VFormatType type); +char* b_vformat_unescape_string (const char *str); + +#ifdef __cplusplus +} +#endif + +#endif /* _VFORMAT_H */ -- 2.11.4.GIT