changeset 198:7c60d6d1c681

decode more recurrence mapi elements
author Carl Byington <carl@five-ten-sg.com>
date Tue, 12 May 2009 19:34:49 -0700
parents 07ceebd115ce
children e3a46f66332b
files ChangeLog TODO configure.in libpst.spec.in python/Makefile.am python/python-libpst.cpp regression/regression-tests.bash src/libpst.c src/libpst.h src/lspst.c src/readpst.c src/timeconv.c
diffstat 12 files changed, 480 insertions(+), 160 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Wed May 06 10:37:46 2009 -0700
+++ b/ChangeLog	Tue May 12 19:34:49 2009 -0700
@@ -5,6 +5,10 @@
     * fix pst_attach_to_mem so the caller does not need to initialize
       the buffer pointer.
     * remove readpst -C switch, obsolete debugging code.
+    * update version to 4:0:0 since we made many changes to the interface.
+    * removed contact->access_method since we don't have a mapi element for it.
+    * changed pst_attach_to_mem to return pst_binary structure.
+    * decode more recurrence mapi elements.
 
 LibPST 0.6.37 (2009-04-17)
 ===============================
--- a/TODO	Wed May 06 10:37:46 2009 -0700
+++ b/TODO	Tue May 12 19:34:49 2009 -0700
@@ -4,9 +4,10 @@
 
 pst2diii needs header and mime type updates from readpst.
 
-At the next soname bump (to libpst.so.3) we should
-    remove contact->access_method (or find the proper mapi element id for it)
-    remove pst_x_attrib_ll->type (unused)
-    reorder appointment fields to collect related fields
-    remove readpstlog, and produce directly ascii debug log files
-    change pst_attach_to_mem to return pst_binary
+At the next soname bump (to libpst.so.5) we should
+    remove readpstlog, and produce ascii debug log files
+    try fork() with child limit to get parallel readpst
+    move some of readpst into the shared library, in particular write_normal_email()
+    add helper function item_actual_type (mail, contact, etc)
+    change gmtime() and ctime() to _r versions for thread safe operation
+    <http://www.geocities.com/cainrandom/dev/MAPIRecurrence.html>
--- a/configure.in	Wed May 06 10:37:46 2009 -0700
+++ b/configure.in	Tue May 12 19:34:49 2009 -0700
@@ -19,9 +19,9 @@
 #  6. libtool will build libpst.so.x.y.z where the SONAME is libpst.so.x
 #     and x=current-age, y=age, z=revision
 
-libpst_version_info='3:0:1'
+libpst_version_info='4:0:0'
 AC_SUBST(LIBPST_VERSION_INFO, [$libpst_version_info])
-libpst_so_major='2'
+libpst_so_major='4'
 AC_SUBST(LIBPST_SO_MAJOR, [$libpst_so_major])
 
 # libpst
@@ -31,15 +31,6 @@
 # 0.6.38    libpst.so.2     libpst.so.2.1.0
 
 
-# check for boost
-AX_PYTHON
-AX_BOOST_PYTHON
-if test "$ax_python_bin" = "no"; then
-    AC_MSG_ERROR(python binary not found)
-fi
-
-AC_SUBST(PYTHON_VERSION, [$ax_python_bin])
-
 
 # Check for win32
 AC_MSG_CHECKING([for Win32])
@@ -155,6 +146,7 @@
 
 # Checks for library functions.
 AC_FUNC_FSEEKO
+AC_FUNC_STAT
 AC_FUNC_LSTAT
 AC_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK
 if test "$cross_compiling" != "yes"; then
@@ -262,6 +254,42 @@
 fi
 
 
