<commit_before>/* * wocky-caps-cache.c - Source for WockyCapsCache
 * Copyright (C) 2010 Collabora Ltd.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "wocky-caps-cache.h"

#include <errno.h>
#include <stdio.h>
#include <string.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <sqlite3.h>

#include "wocky-xmpp-reader.h"
#include "wocky-xmpp-writer.h"

#define DEBUG_FLAG DEBUG_PRESENCE
#include "wocky-debug.h"

#define DB_USER_VERSION 2

G_DEFINE_TYPE (WockyCapsCache, wocky_caps_cache, G_TYPE_OBJECT)

static WockyCapsCache *shared_cache = NULL;

struct _WockyCapsCachePrivate
{
  gchar *path;
  sqlite3 *db;
  guint inserts;

  WockyXmppReader *reader;
  WockyXmppWriter *writer;
};

enum
{
  PROP_PATH = 1,
};

static void wocky_caps_cache_constructed (GObject *object);
static gboolean caps_cache_get_one_uint (WockyCapsCache *self,
    const gchar *sql, guint *value);

static void
wocky_caps_cache_get_property (GObject *object,
    guint property_id,
    GValue *value,
    GParamSpec *pspec)
{
  WockyCapsCache *self = (WockyCapsCache *) object;

  switch (property_id)
    {
    case PROP_PATH:
      g_value_set_string (value, self->priv->path);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    }
}

static void
wocky_caps_cache_set_property (GObject *object,
    guint property_id,
    const GValue *value,
    GParamSpec *pspec)
{
  WockyCapsCache *self = (WockyCapsCache *) object;

  switch (property_id)
    {
    case PROP_PATH:
      g_free (self->priv->path);
      self->priv->path = g_value_dup_string (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    }
}

static void
wocky_caps_cache_dispose (GObject *object)
{
  G_OBJECT_CLASS (wocky_caps_cache_parent_class)->dispose (object);
}

static void
wocky_caps_cache_finalize (GObject *object)
{
  WockyCapsCache *self = WOCKY_CAPS_CACHE (object);

  g_free (self->priv->path);
  self->priv->path = NULL;

  if (self->priv->db != NULL)
    {
      sqlite3_close (self->priv->db);
      self->priv->db = NULL;
    }

  if (self->priv->reader != NULL)
    {
      g_object_unref (self->priv->reader);
      self->priv->reader = NULL;
    }

  if (self->priv->writer != NULL)
    {
      g_object_unref (self->priv->writer);
      self->priv->writer = NULL;
    }

  G_OBJECT_CLASS (wocky_caps_cache_parent_class)->finalize (object);
}

static void
wocky_caps_cache_class_init (WockyCapsCacheClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  g_type_class_add_private (klass, sizeof (WockyCapsCachePrivate));

  object_class->constructed = wocky_caps_cache_constructed;
  object_class->get_property = wocky_caps_cache_get_property;
  object_class->set_property = wocky_caps_cache_set_property;
  object_class->dispose = wocky_caps_cache_dispose;
  object_class->finalize = wocky_caps_cache_finalize;

  /**
   * WockyCapsCache:path:
   *
   * The path on disk to the SQLite database where this
   * #WockyCapsCache stores its information.
   */
  g_object_class_install_property (object_class, PROP_PATH,
      g_param_spec_string ("path", "Path", "The path to the cache", NULL,
          G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
          G_PARAM_STATIC_STRINGS));
}

static gchar *
get_path (void)
{
  gchar *free_dir = NULL, *ret;
  const gchar *dir, *path;

  /* This should still work if it's the magic value ":memory:". */
  path = g_getenv ("WOCKY_CAPS_CACHE");

  if (path != NULL)
    {
      dir = free_dir = g_path_get_dirname (path);
      ret = g_strdup (path);
    }
  else
    {
      dir = g_getenv ("WOCKY_CACHE_DIR");

      if (dir != NULL)
        {
          ret = g_build_path (G_DIR_SEPARATOR_S, dir, "caps-cache.db", NULL);
        }
      else
        {
          ret = g_build_path (G_DIR_SEPARATOR_S,
              g_get_user_cache_dir (), "wocky", "caps", "caps-cache.db",
              NULL);
          dir = free_dir = g_path_get_dirname (ret);
        }
    }

  /* Any errors are ignored here, on the basis that we'll find out the path is
   * duff when we try to open the database anyway.
   */
  g_mkdir_with_parents (dir, 0755);
  g_free (free_dir);
  return ret;
}

