<?xml version="1.0"?>
<!-- SYNTHESIS SYNC CLIENT Version 3.1 Configuration file -->
<!-- (works for V3.1.1.0 and later) -->

<sysync_config version="1.0">

  <configvar platform="win32"  name="logpath" value="$(exedir_path)\sysynclogs"/>
  <configvar platform="macosx" name="logpath" value="$(userdir_path)/sysynclogs"/>

  <configvar platform="macosx" name="targetdb" value="sunbird"/> <!-- %%% mac does not have that yet -->

  <!-- enter your license details here. Not all versions need a
       license (for example the demo runs without one).
       You should have received license code information
       along with the purchase or evaluation package.
       If not, please contact sales@synthesis.ch
  <licensename>your license text here</licensename>
  <licensecode>YOUR-LICE-NSES-CODE</licensecode>
  -->

  <!-- this string is output to every session debug logfile to identify the config in use -->
  <configidstring>Sunbird/Lightning 0.5 client demo using SQLite direct access</configidstring>

  <debug>
    <!-- path where logfiles are stored -->
    <logpath>$(logpath)</logpath>
    <logflushmode>flush</logflushmode> <!-- buffered is fastest mode, but may loose data on process abort. Other options: "flush" (after every line) or "openclose" (safest, slowest, like in 2.x server) -->
    <!-- per session log -->
    <sessionlogs>yes</sessionlogs> <!-- by default, create a session log file for every sync session (might be disabled for special users/devices in scripts) -->
    <!-- debug format options -->
    <logformat>html</logformat> <!-- html is nicely colored and easily viewable with a web browser. Other options: "xml", "text" -->
    <timestamp>yes</timestamp> <!-- show timestamps for structure elements in log -->
    <timestampall>no</timestampall> <!-- don't show timestamp for every log line -->
    <timedsessionlognames>yes</timedsessionlognames> <!-- session logs also have the session start timestamp in the filename - makes them more easily sortable -->
    <!-- thread logging mode -->
    <subthreadmode>separate</subthreadmode> <!-- write log info from subthreads into separate log files. Other options: "suppress" -->
    <!-- basic debug level selection -->
    <enable option="extended"/> <!-- "extended" is a good choice for start testing. For production, use "normal" or "minimal" -->
    <!-- <enable option="normal"/> --> <!-- "normal" provides rich debug info, but still in reasonable size  -->
    <!-- <enable option="minimal"/> --> <!-- "minimal" just shows basic flow and error. Not suitable for debugging -->
    <!-- <enable option="maximal"/> --> <!-- "maximal" can create VERY LARGE logs and cause HEAVY SLOWDOWN. Only for detail debugging  -->
    <!-- <enable option="all"/> --> <!-- "all" shows EVERYTHING possible, and way too much for any normal situation. For hardcore debugging ONLY! -->
    <!-- additional debug info switches -->
    <enable option="userdata"/> <!-- Make this <disable ...> if you don't want user data in the logs -->
    <enable option="scripts"/> <!-- Make this <enable ...> to show script execution in logs  -->
    <disable option="exotic"/> <!-- Make this <enable ...> to include very in-detail info. CAN PRODUCE ENORMOUS LOGS and HEAVILY IMPACT PERFORMANCE for large slow syncs - use with care!  -->
    <!-- see manual for more debug info switches -->
    <!-- global log options -->
    <globallogs>yes</globallogs> <!-- by default, do not log global session dispatching, creation etc. (not useful in multi-user operation) -->
    <singlegloballog>no</singlegloballog> <!-- a new global log will be started for every start of the server/application -->
    <!-- SyncML message dumping options -->
    <msgdump>no</msgdump> <!-- do not dump syncml traffic 1:1 to files -->
    <xmltranslate>no</xmltranslate> <!-- do not try to translate syncml traffic into XML (DO NOT SET THIS OPTION IN PRODUCTIVE SERVERS!) -->
  </debug>

  <!-- Note: since 2.1.1.5, clients have a <transport> section to specify <keepconnection> behaviour -->
  <transport type="xpt">
    <!-- allow HTTP 1.1 kepp-alive (multiple request-answer-exchanges in single TCP connection) -->
    <keepconnection>true</keepconnection>
  </transport>

  <scripting>
    <looptimeout>5</looptimeout>
  </scripting>


  <datatypes>


    <!-- list of internal fields representing vCalendar data
         (for both events (vEVENT) and tasks (vTODO)) -->
    <fieldlist name="calendar">
      <field name="SYNCLVL" type="integer" compare="never"/>

      <!-- this field defines if the data item is an EVENT or a TODO (task) -->
      <field name="KIND" type="string" compare="always"/>

      <field name="DMODIFIED" type="timestamp" compare="never" age="yes"/>
      <field name="DCREATED" type="timestamp" compare="never"/>

      <field name="CATEGORIES" type="multiline" compare="conflict" merge="fillempty"/>
      <field name="CLASS" type="string" compare="conflict" merge="fillempty"/>

      <field name="SUMMARY" type="multiline" compare="always"/>
      <field name="DESCRIPTION" type="multiline" compare="slowsync" merge="lines"/>

      <field name="ATTENDEES" array="yes" type="multiline" compare="conflict" merge="fillempty"/>

      <field name="LOCATION" type="multiline" compare="conflict" merge="fillempty"/>

      <!-- alarm -->
      <field name="ALARM_TIME" type="timestamp" compare="conflict"/>
      <field name="ALARM_SNOOZE" type="string" compare="conflict"/>
      <field name="ALARM_REPEAT" type="string" compare="conflict"/>
      <field name="ALARM_MSG" type="string" compare="conflict"/>

      <!-- recurrence rule block, fields must be in that order, including
           DTSTART as last field !! -->
      <field name="RR_FREQ" type="string" compare="conflict"/>
      <field name="RR_INTERVAL" type="integer" compare="conflict"/>
      <field name="RR_FMASK" type="integer" compare="conflict"/>
      <field name="RR_LMASK" type="integer" compare="conflict"/>
      <field name="RR_END" type="timestamp" compare="conflict"/>

      <!-- Note: DTSTART/DTEND are compared in the <comparescript>,
                 therefore compare is set no "never" here -->
      <field name="DTSTART" type="timestamp" compare="never"/> <!-- also used as DUE in vTODO -->

      <field name="DTEND" type="timestamp" compare="never"/>

      <field name="ALLDAY" type="integer" compare="never"/>

      <!-- exception dates to the recurrence rule -->
      <field name="EXDATES" array="yes" type="timestamp" compare="conflict"/>

      <!-- for tasks only -->
      <field name="PRIORITY" type="integer" compare="conflict"/>
      <field name="STATUS" type="string" compare="conflict" merge="fillempty"/>

    </fieldlist>

    <!-- vCalendar profile, including subprofiles for
         both events (vEVENT) and tasks (vTODO) -->
    <mimeprofile name="vcalendar" fieldlist="calendar">

      <profile name="VCALENDAR" nummandatory="1">

        <property name="VERSION" mandatory="yes">
          <value conversion="version"/>
        </property>

        <property name="TZ" show="true" suppressempty="yes">
          <value field="DTSTART" conversion="tz"/>
        </property>

        <property name="DAYLIGHT" mode="daylight" show="true" suppressempty="yes">
          <value field="DTSTART" conversion="daylight"/>
        </property>

        <!-- sub-profile for events -->
        <subprofile name="VEVENT" nummandatory="1" field="KIND" value="EVENT" showprops="true">

          <property name="LAST-MODIFIED" delayedparsing="1">
            <value field="DMODIFIED"/>
          </property>

          <property name="DCREATED" suppressempty="yes">
            <value field="DCREATED"/>
          </property>

          <property name="CATEGORIES" suppressempty="yes">
            <value field="CATEGORIES"/>
          </property>

          <property name="CLASS" suppressempty="yes">
            <value field="CLASS"/>
          </property>

          <property name="SUMMARY" mandatory="yes">
            <value field="SUMMARY"/>
          </property>

          <property name="DESCRIPTION" mandatory="yes">
            <value field="DESCRIPTION"/>
          </property>

          <property name="LOCATION" suppressempty="yes">
            <value field="LOCATION"/>
          </property>

          <property name="DTSTART" suppressempty="yes">
            <value field="DTSTART"/>
            <!--
            <parameter name="TZID" default="no" show="yes">
              <value field="DTSTART" conversion="TZID"/>
            </parameter>
            -->
          </property>

          <property name="DTEND" suppressempty="yes">
            <value field="DTEND"/>
            <!--
            <parameter name="TZID" default="no" show="yes">
              <value field="DTEND" conversion="TZID"/>
            </parameter>
            -->
          </property>

          <property name="ATTENDEE" suppressempty="yes">
            <value field="ATTENDEES"/>
            <position field="ATTENDEES" repeat="ARRAY" increment="1" minshow="0"/>
          </property>

          <!-- recurrence rule (with delayed parsing, as it is dependent on DTSTART) -->
          <property name="RRULE" suppressempty="yes" delayedparsing="1">
            <!-- Note: RR_FREQ is the beginning of a block of fields
                 suitable for the "rrule" conversion mode -->
            <value field="RR_FREQ" conversion="rrule"/>
          </property>

          <!-- up to five exceptions for recurrence rule -->
          <property name="EXDATE" values="list" suppressempty="yes">
            <value field="EXDATES"/>
            <position field="EXDATES" repeat="ARRAY" increment="1" minshow="0"/>
          </property>

          <!-- AALARM and DALARM both use the same fields -->
          <property name="AALARM" values="4" suppressempty="yes">
            <value index="0" field="ALARM_TIME" conversion="emptyonly"/>
            <value index="1" field="ALARM_SNOOZE" conversion="emptyonly"/>
            <value index="2" field="ALARM_REPEAT" conversion="emptyonly"/>
            <value index="3" field="ALARM_MSG" conversion="emptyonly"/>
          </property>
          <property name="DALARM" values="4" suppressempty="yes">
            <value index="0" field="ALARM_TIME" conversion="emptyonly"/>
            <value index="1" field="ALARM_SNOOZE" conversion="emptyonly"/>
            <value index="2" field="ALARM_REPEAT" conversion="emptyonly"/>
            <value index="3" field="ALARM_MSG" conversion="emptyonly"/>
          </property>

        </subprofile>

        <!-- sub-profile for tasks (todo's) -->
        <subprofile name="VTODO" nummandatory="1" field="KIND" value="TODO" showprops="true">

          <property name="LAST-MODIFIED" show="false">
            <value field="DMODIFIED"/>
          </property>

          <property name="DCREATED" suppressempty="yes" show="false">
            <value field="DCREATED"/>
          </property>

          <property name="CATEGORIES" suppressempty="yes" show="false">
            <value field="CATEGORIES"/>
          </property>

          <property name="CLASS" suppressempty="yes" show="false">
            <value field="CLASS"/>
          </property>

          <property name="SUMMARY" mandatory="yes" show="false">
            <value field="SUMMARY"/>
          </property>

          <property name="DESCRIPTION" mandatory="yes" show="false">
            <value field="DESCRIPTION"/>
          </property>

          <property name="LOCATION" suppressempty="yes" show="false">
            <value field="LOCATION"/>
          </property>


          <property name="DUE" suppressempty="yes" show="true">
            <value field="DTSTART"/> <!-- we use DTSTART field for DUE property in tasks -->
          </property>

          <property name="ATTENDEE" suppressempty="yes" show="false">
            <value field="ATTENDEES"/>
            <position field="ATTENDEES" repeat="ARRAY" increment="1" minshow="0"/>
          </property>

          <property name="PRIORITY" suppressempty="yes" show="true">

            <value field="PRIORITY"/>
          </property>

          <property name="STATUS" suppressempty="yes" show="true">
            <value field="STATUS" conversion="emptyonly"/>
          </property>

          <!-- AALARM and DALARM both use the same fields -->
          <property name="AALARM" values="4" suppressempty="yes" show="false">
            <value index="0" field="ALARM_TIME" conversion="emptyonly"/>
            <value index="1" field="ALARM_SNOOZE" conversion="emptyonly"/>
            <value index="2" field="ALARM_REPEAT" conversion="emptyonly"/>
            <value index="3" field="ALARM_MSG" conversion="emptyonly"/>
          </property>
          <property name="DALARM" values="4" suppressempty="yes" show="false">
            <value index="0" field="ALARM_TIME" conversion="emptyonly"/>
            <value index="1" field="ALARM_SNOOZE" conversion="emptyonly"/>
            <value index="2" field="ALARM_REPEAT" conversion="emptyonly"/>
            <value index="3" field="ALARM_MSG" conversion="emptyonly"/>
          </property>

        </subprofile>

        <subprofile name="VTIMEZONE" mode="vtimezones" showprops="true"/>

      </profile>
    </mimeprofile>


    <!-- vCalendar 1.0 datatype, using vCalendar profile defined
         above -->
    <datatype name="vcalendar10" basetype="vcalendar">
      <version>1.0</version>
      <use mimeprofile="vcalendar"/>

      <!-- Scripting allows sophisticated data adjustment:
           make sure we have always have a SUMMARY, but avoid
           SUMMARY and DESCRIPTION being identical,
           as well as always having a DTSTART
           and an alarm message -->
      <incomingscript><![CDATA[
        // make sure we have all trailing and leading spaces eliminated
        DESCRIPTION=NORMALIZED(DESCRIPTION);
        SUMMARY=NORMALIZED(SUMMARY);
        // make sure we have at least a summary
        if (SUMMARY==EMPTY) SUMMARY=DESCRIPTION; // use description if we don't have a summary
        if (SUMMARY==EMPTY) SUMMARY="unnamed"; // set dummy summary if we still don't have one
        if (DESCRIPTION==SUMMARY) DESCRIPTION=EMPTY; // do not save twice
        // make sure we have DTSTART
        if (DTSTART==EMPTY) DTSTART=DTEND;
      ]]></incomingscript>

      <outgoingscript><![CDATA[
        // make sure we have all trailing and leading spaces eliminated
        DESCRIPTION=NORMALIZED(DESCRIPTION);
        SUMMARY=NORMALIZED(SUMMARY);
        // make sure we have both description AND summary
        if (DESCRIPTION==EMPTY) DESCRIPTION=SUMMARY; // use summary text to send
        if (SUMMARY==EMPTY) SUMMARY="unnamed";
        if (DESCRIPTION==EMPTY) DESCRIPTION=SUMMARY; // use summary text to send
        // make sure we have an alarm message
        if (ALARM_TIME!=EMPTY && ALARM_MSG==EMPTY) ALARM_MSG="alarm";
        // make sure we have DTSTART
        if (DTSTART==EMPTY) DTSTART=DTEND;
      ]]></outgoingscript>

    </datatype>


  </datatypes>


  <client type="sql">

    <binfilespath platform="win32">$(prefs_path)\synthesis.ch\syncml_client_$(targetdb)</binfilespath>

    <sessioninitscript><![CDATA[
      string s,res;
      string matches[];
      integer n;
      integer h,m;

      // %%% testing NUMFORMAT
      h=2;
      m=30;
      DEBUGMESSAGE (NUMFORMAT(h,3,"0","+") + ":" + NUMFORMAT(m,2,"0"));
      h=-2;
      m=30;
      DEBUGMESSAGE (NUMFORMAT(h,3,"0","+") + ":" + NUMFORMAT(m,2,"0"));
      h=11;
      m=30;
      DEBUGMESSAGE (NUMFORMAT(h,3,"0","+") + ":" + NUMFORMAT(m,2,"$"));
      h=11;
      m=3;
      DEBUGMESSAGE (NUMFORMAT(h,3,"0","+") + ":" + NUMFORMAT(m,-2,"x"));

      // %%% testing regexps
      s = "the quick brown fox faxes fudi fast";
      /*
      n = REGEX_FIND(s,"/QUICK/i",0);
      n = REGEX_FIND(s,"/QUICK (.*) fox/i",0);
      n = REGEX_FIND(s,"/(BroWN) (.*)/i",0);

      n = REGEX_MATCH(s,"/(QUICK) (.*) fox/i",0,matches);
      DEBUGMESSAGE(matches[0]);
      DEBUGMESSAGE(matches[1]);
      DEBUGMESSAGE(matches[2]);

      res = REGEX_REPLACE(s,"/f(.)/","[F]\\1(\\2)",0,2); // 2 recurrences
      DEBUGMESSAGE(res);
      */

      res = REGEX_REPLACE(s,"/f(.)/","[F]\\1(\\2)",0,0); // all
      DEBUGMESSAGE(res);

    ]]></sessioninitscript>


    <!-- IMPORTANT: Note that the following charset, lineend and quoting settings are relevant for session-level data access only.
         Most data access takes place in individual <datastores>, which each has the same three settings
         again locally (see below in the <datastore> sections). -->

    <!-- text db plugin is designed for UTF-8, make sure data is passed as UTF-8 (and not the ISO-8859-1 default) -->
    <datacharset>UTF-8</datacharset>
    <!-- text db plugin is designed for Unix linefeeds (\n, 0x0A), make data is passed with unix linefeeds (and not the DOS CRLF = 0x0D,0x0A default) -->
    <datalineends>unix</datalineends>

    <!-- If you want a text logfile, specify its full path here: -->
    <logfile platform="win32">$(logpath)\$(targetdb)_synclog.txt</logfile>

    <!-- %%% mac does not have $(targetdb) yet -->
    <logfile platform="macosx">$(logpath)/sunbirdsynclog.txt</logfile>

    <logenabled>yes</logenabled> <!-- log enabled by default (session login scripts might disable it for special users/devices) -->

    <!-- the logfile format can be customized with <loglabels> and <logformat>
         but if these are not specified, a default format will be used
    -->
    <!-- This sample produces a simplified logfile:
    <loglabels>SyncEndTime\tUser\tStatus\tSynctype\tRemoteName\tDatabase\tLocAdded\tLocUpdated\tLocDeleted\n\n</loglabels>
    <logformat>%seT\t%U\t%sS\t%tS\t%nR\t%lD\t%laI\t%luI\t%ldI\n</logformat>
    -->


    <!-- timeout for unfinished sessions in seconds -->
    <sessiontimeout>20</sessiontimeout>


    <datastore name="events" type="sql">

      <alertprepscript><![CDATA[
        if (targetsetting("extras") & 1 == 1) {
          setdaysrange(
            targetsetting("limit1"),
            targetsetting("limit2")
          );
        }
      ]]></alertprepscript>

      <dbtypeid>1001</dbtypeid> <!-- %%% DBID_MAPI_CONTACTS for now -->

      <sqlitefile platform="win32">$(mozillaprofile)\storage.sdb</sqlitefile>

      <!-- %%% mac does not have lightning support yet -->
      <sqlitefile platform="macosx">$(prefs_path)/Sunbird/Profiles/$(sunbirdprofile)/storage.sdb</sqlitefile>

      <lastmodfieldtype>unixtime_us</lastmodfieldtype>

      <!-- text db plugin is designed for UTF-8, make sure data is passed as UTF-8 (and not the ISO-8859-1 default) -->
      <datacharset>UTF-8</datacharset>
      <!-- text db plugin is designed for Unix linefeeds (\n, 0x0A), make data is passed with unix linefeeds (and not the DOS CRLF = 0x0D,0x0A default) -->
      <datalineends>unix</datalineends>

      <!-- specifies the time zone for timestamps stored in the database (UTC, SYSTEM, ...) -->
      <datatimezone>UTC</datatimezone>

      <!-- if set to yet, all timestamps read from the DB are converted to user time zone (from DB time zone) before sending to remote -->
      <userzoneoutput>yes</userzoneoutput>

      <!-- enable this one if the database needs all fields re-written for every UPDATE
           operation (exotic Triggers etc.). Normally, this is not needed
           (SyncML server will update only changed fields)
      -->
      <updateallfields>no</updateallfields>

      <!-- If this is set to yes, the server will try to convert
           the filter expressions to a WHERE clause which then can be used with %AF or %WF
           in the <selectidandmodifiedsql> statement. In this sample it is set to "yes"
           as it allows for efficient visibility control. It can be turned off if filters
           are not suitable for being included in SQL -->
      <dbcanfilter>yes</dbcanfilter>

      <!-- statement that retrieves KEY and MODIFIED timestamp for all records to be synced.
           Note that %AF or %WF must be included to make filtering work
      -->
      <selectidandmodifiedsql>SELECT id || coalesce(recurrence_id,'') as fullid,last_modified FROM cal_events %WF</selectidandmodifiedsql>

      <!-- We use our own IDs as ROWIDs are not persistent in SunBird -->
      <obtainidafterinsert>no</obtainidafterinsert>

      <localidscript><![CDATA[
        // create new ID from random plus time
        return "SySync:" + NUMFORMAT(RANDOM(10000),4,"0") + "_" + (STRING)MILLISECONDS(NOW());
      ]]></localidscript>

      <!-- statements to read, insert, update and delete single records in the database
           %N represents the name list of all mapped fields
           %v represents the value list of all mapped fields
           %V represents the name=value list of all mapped fields
           %k represents the record key (CONTACTS_KEY, EVENTS_KEY...)
           %f represents the folder key (see <folderkeysql> above)
      -->
      <selectdatasql>SELECT %N FROM cal_events WHERE id || coalesce(recurrence_id,'')='%k'</selectdatasql>
      <insertdatasql>INSERT INTO cal_events (id,last_modified,time_created,%N,cal_id,priority,event_start_tz,event_end_tz) VALUES ('%k',%M,%M,%v,0,0,'/mozilla.org/20070129_1/Africa/Ceuta','/mozilla.org/20070129_1/Africa/Ceuta')</insertdatasql>
      <updatedatasql>UPDATE cal_events SET last_modified=%M,%V WHERE id || coalesce(recurrence_id,'')='%k'</updatedatasql>
      <deletedatasql>DELETE FROM cal_events WHERE id || coalesce(recurrence_id,'')='%k'</deletedatasql>

      <!-- Database has combined Date/time field for saving time of last modification. -->
      <modtimestamp>yes</modtimestamp>

      <!-- filter to let only suitable vCalendar items pass -->
      <acceptfilter>F.KIND:=EVENT</acceptfilter>

      <!-- if set, SyncML DS 1.2 filtering is possible and will be indicated in server devInf -->
      <ds12filters>yes</ds12filters>

      <!-- if set, datastore supports filtering by a date range specification (SINCE,BEFORE or /dr())
           Note: if this is set to yes, actual range filtering must be implemented in the datatype
           and/or the datastore, usually with scripting. See <filterscript> for vCalendar above -->
      <daterangesupport>yes</daterangesupport>


      <!-- Mapping of the fields in SYNC_EVENTS to the common fieldlist "calendar"
           which is shared between events and tasks -->
      <fieldmap fieldlist="calendar">

        <initscript><![CDATA[
          // general vars
          integer ok, version, daylist[], numelems, i, wd, rc, rcidx;
          string s, dateslist[];
          // flags
          integer flags; // 0x08 = allday, 0x04 = must always be present
          // recurrence params
          string recur_type;
          integer interval,count;
          timestamp end_date,ts;
          string day; // comma separated list of weekday as 1=Su, 2=Mo ... 7=Sa
                      // plus for monthly: 8*occurrence in month
                      // (e.g: 37 = 0x25 = 4th thursday, -13 = 0x0D = last thursday)
          string monthday; // comma separated list of days in month
          string dates; // for exception dates

          // init random seed
          RANDOM(1,NOW());

          // init flags
          flags = 4;

          // check for correct schema version
          SQLEXECUTE("select version from cal_calendar_schema_version");
          ok = SQLFETCHROW();
          if (ok) {
            SQLGETCOLUMN(1,version,"numeric");
            ok = version == 7; // this configuration is for SunBird 0.5 with schema version 7
          }
          SQLCOMMIT();
          if (!ok) {
            DEBUGMESSAGE("SunBird SQLite database has none or wrong schema version");
            ABORTSESSION(510);
            RETURN false; // fail the script
          }
        ]]></initscript>


        <beforewritescript><![CDATA[
          // create flags
          flags = 0x04; // we need Bit2 always
          // check for allay (non-floating timestamps checked in user time zone)
          i = ALLDAYCOUNT(DTSTART,DTEND,true);
          if (i>0) {
            // all day - make dates span full day (required by SunBird)
            flags = flags | 0x08; // set allday bit
            DTSTART = CONVERTTOUSERZONE(DTSTART); // convert non-floating to user zone
            SETFLOATING(DTSTART); // make floating
            DTSTART = DATEONLY(DTSTART); // set to beginning of day
            DTEND = DTSTART + DAYUNITS(i); // make end exact number of days after start
          }
          // check for recurrence
          if (NORMALIZED(RR_FREQ)!=EMPTY)
            flags = flags | 0x10; // set recurrence bit
        ]]></beforewritescript>


        <afterwritescript><![CDATA[
          // update separately stored properties
          // - delete all associated properties
          SQLEXECUTE("DELETE FROM cal_properties WHERE (key='LOCATION' OR key='DESCRIPTION') AND item_id='%k'");
          if (!DELETING()) {
            // (re-)add properties
            // - location
            if (LOCATION!=EMPTY) {
              SQLEXECUTE(
                "INSERT INTO cal_properties (key,item_id,value) VALUES ('LOCATION','%k'," +
                DBLITERAL(LOCATION,"string") + ")"
              );
            }
            // - description
            if (DESCRIPTION!=EMPTY) {
              SQLEXECUTE(
                "INSERT INTO cal_properties (key,item_id,value) VALUES ('DESCRIPTION','%k'," +
                DBLITERAL(DESCRIPTION,"string") + ")"
              );
            }
          }
          SQLCOMMIT();
          // update recurrence
          // - delete all associated recurrence info
          SQLEXECUTE("DELETE FROM cal_recurrence WHERE item_id='%k'");
          if (!DELETING()) {
            // (re-)add recurrence info
            // - recurrence itself
            interval = RR_INTERVAL;
            end_date = RR_END;
            day = "";
            monthday = "";
            daylist = EMPTY;
            recur_type = EMPTY;
            // - check type of recurrence
            if (SUBSTR(RR_FREQ,0,1)=="D") {
              recur_type = "DAILY";
            }
            else if (RR_FREQ=="WW") {
              recur_type = "WEEKLY";
              // create list of days 1..7 = SU..SA
              i=0;
              while (i<7) {
                if (RR_FMASK & (1<<i))
                  daylist[SIZE(daylist)]=i+1;
                i=i+1;
              }
              day = EXPLODE(",",daylist);
            }
            else if (RR_FREQ=="MD") {
              recur_type = "MONTHLY";
              // create list of monthdays 1..31
              i=0;
              while (i<31) {
                if (RR_FMASK & (1<<i))
                  daylist[SIZE(daylist)] = i+1;
                if (RR_LMASK & (1<<i))
                  daylist[SIZE(daylist)] = -(i+1);
                i=i+1;
              }
              monthday = EXPLODE(",",daylist);
            }
            else if (RR_FREQ=="MW") {
              recur_type = "MONTHLY";
              // create list of nth weekday in month as (weekday 1..7) + (nTh * 8)
              i=0;
              while (i<35) {
                if (RR_FMASK & (1<<i))
                  daylist[SIZE(daylist)] = (i%7)+1 + 8*(i/7+1);
                if (RR_LMASK & (1<<i))
                  daylist[SIZE(daylist)] = -((i%7)+1 + 8*(i/7+1));
                i=i+1;
              }
              day = EXPLODE(",",daylist);
            }
            else if (SUBSTR(RR_FREQ,0,1)=="Y") {
              recur_type="YEARLY";
            }
            // - now write recurrence, if any
            if (NORMALIZED(RR_FREQ) != EMPTY) {
              // - write
              rcidx = 0;
              SQLEXECUTE(
                "INSERT INTO cal_recurrence (item_id, recur_index, recur_type, interval, end_date, day, monthday) VALUES ('%k', " +
                DBLITERAL(rcidx,"numeric") + "," +
                DBLITERAL(recur_type,"string") + "," +
                DBLITERAL(interval,"numeric") + "," +
                DBLITERAL(end_date,"unixtime_us") + "," +
                DBLITERAL(day,"string") + "," +
                DBLITERAL(monthday,"string") + ")"
              );
              rcidx=rcidx+1;
              // - exceptions
              i=0;
              WHILE(i<SIZE(EXDATES)) {
                dates = "ZT" + (string)(TIMESTAMPTODBINT(CONVERTTODATAZONE(EXDATES[i]),"unixtime_us")) + ":/mozilla.org/20070129_1/Africa/Ceuta";
                // create x-date exception record
                SQLEXECUTE(
                  "INSERT INTO cal_recurrence (item_id, recur_index, recur_type, is_negative, dates) VALUES ('%k', " +
                  DBLITERAL(rcidx,"numeric") + "," +
                  "'x-date',1," +
                  DBLITERAL(dates,"string") + ")"
                );
                rcidx=rcidx+1;
                i=i+1;
              }
              // done
              SQLCOMMIT();
            }
          } // not DELETING()
        ]]></afterwritescript>


        <afterreadscript><![CDATA[
          // apply flags
          if (flags & 8) {
            // all day, adjust time and make timestamps floating
            MAKEALLDAY(DTSTART,DTEND);
            SETFLOATING(RR_END);
          }
          // get separately stored properties
          // - location
          SQLEXECUTE("SELECT value FROM cal_properties WHERE key='LOCATION' AND item_id='%k'");
          IF (SQLFETCHROW()) {
            SQLGETCOLUMN(1,LOCATION,"string");
          }
          // - description
          SQLEXECUTE("SELECT value FROM cal_properties WHERE key='DESCRIPTION' AND item_id='%k'");
          IF (SQLFETCHROW()) {
            SQLGETCOLUMN(1,DESCRIPTION,"string");
          }
          SQLCOMMIT();
          // - recurrence
          SQLEXECUTE("SELECT recur_type, count, interval, end_date, day, monthday, dates FROM cal_recurrence WHERE item_id='%k'");
          WHILE (SQLFETCHROW()) {
            // get fields
            SQLGETCOLUMN(1,recur_type,"string");
            SQLGETCOLUMN(2,count,"numeric");
            SQLGETCOLUMN(3,interval,"numeric");
            SQLGETCOLUMN(4,end_date,"unixtime_us");
            SQLGETCOLUMN(5,day,"string");
            SQLGETCOLUMN(6,monthday,"string");
            SQLGETCOLUMN(7,dates,"string");
            DEBUGSHOWVARS();
            // calculate internal fields
            if (recur_type=="x-date") {
              // evaluate date(s) and make exceptions out of it
              numelems = REGEX_SPLIT(dates,"/[\s,]+/",dateslist);
              i=0;
              WHILE(i<numelems) {
                REGEX_MATCH(dateslist[i],"/(?<=ZT)\\d+/",0,rc);
                ts = DBINTTOTIMESTAMP(rc,"unixtime_us");
                EXDATES[SIZE(EXDATES)] = CONVERTTODATAZONE(ts,true); // unfloat!
                i = i+1;
              }
            }
            else {
              // normal recurrence
              RR_FMASK = 0;
              RR_LMASK = 0;
              RR_INTERVAL = interval;
              RR_END = end_date;
              RR_FREQ = EMPTY;
              if (recur_type=="DAILY") {
                // daily
                RR_FREQ = "D ";
              }
              else if (recur_type=="WEEKLY") {
                // weekly by weekday list
                RR_FREQ = "WW";
                numelems = REGEX_SPLIT(day,"/[\s,]+/",daylist);
                i=0;
                while (i<numelems) {
                  RR_FMASK = RR_FMASK | (1<<(daylist[i]-1));
                  i = i+1;
                }
              }
              else if (recur_type=="MONTHLY") {
                if (monthday!=EMPTY) {
                  // monthly by monthday
                  RR_FREQ = "MD";
                  numelems = REGEX_SPLIT(monthday,"/[\s,]+/",daylist);
                  i=0;
                  while (i<numelems) {
                    rc = abs(daylist[i])-1;
                    if (daylist[i]>0)
                      RR_FMASK = RR_FMASK | (1<<rc);
                    else
                      RR_LMASK = RR_LMASK | (1<<rc);
                    i = i+1;
                  }
                }
                else {
                  // monthly by weekday
                  RR_FREQ = "MW";
                  numelems = REGEX_SPLIT(day,"/[\s,]+/",daylist);
                  i=0;
                  while (i<numelems) {
                    // format: Bit0..2 = weekday with offset 1, Bit 3,4 = occurrence in month with offset 1
                    rc = abs(daylist[i]);
                    wd = (rc & 0x07)-1;
                    rc = (rc/8-1)*7 + wd; // bit number for internal masks
                    if (daylist[i]>0) {
                      // counting from beginning
                      RR_FMASK = RR_FMASK | (1<<rc);
                    }
                    else {
                      // counting from end
                      RR_LMASK = RR_LMASK | (1<<rc);
                    }
                    i = i+1;
                  }
                }
              }
              else if (recur_type=="YEARLY") {
                RR_FREQ = "Y ";
              }
              // make sure start date is first recurrence
              DTSTART = RECURRENCE_DATE(
                DTSTART,RR_FREQ,RR_INTERVAL,RR_FMASK,RR_LMASK,
                true,1
              );
              // calculate end date if not specified
              if (RR_END==EMPTY) {
                // get date of count-th. recurrence = end date
                RR_END = RECURRENCE_DATE(
                  DTSTART,RR_FREQ,RR_INTERVAL,RR_FMASK,RR_LMASK,
                  true,count
                );
              }
            }
          }
          SQLCOMMIT();
          DEBUGSHOWITEM();
        ]]></afterreadscript>


        <map name="last_modified"  references="DMODIFIED" type="unixtime_us" mode="r"/>
        <map name="time_created"   references="DCREATED" type="unixtime_us" mode="r"/>

        <map name="privacy"        references="CLASS" type="string" mode="rw" size="63"/>

        <map name="title"          references="SUMMARY" type="string" mode="rw" size="254"/>

        <map name="flags"          references="flags" type="string" mode="rw"/>

        <map name="event_start"    references="DTSTART" type="unixtime_us" mode="rw"/>
        <map name="event_end"      references="DTEND" type="unixtime_us" mode="rw"/>

        <map name="alarm_time"     references="ALARM_TIME" type="unixtime_us" mode="rw"/>
      </fieldmap>

      <!-- datatypes supported by this datastore -->
      <typesupport>
        <use datatype="vcalendar10" mode="rw" preferred="yes" variant="VEVENT"/>
      </typesupport>

    </datastore>


  </client>

</sysync_config>
