[evolution-data-server/wip/offline-cache] Create SExp parser for ECalCache (untested)



commit c3d3b7733efe318ad3273097d5ca75078305bc44
Author: Milan Crha <mcrha redhat com>
Date:   Tue Feb 14 19:47:32 2017 +0100

    Create SExp parser for ECalCache (untested)

 src/calendar/libedata-cal/e-cal-cache.c | 1030 ++++++++++++++++++++++++++++++-
 src/calendar/libedata-cal/e-cal-cache.h |    4 +-
 2 files changed, 1011 insertions(+), 23 deletions(-)
---
diff --git a/src/calendar/libedata-cal/e-cal-cache.c b/src/calendar/libedata-cal/e-cal-cache.c
index bde0ed3..21a8dd4 100644
--- a/src/calendar/libedata-cal/e-cal-cache.c
+++ b/src/calendar/libedata-cal/e-cal-cache.c
@@ -32,10 +32,13 @@
 #include "evolution-data-server-config.h"
 
 #include <glib/gi18n-lib.h>
+#include <sqlite3.h>
 
 #include <libebackend/libebackend.h>
 #include <libecal/libecal.h>
 
+#include "e-cal-backend-sexp.h"
+
 #include "e-cal-cache.h"
 
 #define E_CAL_CACHE_VERSION            1
@@ -55,15 +58,21 @@
 #define ECC_COLUMN_CLASSIFICATION      "classification"
 #define ECC_COLUMN_STATUS              "status"
 #define ECC_COLUMN_PRIORITY            "priority"
+#define ECC_COLUMN_PERCENT_COMPLETE    "percent_complete"
 #define ECC_COLUMN_CATEGORIES          "categories"
 #define ECC_COLUMN_HAS_ALARM           "has_alarm"
+#define ECC_COLUMN_HAS_ATTACHMENT      "has_attachment"
 #define ECC_COLUMN_HAS_START           "has_start"
 #define ECC_COLUMN_HAS_RECURRENCES     "has_recurrences"
 #define ECC_COLUMN_EXTRA               "bdata"
 
 struct _ECalCachePrivate {
        GHashTable *loaded_timezones; /* gchar *tzid ~> icaltimezone * */
-       GMutex loaded_timezones_lock;
+       GHashTable *modified_timezones; /* gchar *tzid ~> icaltimezone * */
+       GRecMutex timezones_lock;
+
+       GHashTable *sexps; /* gint ~> ECalBackendSExp * */
+       GMutex sexps_lock;
 };
 
 enum {
@@ -73,8 +82,11 @@ enum {
 
 static guint signals[LAST_SIGNAL];
 
+static void ecc_timezone_cache_init (ETimezoneCacheInterface *iface);
+
 G_DEFINE_TYPE_WITH_CODE (ECalCache, e_cal_cache, E_TYPE_CACHE,
-                        G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL))
+                        G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL)
+                        G_IMPLEMENT_INTERFACE (E_TYPE_TIMEZONE_CACHE, ecc_timezone_cache_init))
 
 G_DEFINE_BOXED_TYPE (ECalCacheSearchData, e_cal_cache_search_data, e_cal_cache_search_data_copy, 
e_cal_cache_search_data_free)
 
@@ -154,6 +166,96 @@ e_cal_cache_search_data_free (gpointer ptr)
        }
 }
 
+static gint
+ecc_take_sexp_object (ECalCache *cal_cache,
+                     ECalBackendSExp *sexp)
+{
+       gint sexp_id;
+
+       g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), 0);
+       g_return_val_if_fail (E_IS_CAL_BACKEND_SEXP (sexp), 0);
+
+       g_mutex_lock (&cal_cache->priv->sexps_lock);
+
+       sexp_id = GPOINTER_TO_INT (sexp);
+       while (g_hash_table_contains (cal_cache->priv->sexps, GINT_TO_POINTER (sexp_id))) {
+               sexp_id++;
+       }
+
+       g_hash_table_insert (cal_cache->priv->sexps, GINT_TO_POINTER (sexp_id), sexp);
+
+       g_mutex_unlock (&cal_cache->priv->sexps_lock);
+
+       return sexp_id;
+}
+
+static void
+ecc_free_sexp_object (ECalCache *cal_cache,
+                     gint sexp_id)
+{
+       g_return_if_fail (E_IS_CAL_CACHE (cal_cache));
+
+       g_mutex_lock (&cal_cache->priv->sexps_lock);
+
+       g_warn_if_fail (g_hash_table_remove (cal_cache->priv->sexps, GINT_TO_POINTER (sexp_id)));
+
+       g_mutex_unlock (&cal_cache->priv->sexps_lock);
+}
+
+static ECalBackendSExp *
+ecc_ref_sexp_object (ECalCache *cal_cache,
+                    gint sexp_id)
+{
+       ECalBackendSExp *sexp;
+
+       g_mutex_lock (&cal_cache->priv->sexps_lock);
+
+       sexp = g_hash_table_lookup (cal_cache->priv->sexps, GINT_TO_POINTER (sexp_id));
+       if (sexp)
+               g_object_ref (sexp);
+
+       g_mutex_unlock (&cal_cache->priv->sexps_lock);
+
+       return sexp;
+}
+
+/* check_sexp(sexp_id, icalstring) */
+static void
+ecc_check_sexp_func (sqlite3_context *context,
+                    gint argc,
+                    sqlite3_value **argv)
+{
+       ECalCache *cal_cache;
+       ECalBackendSExp *sexp_obj;
+       gint sexp_id;
+       const gchar *icalstring;
+
+       g_return_if_fail (context != NULL);
+       g_return_if_fail (argc != 2);
+
+       cal_cache = sqlite3_user_data (context);
+       sexp_id = sqlite3_value_int (argv[0]);
+       icalstring = (const gchar *) sqlite3_value_text (argv[1]);
+
+       if (!E_IS_CAL_CACHE (cal_cache) || !icalstring || !*icalstring) {
+               sqlite3_result_int (context, 0);
+               return;
+       }
+
+       sexp_obj = ecc_ref_sexp_object (cal_cache, sexp_id);
+       if (!sexp_obj) {
+               sqlite3_result_int (context, 0);
+               return;
+       }
+
+       if (e_cal_backend_sexp_match_object (sexp_obj, icalstring, E_TIMEZONE_CACHE (cal_cache)))
+               sqlite3_result_int (context, 1);
+       else
+               sqlite3_result_int (context, 0);
+
+       g_object_unref (sexp_obj);
+}
+
 static gboolean
 e_cal_cache_get_string (ECache *cache,
                        gint ncols,
@@ -218,8 +320,10 @@ e_cal_cache_populate_other_columns (ECalCache *cal_cache,
        add_column (ECC_COLUMN_CLASSIFICATION, "TEXT", NULL);
        add_column (ECC_COLUMN_STATUS, "TEXT", NULL);
        add_column (ECC_COLUMN_PRIORITY, "INTEGER", NULL);
+       add_column (ECC_COLUMN_PERCENT_COMPLETE, "INTEGER", NULL);
        add_column (ECC_COLUMN_CATEGORIES, "TEXT", NULL);
        add_column (ECC_COLUMN_HAS_ALARM, "INTEGER", NULL);
+       add_column (ECC_COLUMN_HAS_ATTACHMENT, "INTEGER", NULL);
        add_column (ECC_COLUMN_HAS_START, "INTEGER", NULL);
        add_column (ECC_COLUMN_HAS_RECURRENCES, "INTEGER", NULL);
        add_column (ECC_COLUMN_EXTRA, "TEXT", NULL);
@@ -241,7 +345,7 @@ ecc_encode_id_sql (const gchar *uid,
        return g_strdup (uid);
 }
 
-/*static gboolean
+static gboolean
 ecc_decode_id_sql (const gchar *id,
                   gchar **out_uid,
                   gchar **out_rid)
@@ -270,11 +374,11 @@ ecc_decode_id_sql (const gchar *id,
        if (split[1])
                *out_rid = split[1];
 
-       / * array elements are taken by the out arguments * /
+       /* array elements are taken by the out arguments */
        g_free (split);
 
        return TRUE;
-}*/
+}
 
 static gchar *
 ecc_encode_itt_to_sql (struct icaltimetype itt)
@@ -591,7 +695,7 @@ ecc_fill_other_columns (ECalCache *cal_cache,
        icalproperty_status status;
        struct icaltimetype *itt;
        const gchar *str = NULL;
-       gint *priority = NULL;
+       gint *pint = NULL;
        gboolean has;
 
        g_return_if_fail (E_IS_CAL_CACHE (cal_cache));
@@ -631,12 +735,18 @@ ecc_fill_other_columns (ECalCache *cal_cache,
        e_cal_component_get_status (comp, &status);
        add_value (ECC_COLUMN_STATUS, g_strdup (ecc_get_status_as_string (status)));
 
-       e_cal_component_get_priority (comp, &priority);
-       add_value (ECC_COLUMN_PRIORITY, priority && *priority ? g_strdup_printf ("%d", *priority) : NULL);
+       e_cal_component_get_priority (comp, &pint);
+       add_value (ECC_COLUMN_PRIORITY, pint && *pint ? g_strdup_printf ("%d", *pint) : NULL);
+
+       e_cal_component_get_percent (comp, &pint);
+       add_value (ECC_COLUMN_PERCENT_COMPLETE, pint && *pint ? g_strdup_printf ("%d", *pint) : NULL);
 
        has = e_cal_component_has_alarms (comp);
        add_value (ECC_COLUMN_HAS_ALARM, g_strdup (has ? "1" : "0"));
 
+       has = e_cal_component_has_attachments (comp);
+       add_value (ECC_COLUMN_HAS_ALARM, g_strdup (has ? "1" : "0"));
+
        e_cal_component_get_dtstart (comp, &dt);
        has = dt.value != NULL;
        add_value (ECC_COLUMN_HAS_START, g_strdup (has ? "1" : "0"));
@@ -653,6 +763,699 @@ ecc_fill_other_columns (ECalCache *cal_cache,
        add_value (ECC_COLUMN_CATEGORIES, ecc_extract_categories (comp));
 }
 
+typedef struct _SExpToSqlContext {
+       ECalCache *cal_cache;
+       gint sexp_id;
+} SExpToSqlContext;
+
+static ESExpResult *
+ecc_sexp_func_and_or (ESExp *esexp,
+                     gint argc,
+                     ESExpTerm **argv,
+                     gpointer user_data,
+                     const gchar *oper)
+{
+       SExpToSqlContext *ctx = user_data;
+       ESExpResult *result, *r1;
+       GString *stmt;
+       gint ii;
+
+       g_return_val_if_fail (ctx != NULL, NULL);
+
+       stmt = g_string_new ("(");
+
+       for (ii = 0; ii < argc; ii++) {
+               r1 = e_sexp_term_eval (esexp, argv[ii]);
+
+               if (stmt->len > 1)
+                       g_string_append_printf (stmt, " %s ", oper);
+
+               if (r1 && r1->type == ESEXP_RES_STRING) {
+                       g_string_append_printf (stmt, "(%s)", r1->value.string);
+               } else {
+                       g_string_append_printf (stmt, "check_sexp(%d,%s)",
+                               ctx->sexp_id, E_CACHE_COLUMN_OBJECT);
+               }
+
+               e_sexp_result_free (esexp, r1);
+       }
+
+       if (stmt->len == 1) {
+               if (g_str_equal (oper, "AND"))
+                       g_string_append_c (stmt, '1');
+               else
+                       g_string_append_c (stmt, '0');
+       }
+
+       g_string_append_c (stmt, ')');
+
+       result = e_sexp_result_new (esexp, ESEXP_RES_STRING);
+       result->value.string = g_string_free (stmt, FALSE);
+
+       return result;
+}
+
+static ESExpResult *
+ecc_sexp_func_and (ESExp *esexp,
+                  gint argc,
+                  ESExpTerm **argv,
+                  gpointer user_data)
+{
+       return ecc_sexp_func_and_or (esexp, argc, argv, user_data, "AND");
+}
+
+static ESExpResult *
+ecc_sexp_func_or (ESExp *esexp,
+                  gint argc,
+                  ESExpTerm **argv,
+                  gpointer user_data)
+{
+       return ecc_sexp_func_and_or (esexp, argc, argv, user_data, "OR");
+}
+
+static ESExpResult *
+ecc_sexp_func_not (ESExp *esexp,
+                  gint argc,
+                  ESExpResult **argv,
+                  gpointer user_data)
+{
+       SExpToSqlContext *ctx = user_data;
+       ESExpResult *result;
+
+       g_return_val_if_fail (ctx != NULL, NULL);
+
+       if (argc != 1)
+               return NULL;
+
+       result = e_sexp_result_new (esexp, ESEXP_RES_STRING);
+
+       if (!argv[0] || argv[0]->type != ESEXP_RES_STRING) {
+               result->value.string =
+                       g_strdup_printf ("check_sexp(%d,%s)",
+                               ctx->sexp_id,
+                               E_CACHE_COLUMN_OBJECT);
+       } else {
+               result->value.string = g_strdup_printf ("NOT (%s)", argv[0]->value.string);
+       }
+
+       return result;
+}
+
+static ESExpResult *
+ecc_sexp_func_uid (ESExp *esexp,
+                  gint argc,
+                  ESExpResult **argv,
+                  gpointer user_data)
+{
+       SExpToSqlContext *ctx = user_data;
+       ESExpResult *result;
+       const gchar *uid;
+
+       g_return_val_if_fail (ctx != NULL, NULL);
+
+       if (argc != 1 ||
+           argv[0]->type != ESEXP_RES_STRING) {
+               return NULL;
+       }
+
+       uid = argv[0]->value.string;
+
+       result = e_sexp_result_new (esexp, ESEXP_RES_STRING);
+
+       if (!uid) {
+               result->value.string = g_strdup (E_CACHE_COLUMN_UID " IS NULL");
+       } else {
+               gchar *stmt;
+
+               stmt = e_cache_sqlite_stmt_printf (E_CACHE_COLUMN_UID "=%Q OR " E_CACHE_COLUMN_UID " LIKE 
'%q\n%%'", uid, uid);
+
+               result->value.string = g_strdup (stmt);
+
+               e_cache_sqlite_stmt_free (stmt);
+       }
+
+       return result;
+}
+
+static ESExpResult *
+ecc_sexp_func_occur_in_time_range (ESExp *esexp,
+                                  gint argc,
+                                  ESExpResult **argv,
+                                  gpointer user_data)
+{
+       SExpToSqlContext *ctx = user_data;
+       ESExpResult *result;
+       icaltimezone *default_zone = NULL;
+       struct icaltimetype itt_start, itt_end;
+       gchar *start_str, *end_str;
+
+       g_return_val_if_fail (ctx != NULL, NULL);
+
+       if ((argc != 2 && argc != 3) ||
+           argv[0]->type != ESEXP_RES_TIME ||
+           argv[1]->type != ESEXP_RES_TIME ||
+           (argc == 3 && argv[2]->type != ESEXP_RES_STRING)) {
+               return NULL;
+       }
+
+       if (argc == 3)
+               default_zone = ecc_resolve_tzid_cb (argv[2]->value.string, ctx->cal_cache);
+
+       itt_start = icaltime_from_timet_with_zone (argv[0]->value.time, 0, default_zone);
+       itt_end = icaltime_from_timet_with_zone (argv[1]->value.time, 0, default_zone);
+
+       start_str = ecc_encode_itt_to_sql (itt_start);
+       end_str = ecc_encode_itt_to_sql (itt_end);
+
+       result = e_sexp_result_new (esexp, ESEXP_RES_STRING);
+       result->value.string = g_strdup_printf (
+               "((" ECC_COLUMN_OCCUR_START " IS NULL OR " ECC_COLUMN_OCCUR_START "<'%s')"
+               " AND (" ECC_COLUMN_OCCUR_END " IS NULL OR " ECC_COLUMN_OCCUR_END ">'%s'))",
+               start_str, end_str);
+
+       g_free (start_str);
+       g_free (end_str);
+
+       return result;
+}
+
+static ESExpResult *
+ecc_sexp_func_due_in_time_range (ESExp *esexp,
+                                gint argc,
+                                ESExpResult **argv,
+                                gpointer user_data)
+{
+       SExpToSqlContext *ctx = user_data;
+       ESExpResult *result;
+       gchar *start_str, *end_str;
+
+       g_return_val_if_fail (ctx != NULL, NULL);
+
+       if (argc != 2 ||
+           argv[0]->type != ESEXP_RES_TIME ||
+           argv[1]->type != ESEXP_RES_TIME) {
+               return NULL;
+       }
+
+       start_str = ecc_encode_timet_to_sql (ctx->cal_cache, argv[0]->value.time);
+       end_str = ecc_encode_timet_to_sql (ctx->cal_cache, argv[1]->value.time);
+
+       result = e_sexp_result_new (esexp, ESEXP_RES_STRING);
+       result->value.string = g_strdup_printf ("(%s NOT NULL AND %s>='%s' AND %s<='%s')",
+               ECC_COLUMN_DUE, ECC_COLUMN_DUE, start_str,
+               ECC_COLUMN_DUE, end_str);
+
+       g_free (start_str);
+       g_free (end_str);
+
+       return result;
+}
+
+static ESExpResult *
+ecc_sexp_func_contains (ESExp *esexp,
+                       gint argc,
+                       ESExpResult **argv,
+                       gpointer user_data)
+{
+       SExpToSqlContext *ctx = user_data;
+       ESExpResult *result;
+       const gchar *field, *column = NULL;
+       gchar *str;
+
+       g_return_val_if_fail (ctx != NULL, NULL);
+
+       if (argc != 2 ||
+           argv[0]->type != ESEXP_RES_STRING ||
+           argv[1]->type != ESEXP_RES_STRING) {
+               return NULL;
+       }
+
+       field = argv[0]->value.string;
+       str = e_util_utf8_decompose (argv[1]->value.string);
+
+       if (g_str_equal (field, "comment"))
+               column = ECC_COLUMN_COMMENT;
+       else if (g_str_equal (field, "description"))
+               column = ECC_COLUMN_DESCRIPTION;
+       else if (g_str_equal (field, "summary"))
+               column = ECC_COLUMN_SUMMARY;
+       else if (g_str_equal (field, "location"))
+               column = ECC_COLUMN_LOCATION;
+       else if (g_str_equal (field, "attendee"))
+               column = ECC_COLUMN_ATTENDEES;
+       else if (g_str_equal (field, "organizer"))
+               column = ECC_COLUMN_ORGANIZER;
+       else if (g_str_equal (field, "classification"))
+               column = ECC_COLUMN_CLASSIFICATION;
+       else if (g_str_equal (field, "status"))
+               column = ECC_COLUMN_STATUS;
+       else if (g_str_equal (field, "priority"))
+               column = ECC_COLUMN_PRIORITY;
+
+       result = e_sexp_result_new (esexp, ESEXP_RES_STRING);
+
+       /* everything matches an empty string */
+       if (!str || !*str) {
+               result->value.string = g_strdup ("1=1");
+       } else if (column) {
+               gchar *stmt;
+
+               stmt = e_cache_sqlite_stmt_printf ("%s LIKE '%%%q%%'", column, str);
+               result->value.string = g_strdup (stmt);
+               e_cache_sqlite_stmt_free (stmt);
+       } else if (g_str_equal (field, "any")) {
+               GString *stmt;
+
+               stmt = g_string_new ("");
+
+               e_cache_sqlite_stmt_append_printf (stmt, "(%s LIKE '%%%q%%'", ECC_COLUMN_COMMENT, str);
+               e_cache_sqlite_stmt_append_printf (stmt, " OR %s LIKE '%%%q%%'", ECC_COLUMN_DESCRIPTION, str);
+               e_cache_sqlite_stmt_append_printf (stmt, " OR %s LIKE '%%%q%%'", ECC_COLUMN_SUMMARY, str);
+               e_cache_sqlite_stmt_append_printf (stmt, " OR %s LIKE '%%%q%%')", ECC_COLUMN_LOCATION, str);
+
+               result->value.string = g_string_free (stmt, FALSE);
+       } else {
+               g_strdup_printf ("check_sexp(%d,%s)",
+                       ctx->sexp_id,
+                       E_CACHE_COLUMN_OBJECT);
+       }
+
+       g_free (str);
+
+       return result;
+}
+
+static ESExpResult *
+ecc_sexp_func_has_start (ESExp *esexp,
+                        gint argc,
+                        ESExpResult **argv,
+                        gpointer user_data)
+{
+       SExpToSqlContext *ctx = user_data;
+       ESExpResult *result;
+
+       g_return_val_if_fail (ctx != NULL, NULL);
+
+       result = e_sexp_result_new (esexp, ESEXP_RES_STRING);
+       result->value.string = g_strdup_printf ("(%s NOT NULL AND %s=1)",
+               ECC_COLUMN_HAS_START, ECC_COLUMN_HAS_START);
+
+       return result;
+}
+
+static ESExpResult *
+ecc_sexp_func_has_alarms (ESExp *esexp,
+                         gint argc,
+                         ESExpResult **argv,
+                         gpointer user_data)
+{
+       SExpToSqlContext *ctx = user_data;
+       ESExpResult *result;
+
+       g_return_val_if_fail (ctx != NULL, NULL);
+
+       result = e_sexp_result_new (esexp, ESEXP_RES_STRING);
+       result->value.string = g_strdup_printf ("(%s NOT NULL AND %s=1)",
+               ECC_COLUMN_HAS_ALARM, ECC_COLUMN_HAS_ALARM);
+
+       return result;
+}
+
+static ESExpResult *
+ecc_sexp_func_has_recurrences (ESExp *esexp,
+                              gint argc,
+                              ESExpResult **argv,
+                              gpointer user_data)
+{
+       SExpToSqlContext *ctx = user_data;
+       ESExpResult *result;
+
+       g_return_val_if_fail (ctx != NULL, NULL);
+
+       result = e_sexp_result_new (esexp, ESEXP_RES_STRING);
+       result->value.string = g_strdup_printf ("(%s NOT NULL AND %s=1)",
+               ECC_COLUMN_HAS_RECURRENCES, ECC_COLUMN_HAS_RECURRENCES);
+
+       return result;
+}
+
+/* (has-categories? STR+)
+ * (has-categories? #f)
+ */
+static ESExpResult *
+ecc_sexp_func_has_categories (ESExp *esexp,
+                             gint argc,
+                             ESExpResult **argv,
+                             gpointer user_data)
+{
+       SExpToSqlContext *ctx = user_data;
+       ESExpResult *result;
+       gboolean unfiled;
+
+       g_return_val_if_fail (ctx != NULL, NULL);
+
+       if (argc < 1)
+               return NULL;
+
+       unfiled = argc == 1 && argv[0]->type == ESEXP_RES_BOOL;
+
+       result = e_sexp_result_new (esexp, ESEXP_RES_STRING);
+
+       if (unfiled) {
+               result->value.string = g_strdup_printf ("%s IS NULL",
+                       ECC_COLUMN_CATEGORIES);
+       } else {
+               GString *tmp;
+               gint ii;
+
+               tmp = g_string_new ("(" ECC_COLUMN_CATEGORIES " NOT NULL");
+
+               for (ii = 0; ii < argc; ii++) {
+                       if (argv[ii]->type != ESEXP_RES_STRING) {
+                               g_warn_if_reached ();
+                               continue;
+                       }
+
+                       e_cache_sqlite_stmt_append_printf (tmp, " AND " ECC_COLUMN_CATEGORIES " LIKE 
'%%\n%q\n%%'",
+                               argv[ii]->value.string);
+               }
+
+               g_string_append_c (tmp, ')');
+
+               result->value.string = g_string_free (tmp, FALSE);
+       }
+
+       return result;
+}
+
+static ESExpResult *
+ecc_sexp_func_is_completed (ESExp *esexp,
+                           gint argc,
+                           ESExpResult **argv,
+                           gpointer user_data)
+{
+       SExpToSqlContext *ctx = user_data;
+       ESExpResult *result;
+
+       g_return_val_if_fail (ctx != NULL, NULL);
+
+       result = e_sexp_result_new (esexp, ESEXP_RES_STRING);
+       result->value.string = g_strdup_printf ("%s NOT NULL",
+               ECC_COLUMN_COMPLETED);
+
+       return result;
+}
+
+static ESExpResult *
+ecc_sexp_func_completed_before (ESExp *esexp,
+                               gint argc,
+                               ESExpResult **argv,
+                               gpointer user_data)
+{
+       SExpToSqlContext *ctx = user_data;
+       gchar *tmp;
+       ESExpResult *result;
+
+       g_return_val_if_fail (ctx != NULL, NULL);
+
+       if (argc != 1 ||
+           argv[0]->type != ESEXP_RES_TIME) {
+               return NULL;
+       }
+
+       tmp = ecc_encode_timet_to_sql (ctx->cal_cache, argv[0]->value.time);
+
+       result = e_sexp_result_new (esexp, ESEXP_RES_STRING);
+       result->value.string = g_strdup_printf ("(%s NOT NULL AND %s<'%s')",
+               ECC_COLUMN_COMPLETED, ECC_COLUMN_COMPLETED, tmp);
+
+       g_free (tmp);
+
+       return result;
+}
+
+static ESExpResult *
+ecc_sexp_func_has_attachment (ESExp *esexp,
+                             gint argc,
+                             ESExpResult **argv,
+                             gpointer user_data)
+{
+       SExpToSqlContext *ctx = user_data;
+       ESExpResult *result;
+
+       g_return_val_if_fail (ctx != NULL, NULL);
+
+       result = e_sexp_result_new (esexp, ESEXP_RES_STRING);
+       result->value.string = g_strdup_printf ("(%s NOT NULL AND %s=1)",
+               ECC_COLUMN_HAS_ATTACHMENT, ECC_COLUMN_HAS_ATTACHMENT);
+
+       return result;
+}
+
+static ESExpResult *
+ecc_sexp_func_percent_complete (ESExp *esexp,
+                               gint argc,
+                               ESExpResult **argv,
+                               gpointer user_data)
+{
+       SExpToSqlContext *ctx = user_data;
+       ESExpResult *result;
+
+       g_return_val_if_fail (ctx != NULL, NULL);
+
+       result = e_sexp_result_new (esexp, ESEXP_RES_STRING);
+       result->value.string = g_strdup (ECC_COLUMN_PERCENT_COMPLETE);
+
+       return result;
+}
+
+/* check_sexp(sexp_id, icalstring); that's a fallback for anything
+   not being part of the summary */
+static ESExpResult *
+ecc_sexp_func_check_sexp (ESExp *esexp,
+                         gint argc,
+                         ESExpResult **argv,
+                         gpointer user_data)
+{
+       SExpToSqlContext *ctx = user_data;
+       ESExpResult *result;
+
+       g_return_val_if_fail (ctx != NULL, NULL);
+
+       result = e_sexp_result_new (esexp, ESEXP_RES_STRING);
+       result->value.string =
+               g_strdup_printf ("check_sexp(%d,%s)",
+                       ctx->sexp_id,
+                       E_CACHE_COLUMN_OBJECT);
+
+       return result;
+}
+
+static ESExpResult *
+ecc_sexp_func_icheck_sexp (ESExp *esexp,
+                          gint argc,
+                          ESExpTerm **argv,
+                          gpointer user_data)
+{
+       SExpToSqlContext *ctx = user_data;
+       ESExpResult *result;
+
+       g_return_val_if_fail (ctx != NULL, NULL);
+
+       result = e_sexp_result_new (esexp, ESEXP_RES_STRING);
+       result->value.string =
+               g_strdup_printf ("check_sexp(%d,%s)",
+                       ctx->sexp_id,
+                       E_CACHE_COLUMN_OBJECT);
+
+       return result;
+}
+
+static struct {
+       const gchar *name;
+       gpointer func;
+       gint type; /* 1 for term-function, 0 for result-function */
+} symbols[] = {
+       { "and",                        ecc_sexp_func_and, 1 },
+       { "or",                         ecc_sexp_func_or, 1 },
+       { "not",                        ecc_sexp_func_not, 0 },
+       { "<",                          ecc_sexp_func_icheck_sexp, 1 },
+       { ">",                          ecc_sexp_func_icheck_sexp, 1 },
+       { "=",                          ecc_sexp_func_icheck_sexp, 1 },
+       { "+",                          ecc_sexp_func_check_sexp, 0 },
+       { "-",                          ecc_sexp_func_check_sexp, 0 },
+       { "cast-int",                   ecc_sexp_func_check_sexp, 0 },
+       { "cast-string",                ecc_sexp_func_check_sexp, 0 },
+       { "if",                         ecc_sexp_func_icheck_sexp, 1 },
+       { "begin",                      ecc_sexp_func_icheck_sexp, 1 },
+
+       /* Time-related functions */
+       { "time-now",                   e_cal_backend_sexp_func_time_now, 0 },
+       { "make-time",                  e_cal_backend_sexp_func_make_time, 0 },
+       { "time-add-day",               e_cal_backend_sexp_func_time_add_day, 0 },
+       { "time-day-begin",             e_cal_backend_sexp_func_time_day_begin, 0 },
+       { "time-day-end",               e_cal_backend_sexp_func_time_day_end, 0 },
+
+       /* Component-related functions */
+       { "uid?",                       ecc_sexp_func_uid, 0 },
+       { "occur-in-time-range?",       ecc_sexp_func_occur_in_time_range, 0 },
+       { "due-in-time-range?",         ecc_sexp_func_due_in_time_range, 0 },
+       { "contains?",                  ecc_sexp_func_contains, 0 },
+       { "has-start?",                 ecc_sexp_func_has_start, 0 },
+       { "has-alarms?",                ecc_sexp_func_has_alarms, 0 },
+       { "has-alarms-in-range?",       ecc_sexp_func_check_sexp, 0 },
+       { "has-recurrences?",           ecc_sexp_func_has_recurrences, 0 },
+       { "has-categories?",            ecc_sexp_func_has_categories, 0 },
+       { "is-completed?",              ecc_sexp_func_is_completed, 0 },
+       { "completed-before?",          ecc_sexp_func_completed_before, 0 },
+       { "has-attachments?",           ecc_sexp_func_has_attachment, 0 },
+       { "percent-complete?",          ecc_sexp_func_percent_complete, 0 },
+       { "occurrences-count?",         ecc_sexp_func_check_sexp, 0 }
+};
+
+static gboolean
+ecc_convert_sexp_to_sql (ECalCache *cal_cache,
+                        const gchar *sexp_str,
+                        gint sexp_id,
+                        gchar **out_where_clause,
+                        GCancellable *cancellable,
+                        GError **error)
+{
+       SExpToSqlContext ctx;
+       ESExp *sexp_parser;
+       gint esexp_error, ii;
+       gboolean success = FALSE;
+
+       g_return_val_if_fail (out_where_clause != NULL, FALSE);
+
+       *out_where_clause = NULL;
+
+       /* Include everything */
+       if (!sexp_str || !*sexp_str)
+               return TRUE;
+
+       ctx.cal_cache = cal_cache;
+       ctx.sexp_id = sexp_id;
+
+       sexp_parser = e_sexp_new ();
+
+       for (ii = 0; ii < G_N_ELEMENTS (symbols); ii++) {
+               if (symbols[ii].type == 1) {
+                       e_sexp_add_ifunction (sexp_parser, 0, symbols[ii].name, symbols[ii].func, &ctx);
+               } else {
+                       e_sexp_add_function (sexp_parser, 0, symbols[ii].name, symbols[ii].func, &ctx);
+               }
+       }
+
+       e_sexp_input_text (sexp_parser, sexp_str, strlen (sexp_str));
+       esexp_error = e_sexp_parse (sexp_parser);
+
+       if (esexp_error != -1) {
+               ESExpResult *result;
+
+               result = e_sexp_eval (sexp_parser);
+
+               if (result) {
+                       if (result->type == ESEXP_RES_STRING) {
+                               /* Just steal the string from the ESexpResult */
+                               *out_where_clause = result->value.string;
+                               result->value.string = NULL;
+                               success = TRUE;
+                       }
+               }
+
+               e_sexp_result_free (sexp_parser, result);
+       }
+
+       g_object_unref (sexp_parser);
+
+       if (!success) {
+               g_set_error (error, E_CACHE_ERROR, E_CACHE_ERROR_INVALID_QUERY,
+                       _("Invalid query: %s"), sexp_str);
+       }
+
+       return success;
+}
+
+typedef struct {
+       gint extra_idx;
+       ECalCacheSearchFunc func;
+       gpointer func_user_data;
+} SearchContext;
+
+static gboolean
+ecc_search_foreach_cb (ECache *cache,
+                      const gchar *uid,
+                      const gchar *revision,
+                      const gchar *object,
+                      EOfflineState offline_state,
+                      gint ncols,
+                      const gchar *column_names[],
+                      const gchar *column_values[],
+                      gpointer user_data)
+{
+       SearchContext *ctx = user_data;
+       gchar *comp_uid = NULL, *comp_rid = NULL;
+       gboolean can_continue;
+
+       g_return_val_if_fail (ctx != NULL, FALSE);
+       g_return_val_if_fail (ctx->func != NULL, FALSE);
+
+       if (ctx->extra_idx == -1) {
+               gint ii;
+
+               for (ii = 0; ii < ncols; ii++) {
+                       if (column_names[ii] && g_ascii_strcasecmp (column_names[ii], ECC_COLUMN_EXTRA) == 0) 
{
+                               ctx->extra_idx = ii;
+                               break;
+                       }
+               }
+       }
+
+       g_return_val_if_fail (ctx->extra_idx == -1, FALSE);
+
+       g_warn_if_fail (ecc_decode_id_sql (uid, &comp_uid, &comp_rid));
+
+       /* This type-cast for performance reason */
+       can_continue = ctx->func ((ECalCache *) cache, comp_uid, comp_rid, revision, object,
+               column_values[ctx->extra_idx], offline_state, ctx->func_user_data);
+
+       g_free (comp_uid);
+       g_free (comp_rid);
+
+       return can_continue;
+}
+
+static gboolean
+ecc_search_internal (ECalCache *cal_cache,
+                    const gchar *sexp_str,
+                    gint sexp_id,
+                    ECalCacheSearchFunc func,
+                    gpointer user_data,
+                    GCancellable *cancellable,
+                    GError **error)
+{
+       gchar *where_clause = NULL;
+       SearchContext ctx;
+       gboolean success;
+
+       if (!ecc_convert_sexp_to_sql (cal_cache, sexp_str, sexp_id, &where_clause, cancellable, error)) {
+               return FALSE;
+       }
+
+       ctx.extra_idx = -1;
+       ctx.func = func;
+       ctx.func_user_data = user_data;
+
+       success = e_cache_foreach (E_CACHE (cal_cache), E_CACHE_EXCLUDE_DELETED,
+               where_clause, ecc_search_foreach_cb, &ctx,
+               cancellable, error);
+
+       g_free (where_clause);
+
+       return success;
+}
+
 static gboolean
 ecc_init_aux_tables (ECalCache *cal_cache,
                     GCancellable *cancellable,
@@ -672,6 +1475,38 @@ ecc_init_aux_tables (ECalCache *cal_cache,
 }
 
 static gboolean
+ecc_init_sqlite_functions (ECalCache *cal_cache,
+                          GCancellable *cancellable,
+                          GError **error)
+{
+       gint ret;
+       gpointer sqlitedb;
+
+       g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE);
+
+       sqlitedb = e_cache_get_sqlitedb (E_CACHE (cal_cache));
+       g_return_val_if_fail (sqlitedb != NULL, FALSE);
+
+       /* check_sexp(sexp_id, icalstring) */
+       ret = sqlite3_create_function (sqlitedb,
+               "check_sexp", 2, SQLITE_UTF8 | SQLITE_DETERMINISTIC,
+               cal_cache, ecc_check_sexp_func,
+               NULL, NULL);
+
+       if (ret != SQLITE_OK) {
+               const gchar *errmsg = sqlite3_errmsg (sqlitedb);
+
+               g_set_error (error, E_CACHE_ERROR, E_CACHE_ERROR_ENGINE,
+                       _("Failed to create SQLite function, error code '%d': %s"),
+                       ret, errmsg ? errmsg : _("Unknown error"));
+
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static gboolean
 e_cal_cache_migrate (ECache *cache,
                     gint from_version,
                     GCancellable *cancellable,
@@ -712,6 +1547,8 @@ e_cal_cache_initialize (ECalCache *cal_cache,
 
        success = success && ecc_init_aux_tables (cal_cache, cancellable, error);
 
+       success = success && ecc_init_sqlite_functions (cal_cache, cancellable, error);
+
        /* Check for data migration */
        success = success && e_cal_cache_migrate (cache, e_cache_get_version (cache), cancellable, error);
 
@@ -1408,7 +2245,7 @@ e_cal_cache_get_components_in_range_as_strings (ECalCache *cal_cache,
 }
 
 static gboolean
-ecc_search_components_cb (ECache *cache,
+ecc_search_components_cb (ECalCache *cal_cache,
                          const gchar *uid,
                          const gchar *rid,
                          const gchar *revision,
@@ -1429,7 +2266,7 @@ ecc_search_components_cb (ECache *cache,
 }
 
 static gboolean
-ecc_search_icalstrings_cb (ECache *cache,
+ecc_search_icalstrings_cb (ECalCache *cal_cache,
                           const gchar *uid,
                           const gchar *rid,
                           const gchar *revision,
@@ -1449,7 +2286,7 @@ ecc_search_icalstrings_cb (ECache *cache,
 }
 
 static gboolean
-ecc_search_ids_cb (ECache *cache,
+ecc_search_ids_cb (ECalCache *cal_cache,
                   const gchar *uid,
                   const gchar *rid,
                   const gchar *revision,
@@ -1625,9 +2462,28 @@ e_cal_cache_search_with_callback (ECalCache *cal_cache,
                                  GCancellable *cancellable,
                                  GError **error)
 {
+       ECalBackendSExp *bsexp = NULL;
+       gint sexp_id = -1;
        gboolean success;
 
        g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE);
+       g_return_val_if_fail (func != NULL, FALSE);
+
+       if (sexp && *sexp) {
+               bsexp = e_cal_backend_sexp_new (sexp);
+               if (!bsexp) {
+                       g_set_error (error, E_CACHE_ERROR, E_CACHE_ERROR_INVALID_QUERY,
+                               _("Invalid query: %s"), sexp);
+                       return FALSE;
+               }
+
+               sexp_id = ecc_take_sexp_object (cal_cache, bsexp);
+       }
+
+       success = ecc_search_internal (cal_cache, sexp, sexp_id, func, user_data, cancellable, error);
+
+       if (bsexp)
+               ecc_free_sexp_object (cal_cache, sexp_id);
 
        return success;
 }
@@ -1748,11 +2604,17 @@ e_cal_cache_get_timezone (ECalCache *cal_cache,
        g_return_val_if_fail (tzid != NULL, FALSE);
        g_return_val_if_fail (out_zone != NULL, FALSE);
 
-       g_mutex_lock (&cal_cache->priv->loaded_timezones_lock);
+       g_rec_mutex_lock (&cal_cache->priv->timezones_lock);
 
        *out_zone = g_hash_table_lookup (cal_cache->priv->loaded_timezones, tzid);
        if (*out_zone) {
-               g_mutex_unlock (&cal_cache->priv->loaded_timezones_lock);
+               g_rec_mutex_unlock (&cal_cache->priv->timezones_lock);
+               return TRUE;
+       }
+
+       *out_zone = g_hash_table_lookup (cal_cache->priv->modified_timezones, tzid);
+       if (*out_zone) {
+               g_rec_mutex_unlock (&cal_cache->priv->timezones_lock);
                return TRUE;
        }
 
@@ -1770,7 +2632,7 @@ e_cal_cache_get_timezone (ECalCache *cal_cache,
                }
        }
 
-       g_mutex_unlock (&cal_cache->priv->loaded_timezones_lock);
+       g_rec_mutex_unlock (&cal_cache->priv->timezones_lock);
 
        g_free (zone_str);
 
@@ -1895,7 +2757,7 @@ e_cal_cache_list_timezones (ECalCache *cal_cache,
        g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE);
        g_return_val_if_fail (out_timezones != NULL, FALSE);
 
-       g_mutex_lock (&cal_cache->priv->loaded_timezones_lock);
+       g_rec_mutex_lock (&cal_cache->priv->timezones_lock);
 
        success = e_cache_sqlite_select (E_CACHE (cal_cache),
                "SELECT COUNT(*) FROM " ECC_TABLE_TIMEZONES,
@@ -1908,10 +2770,19 @@ e_cal_cache_list_timezones (ECalCache *cal_cache,
                e_cache_sqlite_stmt_free (stmt);
        }
 
-       if (success)
-               *out_timezones = g_hash_table_get_values (cal_cache->priv->loaded_timezones);
+       if (success) {
+               GList *loaded, *modified;
+
+               loaded = g_hash_table_get_values (cal_cache->priv->loaded_timezones);
+               modified = g_hash_table_get_values (cal_cache->priv->modified_timezones);
 
-       g_mutex_unlock (&cal_cache->priv->loaded_timezones_lock);
+               if (loaded && modified)
+                       *out_timezones = g_list_concat (loaded, modified);
+               else
+                       *out_timezones = loaded ? loaded : modified;
+       }
+
+       g_rec_mutex_unlock (&cal_cache->priv->timezones_lock);
 
        return success;
 }
@@ -2030,7 +2901,7 @@ e_cal_cache_remove_all_locked (ECache *cache,
        g_return_val_if_fail (E_IS_CAL_CACHE (cache), FALSE);
        g_return_val_if_fail (E_CACHE_CLASS (e_cal_cache_parent_class)->remove_all_locked != NULL, FALSE);
 
-       /* Cannot free content of priv->loaded_timezones, because those can be used anywhere */
+       /* Cannot free content of priv->loaded_timezones and priv->modified_timezones, because those can be 
used anywhere */
        success = ecc_empty_aux_tables (cache, cancellable, error);
 
        success = success && E_CACHE_CLASS (e_cal_cache_parent_class)->remove_all_locked (cache, uids, 
cancellable, error);
@@ -2048,13 +2919,118 @@ cal_cache_free_zone (gpointer ptr)
 }
 
 static void
+ecc_add_cached_timezone (ETimezoneCache *cache,
+                        icaltimezone *zone)
+{
+       ECalCache *cal_cache;
+       const gchar *tzid;
+
+       cal_cache = E_CAL_CACHE (cache);
+
+       tzid = icaltimezone_get_tzid (zone);
+       if (tzid == NULL)
+               return;
+
+       e_cal_cache_put_timezone (cal_cache, zone, NULL, NULL);
+}
+
+static icaltimezone *
+ecc_get_cached_timezone (ETimezoneCache *cache,
+                        const gchar *tzid)
+{
+       ECalCache *cal_cache;
+       icaltimezone *zone = NULL;
+       icaltimezone *builtin_zone = NULL;
+       icalcomponent *icalcomp;
+       icalproperty *prop;
+       const gchar *builtin_tzid;
+
+       cal_cache = E_CAL_CACHE (cache);
+
+       if (g_str_equal (tzid, "UTC"))
+               return icaltimezone_get_utc_timezone ();
+
+       g_rec_mutex_lock (&cal_cache->priv->timezones_lock);
+
+       /* See if we already have it in the cache. */
+       zone = g_hash_table_lookup (cal_cache->priv->loaded_timezones, tzid);
+
+       if (zone != NULL)
+               goto exit;
+
+       /* Try to replace the original time zone with a more complete
+        * and/or potentially updated built-in time zone.  Note this also
+        * applies to TZIDs which match built-in time zones exactly: they
+        * are extracted via icaltimezone_get_builtin_timezone_from_tzid(). */
+
+       builtin_tzid = e_cal_match_tzid (tzid);
+
+       if (builtin_tzid != NULL)
+               builtin_zone = icaltimezone_get_builtin_timezone_from_tzid (builtin_tzid);
+
+       if (builtin_zone == NULL) {
+               e_cal_cache_get_timezone (cal_cache, tzid, &zone, NULL, NULL);
+               goto exit;
+       }
+
+       /* Use the built-in time zone *and* rename it.  Likely the caller
+        * is asking for a specific TZID because it has an event with such
+        * a TZID.  Returning an icaltimezone with a different TZID would
+        * lead to broken VCALENDARs in the caller. */
+
+       icalcomp = icaltimezone_get_component (builtin_zone);
+       icalcomp = icalcomponent_new_clone (icalcomp);
+
+       prop = icalcomponent_get_first_property (icalcomp, ICAL_ANY_PROPERTY);
+
+       while (prop != NULL) {
+               if (icalproperty_isa (prop) == ICAL_TZID_PROPERTY) {
+                       icalproperty_set_value_from_string (prop, tzid, "NO");
+                       break;
+               }
+
+               prop = icalcomponent_get_next_property (icalcomp, ICAL_ANY_PROPERTY);
+       }
+
+       if (icalcomp != NULL) {
+               zone = icaltimezone_new ();
+               if (icaltimezone_set_component (zone, icalcomp)) {
+                       tzid = icaltimezone_get_tzid (zone);
+                       g_hash_table_insert (cal_cache->priv->modified_timezones, g_strdup (tzid), zone);
+               } else {
+                       icalcomponent_free (icalcomp);
+                       icaltimezone_free (zone, 1);
+                       zone = NULL;
+               }
+       }
+
+ exit:
+       g_rec_mutex_unlock (&cal_cache->priv->timezones_lock);
+
+       return zone;
+}
+
+static GList *
+ecc_list_cached_timezones (ETimezoneCache *cache)
+{
+       GList *timezones = NULL;
+
+       e_cal_cache_list_timezones (E_CAL_CACHE (cache), &timezones, NULL, NULL);
+
+       return timezones;
+}
+
+static void
 e_cal_cache_finalize (GObject *object)
 {
        ECalCache *cal_cache = E_CAL_CACHE (object);
 
        g_hash_table_destroy (cal_cache->priv->loaded_timezones);
+       g_hash_table_destroy (cal_cache->priv->modified_timezones);
+       g_hash_table_destroy (cal_cache->priv->sexps);
 
-       g_mutex_clear (&cal_cache->priv->loaded_timezones_lock);
+       g_rec_mutex_clear (&cal_cache->priv->timezones_lock);
+       g_mutex_clear (&cal_cache->priv->sexps_lock);
 
        /* Chain up to parent's method. */
        G_OBJECT_CLASS (e_cal_cache_parent_class)->finalize (object);
@@ -2096,10 +3072,22 @@ e_cal_cache_class_init (ECalCacheClass *klass)
 }
 
 static void
+ecc_timezone_cache_init (ETimezoneCacheInterface *iface)
+{
+       iface->add_timezone = ecc_add_cached_timezone;
+       iface->get_timezone = ecc_get_cached_timezone;
+       iface->list_timezones = ecc_list_cached_timezones;
+}
+
+static void
 e_cal_cache_init (ECalCache *cal_cache)
 {
        cal_cache->priv = G_TYPE_INSTANCE_GET_PRIVATE (cal_cache, E_TYPE_CAL_CACHE, ECalCachePrivate);
        cal_cache->priv->loaded_timezones = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, 
cal_cache_free_zone);
+       cal_cache->priv->modified_timezones = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, 
cal_cache_free_zone);
+
+       cal_cache->priv->sexps = g_hash_table_new_full (g_int_hash, g_int_equal, NULL, g_object_unref);
 
-       g_mutex_init (&cal_cache->priv->loaded_timezones_lock);
+       g_rec_mutex_init (&cal_cache->priv->timezones_lock);
+       g_mutex_init (&cal_cache->priv->sexps_lock);
 }
diff --git a/src/calendar/libedata-cal/e-cal-cache.h b/src/calendar/libedata-cal/e-cal-cache.h
index 2058b2b..64ea910 100644
--- a/src/calendar/libedata-cal/e-cal-cache.h
+++ b/src/calendar/libedata-cal/e-cal-cache.h
@@ -91,7 +91,7 @@ void          e_cal_cache_search_data_free    (/* ECalCacheSearchData * */ gpointer data);
 
 /**
  * ECalCacheSearchFunc:
- * @cache: an #ECache
+ * @cal_cache: an #ECalCache
  * @uid: a unique object identifier
  * @rid: (nullable): an optional Recurrence-ID of the object
  * @revision: the object revision
@@ -107,7 +107,7 @@ void                e_cal_cache_search_data_free    (/* ECalCacheSearchData * */ gpointer 
data);
  *
  * Since: 3.26
  **/
-typedef gboolean (* ECalCacheSearchFunc)       (ECache *cache,
+typedef gboolean (* ECalCacheSearchFunc)       (ECalCache *cal_cache,
                                                 const gchar *uid,
                                                 const gchar *rid,
                                                 const gchar *revision,


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]