static gboolean
caps_cache_check_version (WockyCapsCache *self)
{
  guint version;

  if (!caps_cache_get_one_uint (self, "PRAGMA user_version;", &version))
    return FALSE;

  if (version == 0)
    {
    /* ______________________________________________________________________
      ( Unfortunately the first incarnation of the caps cache db didn't set  )
      ( user_version, so we can't tell if 0 means this is a new, empty       )
      ( database or an old one.                                              )
      (                                                                      )
      ( So... let's check if the capabilities table exists. If so, we'll     )
      ( pretend user_version was 1.                                          )
      (                                                                      )
      ( When there's nothing left to burn, you have to set yourself on fire. )
       ----------------------------------------------------------------------
                o   ^__^
                 o  (oo)\_______
                    (__)\       )\/\
                        ||----w |
                        ||     ||   */
      guint dummy;

      if (caps_cache_get_one_uint (self, "PRAGMA table_info(capabilities)",
            &dummy))
        {
          DEBUG ("capabilities table exists; this isn't a new database");
          version = 1;
        }
    }

  switch (version)
    {
      case 0:
        DEBUG ("opened new, empty database at %s", self->priv->path);
        return TRUE;

      case DB_USER_VERSION:
        DEBUG ("opened %s, user_version %u", self->priv->path, version);
        return TRUE;

      default:
        DEBUG ("%s is version %u, not our version %u; let's nuke it",
            self->priv->path, version, DB_USER_VERSION);
        return FALSE;
    }
}

static gboolean
caps_cache_open (WockyCapsCache *self)
{
  gint ret;
  gchar *error;

  g_return_val_if_fail (self->priv->db == NULL, FALSE);

  ret = sqlite3_open (self->priv->path, &self->priv->db);

  if (ret != SQLITE_OK)
    {
      DEBUG ("opening database %s failed: %s", self->priv->path,
          sqlite3_errmsg (self->priv->db));
      goto err;
    }

  if (!caps_cache_check_version (self))
    goto err;

  ret = sqlite3_exec (self->priv->db,
      "PRAGMA user_version = " G_STRINGIFY (DB_USER_VERSION) ";"
      "PRAGMA journal_mode = MEMORY;"
      "PRAGMA synchronous = OFF",
      NULL, NULL, &error);

  if (ret != SQLITE_OK)
    {
      DEBUG ("failed to set user_version, turn off fsync() and "
          "turn off on-disk journalling: %s", error);
      sqlite3_free (error);
      goto err;
    }

  ret = sqlite3_exec (self->priv->db,
      "CREATE TABLE IF NOT EXISTS capabilities (\n"
      "  node text PRIMARY KEY,\n"
      "  disco_reply text,\n"
      "  timestamp int)", NULL, NULL, &error);

  if (ret != SQLITE_OK)
    {
      DEBUG ("failed to ensure table exists: %s", error);
      sqlite3_free (error);
      goto err;
    }

  return TRUE;

 err:
  sqlite3_close (self->priv->db);
  self->priv->db = NULL;
  return FALSE;
}

static void
wocky_caps_cache_constructed (GObject *object)
{
  WockyCapsCache *self = WOCKY_CAPS_CACHE (object);

  if (!caps_cache_open (self))
    {
      /* Couldn't open it, or it's got a different user_version. Nuke it and
       * try again. */
      int ret = unlink (self->priv->path);

      if (ret != 0)
        DEBUG ("removing database failed: %s", g_strerror (errno));
      else if (caps_cache_open (self))
        g_assert (self->priv->db != NULL);
    }

  if (self->priv->db == NULL)
    {
      DEBUG ("couldn't open db; giving up");
      return;
    }

  self->priv->reader = wocky_xmpp_reader_new_no_stream ();
  self->priv->writer = wocky_xmpp_writer_new_no_stream ();
}

static void
wocky_caps_cache_init (WockyCapsCache *self)
{
  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (
      self, WOCKY_TYPE_CAPS_CACHE, WockyCapsCachePrivate);
}

/**
 * wocky_caps_cache_new:
 * @path: full path to where the cache SQLite database should be stored
 *
 * Convenience function to create a new #WockyCapsCache.
 *
 * Returns: a new #WockyCapsCache.
 */
WockyCapsCache *
wocky_caps_cache_new (const gchar *path)
{
  return g_object_new (WOCKY_TYPE_CAPS_CACHE,
      "path", path,
      NULL);
}

