added a sync source which stores contacts in a SQLite database
git-svn-id: https://zeitsenke.de/svn/SyncEvolution/trunk@381 15ad00c4-1369-45f4-8270-35d70d36bdcd
This commit is contained in:
parent
ce163f1bb1
commit
03aaa74e45
13 changed files with 986 additions and 29 deletions
34
configure.in
34
configure.in
|
@ -179,7 +179,7 @@ PKG_CHECK_MODULES(EPACKAGE, [$pkg_emodules_12], EDSFOUND=yes, [EDSFOUND=no])
|
|||
if test "x${EDSFOUND}" = "xno"; then
|
||||
PKG_CHECK_MODULES(EPACKAGE, [$pkg_emodules_11], EDSFOUND=yes, [EDSFOUND=no])
|
||||
if test "x${EDSFOUND}" = "xno"; then
|
||||
PKG_CHECK_MODULES(EPACKAGE, [$pkg_emodules_10],,AC_MSG_ERROR($evomissing))
|
||||
PKG_CHECK_MODULES(EPACKAGE, [$pkg_emodules_10], EDSFOUND=yes, [EDSFOUND=no])
|
||||
fi
|
||||
fi
|
||||
AC_SUBST(EPACKAGE_CFLAGS)
|
||||
|
@ -207,21 +207,39 @@ fi
|
|||
AC_SUBST(EBOOK_CFLAGS)
|
||||
AC_SUBST(EBOOK_LIBS)
|
||||
|
||||
|
||||
dnl check for sqlite
|
||||
PKG_CHECK_MODULES(SQLITE, sqlite3, SQLITEFOUND=yes, [SQLITEFOUND=no])
|
||||
AC_SUBST(SQLITE_CFLAGS)
|
||||
AC_SUBST(SQLITE_LIBS)
|
||||
|
||||
dnl select backends
|
||||
AC_ARG_ENABLE(ebook, AS_HELP_STRING([--disable-ebook], [disable access to addressbooks (default on if available)]),
|
||||
[enable_ebook="$enableval"], [enable_ebook=$EBOOKFOUND])
|
||||
AC_ARG_ENABLE(ecal, AS_HELP_STRING([--disable-ecal], [disable access to calendars and tasks (default on if available)]),
|
||||
[enable_ecal="$enableval"], [enable_ecal=$ECALFOUND])
|
||||
AC_ARG_ENABLE(sqlite, AS_HELP_STRING([--enable-sqlite], [enable access to PIM data stored in SQLite files (default off)]),
|
||||
[enable_sqlite="$enableval"], [enable_sqlite="no"])
|
||||
|
||||
enable_evo="no"
|
||||
enable_any="no"
|
||||
if test "$enable_ebook" = "yes"; then
|
||||
test "x${EBOOKFOUND}" == "xyes" || AC_MSG_ERROR(["--enable-ebook requires pkg-config information for libebook, which was not found"])
|
||||
AC_DEFINE(ENABLE_EBOOK, 1, [libebook available])
|
||||
enable_evo="yes"
|
||||
enable_any="yes"
|
||||
fi
|
||||
|
||||
if test "$enable_ecal" = "yes"; then
|
||||
test "x${ECALFOUND}" == "xyes" || AC_MSG_ERROR(["--enable-ecal requires pkg-config information for libecal, which was not found"])
|
||||
AC_DEFINE(ENABLE_ECAL, 1, [libecal available])
|
||||
enable_evo="yes"
|
||||
enable_any="yes"
|
||||
fi
|
||||
|
||||
if test "$enable_sqlite" = "yes"; then
|
||||
test "x${SQLITEFOUND}" == "xyes" || AC_MSG_ERROR(["--enable-sqlite requires pkg-config information for sqlite, which was not found"])
|
||||
AC_DEFINE(ENABLE_SQLITE, 1, [sqlite available])
|
||||
enable_any="yes"
|
||||
fi
|
||||
|
||||
|
@ -229,6 +247,17 @@ if test "$enable_any" = "no"; then
|
|||
AC_MSG_ERROR([no backend enabled - refusing to continue: $anymissing])
|
||||
fi
|
||||
|
||||
if test "$enable_evo" = "yes"; then
|
||||
if test "$EDSFOUND" = "yes"; then
|
||||
AC_DEFINE(HAVE_EDS, 1, [evolution-dataserver available])
|
||||
else
|
||||
AC_MSG_ERROR($evomissing)
|
||||
fi
|
||||
else
|
||||
EPACKAGE_CFLAGS=
|
||||
EPACKAGE_LIBS=
|
||||
fi
|
||||
|
||||
dnl check for glib - calling g_type_init() is expected on Maemo
|
||||
PKG_CHECK_MODULES(GLIB, "glib-2.0", GLIBFOUND=yes, GLIBFOUND=no)
|
||||
if test "x${GLIBFOUND}" = "xno"; then
|
||||
|
@ -252,6 +281,9 @@ fi
|
|||
if test "$enable_ecal" == "yes"; then
|
||||
SYNCEVOLUTION_MODULES="$SYNCEVOLUTION_MODULES syncecal.la"
|
||||
fi
|
||||
if test "$enable_sqlite" == "yes"; then
|
||||
SYNCEVOLUTION_MODULES="$SYNCEVOLUTION_MODULES syncsqlite.la"
|
||||
fi
|
||||
|
||||
dnl figure out whether we link all code statically or as modules
|
||||
if test "$enable_shared" == "yes"; then
|
||||
|
|
|
@ -138,6 +138,10 @@ class EvolutionCalendarSource : public EvolutionSyncSource
|
|||
void getChanges();
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
typedef int ECalSourceType;
|
||||
|
||||
#endif // ENABLE_ECAL
|
||||
|
||||
#endif // INCL_EVOLUTIONSYNCSOURCE
|
||||
|
|
|
@ -23,12 +23,14 @@
|
|||
#include "EvolutionContactSource.h"
|
||||
#include "EvolutionCalendarSource.h"
|
||||
#include "EvolutionMemoSource.h"
|
||||
#include "SQLiteContactSource.h"
|
||||
|
||||
#include <common/base/Log.h>
|
||||
|
||||
#include <list>
|
||||
#include <dlfcn.h>
|
||||
|
||||
#ifdef HAVE_EDS
|
||||
ESource *EvolutionSyncSource::findSource( ESourceList *list, const string &id )
|
||||
{
|
||||
for (GSList *g = e_source_list_peek_groups (list); g; g = g->next) {
|
||||
|
@ -47,21 +49,28 @@ ESource *EvolutionSyncSource::findSource( ESourceList *list, const string &id )
|
|||
}
|
||||
return NULL;
|
||||
}
|
||||
#endif // HAVE_EDS
|
||||
|
||||
void EvolutionSyncSource::throwError( const string &action, GError *gerror )
|
||||
void EvolutionSyncSource::throwError( const string &action
|
||||
#ifdef HAVE_EDS
|
||||
, GError *gerror
|
||||
#endif
|
||||
)
|
||||
{
|
||||
string gerrorstr;
|
||||
#ifdef HAVE_EDS
|
||||
if (gerror) {
|
||||
gerrorstr += ": ";
|
||||
gerrorstr += gerror->message;
|
||||
g_clear_error(&gerror);
|
||||
} else {
|
||||
} else
|
||||
#endif
|
||||
gerrorstr = ": failed";
|
||||
}
|
||||
|
||||
throw runtime_error(string(getName()) + ": " + action + gerrorstr);
|
||||
}
|
||||
|
||||
|
||||
void EvolutionSyncSource::resetItems()
|
||||
{
|
||||
m_allItems.clear();
|
||||
|
@ -143,6 +152,7 @@ EvolutionSyncSource *EvolutionSyncSource::createSource(
|
|||
const char *modules[] = {
|
||||
"syncebook.so.0",
|
||||
"syncecal.so.0",
|
||||
"syncsqlite.so.0",
|
||||
NULL
|
||||
};
|
||||
|
||||
|
@ -246,6 +256,14 @@ EvolutionSyncSource *EvolutionSyncSource::createSource(
|
|||
if (error) {
|
||||
throw runtime_error(name + ": access to calendars not compiled into this binary, " + mimeType + " not supported");
|
||||
}
|
||||
#endif
|
||||
} else if (mimeType == "sqlite") {
|
||||
#ifdef ENABLE_SQLITE
|
||||
return new SQLiteContactSource(name, sc, strippedChangeId, id);
|
||||
#else
|
||||
if (error) {
|
||||
throw runtime_error(name + ": access to sqlite not compiled into this binary, " + mimeType + " not supported");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif // ENABLE_MODULES
|
||||
|
|
|
@ -26,16 +26,18 @@
|
|||
#include <ostream>
|
||||
using namespace std;
|
||||
|
||||
#ifdef HAVE_EDS
|
||||
#include <libedataserver/e-source.h>
|
||||
#include <libedataserver/e-source-list.h>
|
||||
#endif
|
||||
|
||||
#include <spds/SyncSource.h>
|
||||
#include <spdm/ManagementNode.h>
|
||||
#include <base/Log.h>
|
||||
|
||||
/**
|
||||
* This class implements the functionality shared by
|
||||
* both EvolutionCalenderSource and EvolutionContactSource:
|
||||
* SyncEvolution accesses all sources through this interface. This
|
||||
* class also implements common functionality for all SyncSources:
|
||||
* - handling of change IDs and URI
|
||||
* - finding the calender/contact backend
|
||||
* - default implementation of SyncSource interface
|
||||
|
@ -249,6 +251,7 @@ class EvolutionSyncSource : public SyncSource
|
|||
|
||||
|
||||
protected:
|
||||
#ifdef HAVE_EDS
|
||||
/**
|
||||
* searches the list for a source with the given uri or name
|
||||
*
|
||||
|
@ -257,6 +260,7 @@ class EvolutionSyncSource : public SyncSource
|
|||
* @return pointer to source or NULL if not found
|
||||
*/
|
||||
ESource *findSource( ESourceList *list, const string &id );
|
||||
#endif
|
||||
|
||||
/**
|
||||
* throw an exception after a Gnome action failed and
|
||||
|
@ -266,7 +270,11 @@ class EvolutionSyncSource : public SyncSource
|
|||
* @param gerror if not NULL: a more detailed description of the failure,
|
||||
* will be freed
|
||||
*/
|
||||
void throwError( const string &action, GError *gerror );
|
||||
void throwError( const string &action
|
||||
#ifdef HAVE_EDS
|
||||
, GError *gerror = NULL
|
||||
#endif
|
||||
);
|
||||
|
||||
/**
|
||||
* source specific part of beginSync() - throws exceptions in case of error
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
INCLUDES = @EPACKAGE_CFLAGS@ @EBOOK_CFLAGS@ @ECAL_CFLAGS@ @GLIB_CFLAGS@ @SYNC4J_CFLAGS@
|
||||
INCLUDES = @EPACKAGE_CFLAGS@ @EBOOK_CFLAGS@ @ECAL_CFLAGS@ @GLIB_CFLAGS@ @SYNC4J_CFLAGS@ @SQLITE_CFLAGS@
|
||||
|
||||
# Please remove '-Wno-return-type' when Evo fixes the warning in their headers.
|
||||
AM_CFLAGS = -Wall -Werror -Wno-return-type
|
||||
|
||||
bin_PROGRAMS = syncevolution
|
||||
bin_SCRIPTS = synccompare
|
||||
EXTRA_LTLIBRARIES = syncecal.la syncebook.la
|
||||
MOSTLYCLEANFILES = syncecal.la syncebook.la
|
||||
EXTRA_LTLIBRARIES = syncecal.la syncebook.la syncsqlite.la
|
||||
MOSTLYCLEANFILES = syncecal.la syncebook.la syncsqlite.la
|
||||
pkglib_LTLIBRARIES = @SYNCEVOLUTION_LTLIBRARIES@
|
||||
EXTRA_PROGRAMS = client-test
|
||||
check_PROGRAMS = @CPPUNIT_TESTS@
|
||||
|
@ -53,6 +53,13 @@ SYNCEBOOK_SOURCES = \
|
|||
EvolutionContactSource.h \
|
||||
EvolutionContactSource.cpp
|
||||
|
||||
SYNCSQLITE_SOURCES = \
|
||||
SQLiteSyncSource.h \
|
||||
SQLiteSyncSource.cpp \
|
||||
SQLiteContactSource.h \
|
||||
SQLiteContactSource.cpp
|
||||
|
||||
|
||||
CORE_LDADD = @EPACKAGE_LIBS@ @GLIB_LIBS@ @SYNC4J_LIBS@ @LIBS@
|
||||
|
||||
# put link to static c++ library into current directory, needed if compiling with --enable-static-c++
|
||||
|
@ -63,7 +70,7 @@ syncevolution_SOURCES = \
|
|||
syncevolution.cpp \
|
||||
$(CORE_SOURCES)
|
||||
|
||||
# SYNCEVOLUTION_LDADD@ will be replaced with libsyncebook.la/libsyncecal.la
|
||||
# SYNCEVOLUTION_LDADD@ will be replaced with libsyncebook.la/libsyncecal.la/libsyncsqlite.la
|
||||
# if linking statically against them, empty otherwise;
|
||||
# either way this does not lead to a dependency on those libs - done explicitly
|
||||
#
|
||||
|
@ -80,6 +87,10 @@ syncebook_la_SOURCES = $(SYNCEBOOK_SOURCES)
|
|||
syncebook_la_LIBADD = @EBOOK_LIBS@
|
||||
syncebook_la_LDFLAGS = -module -rpath '$(pkglibdir)'
|
||||
|
||||
syncsqlite_la_SOURCES = $(SYNCSQLITE_SOURCES)
|
||||
syncsqlite_la_LIBADD = @SQLITE_LIBS@
|
||||
syncsqlite_la_LDFLAGS = -module -rpath '$(pkglibdir)'
|
||||
|
||||
# test suite - *not* declared as an obligatory check,
|
||||
# because some of them are known to fail and thus
|
||||
# prevent a successful "distcheck"
|
||||
|
|
420
src/SQLiteContactSource.cpp
Normal file
420
src/SQLiteContactSource.cpp
Normal file
|
@ -0,0 +1,420 @@
|
|||
/*
|
||||
* Copyright (C) 2007 Patrick Ohly
|
||||
* Copyright (C) 2007 Funambol
|
||||
*
|
||||
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef ENABLE_SQLITE
|
||||
|
||||
#include "SQLiteContactSource.h"
|
||||
|
||||
#include <common/base/Log.h>
|
||||
#include "vocl/VConverter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <sstream>
|
||||
|
||||
using namespace vocl;
|
||||
|
||||
const char *SQLiteContactSource::getDefaultSchema()
|
||||
{
|
||||
return
|
||||
"BEGIN TRANSACTION;"
|
||||
"CREATE TABLE ABMultiValue (UID INTEGER PRIMARY KEY, record_id INTEGER, property INTEGER, identifier INTEGER, label INTEGER, value TEXT);"
|
||||
"CREATE TABLE ABMultiValueEntry (parent_id INTEGER, key INTEGER, value TEXT, UNIQUE(parent_id, key));"
|
||||
"CREATE TABLE ABMultiValueEntryKey (value TEXT, UNIQUE(value));"
|
||||
"CREATE TABLE ABMultiValueLabel (value TEXT, UNIQUE(value));"
|
||||
"CREATE TABLE ABPerson (ROWID INTEGER PRIMARY KEY AUTOINCREMENT, First TEXT, Last TEXT, Middle TEXT, FirstPhonetic TEXT, MiddlePhonetic TEXT, LastPhonetic TEXT, Organization TEXT, Department TEXT, Note TEXT, Kind INTEGER, Birthday TEXT, JobTitle TEXT, Nickname TEXT, Prefix TEXT, Suffix TEXT, FirstSort TEXT, LastSort TEXT, CreationDate INTEGER, ModificationDate INTEGER, CompositeNameFallback TEXT);"
|
||||
"INSERT INTO ABMultiValueLabel VALUES('_$!<Mobile>!$_');"
|
||||
"INSERT INTO ABMultiValueLabel VALUES('_$!<Home>!$_');"
|
||||
"INSERT INTO ABMultiValueLabel VALUES('_$!<Work>!$_');"
|
||||
"INSERT INTO ABMultiValueEntryKey VALUES('CountryCode');"
|
||||
"INSERT INTO ABMultiValueEntryKey VALUES('City');"
|
||||
"INSERT INTO ABMultiValueEntryKey VALUES('Street');"
|
||||
"INSERT INTO ABMultiValueEntryKey VALUES('State');"
|
||||
"INSERT INTO ABMultiValueEntryKey VALUES('ZIP');"
|
||||
"INSERT INTO ABMultiValueEntryKey VALUES('Country');"
|
||||
"COMMIT;"
|
||||
;
|
||||
}
|
||||
|
||||
enum {
|
||||
PERSON_LAST,
|
||||
PERSON_MIDDLE,
|
||||
PERSON_FIRST,
|
||||
PERSON_PREFIX,
|
||||
PERSON_SUFFIX,
|
||||
PERSON_LASTSORT,
|
||||
PERSON_FIRSTSORT,
|
||||
PERSON_ORGANIZATION,
|
||||
PERSON_DEPARTMENT,
|
||||
PERSON_NOTE,
|
||||
PERSON_BIRTHDAY,
|
||||
PERSON_JOBTITLE,
|
||||
PERSON_NICKNAME,
|
||||
|
||||
LAST_COL
|
||||
};
|
||||
|
||||
const SQLiteSyncSource::Mapping *SQLiteContactSource::getConstMapping()
|
||||
{
|
||||
static const Mapping mapping[LAST_COL] = {
|
||||
{ "Last", "ABPerson" },
|
||||
{ "Middle", "ABPerson" },
|
||||
{ "First", "ABPerson" },
|
||||
{ "Prefix", "ABPerson" },
|
||||
{ "Suffix", "ABPerson" },
|
||||
{ "FirstSort", "ABPerson" },
|
||||
{ "LastSort", "ABPerson" },
|
||||
{ "Organization", "ABPerson" },
|
||||
{ "Department", "ABPerson" },
|
||||
{ "Note", "ABPerson", "NOTE" },
|
||||
{ "Birthday", "ABPerson", "BIRTHDAY" },
|
||||
{ "JobTitle", "ABPerson", "ROLE" },
|
||||
{ "Nickname", "ABPerson", "NICKNAME" }
|
||||
};
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
void SQLiteContactSource::open()
|
||||
{
|
||||
SQLiteSyncSource::open();
|
||||
|
||||
// query database for certain constant indices
|
||||
m_addrCountryCode = findKey("ABMultiValueEntryKey", "value", "CountryCode");
|
||||
m_addrCity = findKey("ABMultiValueEntryKey", "value", "City");
|
||||
m_addrStreet = findKey("ABMultiValueEntryKey", "value", "Street");
|
||||
m_addrState = findKey("ABMultiValueEntryKey", "value", "State");
|
||||
m_addrZIP = findKey("ABMultiValueEntryKey", "value", "ZIP");
|
||||
m_typeMobile = findKey("ABMultiValueLabel", "value", "_$!<Mobile>!$_");
|
||||
m_typeHome = findKey("ABMultiValueLabel", "value", "_$!<Home>!$_");
|
||||
m_typeWork = findKey("ABMultiValueLabel", "value", "_$!<Work>!$_");
|
||||
}
|
||||
|
||||
|
||||
void SQLiteContactSource::beginSyncThrow(bool needAll,
|
||||
bool needPartial,
|
||||
bool deleteLocal)
|
||||
{
|
||||
syncml_time_t lastSyncTime = anchorToTimestamp(getLastAnchor());
|
||||
|
||||
eptr<sqlite3_stmt> all(prepareSQL("SELECT ROWID, CreationDate, ModificationDate FROM ABPerson;"));
|
||||
while (checkSQL(sqlite3_step(all)) == SQLITE_ROW) {
|
||||
string uid = toString(SQLITE3_COLUMN_KEY(all, 0));
|
||||
m_allItems.addItem(uid);
|
||||
|
||||
// find new and updated items by comparing their creation resp. modification time stamp
|
||||
// against the end of the last sync
|
||||
if (needPartial) {
|
||||
syncml_time_t creationTime = getTimeColumn(all, 1);
|
||||
syncml_time_t modTime = getTimeColumn(all, 2);
|
||||
|
||||
if (creationTime > lastSyncTime) {
|
||||
m_newItems.addItem(uid);
|
||||
} else if (modTime > lastSyncTime) {
|
||||
m_updatedItems.addItem(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: find deleted items
|
||||
|
||||
// all modifications from now on will be rolled back on an error:
|
||||
// if the server does the same, theoretically client and server
|
||||
// could restart with a two-way sync
|
||||
//
|
||||
// TODO: currently syncevolution resets the last anchor in case of
|
||||
// a failure and thus forces a slow sync - avoid that for SQLite
|
||||
// database sources
|
||||
eptr<sqlite3_stmt> start(prepareSQL("BEGIN TRANSACTION;"));
|
||||
checkSQL(sqlite3_step(start));
|
||||
|
||||
|
||||
if (deleteLocal) {
|
||||
for (itemList::const_iterator it = m_allItems.begin();
|
||||
it != m_allItems.end();
|
||||
it++) {
|
||||
deleteItemThrow(*it);
|
||||
}
|
||||
m_allItems.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void SQLiteContactSource::endSyncThrow()
|
||||
{
|
||||
// complete the transaction started in beginSyncThrow()
|
||||
eptr<sqlite3_stmt> end(prepareSQL("COMMIT;"));
|
||||
checkSQL(sqlite3_step(end));
|
||||
}
|
||||
|
||||
void SQLiteContactSource::exportData(ostream &out)
|
||||
{
|
||||
eptr<sqlite3_stmt> all(prepareSQL("SELECT ROWID FROM ABPerson;"));
|
||||
while (checkSQL(sqlite3_step(all)) == SQLITE_ROW) {
|
||||
string uid = toString(SQLITE3_COLUMN_KEY(all, 1));
|
||||
auto_ptr<SyncItem> item(createItem(uid, SYNC_STATE_NONE));
|
||||
|
||||
out << item->getData();
|
||||
out << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
SyncItem *SQLiteContactSource::createItem( const string &uid, SyncState state )
|
||||
{
|
||||
logItem(uid, "extracting from database");
|
||||
|
||||
eptr<sqlite3_stmt> contact(prepareSQL("SELECT * FROM ABPerson WHERE ROWID = '%s';", uid.c_str()));
|
||||
if (checkSQL(sqlite3_step(contact)) != SQLITE_ROW) {
|
||||
throw runtime_error(string(getName()) + ": contact not found: " + uid);
|
||||
}
|
||||
|
||||
VObject vobj;
|
||||
string tmp;
|
||||
const unsigned char *text;
|
||||
|
||||
vobj.addProperty("BEGIN", "VCARD");
|
||||
vobj.addProperty("VERSION", "2.1");
|
||||
vobj.setVersion("2.1");
|
||||
|
||||
tmp = getTextColumn(contact, m_mapping[PERSON_LAST].colindex);
|
||||
tmp += VObject::SEMICOLON_REPLACEMENT;
|
||||
tmp += getTextColumn(contact, m_mapping[PERSON_MIDDLE].colindex);
|
||||
tmp += VObject::SEMICOLON_REPLACEMENT;
|
||||
tmp += getTextColumn(contact, m_mapping[PERSON_FIRST].colindex);
|
||||
if (tmp.size() > 2) {
|
||||
vobj.addProperty("N", tmp.c_str());
|
||||
}
|
||||
|
||||
tmp = getTextColumn(contact, m_mapping[PERSON_ORGANIZATION].colindex);
|
||||
tmp += VObject::SEMICOLON_REPLACEMENT;
|
||||
tmp += getTextColumn(contact, m_mapping[PERSON_DEPARTMENT].colindex);
|
||||
if (tmp.size() > 1) {
|
||||
vobj.addProperty("ORG", tmp.c_str());
|
||||
}
|
||||
|
||||
rowToVObject(contact, vobj);
|
||||
|
||||
vobj.addProperty("END", "VCARD");
|
||||
|
||||
vobj.fromNativeEncoding();
|
||||
|
||||
arrayptr<char> finalstr(vobj.toString(), "VOCL string");
|
||||
LOG.debug("%s", (char *)finalstr);
|
||||
|
||||
auto_ptr<SyncItem> item( new SyncItem( uid.c_str() ) );
|
||||
item->setData( (char *)finalstr, strlen(finalstr) );
|
||||
item->setDataType( getMimeType() );
|
||||
item->setModificationTime( 0 );
|
||||
item->setState( state );
|
||||
|
||||
return item.release();
|
||||
}
|
||||
|
||||
int SQLiteContactSource::addItemThrow(SyncItem& item)
|
||||
{
|
||||
return insertItemThrow(item, NULL, "");
|
||||
}
|
||||
|
||||
int SQLiteContactSource::updateItemThrow(SyncItem& item)
|
||||
{
|
||||
// Make sure that there is no contact with this uid,
|
||||
// then insert the new data. If there was no such uid,
|
||||
// then this behaves like an add.
|
||||
string creationTime = findColumn("ABPerson", "ROWID", item.getKey(), "CreationDate", "");
|
||||
deleteItemThrow(item.getKey());
|
||||
return insertItemThrow(item, item.getKey(), creationTime);
|
||||
}
|
||||
|
||||
int SQLiteContactSource::insertItemThrow(SyncItem &item, const char *uid, const string &creationTime)
|
||||
{
|
||||
string data(getData(item));
|
||||
std::auto_ptr<VObject> vobj(VConverter::parse((char *)data.c_str()));
|
||||
if (vobj.get() == 0) {
|
||||
throwError(string("parsing contact ") + item.getKey());
|
||||
}
|
||||
vobj->toNativeEncoding();
|
||||
|
||||
stringstream cols;
|
||||
stringstream values;
|
||||
VProperty *prop;
|
||||
|
||||
// always set the name, even if not in the vcard
|
||||
prop = vobj->getProperty("N");
|
||||
string first, middle, last, prefix, suffix, firstsort, lastsort;
|
||||
if (prop && prop->getValue()) {
|
||||
string fn = prop->getValue();
|
||||
size_t sep1 = fn.find(VObject::SEMICOLON_REPLACEMENT);
|
||||
size_t sep2 = sep1 == fn.npos ? fn.npos : fn.find(VObject::SEMICOLON_REPLACEMENT, sep1 + 1);
|
||||
size_t sep3 = sep2 == fn.npos ? fn.npos : fn.find(VObject::SEMICOLON_REPLACEMENT, sep2 + 1);
|
||||
size_t sep4 = sep3 == fn.npos ? fn.npos : fn.find(VObject::SEMICOLON_REPLACEMENT, sep3 + 1);
|
||||
|
||||
last = fn.substr(0, sep1);
|
||||
if (sep1 != fn.npos) {
|
||||
first = fn.substr(sep1 + 1, (sep2 == fn.npos) ? fn.npos : sep2 - sep1 - 1);
|
||||
}
|
||||
if (sep2 != fn.npos) {
|
||||
middle = fn.substr(sep2 + 1, (sep3 == fn.npos) ? fn.npos : sep3 - sep2 - 1);
|
||||
}
|
||||
if (sep3 != fn.npos) {
|
||||
prefix = fn.substr(sep3 + 1, (sep4 == fn.npos) ? fn.npos : sep4 - sep3 - 1);
|
||||
}
|
||||
if (sep4 != fn.npos) {
|
||||
suffix = fn.substr(sep4 + 1);
|
||||
}
|
||||
}
|
||||
cols << m_mapping[PERSON_FIRST].colname << ", " <<
|
||||
m_mapping[PERSON_MIDDLE].colname << ", " <<
|
||||
m_mapping[PERSON_LAST].colname << ", " <<
|
||||
m_mapping[PERSON_LASTSORT].colname << ", " <<
|
||||
m_mapping[PERSON_FIRSTSORT].colname;
|
||||
values << "?, ?, ?, ?, ?";
|
||||
|
||||
// synthesize sort keys: upper case with specific order of first/last name
|
||||
firstsort = first + " " + last;
|
||||
transform(firstsort.begin(), firstsort.end(), firstsort.begin(), ::toupper);
|
||||
lastsort = last + " " + first;
|
||||
transform(lastsort.begin(), lastsort.end(), lastsort.begin(), ::toupper);
|
||||
|
||||
// optional fixed UID, potentially fixed creation time
|
||||
if (uid) {
|
||||
cols << ", ROWID";
|
||||
values << ", ?";
|
||||
}
|
||||
cols << ", CreationDate, ModificationDate";
|
||||
values << ", ?, ?";
|
||||
|
||||
eptr<sqlite3_stmt> insert(prepareSQL("INSERT INTO ABPerson( %s ) "
|
||||
"VALUES( %s );",
|
||||
cols.str().c_str(),
|
||||
values.str().c_str()));
|
||||
|
||||
// now bind parameter values in the same order as the columns specification above
|
||||
int param = 1;
|
||||
checkSQL(sqlite3_bind_text(insert, param++, first.c_str(), -1, SQLITE_TRANSIENT));
|
||||
checkSQL(sqlite3_bind_text(insert, param++, middle.c_str(), -1, SQLITE_TRANSIENT));
|
||||
checkSQL(sqlite3_bind_text(insert, param++, last.c_str(), -1, SQLITE_TRANSIENT));
|
||||
checkSQL(sqlite3_bind_text(insert, param++, lastsort.c_str(), -1, SQLITE_TRANSIENT));
|
||||
checkSQL(sqlite3_bind_text(insert, param++, firstsort.c_str(), -1, SQLITE_TRANSIENT));
|
||||
if (uid) {
|
||||
checkSQL(sqlite3_bind_text(insert, param++, uid, -1, SQLITE_TRANSIENT));
|
||||
checkSQL(sqlite3_bind_text(insert, param++, creationTime.c_str(), -1, SQLITE_TRANSIENT));
|
||||
} else {
|
||||
checkSQL(sqlite3_bind_int64(insert, param++, (long long)time(NULL)));
|
||||
}
|
||||
checkSQL(sqlite3_bind_int64(insert, param++, (long long)time(NULL)));
|
||||
|
||||
checkSQL(sqlite3_step(insert));
|
||||
|
||||
if (!uid) {
|
||||
// figure out which UID was assigned to the new contact
|
||||
string newuid = findColumn("SQLITE_SEQUENCE", "NAME", "ABPerson", "SEQ", "");
|
||||
item.setKey(newuid.c_str());
|
||||
}
|
||||
|
||||
return STC_OK;
|
||||
}
|
||||
|
||||
|
||||
int SQLiteContactSource::deleteItemThrow(const string &uid)
|
||||
{
|
||||
int status = STC_OK;
|
||||
|
||||
// delete address field members of contact
|
||||
eptr<sqlite3_stmt> del(prepareSQL("DELETE FROM ABMultiValueEntry "
|
||||
"WHERE ABMultiValueEntry.parent_id IN "
|
||||
"(SELECT ABMultiValue.uid FROM ABMultiValue WHERE "
|
||||
" ABMultiValue.record_id = ?);"));
|
||||
checkSQL(sqlite3_bind_text(del, 1, uid.c_str(), -1, SQLITE_TRANSIENT));
|
||||
checkSQL(sqlite3_step(del));
|
||||
|
||||
// delete addresses and emails of contact
|
||||
del.set(prepareSQL("DELETE FROM ABMultiValue WHERE "
|
||||
"ABMultiValue.record_id = ?;"));
|
||||
checkSQL(sqlite3_bind_text(del, 1, uid.c_str(), -1, SQLITE_TRANSIENT));
|
||||
checkSQL(sqlite3_step(del));
|
||||
|
||||
// now delete the contact itself
|
||||
del.set(prepareSQL("DELETE FROM ABPerson WHERE "
|
||||
"ABPerson.ROWID = ?;"));
|
||||
checkSQL(sqlite3_bind_text(del, 1, uid.c_str(), -1, SQLITE_TRANSIENT));
|
||||
checkSQL(sqlite3_step(del));
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
int SQLiteContactSource::deleteItemThrow(SyncItem& item)
|
||||
{
|
||||
return deleteItemThrow(item.getKey());
|
||||
}
|
||||
|
||||
void SQLiteContactSource::logItem(const string &uid, const string &info, bool debug)
|
||||
{
|
||||
if (LOG.getLevel() >= (debug ? LOG_LEVEL_DEBUG : LOG_LEVEL_INFO)) {
|
||||
(LOG.*(debug ? &Log::debug : &Log::info))("%s: %s %s",
|
||||
getName(),
|
||||
findColumn("ABPerson",
|
||||
"ROWID",
|
||||
uid.c_str(),
|
||||
"FirstSort",
|
||||
uid.c_str()).c_str(),
|
||||
info.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void SQLiteContactSource::logItem(SyncItem &item, const string &info, bool debug)
|
||||
{
|
||||
if (LOG.getLevel() >= (debug ? LOG_LEVEL_DEBUG : LOG_LEVEL_INFO)) {
|
||||
string data(getData(item));
|
||||
|
||||
// avoid pulling in a full vcard parser by just searching for a specific property,
|
||||
// FN in this case
|
||||
string name = "???";
|
||||
string prop = "\nFN:";
|
||||
size_t start = data.find(prop);
|
||||
if (start != data.npos) {
|
||||
start += prop.size();
|
||||
size_t end = data.find("\n", start);
|
||||
if (end != data.npos) {
|
||||
name = data.substr(start, end - start);
|
||||
}
|
||||
}
|
||||
|
||||
(LOG.*(debug ? &Log::debug : &Log::info))("%s: %s %s",
|
||||
getName(),
|
||||
name.c_str(),
|
||||
info.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifdef ENABLE_MODULES
|
||||
|
||||
extern "C" EvolutionSyncSource *SyncEvolutionCreateSource(const string &name,
|
||||
SyncSourceConfig *sc,
|
||||
const string &changeId,
|
||||
const string &id,
|
||||
const string &mimeType)
|
||||
{
|
||||
return new SQLiteContactSource(name, sc, changeId, id);
|
||||
}
|
||||
|
||||
#endif /* ENABLE_MODULES */
|
||||
|
||||
#endif /* ENABLE_SQLITE */
|
89
src/SQLiteContactSource.h
Normal file
89
src/SQLiteContactSource.h
Normal file
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright (C) 2007 Patrick Ohly
|
||||
* Copyright (C) 2007 Funambol
|
||||
*
|
||||
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef INCL_SQLITECONTACTSOURCE
|
||||
#define INCL_SQLITECONTACTSOURCE
|
||||
|
||||
#include "SQLiteSyncSource.h"
|
||||
|
||||
#ifdef ENABLE_SQLITE
|
||||
|
||||
/**
|
||||
* Specialization of SQLiteSyncSource for contacts
|
||||
* with a schema as used by Mac OS X.
|
||||
*/
|
||||
class SQLiteContactSource : public SQLiteSyncSource
|
||||
{
|
||||
public:
|
||||
SQLiteContactSource( const string name, SyncSourceConfig *sc, const string &changeId, const string &id ) :
|
||||
SQLiteSyncSource( name, sc, changeId, id)
|
||||
{}
|
||||
virtual ~SQLiteContactSource() {}
|
||||
|
||||
protected:
|
||||
/* implementation of EvolutionSyncSource interface */
|
||||
virtual void open();
|
||||
virtual SyncItem *createItem( const string &uid, SyncState state );
|
||||
virtual void exportData(ostream &out);
|
||||
virtual string fileSuffix() { return "vcf"; }
|
||||
virtual const char *getMimeType() { return "text/x-vcard:2.1"; }
|
||||
virtual const char *getMimeVersion() { return "2.1"; }
|
||||
virtual const char *getSupportedTypes() { return "text/vcard:3.0,text/x-vcard:2.1"; }
|
||||
virtual ArrayElement* clone() { new SQLiteContactSource(getName(), &getConfig(), m_changeId, m_id); }
|
||||
virtual void beginSyncThrow(bool needAll,
|
||||
bool needPartial,
|
||||
bool deleteLocal);
|
||||
|
||||
virtual void endSyncThrow();
|
||||
virtual int addItemThrow(SyncItem& item);
|
||||
virtual int updateItemThrow(SyncItem& item);
|
||||
virtual int deleteItemThrow(SyncItem& item);
|
||||
|
||||
virtual void logItem(const string &uid, const string &info, bool debug = false);
|
||||
virtual void logItem(SyncItem &item, const string &info, bool debug = false);
|
||||
|
||||
/* implementation of SQLiteSyncSource interface */
|
||||
virtual const char *getDefaultSchema();
|
||||
virtual const Mapping *getConstMapping();
|
||||
|
||||
private:
|
||||
/** constant key values defined by tables in the database, queried during open() */
|
||||
key_t m_addrCountryCode,
|
||||
m_addrCity,
|
||||
m_addrStreet,
|
||||
m_addrState,
|
||||
m_addrZIP,
|
||||
m_addrCountry,
|
||||
m_typeMobile,
|
||||
m_typeHome,
|
||||
m_typeWork;
|
||||
|
||||
/** same as deleteItemThrow() but works with the uid directly */
|
||||
virtual int deleteItemThrow(const string &uid);
|
||||
|
||||
/**
|
||||
* inserts the contact under a specific UID (if given) or
|
||||
* adds under a new UID
|
||||
*/
|
||||
virtual int insertItemThrow(SyncItem &item, const char *uid, const string &creationTime);
|
||||
|
||||
};
|
||||
|
||||
#endif // ENABLE_SQLITE
|
||||
#endif // INCL_SQLITECONTACTSOURCE
|
203
src/SQLiteSyncSource.cpp
Normal file
203
src/SQLiteSyncSource.cpp
Normal file
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
* Copyright (C) 2007 Patrick Ohly
|
||||
* Copyright (C) 2007 Funambol
|
||||
*
|
||||
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef ENABLE_SQLITE
|
||||
|
||||
#include "SQLiteSyncSource.h"
|
||||
#include "base/util/StringBuffer.h"
|
||||
#include "vocl/VConverter.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
void SQLiteSyncSource::throwError(const string &operation)
|
||||
{
|
||||
string descr = string(getName()) + ": '" + m_id + "': " + operation + " failed";
|
||||
|
||||
if (m_db) {
|
||||
const char *error = sqlite3_errmsg(m_db);
|
||||
descr += ": ";
|
||||
descr += error ? error : "unspecified error";
|
||||
}
|
||||
|
||||
throw runtime_error(descr);
|
||||
}
|
||||
|
||||
sqlite3_stmt *SQLiteSyncSource::prepareSQLWrapper(const char *sql, const char **nextsql)
|
||||
{
|
||||
sqlite3_stmt *stmt = NULL;
|
||||
|
||||
checkSQL(sqlite3_prepare(m_db, sql, -1, &stmt, nextsql), sql);
|
||||
return stmt;
|
||||
}
|
||||
|
||||
sqlite3_stmt *SQLiteSyncSource::prepareSQL(const char *sqlfmt, ...)
|
||||
{
|
||||
StringBuffer sql;
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, sqlfmt);
|
||||
sql.vsprintf(sqlfmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
return prepareSQLWrapper(sql.c_str());
|
||||
}
|
||||
|
||||
|
||||
SQLiteSyncSource::key_t SQLiteSyncSource::findKey(const char *database, const char *keyname, const char *key)
|
||||
{
|
||||
eptr<sqlite3_stmt> query(prepareSQL("SELECT ROWID FROM %s WHERE %s = '%s';", database, keyname, key));
|
||||
|
||||
int res = checkSQL(sqlite3_step(query), "getting key");
|
||||
if (res == SQLITE_ROW) {
|
||||
return sqlite3_column_int64(query, 0);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
string SQLiteSyncSource::findColumn(const char *database, const char *keyname, const char *key, const char *column, const char *def)
|
||||
{
|
||||
eptr<sqlite3_stmt> query(prepareSQL("SELECT %s FROM %s WHERE %s = '%s';", column, database, keyname, key));
|
||||
|
||||
int res = checkSQL(sqlite3_step(query), "getting key");
|
||||
if (res == SQLITE_ROW) {
|
||||
const unsigned char *text = sqlite3_column_text(query, 0);
|
||||
|
||||
return text ? (const char *)text : def;
|
||||
} else {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
string SQLiteSyncSource::getTextColumn(sqlite3_stmt *stmt, int col, const char *def)
|
||||
{
|
||||
const unsigned char *text = sqlite3_column_text(stmt, 0);
|
||||
return text ? (const char *)text : def;
|
||||
}
|
||||
|
||||
SQLiteSyncSource::syncml_time_t SQLiteSyncSource::getTimeColumn(sqlite3_stmt *stmt, int col)
|
||||
{
|
||||
// assumes that the database stores the result of time() directly
|
||||
return sqlite3_column_int64(stmt, col);
|
||||
}
|
||||
|
||||
void SQLiteSyncSource::rowToVObject(sqlite3_stmt *stmt, vocl::VObject &vobj)
|
||||
{
|
||||
const unsigned char *text;
|
||||
const char *tablename;
|
||||
int i;
|
||||
|
||||
for (i = 0; m_mapping[i].colname; i++) {
|
||||
if (m_mapping[i].colindex < 0 ||
|
||||
!m_mapping[i].propname) {
|
||||
continue;
|
||||
}
|
||||
tablename = sqlite3_column_table_name(stmt, m_mapping[i].colindex);
|
||||
if (!tablename || strcasecmp(tablename, m_mapping[i].tablename)) {
|
||||
continue;
|
||||
}
|
||||
text = sqlite3_column_text(stmt, m_mapping[i].colindex);
|
||||
if (text) {
|
||||
vobj.addProperty(m_mapping[i].propname, (const char *)text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SQLiteSyncSource::open()
|
||||
{
|
||||
const string prefix("file://");
|
||||
bool create = m_id.substr(0, prefix.size()) == prefix;
|
||||
string filename = create ? m_id.substr(prefix.size()) : m_id;
|
||||
|
||||
if (!create && access(filename.c_str(), F_OK)) {
|
||||
throw runtime_error(string(getName()) + ": no such database: '" + filename + "'");
|
||||
}
|
||||
|
||||
sqlite3 *db;
|
||||
int res = sqlite3_open(filename.c_str(), &db);
|
||||
m_db = db;
|
||||
checkSQL(res, "opening");
|
||||
|
||||
// check whether file is empty = newly created, define schema if that's the case
|
||||
eptr<sqlite3_stmt> check(prepareSQLWrapper("SELECT * FROM sqlite_master;"));
|
||||
switch (sqlite3_step(check)) {
|
||||
case SQLITE_ROW:
|
||||
// okay
|
||||
break;
|
||||
case SQLITE_DONE: {
|
||||
// empty
|
||||
const char *schema = getDefaultSchema();
|
||||
const char *nextsql = schema;
|
||||
while (nextsql && *nextsql) {
|
||||
const char *sql = nextsql;
|
||||
eptr<sqlite3_stmt> create(prepareSQLWrapper(sql, &nextsql));
|
||||
while (TRUE) {
|
||||
int res = sqlite3_step(create);
|
||||
if (res == SQLITE_DONE) {
|
||||
break;
|
||||
} else if (res == SQLITE_ROW) {
|
||||
// continue
|
||||
} else {
|
||||
throwError("creating database");\
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throwError("checking content");
|
||||
break;
|
||||
}
|
||||
|
||||
// query database schema to find columns we need
|
||||
const Mapping *mapping = getConstMapping();
|
||||
int i;
|
||||
for (i = 0; mapping[i].colname; i++) ;
|
||||
m_mapping.set(new Mapping[i + 1]);
|
||||
eptr<sqlite3_stmt> query;
|
||||
string tablename;
|
||||
for (i = 0; mapping[i].colname; i++) {
|
||||
m_mapping[i] = mapping[i];
|
||||
|
||||
// switching to a different table?
|
||||
if (tablename != m_mapping[i].tablename) {
|
||||
tablename = m_mapping[i].tablename;
|
||||
query.set(prepareSQL("SELECT * FROM %s;", tablename.c_str()));
|
||||
}
|
||||
|
||||
// search for this column name
|
||||
for (m_mapping[i].colindex = sqlite3_column_count(query) - 1;
|
||||
m_mapping[i].colindex >= 0;
|
||||
m_mapping[i].colindex--) {
|
||||
const char *name = sqlite3_column_name(query, m_mapping[i].colindex);
|
||||
if (name && !strcasecmp(m_mapping[i].colname, name)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
memset(&m_mapping[i], 0, sizeof(m_mapping[i]));
|
||||
}
|
||||
|
||||
void SQLiteSyncSource::close()
|
||||
{
|
||||
}
|
||||
|
||||
#endif /* ENABLE_SQLITE */
|
142
src/SQLiteSyncSource.h
Normal file
142
src/SQLiteSyncSource.h
Normal file
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* Copyright (C) 2007 Patrick Ohly
|
||||
* Copyright (C) 2007 Funambol
|
||||
*
|
||||
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef INCL_SQLITESYNCSOURCE
|
||||
#define INCL_SQLITESYNCSOURCE
|
||||
|
||||
#include "EvolutionSyncSource.h"
|
||||
|
||||
#ifdef ENABLE_SQLITE
|
||||
|
||||
#include <sqlite3.h>
|
||||
#include "EvolutionSmartPtr.h"
|
||||
|
||||
namespace vocl {
|
||||
class VObject;
|
||||
}
|
||||
void inline unref(sqlite3 *db) { sqlite3_close(db); }
|
||||
void inline unref(sqlite3_stmt *stmt) { sqlite3_finalize(stmt); }
|
||||
|
||||
/**
|
||||
* This class implements access to SQLite database files:
|
||||
* - opening the database file
|
||||
* - error reporting
|
||||
* - creating a database file in debugging mode
|
||||
*/
|
||||
class SQLiteSyncSource : public EvolutionSyncSource
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Creates a new Evolution sync source.
|
||||
*
|
||||
* @param name the named needed by SyncSource
|
||||
* @param sc obligatory config for this source, must remain valid throughout the lifetime of the source
|
||||
* @param changeId is used to track changes in the Evolution backend
|
||||
* @param id identifies the backend; not specifying it makes this instance
|
||||
* unusable for anything but listing backend databases
|
||||
*/
|
||||
SQLiteSyncSource( const string name, SyncSourceConfig *sc, const string &changeId, const string &id ) :
|
||||
EvolutionSyncSource( name, sc, changeId, id),
|
||||
m_db(NULL)
|
||||
{}
|
||||
virtual ~SQLiteSyncSource() {}
|
||||
|
||||
/* implementation of EvolutionSyncSource interface */
|
||||
virtual sources getSyncBackends() { return sources(); /* we cannot list available databases */ }
|
||||
virtual void open();
|
||||
virtual void close();
|
||||
|
||||
|
||||
protected:
|
||||
/** throw error for a specific sqlite3 operation on m_db */
|
||||
void throwError(const string &operation);
|
||||
|
||||
/**
|
||||
* wrapper around sqlite3_prepare() which operates on the current
|
||||
* database and throws an error if the call fails
|
||||
*
|
||||
* @param sqlfmt printf-style format string for query, followed by parameters for sprintf
|
||||
*/
|
||||
sqlite3_stmt *prepareSQL(const char *sqlfmt, ...);
|
||||
|
||||
/**
|
||||
* wrapper around sqlite3_prepare() which operates on the current
|
||||
* database and throws an error if the call fails
|
||||
*
|
||||
* @param sql preformatted SQL statement(s)
|
||||
* @param nextsql pointer to next statement in sql
|
||||
*/
|
||||
sqlite3_stmt *prepareSQLWrapper(const char *sql, const char **nextsql = NULL);
|
||||
|
||||
|
||||
/** checks the result of an sqlite3 call, throws an error if faulty, otherwise returns the result */
|
||||
int checkSQL(int res, const char *operation = "SQLite call") {
|
||||
if (res != SQLITE_OK && res != SQLITE_ROW && res != SQLITE_DONE) {
|
||||
throwError(operation);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/** type used for row keys */
|
||||
typedef long long key_t;
|
||||
string toString(key_t key) { char buffer[32]; sprintf(buffer, "%lld", key); return buffer; }
|
||||
#define SQLITE3_COLUMN_KEY sqlite3_column_int64
|
||||
|
||||
/** return row ID for a certain row */
|
||||
key_t findKey(const char *database, const char *keyname, const char *key);
|
||||
|
||||
/** return a specific column for a row identified by a certain key column as text, returns default text if not found */
|
||||
string findColumn(const char *database, const char *keyname, const char *key, const char *column, const char *def);
|
||||
|
||||
/** a wrapper for sqlite3_column_test() which will check for NULL and returns default text instead */
|
||||
string getTextColumn(sqlite3_stmt *stmt, int col, const char *def = "");
|
||||
|
||||
typedef unsigned long syncml_time_t;
|
||||
/** transform column to same time base as used by SyncML libary (typically time()) */
|
||||
syncml_time_t getTimeColumn(sqlite3_stmt *stmt, int col);
|
||||
|
||||
/** copies all columns which directly map to a property into the vobj */
|
||||
void rowToVObject(sqlite3_stmt *stmt, vocl::VObject &vobj);
|
||||
|
||||
/** database schema to use when creating new databases, may be NULL */
|
||||
virtual const char *getDefaultSchema() = 0;
|
||||
|
||||
/** information about the database mapping */
|
||||
struct Mapping {
|
||||
const char *colname; /**< column name in SQL table */
|
||||
const char *tablename; /**< name of the SQL table which has this column */
|
||||
const char *propname; /**< optional: vcard/vcalendar property which corresponds to this */
|
||||
int colindex; /**< determined dynamically in open(): index of the column, -1 if not present */
|
||||
};
|
||||
|
||||
/**
|
||||
* array with database mapping, terminated by NULL colname:
|
||||
* variable fields are stored in a copy maintained by the SQLiteSyncSource class
|
||||
*/
|
||||
virtual const Mapping *getConstMapping() = 0;
|
||||
|
||||
/** filled in by SQLiteSyncSource::open() */
|
||||
arrayptr<Mapping> m_mapping;
|
||||
|
||||
/** after opening: current databse */
|
||||
eptr<sqlite3> m_db;
|
||||
};
|
||||
|
||||
#endif // ENABLE_SQLITE
|
||||
#endif // INCL_SQLITESYNCSOURCE
|
|
@ -31,6 +31,7 @@
|
|||
#include "EvolutionCalendarSource.h"
|
||||
#include "EvolutionMemoSource.h"
|
||||
#include "EvolutionContactSource.h"
|
||||
#include "SQLiteContactSource.h"
|
||||
|
||||
/** a wrapper class which automatically does an open() in the constructor and a close() in the destructor */
|
||||
template<class T> class TestEvolutionSyncSource : public T {
|
||||
|
@ -132,7 +133,7 @@ public:
|
|||
/* check sources */
|
||||
const char *sourcelist = getenv("CLIENT_TEST_SOURCES");
|
||||
if (!sourcelist) {
|
||||
sourcelist = "vcard21,vcard30,ical20,text,itodo20";
|
||||
sourcelist = "vcard21,vcard30,ical20,text,itodo20,sqlite";
|
||||
}
|
||||
numSources = 0;
|
||||
for (SourceType sourceType = (SourceType)0; sourceType < TEST_MAX_SOURCE; sourceType = (SourceType)((int)sourceType + 1) ) {
|
||||
|
@ -149,6 +150,11 @@ public:
|
|||
sourceType == TEST_MEMO_SOURCE) {
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
#ifndef ENABLE_SQLITE
|
||||
if (sourceType == TEST_SQLITE_CONTACT_SOURCE) {
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
if (strstr(sourcelist, name.c_str())) {
|
||||
enabledSources[numSources++] = sourceType;
|
||||
|
@ -212,6 +218,7 @@ public:
|
|||
TEST_CALENDAR_SOURCE,
|
||||
TEST_TASK_SOURCE,
|
||||
TEST_MEMO_SOURCE,
|
||||
TEST_SQLITE_CONTACT_SOURCE,
|
||||
TEST_MAX_SOURCE
|
||||
};
|
||||
|
||||
|
@ -286,6 +293,11 @@ public:
|
|||
config.dump = dumpMemoSource;
|
||||
config.testcases = "testcases/imemo20.ics";
|
||||
break;
|
||||
case TEST_SQLITE_CONTACT_SOURCE:
|
||||
getTestData("vcard21", config);
|
||||
config.sourceName = "sqlite";
|
||||
config.type = "sqlite";
|
||||
break;
|
||||
default:
|
||||
CPPUNIT_ASSERT(sourceType < TEST_MAX_SOURCE);
|
||||
break;
|
||||
|
@ -407,6 +419,9 @@ private:
|
|||
case TEST_MEMO_SOURCE:
|
||||
return "text";
|
||||
break;
|
||||
case TEST_SQLITE_CONTACT_SOURCE:
|
||||
return "sqlite";
|
||||
break;
|
||||
default:
|
||||
CPPUNIT_ASSERT(type >= 0 && type < TEST_MAX_SOURCE);
|
||||
break;
|
||||
|
@ -423,41 +438,54 @@ private:
|
|||
SourceType type = ((TestEvolution &)client).enabledSources[source];
|
||||
changeID += isSourceA ? "1" : "2";
|
||||
string database = ((TestEvolution &)client).getDatabaseName(type);
|
||||
SyncSource *ss = NULL;
|
||||
|
||||
switch (type) {
|
||||
case TEST_CONTACT21_SOURCE:
|
||||
case TEST_CONTACT30_SOURCE:
|
||||
#ifdef ENABLE_EBOOK
|
||||
return new TestEvolutionSyncSource<EvolutionContactSource>(changeID, database);
|
||||
#else
|
||||
return NULL;
|
||||
ss = new TestEvolutionSyncSource<EvolutionContactSource>(changeID, database);
|
||||
#endif
|
||||
break;
|
||||
case TEST_CALENDAR_SOURCE:
|
||||
#ifdef ENABLE_ECAL
|
||||
return new TestEvolutionSyncSource<EvolutionCalendarSource>(E_CAL_SOURCE_TYPE_EVENT, changeID, database);
|
||||
#else
|
||||
return NULL;
|
||||
ss = new TestEvolutionSyncSource<EvolutionCalendarSource>(E_CAL_SOURCE_TYPE_EVENT, changeID, database);
|
||||
#endif
|
||||
break;
|
||||
case TEST_TASK_SOURCE:
|
||||
#ifdef ENABLE_ECAL
|
||||
return new TestEvolutionSyncSource<EvolutionCalendarSource>(E_CAL_SOURCE_TYPE_TODO, changeID, database);
|
||||
#else
|
||||
return NULL;
|
||||
ss = new TestEvolutionSyncSource<EvolutionCalendarSource>(E_CAL_SOURCE_TYPE_TODO, changeID, database);
|
||||
#endif
|
||||
break;
|
||||
case TEST_MEMO_SOURCE:
|
||||
#ifdef ENABLE_ECAL
|
||||
return new TestEvolutionSyncSource<EvolutionMemoSource>(E_CAL_SOURCE_TYPE_JOURNAL, changeID, database);
|
||||
#else
|
||||
return NULL;
|
||||
ss = new TestEvolutionSyncSource<EvolutionMemoSource>(E_CAL_SOURCE_TYPE_JOURNAL, changeID, database);
|
||||
#endif
|
||||
break;
|
||||
case TEST_SQLITE_CONTACT_SOURCE:
|
||||
#ifdef ENABLE_SQLITE
|
||||
ss = new TestEvolutionSyncSource<SQLiteContactSource>(changeID, database);
|
||||
|
||||
// this is a hack: it guesses the last sync time stamp by remembering
|
||||
// the last time the sync source was created
|
||||
static time_t lastts[TEST_MAX_SOURCE];
|
||||
char anchor[DIM_ANCHOR];
|
||||
time_t nextts;
|
||||
|
||||
timestampToAnchor(lastts[type], anchor);
|
||||
ss->setLastAnchor(anchor);
|
||||
nextts = time(NULL);
|
||||
while (lastts[type] == nextts) {
|
||||
sleep(1);
|
||||
nextts = time(NULL);
|
||||
}
|
||||
lastts[type] = nextts;
|
||||
#endif
|
||||
default:
|
||||
CPPUNIT_ASSERT(type >= 0 && type < TEST_MAX_SOURCE);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return ss;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -480,7 +508,7 @@ static class RegisterTestEvolution {
|
|||
public:
|
||||
RegisterTestEvolution() :
|
||||
testClient("1") {
|
||||
#ifdef HAVE_GLIB
|
||||
#if defined(HAVE_GLIB) && defined(HAVE_EDS)
|
||||
// this is required on Maemo and does not harm either on a normal
|
||||
// desktop system with Evolution
|
||||
g_type_init();
|
||||
|
|
|
@ -87,7 +87,7 @@ int main( int argc, char **argv )
|
|||
setenv("DBUS_DEFAULT_TIMEOUT", "600000", 0);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GLIB
|
||||
#if defined(HAVE_GLIB) && defined(HAVE_EDS)
|
||||
// this is required on Maemo and does not harm either on a normal
|
||||
// desktop system with Evolution
|
||||
g_type_init();
|
||||
|
|
|
@ -227,8 +227,6 @@ static int hex2int( wchar_t x )
|
|||
0;
|
||||
}
|
||||
|
||||
#define SEMICOLON_REPLACEMENT '\a'
|
||||
|
||||
void VObject::toNativeEncoding()
|
||||
{
|
||||
bool is_30 = !wcscmp(getVersion(), TEXT("3.0"));
|
||||
|
|
|
@ -23,6 +23,7 @@ public:
|
|||
char* getVersion();
|
||||
char* getProdID();
|
||||
void addProperty(VProperty* property);
|
||||
void addProperty(const char *name, const char *value) { VProperty vprop((char *)name, (char *)value); addProperty(&vprop); }
|
||||
void addFirstProperty(VProperty* property);
|
||||
void insertProperty(VProperty* property);
|
||||
bool removeProperty(int index);
|
||||
|
@ -77,6 +78,9 @@ public:
|
|||
// object.
|
||||
//
|
||||
void fromNativeEncoding();
|
||||
|
||||
/** in native property values this special character separates sub-values in multi-value properties */
|
||||
static const char SEMICOLON_REPLACEMENT = '\a';
|
||||
};
|
||||
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue