Blame src/gom-owncloud-miner.c

Packit f01ec2
/*
Packit f01ec2
 * GNOME Online Miners - crawls through your online content
Packit f01ec2
 * Copyright (c) 2013 Red Hat, Inc.
Packit f01ec2
 *
Packit f01ec2
 * This program is free software; you can redistribute it and/or
Packit f01ec2
 * modify it under the terms of the GNU General Public License
Packit f01ec2
 * as published by the Free Software Foundation; either version 2
Packit f01ec2
 * of the License, or (at your option) any later version.
Packit f01ec2
 *
Packit f01ec2
 * This program is distributed in the hope that it will be useful,
Packit f01ec2
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit f01ec2
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit f01ec2
 * GNU General Public License for more details.
Packit f01ec2
 *
Packit f01ec2
 * You should have received a copy of the GNU General Public License
Packit f01ec2
 * along with this program; if not, write to the Free Software
Packit f01ec2
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
Packit f01ec2
 * 02110-1301, USA.
Packit f01ec2
 *
Packit f01ec2
 * Author: Debarshi Ray <debarshir@gnome.org>
Packit f01ec2
 *
Packit f01ec2
 */
Packit f01ec2
Packit f01ec2
#include "config.h"
Packit f01ec2
Packit f01ec2
#include <goa/goa.h>
Packit f01ec2
Packit f01ec2
#include "gom-owncloud-miner.h"
Packit f01ec2
#include "gom-utils.h"
Packit f01ec2
Packit f01ec2
#define MINER_IDENTIFIER "gd:owncloud:miner:8a409711-8fea-4eda-a417-f140ffc6d8f3"
Packit f01ec2
Packit f01ec2
G_DEFINE_TYPE (GomOwncloudMiner, gom_owncloud_miner, GOM_TYPE_MINER)
Packit f01ec2
Packit f01ec2
struct _GomOwncloudMinerPrivate {
Packit f01ec2
  GVolumeMonitor *monitor;
Packit f01ec2
};
Packit f01ec2
Packit f01ec2
#define FILE_ATTRIBUTES \
Packit f01ec2
  G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," \
Packit f01ec2
  G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," \
Packit f01ec2
  G_FILE_ATTRIBUTE_STANDARD_NAME "," \
Packit f01ec2
  G_FILE_ATTRIBUTE_STANDARD_TYPE "," \
Packit f01ec2
  G_FILE_ATTRIBUTE_TIME_MODIFIED
Packit f01ec2
Packit f01ec2
typedef struct {
Packit f01ec2
  GError **error;
Packit f01ec2
  GMainLoop *loop;
Packit f01ec2
  GomAccountMinerJob *job;
Packit f01ec2
} SyncData;
Packit f01ec2
Packit f01ec2
static gboolean
Packit f01ec2
account_miner_job_process_file (GomAccountMinerJob *job,
Packit f01ec2
                                TrackerSparqlConnection *connection,
Packit f01ec2
                                GHashTable *previous_resources,
Packit f01ec2
                                const gchar *datasource_urn,
Packit f01ec2
                                GFile *file,
Packit f01ec2
                                GFileInfo *info,
Packit f01ec2
                                GFile *parent,
Packit f01ec2
                                GCancellable *cancellable,
Packit f01ec2
                                GError **error)
Packit f01ec2
{
Packit f01ec2
  GChecksum *checksum = NULL;
Packit f01ec2
  GDateTime *modification_time;
Packit f01ec2
  GFileType type;
Packit f01ec2
  GTimeVal tv;
Packit f01ec2
  gboolean mtime_changed;
Packit f01ec2
  gboolean resource_exists;
Packit f01ec2
  const gchar *class;
Packit f01ec2
  const gchar *display_name;
Packit f01ec2
  const gchar *id;
Packit f01ec2
  const gchar *name;
Packit f01ec2
  gchar *identifier = NULL;
Packit f01ec2
  gchar *resource = NULL;
Packit f01ec2
  gchar *uri = NULL;
Packit f01ec2
  gint64 new_mtime;
Packit f01ec2
Packit f01ec2
  type = g_file_info_get_file_type (info);
Packit f01ec2
  uri = g_file_get_uri (file);
Packit f01ec2
Packit f01ec2
  checksum = g_checksum_new (G_CHECKSUM_MD5);
Packit f01ec2
  g_checksum_update (checksum, uri, -1);
Packit f01ec2
  id = g_checksum_get_string (checksum);
Packit f01ec2
  identifier = g_strdup_printf ("%sowncloud:%s", (type == G_FILE_TYPE_DIRECTORY ? "gd:collection:" : ""), id);
Packit f01ec2
  g_checksum_reset (checksum);
Packit f01ec2
Packit f01ec2
  /* remove from the list of the previous resources */
Packit f01ec2
  g_hash_table_remove (previous_resources, identifier);
Packit f01ec2
Packit f01ec2
  name = g_file_info_get_name (info);
Packit f01ec2
  if (type == G_FILE_TYPE_REGULAR)
Packit f01ec2
    class = gom_filename_to_rdf_type (name);
Packit f01ec2
  else
Packit f01ec2
    class = "nfo:DataContainer";
Packit f01ec2
Packit f01ec2
  if (class == NULL)
Packit f01ec2
    goto out;
Packit f01ec2
Packit f01ec2
  resource = gom_tracker_sparql_connection_ensure_resource
Packit f01ec2
    (connection,
Packit f01ec2
     cancellable, error,
Packit f01ec2
     &resource_exists,
Packit f01ec2
     datasource_urn, identifier,
Packit f01ec2
     "nfo:RemoteDataObject", class, NULL);
Packit f01ec2
Packit f01ec2
  if (*error != NULL)
Packit f01ec2
    goto out;
Packit f01ec2
Packit f01ec2
  gom_tracker_update_datasource (connection, datasource_urn,
Packit f01ec2
                                 resource_exists, identifier, resource,
Packit f01ec2
                                 cancellable, error);
Packit f01ec2
Packit f01ec2
  if (*error != NULL)
Packit f01ec2
    goto out;
Packit f01ec2
Packit f01ec2
  g_file_info_get_modification_time (info, &tv;;
Packit f01ec2
  modification_time = g_date_time_new_from_timeval_local (&tv;;
Packit f01ec2
  new_mtime = g_date_time_to_unix (modification_time);
Packit f01ec2
  mtime_changed = gom_tracker_update_mtime (connection, new_mtime,
Packit f01ec2
                                            resource_exists, identifier, resource,
Packit f01ec2
                                            cancellable, error);
Packit f01ec2
Packit f01ec2
  if (*error != NULL)
Packit f01ec2
    goto out;
Packit f01ec2
Packit f01ec2
  /* avoid updating the DB if the entry already exists and has not
Packit f01ec2
   * been modified since our last run.
Packit f01ec2
   */
Packit f01ec2
  if (!mtime_changed)
Packit f01ec2
    goto out;
Packit f01ec2
Packit f01ec2
  /* the resource changed - just set all the properties again */
Packit f01ec2
  gom_tracker_sparql_connection_insert_or_replace_triple
Packit f01ec2
    (connection,
Packit f01ec2
     cancellable, error,
Packit f01ec2
     datasource_urn, resource,
Packit f01ec2
     "nie:url", uri);
Packit f01ec2
Packit f01ec2
  if (*error != NULL)
Packit f01ec2
    goto out;
Packit f01ec2
Packit f01ec2
  if (type == G_FILE_TYPE_REGULAR)
Packit f01ec2
    {
Packit f01ec2
      const gchar *mime;
Packit f01ec2
      const gchar *parent_id;
Packit f01ec2
      gchar *parent_identifier;
Packit f01ec2
      gchar *parent_resource_urn;
Packit f01ec2
      gchar *parent_uri;
Packit f01ec2
Packit f01ec2
      if (parent != NULL)
Packit f01ec2
        {
Packit f01ec2
          parent_uri = g_file_get_uri (parent);
Packit f01ec2
          g_checksum_update (checksum, parent_uri, -1);
Packit f01ec2
          parent_id = g_checksum_get_string (checksum);
Packit f01ec2
          parent_identifier = g_strconcat ("gd:collection:owncloud:", parent_id, NULL);
Packit f01ec2
          parent_resource_urn = gom_tracker_sparql_connection_ensure_resource
Packit f01ec2
            (connection, cancellable, error,
Packit f01ec2
             NULL,
Packit f01ec2
             datasource_urn, parent_identifier,
Packit f01ec2
             "nfo:RemoteDataObject", "nfo:DataContainer", NULL);
Packit f01ec2
          g_checksum_reset (checksum);
Packit f01ec2
          g_free (parent_identifier);
Packit f01ec2
          g_free (parent_uri);
Packit f01ec2
Packit f01ec2
          if (*error != NULL)
Packit f01ec2
            goto out;
Packit f01ec2
Packit f01ec2
          gom_tracker_sparql_connection_insert_or_replace_triple
Packit f01ec2
            (connection,
Packit f01ec2
             cancellable, error,
Packit f01ec2
             datasource_urn, resource,
Packit f01ec2
             "nie:isPartOf", parent_resource_urn);
Packit f01ec2
          g_free (parent_resource_urn);
Packit f01ec2
Packit f01ec2
          if (*error != NULL)
Packit f01ec2
            goto out;
Packit f01ec2
        }
Packit f01ec2
Packit f01ec2
      mime = g_file_info_get_content_type (info);
Packit f01ec2
      if (mime != NULL)
Packit f01ec2
        {
Packit f01ec2
          gom_tracker_sparql_connection_insert_or_replace_triple
Packit f01ec2
            (connection,
Packit f01ec2
             cancellable, error,
Packit f01ec2
             datasource_urn, resource,
Packit f01ec2
             "nie:mimeType", mime);
Packit f01ec2
Packit f01ec2
          if (*error != NULL)
Packit f01ec2
            goto out;
Packit f01ec2
        }
Packit f01ec2
    }
Packit f01ec2
Packit f01ec2
  display_name = g_file_info_get_display_name (info);
Packit f01ec2
  gom_tracker_sparql_connection_insert_or_replace_triple
Packit f01ec2
    (connection,
Packit f01ec2
     cancellable, error,
Packit f01ec2
     datasource_urn, resource,
Packit f01ec2
     "nfo:fileName", display_name);
Packit f01ec2
Packit f01ec2
  if (*error != NULL)
Packit f01ec2
    goto out;
Packit f01ec2
Packit f01ec2
 out:
Packit f01ec2
  if (checksum != NULL)
Packit f01ec2
    g_checksum_free (checksum);
Packit f01ec2
  g_free (identifier);
Packit f01ec2
  g_free (resource);
Packit f01ec2
  g_free (uri);
Packit f01ec2
Packit f01ec2
  if (*error != NULL)
Packit f01ec2
    return FALSE;
Packit f01ec2
Packit f01ec2
  return TRUE;
Packit f01ec2
}
Packit f01ec2
Packit f01ec2
static void
Packit f01ec2
account_miner_job_traverse_dir (GomAccountMinerJob *job,
Packit f01ec2
                                TrackerSparqlConnection *connection,
Packit f01ec2
                                GHashTable *previous_resources,
Packit f01ec2
                                const gchar *datasource_urn,
Packit f01ec2
                                GFile *dir,
Packit f01ec2
                                gboolean is_root,
Packit f01ec2
                                GCancellable *cancellable,
Packit f01ec2
                                GError **error)
Packit f01ec2
{
Packit f01ec2
  GError *local_error = NULL;
Packit f01ec2
  GFileEnumerator *enumerator;
Packit f01ec2
  GFileInfo *info;
Packit f01ec2
  gchar *dir_uri;
Packit f01ec2
Packit f01ec2
  dir_uri = g_file_get_uri (dir);
Packit f01ec2
  enumerator = g_file_enumerate_children (dir,
Packit f01ec2
                                          FILE_ATTRIBUTES,
Packit f01ec2
                                          G_FILE_QUERY_INFO_NONE,
Packit f01ec2
                                          cancellable,
Packit f01ec2
                                          &local_error);
Packit f01ec2
  if (local_error != NULL)
Packit f01ec2
    goto out;
Packit f01ec2
Packit f01ec2
  while ((info = g_file_enumerator_next_file (enumerator, cancellable, &local_error)) != NULL)
Packit f01ec2
    {
Packit f01ec2
      GFile *child;
Packit f01ec2
      GFileType type;
Packit f01ec2
      const gchar *name;
Packit f01ec2
      gchar *uri;
Packit f01ec2
Packit f01ec2
      type = g_file_info_get_file_type (info);
Packit f01ec2
      name = g_file_info_get_name (info);
Packit f01ec2
      child = g_file_get_child (dir, name);
Packit f01ec2
Packit f01ec2
      if (type == G_FILE_TYPE_REGULAR || type == G_FILE_TYPE_DIRECTORY)
Packit f01ec2
        {
Packit f01ec2
          account_miner_job_process_file (job,
Packit f01ec2
                                          connection,
Packit f01ec2
                                          previous_resources,
Packit f01ec2
                                          datasource_urn,
Packit f01ec2
                                          child,
Packit f01ec2
                                          info,
Packit f01ec2
                                          is_root ? NULL : dir,
Packit f01ec2
                                          cancellable,
Packit f01ec2
                                          &local_error);
Packit f01ec2
          if (local_error != NULL)
Packit f01ec2
            {
Packit f01ec2
              uri = g_file_get_uri (child);
Packit f01ec2
              g_warning ("Unable to process %s: %s", uri, local_error->message);
Packit f01ec2
              g_free (uri);
Packit f01ec2
              g_clear_error (&local_error);
Packit f01ec2
            }
Packit f01ec2
        }
Packit f01ec2
Packit f01ec2
      if (type == G_FILE_TYPE_DIRECTORY)
Packit f01ec2
        {
Packit f01ec2
          account_miner_job_traverse_dir (job,
Packit f01ec2
                                          connection,
Packit f01ec2
                                          previous_resources,
Packit f01ec2
                                          datasource_urn,
Packit f01ec2
                                          child,
Packit f01ec2
                                          FALSE,
Packit f01ec2
                                          cancellable,
Packit f01ec2
                                          &local_error);
Packit f01ec2
          if (local_error != NULL)
Packit f01ec2
            {
Packit f01ec2
              uri = g_file_get_uri (child);
Packit f01ec2
              g_warning ("Unable to traverse %s: %s", uri, local_error->message);
Packit f01ec2
              g_free (uri);
Packit f01ec2
              g_clear_error (&local_error);
Packit f01ec2
            }
Packit f01ec2
        }
Packit f01ec2
Packit f01ec2
      g_object_unref (child);
Packit f01ec2
      g_object_unref (info);
Packit f01ec2
    }
Packit f01ec2
Packit f01ec2
  if (local_error != NULL)
Packit f01ec2
    goto out;
Packit f01ec2
Packit f01ec2
 out:
Packit f01ec2
  if (local_error != NULL)
Packit f01ec2
    g_propagate_error (error, local_error);
Packit f01ec2
Packit f01ec2
  g_clear_object (&enumerator);
Packit f01ec2
  g_free (dir_uri);
Packit f01ec2
}
Packit f01ec2
Packit f01ec2
static gboolean
Packit f01ec2
is_matching_volume (GVolume *volume, GoaObject *object)
Packit f01ec2
{
Packit f01ec2
  GoaAccount *account;
Packit f01ec2
  GoaFiles *files;
Packit f01ec2
  gboolean retval = FALSE;
Packit f01ec2
  const gchar *presentation_identity;
Packit f01ec2
  const gchar *uri;
Packit f01ec2
  gchar *name;
Packit f01ec2
  gchar *uuid;
Packit f01ec2
Packit f01ec2
  account = goa_object_peek_account (object);
Packit f01ec2
  presentation_identity = goa_account_get_presentation_identity (account);
Packit f01ec2
Packit f01ec2
  files = goa_object_peek_files (object);
Packit f01ec2
  uri = goa_files_get_uri (files);
Packit f01ec2
Packit f01ec2
  name = g_volume_get_name (volume);
Packit f01ec2
  uuid = g_volume_get_uuid (volume);
Packit f01ec2
Packit f01ec2
  if (g_strcmp0 (name, presentation_identity) == 0 && g_strcmp0 (uuid, uri) == 0)
Packit f01ec2
    retval = TRUE;
Packit f01ec2
Packit f01ec2
  g_free (name);
Packit f01ec2
  g_free (uuid);
Packit f01ec2
  return retval;
Packit f01ec2
}
Packit f01ec2
Packit f01ec2
static void
Packit f01ec2
volume_added_cb (GVolumeMonitor *monitor, GVolume *volume, gpointer user_data)
Packit f01ec2
{
Packit f01ec2
  SyncData *data = (SyncData *) user_data;
Packit f01ec2
  GoaObject *object;
Packit f01ec2
Packit f01ec2
  object = GOA_OBJECT (g_hash_table_lookup (data->job->services, "documents"));
Packit f01ec2
  if (!is_matching_volume (volume, object))
Packit f01ec2
    return;
Packit f01ec2
Packit f01ec2
  g_object_set_data_full (G_OBJECT (object), "volume", g_object_ref (volume), g_object_unref);
Packit f01ec2
  g_main_loop_quit (data->loop);
Packit f01ec2
}
Packit f01ec2
Packit f01ec2
static void
Packit f01ec2
volume_mount_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
Packit f01ec2
{
Packit f01ec2
  GVolume *volume = G_VOLUME (source_object);
Packit f01ec2
  SyncData *data = (SyncData *) user_data;
Packit f01ec2
Packit f01ec2
  g_volume_mount_finish (volume, res, data->error);
Packit f01ec2
  g_main_loop_quit (data->loop);
Packit f01ec2
}
Packit f01ec2
Packit f01ec2
static void
Packit f01ec2
query_owncloud (GomAccountMinerJob *job,
Packit f01ec2
                TrackerSparqlConnection *connection,
Packit f01ec2
                GHashTable *previous_resources,
Packit f01ec2
                const gchar *datasource_urn,
Packit f01ec2
                GCancellable *cancellable,
Packit f01ec2
                GError **error)
Packit f01ec2
{
Packit f01ec2
  GomOwncloudMiner *self = GOM_OWNCLOUD_MINER (job->miner);
Packit f01ec2
  GomOwncloudMinerPrivate *priv = self->priv;
Packit f01ec2
  GoaObject *object;
Packit f01ec2
  GFile *root;
Packit f01ec2
  GList *l;
Packit f01ec2
  GList *volumes;
Packit f01ec2
  GMainContext *context;
Packit f01ec2
  GMount *mount;
Packit f01ec2
  GVolume *volume;
Packit f01ec2
  SyncData data;
Packit f01ec2
  gboolean found = FALSE;
Packit f01ec2
Packit f01ec2
  object = GOA_OBJECT (g_hash_table_lookup (job->services, "documents"));
Packit f01ec2
  if (object == NULL)
Packit f01ec2
    {
Packit f01ec2
      /* FIXME: use proper #defines and enumerated types */
Packit f01ec2
      g_set_error (error,
Packit f01ec2
                   g_quark_from_static_string ("gom-error"),
Packit f01ec2
                   0,
Packit f01ec2
                   "Can not query without a service");
Packit f01ec2
      return;
Packit f01ec2
    }
Packit f01ec2
Packit f01ec2
  data.job = job;
Packit f01ec2
  volumes = g_volume_monitor_get_volumes (priv->monitor);
Packit f01ec2
Packit f01ec2
  /* find the volume corresponding to this GoaObject */
Packit f01ec2
  for (l = volumes; l != NULL && !found; l = l->next)
Packit f01ec2
    {
Packit f01ec2
      GVolume *volume = G_VOLUME (l->data);
Packit f01ec2
Packit f01ec2
      if (is_matching_volume (volume, object))
Packit f01ec2
        {
Packit f01ec2
          g_object_set_data_full (G_OBJECT (object), "volume", g_object_ref (volume), g_object_unref);
Packit f01ec2
          found = TRUE;
Packit f01ec2
        }
Packit f01ec2
    }
Packit f01ec2
Packit f01ec2
  /* wait for GVfs to add the volume, if it hasn't already */
Packit f01ec2
  if (!found)
Packit f01ec2
    {
Packit f01ec2
      context = g_main_context_get_thread_default ();
Packit f01ec2
      data.loop = g_main_loop_new (context, FALSE);
Packit f01ec2
Packit f01ec2
      g_signal_connect (priv->monitor, "volume-added", G_CALLBACK (volume_added_cb), &data);
Packit f01ec2
      g_main_loop_run (data.loop);
Packit f01ec2
      g_main_loop_unref (data.loop);
Packit f01ec2
    }
Packit f01ec2
Packit f01ec2
  /* mount the volume if needed */
Packit f01ec2
  volume = G_VOLUME (g_object_get_data (G_OBJECT (object), "volume"));
Packit f01ec2
  mount = g_volume_get_mount (volume);
Packit f01ec2
  if (mount == NULL)
Packit f01ec2
    {
Packit f01ec2
      data.error = error;
Packit f01ec2
Packit f01ec2
      context = g_main_context_new ();
Packit f01ec2
      g_main_context_push_thread_default (context);
Packit f01ec2
      data.loop = g_main_loop_new (context, FALSE);
Packit f01ec2
Packit f01ec2
      g_volume_mount (volume, G_MOUNT_MOUNT_NONE, NULL, cancellable, volume_mount_cb, &data);
Packit f01ec2
      g_main_loop_run (data.loop);
Packit f01ec2
Packit f01ec2
      g_main_loop_unref (data.loop);
Packit f01ec2
      g_main_context_pop_thread_default (context);
Packit f01ec2
      g_main_context_unref (context);
Packit f01ec2
Packit f01ec2
      if (*error != NULL)
Packit f01ec2
        return;
Packit f01ec2
Packit f01ec2
      mount = g_volume_get_mount (volume);
Packit f01ec2
    }
Packit f01ec2
Packit f01ec2
  root = g_mount_get_root (mount);
Packit f01ec2
  account_miner_job_traverse_dir (job, connection, previous_resources, datasource_urn, root, TRUE, cancellable, error);
Packit f01ec2
Packit f01ec2
  g_object_unref (root);
Packit f01ec2
  g_object_unref (mount);
Packit f01ec2
  g_list_free_full (volumes, g_object_unref);
Packit f01ec2
}
Packit f01ec2
Packit f01ec2
static GHashTable *
Packit f01ec2
create_services (GomMiner *self,
Packit f01ec2
                 GoaObject *object)
Packit f01ec2
{
Packit f01ec2
  GHashTable *services;
Packit f01ec2
Packit f01ec2
  services = g_hash_table_new_full (g_str_hash, g_str_equal,
Packit f01ec2
                                    NULL, (GDestroyNotify) g_object_unref);
Packit f01ec2
Packit f01ec2
  if (gom_miner_supports_type (self, "documents"))
Packit f01ec2
    g_hash_table_insert (services, "documents", g_object_ref (object));
Packit f01ec2
Packit f01ec2
  return services;
Packit f01ec2
}
Packit f01ec2
Packit f01ec2
static void
Packit f01ec2
gom_owncloud_miner_dispose (GObject *object)
Packit f01ec2
{
Packit f01ec2
  GomOwncloudMiner *self = GOM_OWNCLOUD_MINER (object);
Packit f01ec2
Packit f01ec2
  g_clear_object (&self->priv->monitor);
Packit f01ec2
Packit f01ec2
  G_OBJECT_CLASS (gom_owncloud_miner_parent_class)->dispose (object);
Packit f01ec2
}
Packit f01ec2
Packit f01ec2
static void
Packit f01ec2
gom_owncloud_miner_init (GomOwncloudMiner *self)
Packit f01ec2
{
Packit f01ec2
  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GOM_TYPE_OWNCLOUD_MINER, GomOwncloudMinerPrivate);
Packit f01ec2
  self->priv->monitor = g_volume_monitor_get ();
Packit f01ec2
}
Packit f01ec2
Packit f01ec2
static void
Packit f01ec2
gom_owncloud_miner_class_init (GomOwncloudMinerClass *klass)
Packit f01ec2
{
Packit f01ec2
  GObjectClass *oclass = G_OBJECT_CLASS (klass);
Packit f01ec2
  GomMinerClass *miner_class = GOM_MINER_CLASS (klass);
Packit f01ec2
Packit f01ec2
  oclass->dispose = gom_owncloud_miner_dispose;
Packit f01ec2
Packit f01ec2
  miner_class->goa_provider_type = "owncloud";
Packit f01ec2
  miner_class->miner_identifier = MINER_IDENTIFIER;
Packit f01ec2
  miner_class->version = 1;
Packit f01ec2
Packit f01ec2
  miner_class->create_services = create_services;
Packit f01ec2
  miner_class->query = query_owncloud;
Packit f01ec2
Packit f01ec2
  g_type_class_add_private (klass, sizeof (GomOwncloudMinerPrivate));
Packit f01ec2
}