/**
 * wocky_caps_cache_dup_shared:
 * @path: full path to where the cache SQLite database should be stored<commit_msg>caps-cache: remove copy-paste error in documentation<commit_after>/* * wocky-caps-cache.c - Source for WockyCapsCache
 * Copyright (C) 2010 Collabora Ltd.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "wocky-caps-cache.h"

#include <errno.h>
#include <stdio.h>
#include <string.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <sqlite3.h>

#include "wocky-xmpp-reader.h"
#include "wocky-xmpp-writer.h"

#define DEBUG_FLAG DEBUG_PRESENCE
#include "wocky-debug.h"

#define DB_USER_VERSION 2

G_DEFINE_TYPE (WockyCapsCache, wocky_caps_cache, G_TYPE_OBJECT)

static WockyCapsCache *shared_cache = NULL;

struct _WockyCapsCachePrivate
{
  gchar *path;
  sqlite3 *db;
  guint inserts;

  WockyXmppReader *reader;
  WockyXmppWriter *writer;
};

enum
{
  PROP_PATH = 1,
};

static void wocky_caps_cache_constructed (GObject *object);
static gboolean caps_cache_get_one_uint (WockyCapsCache *self,
    const gchar *sql, guint *value);

static void
wocky_caps_cache_get_property (GObject *object,
    guint property_id,
    GValue *value,
    GParamSpec *pspec)
{
  WockyCapsCache *self = (WockyCapsCache *) object;

  switch (property_id)
    {
    case PROP_PATH:
      g_value_set_string (value, self->priv->path);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    }
}

static void
wocky_caps_cache_set_property (GObject *object,
    guint property_id,
    const GValue *value,
    GParamSpec *pspec)
{
  WockyCapsCache *self = (WockyCapsCache *) object;

  switch (property_id)
    {
    case PROP_PATH:
      g_free (self->priv->path);
      self->priv->path = g_value_dup_string (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    }
}

static void
wocky_caps_cache_dispose (GObject *object)
{
  G_OBJECT_CLASS (wocky_caps_cache_parent_class)->dispose (object);
}

static void
wocky_caps_cache_finalize (GObject *object)
{
  WockyCapsCache *self = WOCKY_CAPS_CACHE (object);

  g_free (self->priv->path);
  self->priv->path = NULL;

  if (self->priv->db != NULL)
    {
      sqlite3_close (self->priv->db);
      self->priv->db = NULL;
    }

  if (self->priv->reader != NULL)
    {
      g_object_unref (self->priv->reader);
      self->priv->reader = NULL;
    }

  if (self->priv->writer != NULL)
    {
      g_object_unref (self->priv->writer);
      self->priv->writer = NULL;
    }

  G_OBJECT_CLASS (wocky_caps_cache_parent_class)->finalize (object);
}

static void
wocky_caps_cache_class_init (WockyCapsCacheClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  g_type_class_add_private (klass, sizeof (WockyCapsCachePrivate));

  object_class->constructed = wocky_caps_cache_constructed;
  object_class->get_property = wocky_caps_cache_get_property;
  object_class->set_property = wocky_caps_cache_set_property;
  object_class->dispose = wocky_caps_cache_dispose;
  object_class->finalize = wocky_caps_cache_finalize;

  /**
   * WockyCapsCache:path:
   *
   * The path on disk to the SQLite database where this
   * #WockyCapsCache stores its information.
   */
  g_object_class_install_property (object_class, PROP_PATH,
      g_param_spec_string ("path", "Path", "The path to the cache", NULL,
          G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
          G_PARAM_STATIC_STRINGS));
}

static gchar *
get_path (void)
{
  gchar *free_dir = NULL, *ret;
  const gchar *dir, *path;

  /* This should still work if it's the magic value ":memory:". */
  path = g_getenv ("WOCKY_CAPS_CACHE");

  if (path != NULL)
    {
      dir = free_dir = g_path_get_dirname (path);
      ret = g_strdup (path);
    }
  else
    {
      dir = g_getenv ("WOCKY_CACHE_DIR");

      if (dir != NULL)
        {
          ret = g_build_path (G_DIR_SEPARATOR_S, dir, "caps-cache.db", NULL);
        }
      else
        {
          ret = g_build_path (G_DIR_SEPARATOR_S,
              g_get_user_cache_dir (), "wocky", "caps", "caps-cache.db",
              NULL);
          dir = free_dir = g_path_get_dirname (ret);
        }
    }

  /* Any errors are ignored here, on the basis that we'll find out the path is
   * duff when we try to open the database anyway.
   */
  g_mkdir_with_parents (dir, 0755);
  g_free (free_dir);
  return ret;
}