+# The following lines adds the --enable-python option to configure:
+#
+# Give the user the choice to enter one of these:
+# --enable-python
+# --enable-python=yes
+# --enable-python=no
+#
+AC_MSG_CHECKING([whether to build the libpst python interface])
+AC_ARG_ENABLE([python],
+    AC_HELP_STRING([--enable-python], [build libpst python interface]),
+    [
+        case "${enableval}" in
+          yes) ;;
+          no)  ;;
+          *)   AC_MSG_ERROR(bad value ${enableval} for --python) ;;
+        esac
+    ],
+    [
+        enable_python=no
+    ])
+AC_MSG_RESULT([$enable_python])
+AM_CONDITIONAL(PYTHON_INTERFACE, [test "$enable_python" = "yes"])
+if test "$enable_python" = "yes"; then
+	enable_shared="yes"
+    # check for boost
+    AX_PYTHON
+    AX_BOOST_PYTHON
+    if test "$ax_python_bin" = "no"; then
+        AC_MSG_ERROR(python binary not found)
+    fi
+
+    AC_SUBST(PYTHON_VERSION, [$ax_python_bin])
+fi
+
+
+
 AC_OUTPUT(                  \
     Makefile                \
     debian/Makefile         \
--- a/libpst.spec.in	Wed May 06 10:37:46 2009 -0700
+++ b/libpst.spec.in	Tue May 12 19:34:49 2009 -0700
@@ -83,7 +83,7 @@
 
 
 %build
-%configure --enable-libpst-shared
+%configure --enable-libpst-shared --enable-python
 make %{?_smp_mflags}
 
 
@@ -149,7 +149,8 @@
 
 %changelog
 * Mon Apr 20 2009 Carl Byington <carl@five-ten-sg.com> - 0.6.38-1
-- add python interface to the shared library
+- add python interface to the shared library.
+- bump soname to version 4.
 
 * Fri Apr 17 2009 Carl Byington <carl@five-ten-sg.com> - 0.6.37-1
 - add pst_attach_to_mem() back into the shared library interface.
--- a/python/Makefile.am	Wed May 06 10:37:46 2009 -0700
+++ b/python/Makefile.am	Tue May 12 19:34:49 2009 -0700
@@ -1,3 +1,5 @@
+if PYTHON_INTERFACE
+
 if PLATFORM_WIN32
     NO_UNDEFINED = -no-undefined
 else
@@ -12,3 +14,5 @@
 
 # set the include path found by configure
 INCLUDES= -I$(srcdir)/.. -I$(srcdir)/../src $(all_includes) -I$(PYTHON_INCLUDE_DIR)
+
+endif
--- a/python/python-libpst.cpp	Wed May 06 10:37:46 2009 -0700
+++ b/python/python-libpst.cpp	Tue May 12 19:34:49 2009 -0700
@@ -27,25 +27,27 @@
 
 class pst {
 public:
-                   pst(const string filename);
-    virtual        ~pst();
-    pst_desc_tree* pst_getTopOfFolders();
-    ppst_binary    pst_attach_to_mem(pst_item_attach *attach);
-    size_t         pst_attach_to_file(pst_item_attach *attach, FILE* fp);
-    size_t         pst_attach_to_file_base64(pst_item_attach *attach, FILE* fp);
-    pst_desc_tree* pst_getNextDptr(pst_desc_tree* d);
-    pst_item*      pst_parse_item(pst_desc_tree *d_ptr, pst_id2_tree *m_head);
-    void           pst_freeItem(pst_item *item);
-    pst_index_ll*  pst_getID(uint64_t i_id);
-    int            pst_decrypt(uint64_t i_id, char *buf, size_t size, unsigned char type);
-    size_t         pst_ff_getIDblock_dec(uint64_t i_id, char **buf);
-    size_t         pst_ff_getIDblock(uint64_t i_id, char** buf);
-    string         pst_rfc2426_escape(char *str);
-    string         pst_rfc2425_datetime_format(const FILETIME *ft);
-    string         pst_rfc2445_datetime_format(const FILETIME *ft);
-    string         pst_default_charset(pst_item *item);
-    void           pst_convert_utf8_null(pst_item *item, pst_string *str);
-    void           pst_convert_utf8(pst_item *item, pst_string *str);
+                    pst(const string filename);
+    virtual         ~pst();
+    pst_desc_tree*  pst_getTopOfFolders();
+    ppst_binary     pst_attach_to_mem(pst_item_attach *attach);
+    size_t          pst_attach_to_file(pst_item_attach *attach, FILE* fp);
+    size_t          pst_attach_to_file_base64(pst_item_attach *attach, FILE* fp);
+    pst_desc_tree*  pst_getNextDptr(pst_desc_tree* d);
+    pst_item*       pst_parse_item(pst_desc_tree *d_ptr, pst_id2_tree *m_head);
+    void            pst_freeItem(pst_item *item);
+    pst_index_ll*   pst_getID(uint64_t i_id);
+    int             pst_decrypt(uint64_t i_id, char *buf, size_t size, unsigned char type);
+    size_t          pst_ff_getIDblock_dec(uint64_t i_id, char **buf);
+    size_t          pst_ff_getIDblock(uint64_t i_id, char** buf);
+    string          pst_rfc2426_escape(char *str);
+    string          pst_rfc2425_datetime_format(const FILETIME *ft);
+    string          pst_rfc2445_datetime_format(const FILETIME *ft);
+    string          pst_default_charset(pst_item *item);
+    void            pst_convert_utf8_null(pst_item *item, pst_string *str);
+    void            pst_convert_utf8(pst_item *item, pst_string *str);
+    pst_recurrence* pst_convert_recurrence(pst_item_appointment *appt);
+    void            pst_free_recurrence(pst_recurrence* r);
 
     /** helper for python access to fopen() */
     FILE*          ppst_open_file(string filename, string mode);
@@ -79,76 +81,88 @@
     if (is_open) ::pst_close(&pf);
 }
 
-pst_desc_tree* pst::pst_getTopOfFolders() {
+pst_desc_tree*  pst::pst_getTopOfFolders() {
     return topf;
 }
 
-ppst_binary    pst::pst_attach_to_mem(pst_item_attach *attach) {
+ppst_binary     pst::pst_attach_to_mem(pst_item_attach *attach) {
+    pst_binary r = ::pst_attach_to_mem(&pf, attach);
     ppst_binary rc;
-    rc.size = rc.size = ::pst_attach_to_mem(&pf, attach, &rc.data);
+    rc.size = r.size;
+    rc.data = r.data;
     return rc;
 }
 
-size_t         pst::pst_attach_to_file(pst_item_attach *attach, FILE* fp) {
+size_t          pst::pst_attach_to_file(pst_item_attach *attach, FILE* fp) {
     return ::pst_attach_to_file(&pf, attach, fp);
 }
 
-size_t         pst::pst_attach_to_file_base64(pst_item_attach *attach, FILE* fp) {
+size_t          pst::pst_attach_to_file_base64(pst_item_attach *attach, FILE* fp) {
     return ::pst_attach_to_file_base64(&pf, attach, fp);
 }
 
-pst_desc_tree* pst::pst_getNextDptr(pst_desc_tree* d) {
+pst_desc_tree*  pst::pst_getNextDptr(pst_desc_tree* d) {
     return ::pst_getNextDptr(d);
 }
 
-pst_item*      pst::pst_parse_item (pst_desc_tree *d_ptr, pst_id2_tree *m_head) {
+pst_item*       pst::pst_parse_item (pst_desc_tree *d_ptr, pst_id2_tree *m_head) {
     return ::pst_parse_item(&pf, d_ptr, m_head);
 }
 
-void           pst::pst_freeItem(pst_item *item) {
+void            pst::pst_freeItem(pst_item *item) {
     return ::pst_freeItem(item);
 }
 
-pst_index_ll*  pst::pst_getID(uint64_t i_id) {
+pst_index_ll*   pst::pst_getID(uint64_t i_id) {
     return ::pst_getID(&pf, i_id);
 }
 
-int            pst::pst_decrypt(uint64_t i_id, char *buf, size_t size, unsigned char type) {
+int             pst::pst_decrypt(uint64_t i_id, char *buf, size_t size, unsigned char type) {
     return ::pst_decrypt(i_id, buf, size, type);
 }
 
-size_t         pst::pst_ff_getIDblock_dec(uint64_t i_id, char **buf) {
+size_t          pst::pst_ff_getIDblock_dec(uint64_t i_id, char **buf) {
     return ::pst_ff_getIDblock_dec(&pf, i_id, buf);
 }
 
-size_t         pst::pst_ff_getIDblock(uint64_t i_id, char** buf) {
+size_t          pst::pst_ff_getIDblock(uint64_t i_id, char** buf) {
     return ::pst_ff_getIDblock(&pf, i_id, buf);
 }
 
-string         pst::pst_rfc2426_escape(char *str) {
+string          pst::pst_rfc2426_escape(char *str) {
     return ::pst_rfc2426_escape(str);
 }
 
-string         pst::pst_rfc2425_datetime_format(const FILETIME *ft) {
+string          pst::pst_rfc2425_datetime_format(const FILETIME *ft) {
     return ::pst_rfc2425_datetime_format((FILETIME *)ft);   // cast away const is ok, since libpst did not modify it anyway, and the signature will change in more recent versions
 }
 
-string         pst::pst_rfc2445_datetime_format(const FILETIME *ft) {
+string          pst::pst_rfc2445_datetime_format(const FILETIME *ft) {
     return ::pst_rfc2445_datetime_format((FILETIME *)ft);   // cast away const is ok, since libpst did not modify it anyway, and the signature will change in more recent versions
 }
 
-string         pst::pst_default_charset(pst_item *item) {
+string          pst::pst_default_charset(pst_item *item) {
     return ::pst_default_charset(item);
 }
 
-void           pst::pst_convert_utf8_null(pst_item *item, pst_string *str) {
+void            pst::pst_convert_utf8_null(pst_item *item, pst_string *str) {
     ::pst_convert_utf8_null(item, str);
 }
 
-void           pst::pst_convert_utf8(pst_item *item, pst_string *str) {
+void            pst::pst_convert_utf8(pst_item *item, pst_string *str) {
     ::pst_convert_utf8(item, str);
 }
 
+pst_recurrence* pst::pst_convert_recurrence(pst_item_appointment *appt)
+{
+    return ::pst_convert_recurrence(appt);
+}
+
+void            pst::pst_free_recurrence(pst_recurrence* r)
+{
+    ::pst_free_recurrence(r);
+}
+
 FILE*          pst::ppst_open_file(string filename, string mode) {
     return ::fopen(filename.c_str(), mode.c_str());
 }
@@ -189,6 +203,13 @@
     }
 };
 
+struct make_python_pst_recurrence {
+    static PyObject* convert(pst_recurrence* const &s) {
+        if (s) return to_python_indirect<pst_recurrence*, detail::make_reference_holder>()(s);
+        return NULL;
+    }
+};
+
 struct make_python_pst_item_email {
     static PyObject* convert(pst_item_email* const &s) {
         if (s) return to_python_indirect<pst_item_email*, detail::make_reference_holder>()(s);
@@ -231,6 +252,7 @@
     to_python_converter<pst_binary,       make_python_pst_binary>();
     to_python_converter<ppst_binary,      make_python_ppst_binary>();
     to_python_converter<char*,            make_python_string>();
+    to_python_converter<pst_recurrence*,  make_python_pst_recurrence>();
     to_python_converter<pst_item_email*,  make_python_pst_item_email>();
     to_python_converter<pst_item_attach*, make_python_pst_item_attach>();
     to_python_converter<pst_desc_tree*,   make_python_pst_desc_tree>();
@@ -367,7 +389,6 @@
         ;
 
     class_<pst_item_contact>("pst_item_contact")
-        .def_readonly("access_method",                   &pst_item_contact::access_method)
         .def_readonly("account_name",                    &pst_item_contact::account_name)
         .def_readonly("address1",                        &pst_item_contact::address1)
         .def_readonly("address1a",                       &pst_item_contact::address1a)
@@ -490,25 +511,42 @@
         ;
 
     class_<pst_item_journal>("pst_item_journal")
+        .add_property("start", make_getter(&pst_item_journal::start, return_value_policy<reference_existing_object>()))
         .add_property("end",   make_getter(&pst_item_journal::end, return_value_policy<reference_existing_object>()))
-        .add_property("start", make_getter(&pst_item_journal::start, return_value_policy<reference_existing_object>()))
         .def_readonly("type",              &pst_item_journal::type)
         .def_readonly("description",       &pst_item_journal::description)
         ;
 
+    class_<pst_recurrence>("pst_recurrence")
+        .def_readonly("signature",                    &pst_recurrence::signature)
+        .def_readonly("type",                         &pst_recurrence::type)
+        .def_readonly("sub_type",                     &pst_recurrence::sub_type)
+        .def_readonly("parm1",                        &pst_recurrence::parm1)
+        .def_readonly("parm2",                        &pst_recurrence::parm2)
+        .def_readonly("parm3",                        &pst_recurrence::parm3)
+        .def_readonly("parm4",                        &pst_recurrence::parm4)
+        .def_readonly("parm5",                        &pst_recurrence::parm5)
+        .def_readonly("termination",                  &pst_recurrence::termination)
+        .def_readonly("interval",                     &pst_recurrence::interval)
+        .def_readonly("count",                        &pst_recurrence::count)
+        ;
+
     class_<pst_item_appointment>("pst_item_appointment")
+        .add_property("start",            make_getter(&pst_item_appointment::start, return_value_policy<reference_existing_object>()))
         .add_property("end",              make_getter(&pst_item_appointment::end, return_value_policy<reference_existing_object>()))
         .def_readonly("location",                     &pst_item_appointment::location)
+        .def_readonly("alarm",                        &pst_item_appointment::alarm)
         .add_property("reminder",         make_getter(&pst_item_appointment::reminder, return_value_policy<reference_existing_object>()))
         .def_readonly("alarm_minutes",                &pst_item_appointment::alarm_minutes)
         .def_readonly("alarm_filename",               &pst_item_appointment::alarm_filename)
-        .add_property("start",            make_getter(&pst_item_appointment::start, return_value_policy<reference_existing_object>()))
         .def_readonly("timezonestring",               &pst_item_appointment::timezonestring)
         .def_readonly("showas",                       &pst_item_appointment::showas)
         .def_readonly("label",                        &pst_item_appointment::label)
         .def_readonly("all_day",                      &pst_item_appointment::all_day)
-        .def_readonly("recurrence",                   &pst_item_appointment::recurrence)
+        .def_readonly("is_recurring",                 &pst_item_appointment::is_recurring)
         .def_readonly("recurrence_type",              &pst_item_appointment::recurrence_type)
+        .def_readonly("recurrence_description",       &pst_item_appointment::recurrence_description)
+        .def_readonly("recurrence_data",              &pst_item_appointment::recurrence_data)
         .add_property("recurrence_start", make_getter(&pst_item_appointment::recurrence_start, return_value_policy<reference_existing_object>()))
         .add_property("recurrence_end",   make_getter(&pst_item_appointment::recurrence_end, return_value_policy<reference_existing_object>()))
         ;
--- a/regression/regression-tests.bash	Wed May 06 10:37:46 2009 -0700
+++ b/regression/regression-tests.bash	Tue May 12 19:34:49 2009 -0700
@@ -99,27 +99,31 @@
     doldif  18 test-mac.pst
     #doldif  19 harris.pst
     doldif  20 spam.pst
+    dolif   21 rendgen.pst
 else
-    dopst   1 ams.pst
-    dopst   2 sample_64.pst
-    dopst   3 test.pst
-    dopst   4 big_mail.pst
-    dopst   5 mbmg.archive.pst
-    dopst   6 Single2003-read.pst
-    dopst   7 Single2003-unread.pst
-    dopst   8 ol2k3high.pst
-    dopst   9 ol97high.pst
-    dopst  10 returned_message.pst
-    dopst  11 flow.pst
-    dopst  12 test-html.pst
-    dopst  13 test-text.pst
-    dopst  14 joe.romanowski.pst
-    dopst  15 hourig1.pst
-    #dopst  16 hourig2.pst
-    #dopst  17 hourig3.pst
-    dopst  18 test-mac.pst
-    #dopst  19 harris.pst
-    dopst  20 spam.pst
+    #dopst   1 ams.pst
+    #dopst   2 sample_64.pst
+    #dopst   3 test.pst
+    #dopst   4 big_mail.pst
+    #dopst   5 mbmg.archive.pst
+    #dopst   6 Single2003-read.pst
+    #dopst   7 Single2003-unread.pst
+    #dopst   8 ol2k3high.pst
+    #dopst   9 ol97high.pst
+    #dopst  10 returned_message.pst
+    #dopst  11 flow.pst
+    #dopst  12 test-html.pst
+    #dopst  13 test-text.pst
+    #dopst  14 joe.romanowski.pst
+    #dopst  15 hourig1.pst
+    ##dopst  16 hourig2.pst
+    ##dopst  17 hourig3.pst
+    #dopst  18 test-mac.pst
+    ##dopst  19 harris.pst
+    #dopst  20 spam.pst
+    dopst  21 rendgen.pst       # single email appointment
+    dopst  22 rendgen2.pst      # email appointment with no termination date
+    dopst  23 rendgen3.pst      # mime signed email
 fi
 
 grep 'lost:' *err | grep -v 'lost: 0 '
--- a/src/libpst.c	Wed May 06 10:37:46 2009 -0700
+++ b/src/libpst.c	Tue May 12 19:34:49 2009 -0700
@@ -518,28 +518,27 @@
 }
 
 
-size_t pst_attach_to_mem(pst_file *pf, pst_item_attach *attach, char **b) {
+pst_binary pst_attach_to_mem(pst_file *pf, pst_item_attach *attach) {
     pst_index_ll *ptr;
-    pst_holder h = {b, NULL, 0};
-    size_t size = 0;
-    *b = NULL;
+    pst_binary rc;
+    pst_holder h = {&rc.data, NULL, 0};
+    rc.size = 0;
+    rc.data = NULL;
     DEBUG_ENT("pst_attach_to_mem");
     if ((!attach->data.data) && (attach->i_id != (uint64_t)-1)) {
         ptr = pst_getID(pf, attach->i_id);
         if (ptr) {
-            size = pst_ff_getID2data(pf, ptr, &h);
+            rc.size = pst_ff_getID2data(pf, ptr, &h);
         } else {
             DEBUG_WARN(("Couldn't find ID pointer. Cannot handle attachment\n"));
-            *b = NULL;
         }
     } else {
-        size = attach->data.size;
-        *b   = attach->data.data;
+        rc = attach->data;
         attach->data.data = NULL;   // prevent pst_free_item() from trying to free this
         attach->data.size = 0;      // since we have given that buffer to the caller
     }
     DEBUG_RET();
-    return size;
+    return rc;
 }
 
 
@@ -2063,6 +2062,12 @@
     LIST_COPY_BIN(targ);                            \
     DEBUG_EMAIL((label"\n"));                       \
 }
+#define LIST_COPY_APPT_BIN(label, targ) {           \
+    MALLOC_APPOINTMENT(item);                       \
+    LIST_COPY_BIN(targ);                            \
+    DEBUG_EMAIL((label"\n"));                       \
+    DEBUG_EMAIL_HEXPRINT(targ.data, targ.size);     \
+}
 
 #define NULL_CHECK(x) { if (!x) { DEBUG_WARN(("NULL_CHECK: Null Found\n")); break;} }
 
@@ -2163,11 +2168,8 @@
                             item->type = PST_TYPE_JOURNAL;
                         else if (pst_strincmp("IPM.Appointment", item->ascii_type, 15) == 0)
                             item->type = PST_TYPE_APPOINTMENT;
-                        //else if (pst_strincmp("IPM.Schedule.Meeting", item->ascii_type, 20) == 0)
-                        //    item->type = PST_TYPE_APPOINTMENT;
-                        // these seem to be appointments, but they are inside the email folder,
-                        // and unless we are in separate mode, we would dump an appointment
-                        // into the middle of a mailbox file.
+                        else if (pst_strincmp("IPM.Schedule.Meeting", item->ascii_type, 20) == 0)
+                            item->type = PST_TYPE_SCHEDULE;     // meeting requests and responses transported over email
                         else if (pst_strincmp("IPM.StickyNote", item->ascii_type, 14) == 0)
                             item->type = PST_TYPE_STICKYNOTE;
                         else if (pst_strincmp("IPM.Task", item->ascii_type, 8) == 0)
@@ -2926,8 +2928,14 @@
                 case 0x8215: // PR_OUTLOOK_EVENT_ALL_DAY
                     LIST_COPY_APPT_BOOL("All day flag", item->appointment->all_day);
                     break;
+                case 0x8216: // PR_OUTLOOK_EVENT_RECURRENCE_DATA
+                    LIST_COPY_APPT_BIN("Appointment recurrence data", item->appointment->recurrence_data);
+                    break;
+                case 0x8223: // PR_OUTLOOK_EVENT_IS_RECURRING
+                    LIST_COPY_APPT_BOOL("Is recurring", item->appointment->is_recurring);
+                    break;
                 case 0x8231: // Recurrence type
-                    LIST_COPY_APPT_ENUM("Appointment reccurence", item->appointment->recurrence_type, 0, 5,
+                    LIST_COPY_APPT_ENUM("Appointment recurrence type ", item->appointment->recurrence_type, 0, 5,
                         "None",
                         "Daily",
                         "Weekly",
@@ -2935,7 +2943,7 @@
                         "Yearly");
                     break;
                 case 0x8232: // Recurrence description
-                    LIST_COPY_APPT_STR("Appointment recurrence description", item->appointment->recurrence);
+                    LIST_COPY_APPT_STR("Appointment recurrence description", item->appointment->recurrence_description);
                     break;
                 case 0x8234: // TimeZone as String
                     LIST_COPY_APPT_STR("TimeZone of times", item->appointment->timezonestring);
@@ -3333,7 +3341,6 @@
             free(item->message_store);
         }
         if (item->contact) {
-            SAFE_FREE_STR(item->contact->access_method);
             SAFE_FREE_STR(item->contact->account_name);
             SAFE_FREE_STR(item->contact->address1);
             SAFE_FREE_STR(item->contact->address1a);
@@ -3443,19 +3450,20 @@
             item->extra_fields = et;
         }
         if (item->journal) {
+            SAFE_FREE(item->journal->start);
             SAFE_FREE(item->journal->end);
-            SAFE_FREE(item->journal->start);
             SAFE_FREE_STR(item->journal->type);
             free(item->journal);
         }
         if (item->appointment) {
+            SAFE_FREE(item->appointment->start);
+            SAFE_FREE(item->appointment->end);
             SAFE_FREE_STR(item->appointment->location);
             SAFE_FREE(item->appointment->reminder);
             SAFE_FREE_STR(item->appointment->alarm_filename);
-            SAFE_FREE(item->appointment->start);
-            SAFE_FREE(item->appointment->end);
             SAFE_FREE_STR(item->appointment->timezonestring);
-            SAFE_FREE_STR(item->appointment->recurrence);
+            SAFE_FREE_STR(item->appointment->recurrence_description);
+            SAFE_FREE_BIN(item->appointment->recurrence_data);
             SAFE_FREE(item->appointment->recurrence_start);
             SAFE_FREE(item->appointment->recurrence_end);
             free(item->appointment);
@@ -4165,7 +4173,7 @@
 
 char *pst_rfc2425_datetime_format(const FILETIME *ft) {
     static char buffer[30];
-    struct tm *stm = NULL;
+    struct tm* stm = NULL;
     DEBUG_ENT("rfc2425_datetime_format");
     stm = pst_fileTimeToStructTM(ft);
     if (strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%SZ", stm)==0) {
@@ -4178,7 +4186,7 @@
 
 char *pst_rfc2445_datetime_format(const FILETIME *ft) {
     static char buffer[30];
-    struct tm *stm = NULL;
+    struct tm* stm = NULL;
     DEBUG_ENT("rfc2445_datetime_format");
     stm = pst_fileTimeToStructTM(ft);
     if (strftime(buffer, sizeof(buffer), "%Y%m%dT%H%M%SZ", stm)==0) {
@@ -4189,6 +4197,20 @@
 }
 
 
+char *pst_rfc2445_datetime_format_now() {
+    static char buffer[30];
+    struct tm stm;
+    time_t t = time(NULL);
+    DEBUG_ENT("rfc2445_datetime_format_now");
+    gmtime_r(&t, &stm);
+    if (strftime(buffer, sizeof(buffer), "%Y%m%dT%H%M%SZ", &stm)==0) {
+        DEBUG_INFO(("Problem occured formatting date\n"));
+    }
+    DEBUG_RET();
+    return buffer;
+}
+
+
 /** Convert a code page integer into a string suitable for iconv()
  *
  *  @param cp the code page integer used in the pst file
@@ -4287,3 +4309,63 @@
     free(newer);
     DEBUG_RET();
 }
+
+
+/** Decode raw recurrence data into a better structure.
+ * @param appt pointer to appointment structure
+ * @return     pointer to decoded recurrence structure that must be free'd by the caller.
+ */
+pst_recurrence* pst_convert_recurrence(pst_item_appointment* appt)
+{
+    int m[4] = {3,4,4,5};
+    pst_recurrence *r = pst_malloc(sizeof(pst_recurrence));
+    memset(r, 0, sizeof(pst_recurrence));
+    size_t s = appt->recurrence_data.size;
+    size_t i = 0;
+    char*  p = appt->recurrence_data.data;
+    if (p) {
+        if (i+4 <= s) { r->signature        = PST_LE_GET_UINT32(p+i);        i += 4; }
+        if (i   <= s) { r->type             = PST_LE_GET_UINT8(p+i) - 0x0a;  i += 2; }
+        if (i+4 <= s) { r->sub_type         = PST_LE_GET_UINT32(p+i);        i += 4; }
+        if (r->sub_type <= 3) {
+            int n = m[r->sub_type];
+            int j = 0;
+            for (j=0; j<n; j++) {
+                if (i+4 <= s) { *(&r->parm1 + j) = PST_LE_GET_UINT32(p+i);   i += 4; }
+            }
+        }
+        if (i   <= s) { r->termination      = PST_LE_GET_UINT8(p+i) - 0x21;  i += 4; }
+        if (i+4 <= s) { r->count            = PST_LE_GET_UINT32(p+i);        i += 4; }
+        switch (r->type) {
+            case 0: // daily
+                r->interval = r->parm2 / (24 * 60); // was minutes between recurrences
+                if (r->sub_type) r->interval = 0;   // !! don't handle sub-type 1 yet
+                break;
+            case 1: // weekly
+                r->interval = r->parm2;
+                break;
+            case 2: // monthly
+                r->interval = r->parm2;
+                // two flavors, every month on the Dth day, and every month on the Nth Tuesday
+                // those are not handled here.
+                break;
+            case 3: // yearly
+                r->interval = 0;
+                // two flavors, every year on the Dth day of the Mth month, and every year on the Nth Tuesday of the Mth month
+                // those are not handled here.
+                break;
+            default:
+                break;
+        }
+    }
+    return r;
+}
+
+
+/** Free a recurrence structure.
+ * @param r input pointer to be freed
+ */
+void pst_free_recurrence(pst_recurrence* r)
+{
+    if (r) free(r);
+}
--- a/src/libpst.h	Wed May 06 10:37:46 2009 -0700
+++ b/src/libpst.h	Tue May 12 19:34:49 2009 -0700
@@ -24,6 +24,7 @@
 
 
 #define PST_TYPE_NOTE        1
+#define PST_TYPE_SCHEDULE    2
 #define PST_TYPE_APPOINTMENT 8
 #define PST_TYPE_CONTACT     9
 #define PST_TYPE_JOURNAL    10
@@ -32,6 +33,7 @@
 #define PST_TYPE_OTHER      13
 #define PST_TYPE_REPORT     14
 
+
 // defines types of possible encryption
 #define PST_NO_ENCRYPT   0
 #define PST_COMP_ENCRYPT 1
@@ -63,7 +65,7 @@
 #define PST_APP_LABEL_ANNIVERSARY 9
 #define PST_APP_LABEL_PHONE_CALL  10
 
-// define type of reccuring event
+// define type of recuring event
 #define PST_APP_RECUR_NONE        0
 #define PST_APP_RECUR_DAILY       1
 #define PST_APP_RECUR_WEEKLY      2
@@ -357,8 +359,6 @@
 /** This contains the contact related mapi elements
  */
 typedef struct pst_item_contact {
-    /** Unused - need to find the proper mapi element number for this  */
-    pst_string  access_method;
     /** mapi element 0x3a00 PR_ACCOUNT */
     pst_string  account_name;
     /** mapi element 0x3003 PR_EMAIL_ADDRESS, or 0x8083 */
@@ -612,10 +612,10 @@
 /** This contains the journal related mapi elements
  */
 typedef struct pst_item_journal {
+    /** mapi element 0x8706 */
+    FILETIME   *start;
     /** mapi element 0x8708 */
     FILETIME   *end;
-    /** mapi element 0x8706 */
-    FILETIME   *start;
     /** mapi element 0x8700 */
     pst_string  type;
     /** mapi element 0x8712 */
@@ -623,9 +623,50 @@
 } pst_item_journal;
 
 
+/** This contains the recurrence data separated into fields.
+    http://www.geocities.com/cainrandom/dev/MAPIRecurrence.html
+*/
+typedef struct pst_recurrence {
+    /** 0x30043004 */
+    uint32_t    signature;
+    /** @li 0 daily
+     *  @li 1 weekly
+     *  @li 2 monthly
+     *  @li 3 yearly */
+    uint32_t    type;
+    /** implies number of recurrence parameters
+     *  @li 0 has 3 parameters
+     *  @li 1 has 4 parameters
+     *  @li 2 has 4 parameters
+     *  @li 3 has 5 parameters
+     */
+    uint32_t    sub_type;
+    /** must be contiguous, not an array to make python interface easier */
+    uint32_t    parm1;
+    uint32_t    parm2;
+    uint32_t    parm3;
+    uint32_t    parm4;
+    uint32_t    parm5;
+    /** type of termination of the recurrence
+        @li 0 terminates on a date
+        @li 1 terminates based on integer number of occurrences
+        @li 2 never terminates
+     */
+    uint32_t    termination;
+    /** recurrence interval in terms of the recurrence type */
+    uint32_t    interval;
+    /** number of occurrences, even if recurrence terminates based on date */
+    uint32_t    count;
+    // there is more data, including the termination date,
+    // but we can get that from other mapi elements.
+} pst_recurrence;
+
+
 /** This contains the appointment related mapi elements
  */
 typedef struct pst_item_appointment {
+    /** mapi element 0x820d PR_OUTLOOK_EVENT_START_DATE */
+    FILETIME   *start;
     /** mapi element 0x820e PR_OUTLOOK_EVENT_START_END */
     FILETIME   *end;
     /** mapi element 0x8208 PR_OUTLOOK_EVENT_LOCATION */
@@ -640,8 +681,6 @@
     int32_t     alarm_minutes;
     /** mapi element 0x851f */
     pst_string  alarm_filename;
-    /** mapi element 0x820d PR_OUTLOOK_EVENT_START_DATE */
-    FILETIME   *start;
     /** mapi element 0x8234 */
     pst_string  timezonestring;
     /** mapi element 0x8205 PR_OUTLOOK_EVENT_SHOW_TIME_AS
@@ -667,8 +706,10 @@
      *  @li 1 true
      *  @li 0 false */
     int         all_day;
-    /** mapi element 0x8232 recurrence description */
-    pst_string  recurrence;
+    /** mapi element 0x8223 PR_OUTLOOK_EVENT_IS_RECURRING
+     *  @li 1 true
+     *  @li 0 false */
+    int         is_recurring;
     /** mapi element 0x8231
      *  @li 0 none
      *  @li 1 daily
@@ -676,6 +717,10 @@
      *  @li 3 monthly
      *  @li 4 yearly */
     int32_t     recurrence_type;
+    /** mapi element 0x8232 recurrence description */
+    pst_string  recurrence_description;
+    /** mapi element 0x8216 recurrence data */
+    pst_binary  recurrence_data;
     /** mapi element 0x8235 PR_OUTLOOK_EVENT_RECURRENCE_START */
     FILETIME   *recurrence_start;
     /** mapi element 0x8236 PR_OUTLOOK_EVENT_RECURRENCE_END  */
@@ -705,6 +750,7 @@
     pst_item_appointment   *appointment;
     /** derived from mapi elements 0x001a PR_MESSAGE_CLASS or 0x3613 PR_CONTAINER_CLASS
      *  @li  1 PST_TYPE_NOTE
+     *  @li  2 PST_TYPE_SCHEDULE
      *  @li  8 PST_TYPE_APPOINTMENT
      *  @li  9 PST_TYPE_CONTACT
      *  @li 10 PST_TYPE_JOURNAL
@@ -773,8 +819,6 @@
  *  (PST_ATTRIB_HEADER).
  */
 typedef struct pst_x_attrib_ll {
-    /** obsolete field, this is now unused */
-    uint32_t type;
     /** @li 1 PST_MAP_ATTRIB map->int attribute
         @li 2 PST_MAP_HEADER map->string header
      */
@@ -878,11 +922,10 @@
 /** Assemble the binary attachment into a single buffer.
  * @param pf     pointer to the pst_file structure setup by pst_open().
  * @param attach pointer to the attachment record
- * @param b      pointer to location to store the buffer pointer. The
- *               caller must free this buffer.
- * @return       size of the buffer, and return the buffer pointer in *b
+ * @return       structure containing size of and pointer to the buffer.
+ *               the caller must free this buffer.
  */
-size_t         pst_attach_to_mem(pst_file *pf, pst_item_attach *attach, char **b);
+pst_binary     pst_attach_to_mem(pst_file *pf, pst_item_attach *attach);
 
 
 /** Write a binary attachment to a file.
@@ -1000,28 +1043,48 @@
 char *         pst_rfc2445_datetime_format(const FILETIME *ft);
 
 
+/** Convert the current time rfc2445 date/time format 19531015T231000Z
+ * @return   time in rfc2445 format
+ */
+char *         pst_rfc2445_datetime_format_now();
+
+
 /** Get the default character set for this item. This is used to find
  *  the charset for pst_string elements that are not already in utf8 encoding.
- *  @param  item   pointer to the mapi item of interest
- *  @return default character set as a string useable by iconv()
+ * @param  item   pointer to the mapi item of interest
+ * @return default character set as a string useable by iconv()
  */
 const char*    pst_default_charset(pst_item *item);
 
 
 /** Convert str to utf8 if possible; null strings are preserved.
- *  @param item  pointer to the containing mapi item
- *  @param str   pointer to the mapi string of interest
+ * @param item  pointer to the containing mapi item
+ * @param str   pointer to the mapi string of interest
  */
 void           pst_convert_utf8_null(pst_item *item, pst_string *str);
 
 
 /** Convert str to utf8 if possible; null strings are converted into empty strings.
- *  @param item  pointer to the containing mapi item
- *  @param str   pointer to the mapi string of interest
+ * @param item  pointer to the containing mapi item
+ * @param str   pointer to the mapi string of interest
  */
 void           pst_convert_utf8(pst_item *item, pst_string *str);
 
 
+/** Decode raw recurrence data into a better structure.
+ * @param appt pointer to appointment structure
+ * @return     pointer to decoded recurrence structure that must be free'd by the caller.
+ */
+pst_recurrence* pst_convert_recurrence(pst_item_appointment* appt);
+
+
+/** Free a recurrence structure.
+ * @param r input pointer to be freed
+ */
+void pst_free_recurrence(pst_recurrence* r);
+
+
+
 // switch from maximal packing back to default packing
 // undo the packing from the beginning of this file
 #ifdef _MSC_VER
--- a/src/lspst.c	Wed May 06 10:37:46 2009 -0700
+++ b/src/lspst.c	Tue May 12 19:34:49 2009 -0700
@@ -75,6 +75,7 @@
                     process(item, d_ptr->child);
 
                 } else if (item->contact && (item->type == PST_TYPE_CONTACT)) {
+                    if (!ff.type) ff.type = item->type;
                     // Process Contact item
                     if (ff.type != PST_TYPE_CONTACT) {
                         DEBUG_MAIN(("main: I have a contact, but the folder isn't a contacts folder. Processing anyway\n"));
@@ -84,9 +85,10 @@
                         printf("\t%s", pst_rfc2426_escape(item->contact->fullname.str));
                     printf("\n");
 
-                } else if (item->email && (item->type == PST_TYPE_NOTE || item->type == PST_TYPE_REPORT)) {
+                } else if (item->email && ((item->type == PST_TYPE_NOTE) || (item->type == PST_TYPE_SCHEDULE) || (item->type == PST_TYPE_REPORT))) {
+                    if (!ff.type) ff.type = item->type;
                     // Process Email item
-                    if ((ff.type != PST_TYPE_NOTE) && (ff.type != PST_TYPE_REPORT)) {
+                    if ((ff.type != PST_TYPE_NOTE) && (ff.type != PST_TYPE_SCHEDULE) && (ff.type != PST_TYPE_REPORT)) {
                         DEBUG_MAIN(("main: I have an email, but the folder isn't an email folder. Processing anyway\n"));
                     }
                     printf("Email");
@@ -97,6 +99,7 @@
                     printf("\n");
 
                 } else if (item->journal && (item->type == PST_TYPE_JOURNAL)) {
+                    if (!ff.type) ff.type = item->type;
                     // Process Journal item
                     if (ff.type != PST_TYPE_JOURNAL) {
                         DEBUG_MAIN(("main: I have a journal entry, but folder isn't specified as a journal type. Processing...\n"));
@@ -105,6 +108,7 @@
                         printf("Journal\t%s\n", pst_rfc2426_escape(item->subject.str));
 
                 } else if (item->appointment && (item->type == PST_TYPE_APPOINTMENT)) {
+                    if (!ff.type) ff.type = item->type;
                     // Process Calendar Appointment item
                     DEBUG_MAIN(("main: Processing Appointment Entry\n"));
                     if (ff.type != PST_TYPE_APPOINTMENT) {
--- a/src/readpst.c	Wed May 06 10:37:46 2009 -0700
+++ b/src/readpst.c	Tue May 12 19:34:49 2009 -0700
@@ -52,10 +52,13 @@
 void      find_html_charset(char *html, char *charset, size_t charsetlen);
 void      find_rfc822_headers(char** extra_mime_headers);
 void      write_body_part(FILE* f_output, pst_string *body, char *mime, char *charset, char *boundary, pst_file* pst);
+void      write_schedule_part_data(FILE* f_output, pst_item* item, const char* sender, const char* method);
+void      write_schedule_part(FILE* f_output, pst_item* item, const char* sender, const char* boundary);
 void      write_normal_email(FILE* f_output, char f_name[], pst_item* item, int mode, int mode_MH, pst_file* pst, int save_rtf, char** extra_mime_headers);
 void      write_vcard(FILE* f_output, pst_item *item, pst_item_contact* contact, char comment[]);
-void      write_appointment(FILE* f_output, pst_item *item, pst_item_appointment* appointment,
-                            FILETIME* create_date, FILETIME* modify_date);
+void      write_journal(FILE* f_output, pst_item* item);
+int       file_time_compare(FILETIME* left, FILETIME* right);
+void      write_appointment(FILE* f_output, pst_item *item);
 void      create_enter_dir(struct file_ll* f, pst_item *item);
 void      close_enter_dir(struct file_ll *f);
 
@@ -179,10 +182,10 @@
                 }
             }
 
-        } else if (item->email && (item->type == PST_TYPE_NOTE || item->type == PST_TYPE_REPORT)) {
+        } else if (item->email && ((item->type == PST_TYPE_NOTE) || (item->type == PST_TYPE_SCHEDULE) || (item->type == PST_TYPE_REPORT))) {
             if (!ff.type) ff.type = item->type;
             DEBUG_MAIN(("main: Processing Email\n"));
-            if ((ff.type != PST_TYPE_NOTE) && (ff.type != PST_TYPE_REPORT)) {
+            if ((ff.type != PST_TYPE_NOTE) && (ff.type != PST_TYPE_SCHEDULE) && (ff.type != PST_TYPE_REPORT)) {
                 ff.skip_count++;
                 DEBUG_MAIN(("main: I have an email type %"PRIi32", but the folder type %"PRIi32" isn't an email folder. Skipping it\n", item->type, ff.type));
             }
@@ -203,18 +206,8 @@
             else {
                 ff.item_count++;
                 if (mode == MODE_SEPARATE) mk_separate_file(&ff);
-                fprintf(ff.output, "BEGIN:VJOURNAL\n");
-                if (item->subject.str) {
-                    pst_convert_utf8(item, &item->subject);
-                    fprintf(ff.output, "SUMMARY:%s\n", pst_rfc2426_escape(item->subject.str));
-                }
-                if (item->body.str) {
-                    pst_convert_utf8(item, &item->body);
-                    fprintf(ff.output, "DESCRIPTION:%s\n", pst_rfc2426_escape(item->body.str));
-                }
-                if (item->journal->start)
-                    fprintf(ff.output, "DTSTART;VALUE=DATE-TIME:%s\n", pst_rfc2445_datetime_format(item->journal->start));
-                fprintf(ff.output, "END:VJOURNAL\n\n");
+                write_journal(ff.output, item);
+                fprintf(ff.output, "\n");
             }
 
         } else if (item->appointment && (item->type == PST_TYPE_APPOINTMENT)) {
@@ -227,7 +220,8 @@
             else {
                 ff.item_count++;
                 if (mode == MODE_SEPARATE) mk_separate_file(&ff);
-                write_appointment(ff.output, item, item->appointment, item->create_date, item->modify_date);
+                write_appointment(ff.output, item);
+                fprintf(ff.output, "\n");
             }
 
         } else if (item->message_store) {
@@ -1044,6 +1038,41 @@
 }
 
 
+void write_schedule_part_data(FILE* f_output, pst_item* item, const char* sender, const char* method)
+{
+    fprintf(f_output, "BEGIN:VCALENDAR\n");
+    fprintf(f_output, "VERSION:2.0\n");
+    fprintf(f_output, "PRODID:LibPST\n");
+    fprintf(f_output, "METHOD:%s\n", method);
+    fprintf(f_output, "ORGANIZER;CN=\"%s\":MAILTO:%s\n", item->email->outlook_sender_name.str, sender);
+    write_appointment(f_output, item);
+    fprintf(f_output, "END:VCALENDAR\n");
+}
+
+
+void write_schedule_part(FILE* f_output, pst_item* item, const char* sender, const char* boundary)
+{
+    const char* method  = "REQUEST";
+    const char* charset = "utf-8";
+    char fname[20];
+    if (!item->appointment) return;
+
+    // inline appointment request
+    fprintf(f_output, "\n--%s\n", boundary);
+    fprintf(f_output, "Content-Type: %s; method=\"%s\"; charset=\"%s\"\n\n", "text/calendar", method, charset);
+    write_schedule_part_data(f_output, item, sender, method);
+    fprintf(f_output, "\n");
+
+    // attachment appointment request
+    snprintf(fname, sizeof(fname), "i%i.ics", rand());
+    fprintf(f_output, "\n--%s\n", boundary);
+    fprintf(f_output, "Content-Type: %s; charset=\"%s\"; name=\"%s\"\n", "text/calendar", "utf-8", fname);
+    fprintf(f_output, "Content-Disposition: attachment; filename=\"%s\"\n\n", fname);
+    write_schedule_part_data(f_output, item, sender, method);
+    fprintf(f_output, "\n");
+}
+
+
 void write_normal_email(FILE* f_output, char f_name[], pst_item* item, int mode, int mode_MH, pst_file* pst, int save_rtf, char** extra_mime_headers)
 {
     char boundary[60];
@@ -1230,7 +1259,8 @@
     }
     else if (item->attach || (item->email->rtf_compressed.data && save_rtf)
                           || item->email->encrypted_body.data
-                          || item->email->encrypted_htmlbody.data) {
+                          || item->email->encrypted_htmlbody.data
+                          || (item->type == PST_TYPE_SCHEDULE)) {
         // use multipart/mixed if we have attachments
         fprintf(f_output, "Content-Type: multipart/mixed;\n\tboundary=\"%s\"\n", boundary);
     } else {
@@ -1295,6 +1325,10 @@
         write_email_body(f_output, "The body of this email is encrypted. This isn't supported yet, but the body is now an attachment\n");
     }
 
+    if (item->type == PST_TYPE_SCHEDULE) {
+        write_schedule_part(f_output, item, sender, boundary);
+    }
+
     // other attachments
     {
         pst_item_attach* attach;
@@ -1473,19 +1507,62 @@
 }
 
 
-void write_appointment(FILE* f_output, pst_item *item,  pst_item_appointment* appointment,
-                       FILETIME* create_date, FILETIME* modify_date)
+void write_journal(FILE* f_output, pst_item* item)
 {
+    pst_item_journal* journal = item->journal;
+
+    // make everything utf8
+    pst_convert_utf8_null(item, &item->subject);
+    pst_convert_utf8_null(item, &item->body);
+
+    fprintf(f_output, "BEGIN:VJOURNAL\n");
+    fprintf(f_output, "DTSTAMP:%s\n",                     pst_rfc2445_datetime_format_now());
+    if (item->create_date)
+        fprintf(f_output, "CREATED:%s\n",                 pst_rfc2445_datetime_format(item->create_date));
+    if (item->modify_date)
+        fprintf(f_output, "LAST-MOD:%s\n",                pst_rfc2445_datetime_format(item->modify_date));
+    if (item->subject.str)
+        fprintf(f_output, "SUMMARY:%s\n",                 pst_rfc2426_escape(item->subject.str));
+    if (item->body.str)
+        fprintf(f_output, "DESCRIPTION:%s\n",             pst_rfc2426_escape(item->body.str));
+    if (journal && journal->start)
+        fprintf(f_output, "DTSTART;VALUE=DATE-TIME:%s\n", pst_rfc2445_datetime_format(journal->start));
+    fprintf(f_output, "END:VJOURNAL\n");
+}
+
+
+/**
+ compare two FILETIME objects after converting to unix time_t. This
+ allows FILETIMEs that are beyond the range of time_t representaion to
+ be converted to 0, and therefore seem to be less than the right
+ side. The actual recurrence end values seen in pst files are very
+ large (outside the range of 32 bit time_t), but still finite. That is
+ a strange way to represent an infinite recurrence.
+ */
+int file_time_compare(FILETIME* left, FILETIME* right)
+{
+    time_t delta = pst_fileTimeToUnixTime(left) - pst_fileTimeToUnixTime(right);
+    if (delta < 0) return -1;
+    if (delta > 0) return 1;
+    return 0;
+}
+
+
+void write_appointment(FILE* f_output, pst_item* item)
+{
+    pst_item_appointment* appointment = item->appointment;
+
     // make everything utf8
     pst_convert_utf8_null(item, &item->subject);
     pst_convert_utf8_null(item, &item->body);
     pst_convert_utf8_null(item, &appointment->location);
 
     fprintf(f_output, "BEGIN:VEVENT\n");
-    if (create_date)
-        fprintf(f_output, "CREATED:%s\n",                 pst_rfc2445_datetime_format(create_date));
-    if (modify_date)
-        fprintf(f_output, "LAST-MOD:%s\n",                pst_rfc2445_datetime_format(modify_date));
+    fprintf(f_output, "DTSTAMP:%s\n",                     pst_rfc2445_datetime_format_now());
+    if (item->create_date)
+        fprintf(f_output, "CREATED:%s\n",                 pst_rfc2445_datetime_format(item->create_date));
+    if (item->modify_date)
+        fprintf(f_output, "LAST-MOD:%s\n",                pst_rfc2445_datetime_format(item->modify_date));
     if (item->subject.str)
         fprintf(f_output, "SUMMARY:%s\n",                 pst_rfc2426_escape(item->subject.str));
     if (item->body.str)
@@ -1509,6 +1586,15 @@
                 fprintf(f_output, "STATUS:CONFIRMED\n");
                 break;
         }
+        if (appointment->is_recurring) {
+            const char* rules[] = {"DAILY", "WEEKLY", "MONTHLY", "YEARLY"};
+            pst_recurrence *rdata = pst_convert_recurrence(appointment);
+            fprintf(f_output, "RRULE:FREQ=%s", rules[rdata->type]);
+            if (rdata->count)    fprintf(f_output, ";COUNT=%u", rdata->count);
+            if (rdata->interval) fprintf(f_output, ";INTERVAL=%u", rdata->interval);
+            fprintf(f_output, "\n");
+            pst_free_recurrence(rdata);
+        }
         switch (appointment->label) {
             case PST_APP_LABEL_NONE:
                 fprintf(f_output, "CATEGORIES:NONE\n");
@@ -1545,7 +1631,7 @@
                 break;
         }
     }
-    fprintf(f_output, "END:VEVENT\n\n");
+    fprintf(f_output, "END:VEVENT\n");
 }
 
 
@@ -1617,7 +1703,15 @@
                 f->dname, f->item_count, f->skip_count, f->stored_count));
     if (output_mode != OUTPUT_QUIET) printf("\t\"%s\" - %i items done, %i items skipped.\n",
                                             f->dname, f->item_count, f->skip_count);
-    if (f->output) fclose(f->output);
+    if (f->output) {
+        struct stat st;
+        fclose(f->output);
+        stat(f->name, &st);
+        if (!st.st_size) {
+            WARN(("removing empty output file %s ", f->name));
+            remove(f->name);
+        }
+    }
     free(f->name);
     free(f->dname);
 
--- a/src/timeconv.c	Wed May 06 10:37:46 2009 -0700
+++ b/src/timeconv.c	Tue May 12 19:34:49 2009 -0700
@@ -18,15 +18,12 @@
 
 time_t pst_fileTimeToUnixTime(const FILETIME *filetime)
 {
-    int64_t t = filetime->dwHighDateTime;
+    uint64_t t = filetime->dwHighDateTime;
+    const uint64_t bias = 11644473600LL;
     t <<= 32;
     t += filetime->dwLowDateTime;
-    t -= 116444736000000000LL;
-    if (t < 0) {
-        return -1 - ((-t - 1) / 10000000);
-    }
-    else {
-        return t / 10000000;
-    }
+    t /= 10000000;
+    t -= bias;
+    return ((t > (uint64_t)0x000000007fffffff) && (sizeof(time_t) <= 4)) ? 0 : (time_t)t;
 }