static gboolean
caps_cache_check_version (WockyCapsCache *self)
{
  guint version;

  if (!caps_cache_get_one_uint (self, "PRAGMA user_version;", &version))
    return FALSE;

  if (version == 0)
    {
    /* ______________________________________________________________________
      ( Unfortunately the first incarnation of the caps cache db didn't set  )
      ( user_version, so we can't tell if 0 means this is a new, empty       )
      ( database or an old one.                                              )
      (                                                                      )
      ( So... let's check if the capabilities table exists. If so, we'll     )
      ( pretend user_version was 1.                                          )
      (                                                                      )
      ( When there's nothing left to burn, you have to set yourself on fire. )
       ----------------------------------------------------------------------
                o   ^__^
                 o  (oo)\_______
                    (__)\       )\/\
                        ||----w |
                        ||     ||   */
      guint dummy;

      if (caps_cache_get_one_uint (self, "PRAGMA table_info(capabilities)",
            &dummy))
        {
          DEBUG ("capabilities table exists; this isn't a new database");
          version = 1;
        }
    }

  switch (version)
    {
      case 0:
        DEBUG ("opened new, empty database at %s", self->priv->path);
        return TRUE;

      case DB_USER_VERSION:
        DEBUG ("opened %s, user_version %u", self->priv->path, version);
        return TRUE;

      default:
        DEBUG ("%s is version %u, not our version %u; let's nuke it",
            self->priv->path, version, DB_USER_VERSION);
        return FALSE;
    }
}

static gboolean
caps_cache_open (WockyCapsCache *self)
{
  gint ret;
  gchar *error;

  g_return_val_if_fail (self->priv->db == NULL, FALSE);

  ret = sqlite3_open (self->priv->path, &self->priv->db);

  if (ret != SQLITE_OK)
    {
      DEBUG ("opening database %s failed: %s", self->priv->path,
          sqlite3_errmsg (self->priv->db));
      goto err;
    }

  if (!caps_cache_check_version (self))
    goto err;

  ret = sqlite3_exec (self->priv->db,
      "PRAGMA user_version = " G_STRINGIFY (DB_USER_VERSION) ";"
      "PRAGMA journal_mode = MEMORY;"
      "PRAGMA synchronous = OFF",
      NULL, NULL, &error);

  if (ret != SQLITE_OK)
    {
      DEBUG ("failed to set user_version, turn off fsync() and "
          "turn off on-disk journalling: %s", error);
      sqlite3_free (error);
      goto err;
    }

  ret = sqlite3_exec (self->priv->db,
      "CREATE TABLE IF NOT EXISTS capabilities (\n"
      "  node text PRIMARY KEY,\n"
      "  disco_reply text,\n"
      "  timestamp int)", NULL, NULL, &error);

  if (ret != SQLITE_OK)
    {
      DEBUG ("failed to ensure table exists: %s", error);
      sqlite3_free (error);
      goto err;
    }

  return TRUE;

 err:
  sqlite3_close (self->priv->db);
  self->priv->db = NULL;
  return FALSE;
}

static void
wocky_caps_cache_constructed (GObject *object)
{
  WockyCapsCache *self = WOCKY_CAPS_CACHE (object);

  if (!caps_cache_open (self))
    {
      /* Couldn't open it, or it's got a different user_version. Nuke it and
       * try again. */
      int ret = unlink (self->priv->path);

      if (ret != 0)
        DEBUG ("removing database failed: %s", g_strerror (errno));
      else if (caps_cache_open (self))
        g_assert (self->priv->db != NULL);
    }

  if (self->priv->db == NULL)
    {
      DEBUG ("couldn't open db; giving up");
      return;
    }

  self->priv->reader = wocky_xmpp_reader_new_no_stream ();
  self->priv->writer = wocky_xmpp_writer_new_no_stream ();
}

static void
wocky_caps_cache_init (WockyCapsCache *self)
{
  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (
      self, WOCKY_TYPE_CAPS_CACHE, WockyCapsCachePrivate);
}

/**
 * wocky_caps_cache_new:
 * @path: full path to where the cache SQLite database should be stored
 *
 * Convenience function to create a new #WockyCapsCache.
 *
 * Returns: a new #WockyCapsCache.
 */
WockyCapsCache *
wocky_caps_cache_new (const gchar *path)
{
  return g_object_new (WOCKY_TYPE_CAPS_CACHE,
      "path", path,
      NULL);
}

/**
 * wocky_caps_cache_dup_shared:
{'fraction_non_alphanumeric': 0.1140570818885036, 'fraction_numerical': 0.004161109629234463, 'mean_word_length': 2.6077752117013087, 'pattern_counts': {'":': 2, '<': 13, '<?xml version=': 0, '>': 153, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 22, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7025301', 'n_tokens_mistral': 6397, 'n_tokens_neox': 6207, 'n_words': 1640}